diff options
Diffstat (limited to 'drivers/video/fbdev/mxc')
27 files changed, 29357 insertions, 0 deletions
diff --git a/drivers/video/fbdev/mxc/Kconfig b/drivers/video/fbdev/mxc/Kconfig new file mode 100644 index 000000000000..191a58122a19 --- /dev/null +++ b/drivers/video/fbdev/mxc/Kconfig @@ -0,0 +1,123 @@ +config FB_MXC + tristate "MXC Framebuffer support" + depends on FB + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MODE_HELPERS + default y + help + This is a framebuffer device for the MXC LCD Controller. + See <http://www.linux-fbdev.org/> for information on framebuffer + devices. + + If you plan to use the LCD display with your MXC system, say + Y here. + +config FB_MXC_DISP_FRAMEWORK + tristate "Display driver framework" + help + This is a framework that helps with registration and data handling + between fb drivers and display drivers. + + It is selected by drivers which use this framework as linkage code + between display controllers and panels. + +config FB_MXC_SYNC_PANEL + depends on FB_MXC && !FB_IMX64 + depends on MXC_IPU_V3 + select FB_MXC_DISP_FRAMEWORK + tristate "Synchronous Panel Framebuffer" + +config FB_MXC_OVERLAY + depends on FB_MXC + tristate "Overlay Framebuffer" + default n + help + Enhanced LCD controller of MXC has overlay function. + +config FB_MXC_MIPI_DSI_NORTHWEST + tristate "MXC MIPI_DSI_NORTHWEST" + depends on FB_MXC_DISP_FRAMEWORK + depends on FB_MXS + +config FB_MXC_EDID + depends on FB_MXC && I2C + tristate "MXC EDID support" + default y + +config FB_MXC_ADV7535 + tristate "ADI ADV7535 support" + depends on I2C + depends on FB_MXC_MIPI_DSI_NORTHWEST + help + Driver support for the ADV7535 DSI-to-HDMI module + +config FB_MXC_TRULY_PANEL_TFT3P5581E + tristate "TRULY Panel TFT3P5581E" + depends on FB_MXC_DISP_FRAMEWORK + depends on FB_MXC_MIPI_DSI_NORTHWEST + +config FB_MXC_TRULY_WVGA_SYNC_PANEL + tristate "TRULY WVGA Panel" + depends on FB_MXC_DISP_FRAMEWORK + depends on FB_MXC_MIPI_DSI || FB_MXC_MIPI_DSI_SAMSUNG || FB_MXC_MIPI_DSI_NORTHWEST + +config FB_MXC_RK_PANEL_RK055AHD042 + tristate "ROCKTECH Panel RK055AHD042" + depends on FB_MXC_DISP_FRAMEWORK + depends on FB_MXC_MIPI_DSI_NORTHWEST + +config FB_MXC_RK_PANEL_RK055IQH042 + tristate "ROCKTECH Panel RK055IQH042" + depends on FB_MXC_DISP_FRAMEWORK + depends on FB_MXC_MIPI_DSI_NORTHWEST + +config FB_MXC_MIPI_DSI_SAMSUNG + tristate "MXC MIPI_DSI_SAMSUNG" + depends on FB_MXS + +config FB_MXC_MIPI_DSI + tristate "MXC MIPI_DSI" + depends on FB_MXS + +config FB_MXC_LDB + tristate "MXC LDB" + depends on FB_MXC_SYNC_PANEL + depends on MXC_IPU_V3 || FB_MXS + select VIDEOMODE_HELPERS + +config FB_MXC_EINK_PANEL + depends on FB_MXC + depends on DMA_ENGINE + select FB_DEFERRED_IO + tristate "E-Ink Panel Framebuffer" + +config FB_MXC_EINK_V2_PANEL + depends on FB_MXC + depends on DMA_ENGINE + select FB_DEFERRED_IO + tristate "E-Ink Panel Framebuffer based on EPDC V2" + +config FB_MXC_EINK_AUTO_UPDATE_MODE + bool "E-Ink Auto-update Mode Support" + depends on FB_MXC_EINK_PANEL + +config FB_MXC_HDMI + depends on FB_MXC_SYNC_PANEL + depends on MXC_IPU_V3 + depends on I2C + tristate "MXC HDMI driver support" + select MFD_MXC_HDMI + help + Driver for the on-chip MXC HDMI controller. + +config FB_MXS_SII902X + tristate "Si Image SII9022 DVI/HDMI Interface Chip" + depends on FB_MXS && I2C + +config FB_MXC_DCIC + tristate "MXC DCIC" + depends on FB_MXC_SYNC_PANEL + depends on MXC_IPU_V3 || FB_MXS + select VIDEOMODE_HELPERS diff --git a/drivers/video/fbdev/mxc/Makefile b/drivers/video/fbdev/mxc/Makefile new file mode 100644 index 000000000000..6addd0b3a05d --- /dev/null +++ b/drivers/video/fbdev/mxc/Makefile @@ -0,0 +1,17 @@ +obj-$(CONFIG_FB_MXC_DISP_FRAMEWORK) += mxc_dispdrv.o +obj-$(CONFIG_FB_MXC_MIPI_DSI_NORTHWEST) += mipi_dsi_northwest.o +obj-$(CONFIG_FB_MXC_EDID) += mxc_edid.o +obj-$(CONFIG_FB_MXC_ADV7535) += adv7535.o +obj-$(CONFIG_FB_MXC_TRULY_PANEL_TFT3P5581E) += mxcfb_hx8363_wvga.o +obj-$(CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL) += mxcfb_hx8369_wvga.o +obj-$(CONFIG_FB_MXC_RK_PANEL_RK055AHD042) += mxcfb_rm68200_wxga.o +obj-$(CONFIG_FB_MXC_RK_PANEL_RK055IQH042) += mxcfb_rm68191_qhd.o +obj-$(CONFIG_FB_MXC_MIPI_DSI_SAMSUNG) += mipi_dsi_samsung.o +obj-$(CONFIG_FB_MXC_MIPI_DSI) += mipi_dsi.o +obj-$(CONFIG_FB_MXC_LDB) += ldb.o +obj-$(CONFIG_FB_MXS_SII902X) += mxsfb_sii902x.o mxsfb_sii902x_audio.o +obj-$(CONFIG_FB_MXC_HDMI) += mxc_hdmi.o +obj-$(CONFIG_FB_MXC_DCIC) += mxc_dcic.o +obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxc_lcdif.o mxc_ipuv3_fb.o +obj-$(CONFIG_FB_MXC_EINK_PANEL) += mxc_epdc_fb.o +obj-$(CONFIG_FB_MXC_EINK_V2_PANEL) += mxc_epdc_v2_fb.o diff --git a/drivers/video/fbdev/mxc/adv7535.c b/drivers/video/fbdev/mxc/adv7535.c new file mode 100644 index 000000000000..805a402631e1 --- /dev/null +++ b/drivers/video/fbdev/mxc/adv7535.c @@ -0,0 +1,376 @@ +/* + * Copyright 2017 NXP + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/pm_runtime.h> +#include <video/mxc_edid.h> + +#include "mipi_dsi.h" + +/* main registers addr*/ +#define ADV7535_CHIP_REVISION 0x0 +#define ADV7535_DSI_CEC_ADDR 0xE1 + +#define TEST_PATTERN_ENABLE 0 + +struct adv7535_info { + int rev; + struct device *dev; + struct i2c_client *i2c_main; + struct i2c_client *i2c_dsi_cec; + struct fb_videomode *fb_vmode; + unsigned int bpp; +}; + +static int adv7535_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int adv7535_remove(struct i2c_client *client); +static int adv7535_detect(struct i2c_client *client, + struct i2c_board_info *info); + +static const struct i2c_device_id adv7535_id[] = { + {"adv7535", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, adv7535_id); + +static const struct of_device_id adv7535_dt_ids[] = { + { .compatible = "adi,adv7535", .data = NULL, }, + { /* sentinel */ } +}; + +static struct i2c_driver adv7535_i2c_driver = { + .driver = { + .name = "adv7535", + .owner = THIS_MODULE, + .of_match_table = adv7535_dt_ids, + }, + .probe = adv7535_probe, + .remove = adv7535_remove, + .id_table = adv7535_id, + .detect = adv7535_detect, +}; + +#define adv7535_write_reg(reg, val) { \ + int err; \ + \ + err = i2c_smbus_write_byte_data(client, reg, val); \ + if (err < 0) { \ + dev_err(&client->dev, \ + "%s:write reg error:reg=%2x, val=%2x\n",\ + __func__, reg, val); \ + return err; \ + } \ +} \ + +static int adv7535_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + s8 ret; + u8 chip_rev; + struct i2c_adapter *adapter = client->adapter; + + /* check i2c functionality */ + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C | + I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* i2c detection */ + ret = i2c_smbus_read_byte_data(client, + ADV7535_CHIP_REVISION); + if (ret < 0) { + dev_err(&adapter->dev, "ADV7535 not found\n"); + return -ENODEV; + } + + chip_rev = ret; + if (chip_rev != 0x14) { + dev_info(&adapter->dev, "Unsupported chip id: 0x%2x\n", + chip_rev); + return -ENODEV; + } + + dev_info(&adapter->dev, "chip_rev = 0x%x\n", ret); + return 0; +} + +static int adv7535_setup_cfg(struct adv7535_info *info) +{ + struct i2c_client *client = NULL; + + client = info->i2c_main; + adv7535_write_reg(0xd6, 0x48); /* HPD Override */ + adv7535_write_reg(0x41, 0x10); /* Power Up */ + + adv7535_write_reg(0x16, 0x20); /* Fixed Init */ + adv7535_write_reg(0x9A, 0xE0); + adv7535_write_reg(0xBA, 0x70); + adv7535_write_reg(0xDE, 0x82); + adv7535_write_reg(0xE4, 0x40); + adv7535_write_reg(0xE5, 0x80); + + client = info->i2c_dsi_cec; + adv7535_write_reg(0x15, 0xD0); + adv7535_write_reg(0x17, 0xD0); + adv7535_write_reg(0x24, 0x20); + adv7535_write_reg(0x57, 0x11); + + return 0; +} + +static int adv7535_vmode_cfg(struct adv7535_info *info) +{ + struct i2c_client *client = NULL; + struct fb_videomode *fb_vmode = info->fb_vmode; + u32 line_length, frame_height; + u8 low, high; + + line_length = fb_vmode->xres + fb_vmode->left_margin + + fb_vmode->right_margin + fb_vmode->hsync_len; + frame_height = fb_vmode->yres + fb_vmode->upper_margin + + fb_vmode->lower_margin + fb_vmode->vsync_len; + + client = info->i2c_dsi_cec; +#ifdef CONFIG_FB_IMX64 + adv7535_write_reg(0x1C, 0x40); /* 4 Data Lanes */ +#else + adv7535_write_reg(0x1C, 0x20); /* 2 Data Lanes */ +#endif + +#if TEST_PATTERN_ENABLE + adv7535_write_reg(0x55, 0x80); + adv7535_write_reg(0x16, 0x1C); +#else + adv7535_write_reg(0x16, 0x00); /* Pixel Clock */ +#endif + adv7535_write_reg(0x27, 0xCB); /* INT_TIMING_GEN */ + + /* video mode settings */ + low = (line_length << 4); + high = (line_length >> 4); + adv7535_write_reg(0x28, high); /* Total Line Length */ + adv7535_write_reg(0x29, low); + + low = (fb_vmode->hsync_len << 4); + high = (fb_vmode->hsync_len >> 4); + adv7535_write_reg(0x2A, high); /* Hsync Active Width */ + adv7535_write_reg(0x2B, low); + + low = (fb_vmode->right_margin << 4); + high = (fb_vmode->right_margin >> 4); + adv7535_write_reg(0x2C, high); /* Horizontal FP Width */ + adv7535_write_reg(0x2D, low); + + low = (fb_vmode->left_margin << 4); + high = (fb_vmode->left_margin >> 4); + adv7535_write_reg(0x2E, high); /* Horizontal BP Width */ + adv7535_write_reg(0x2F, low); + + low = (frame_height << 4); + high = (frame_height >> 4); + adv7535_write_reg(0x30, high); /* Total Frame Height */ + adv7535_write_reg(0x31, low); + + low = (fb_vmode->vsync_len << 4); + high = (fb_vmode->vsync_len >> 4); + adv7535_write_reg(0x32, high); /* Vsync Active Height */ + adv7535_write_reg(0x33, low); + + low = (fb_vmode->lower_margin << 4); + high = (fb_vmode->lower_margin >> 4); + adv7535_write_reg(0x34, high); /* Vertical FP Height */ + adv7535_write_reg(0x35, low); + + low = (fb_vmode->upper_margin << 4); + high = (fb_vmode->upper_margin >> 4); + adv7535_write_reg(0x36, high); /* Vertical BP Height */ + adv7535_write_reg(0x37, low); + + /* Reset Internal Timing Generator */ + adv7535_write_reg(0x27, 0xCB); + adv7535_write_reg(0x27, 0x8B); + adv7535_write_reg(0x27, 0xCB); + + client = info->i2c_main; + adv7535_write_reg(0xAF, 0x16); /* HDMI Output */ + adv7535_write_reg(0x55, 0x10); /* AVI Info-frame */ +#ifdef CONFIG_FB_IMX64 + adv7535_write_reg(0x56, 0x28); /* 16:9 */ +#else + adv7535_write_reg(0x56, 0x18); +#endif + adv7535_write_reg(0x40, 0x80); /* GCP Enable */ + adv7535_write_reg(0x4C, 0x04); /* 24bpp */ + adv7535_write_reg(0x49, 0x00); + +#ifdef CONFIG_FB_IMX64 + /* low refresh rate */ + if (fb_vmode->refresh < 50) + adv7535_write_reg(0x4A, 0x8C); +#else + adv7535_write_reg(0x17, 0x60); /* VS & HS Low Polarity, DE disabled */ +#endif + + /*TODO Audio Setup */ + + client = info->i2c_dsi_cec; + adv7535_write_reg(0xBE, 0x3D); /* CEC Power Mode */ + + adv7535_write_reg(0x03, 0x89); + + return 0; +} + +static int adv7535_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + u32 vmode_index; + int ret = 0, addr; + struct adv7535_info *info; + struct device *dev = &client->dev; + struct device_node *endpoint = NULL; + + pr_info("adv7535 probing phase\n"); + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + + if (!info) + return -ENOMEM; + + info->dev = &client->dev; + info->i2c_main = client; + i2c_set_clientdata(client, info); + + dev_info(dev, "main addr = 0x%x\n", info->i2c_main->addr); + /* get dsi source endpoint */ + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (!endpoint) { + dev_err(dev, "DSI source endpoint not exsit\n"); + ret = -ENODEV; + goto err1; + } + + info->fb_vmode = devm_kzalloc(dev, sizeof(struct fb_videomode), + GFP_KERNEL); + if (!info->fb_vmode) { + ret = -ENOMEM; + goto err1; + } + + ret = of_property_read_u32(dev->of_node, "video-mode", &vmode_index); + if (ret < 0) { + dev_err(dev, "failed to get required video mode\n"); + goto err2; + } + if (vmode_index >= ARRAY_SIZE(mxc_cea_mode)) + goto err2; + + memcpy(info->fb_vmode, &mxc_cea_mode[vmode_index], + sizeof(struct fb_videomode)); + + ret = of_property_read_u32(dev->of_node, "bpp", &info->bpp); + if (ret < 0) { + dev_err(dev, "failed to get bpp\n"); + goto err2; + } + + if ((ret = adv7535_detect(client, NULL))) + goto err2; + + addr = i2c_smbus_read_byte_data(client, + ADV7535_DSI_CEC_ADDR); + if (addr < 0) { + dev_err(dev, "Cannot get dsi_cec addr\n"); + ret = addr; + goto err2; + } + + dev_info(dev, "dsi cec addr = 0x%x\n", addr); + info->i2c_dsi_cec = i2c_new_dummy(client->adapter, addr >> 1); + if (!info->i2c_dsi_cec) { + dev_err(dev, "Failed to allocate I2C device for dsi_cec\n"); + ret = -ENOMEM; + goto err2; + } + + i2c_set_clientdata(info->i2c_dsi_cec, info); + + /*TODO interrupts */ + + if ((ret = adv7535_setup_cfg(info))) + goto err3; + + if ((ret = adv7535_vmode_cfg(info))) + goto err3; + +#ifndef CONFIG_FB_IMX64 + pm_runtime_enable(info->dev); +#endif + + dev_info(dev, "adv7535 probe finished\n"); + + return 0; +err3: + i2c_unregister_device(info->i2c_dsi_cec); +err2: + devm_kfree(dev, info->fb_vmode); +err1: + devm_kfree(dev, info); + + return ret; +} + +static int adv7535_remove(struct i2c_client *client) +{ + struct adv7535_info *info; + + info = i2c_get_clientdata(client); + i2c_set_clientdata(client, NULL); + +#ifndef CONFIG_FB_IMX64 + pm_runtime_disable(info->dev); +#endif + + i2c_unregister_device(info->i2c_dsi_cec); + + kfree(info->fb_vmode); + kfree(info); + + return 0; +} + +static __init int adv7535_init(void) +{ + u8 err = 0; + + + err = i2c_add_driver(&adv7535_i2c_driver); + if (err != 0) + pr_err("%s: i2c driver register failed, error = %d\n", + __func__, err); + + pr_debug("%s (ret=%d)\n", __func__, err); + return err; +} + +static void __exit adv7535_exit(void) +{ + i2c_del_driver(&adv7535_i2c_driver); +} + +module_init(adv7535_init); +module_exit(adv7535_exit); + +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_DESCRIPTION("ADV7535 video encoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/mxc/crtc.h b/drivers/video/fbdev/mxc/crtc.h new file mode 100644 index 000000000000..a07b9c8f84d9 --- /dev/null +++ b/drivers/video/fbdev/mxc/crtc.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __CRTC__ +#define __CRTC__ + +enum crtc { + CRTC_IPU_DI0, + CRTC_IPU_DI1, + CRTC_IPU1_DI0, + CRTC_IPU1_DI1, + CRTC_IPU2_DI0, + CRTC_IPU2_DI1, + CRTC_LCDIF, + CRTC_LCDIF1, + CRTC_LCDIF2, + CRTC_MAX, +}; + +struct ipu_di_crtc_map { + enum crtc crtc; + int ipu_id; + int ipu_di; +}; + +static const struct ipu_di_crtc_map ipu_di_crtc_maps[] = { + {CRTC_IPU1_DI0, 0, 0}, {CRTC_IPU1_DI1, 0, 1}, + {CRTC_IPU2_DI0, 1, 0}, {CRTC_IPU2_DI1, 1, 1}, +}; + +static inline int ipu_di_to_crtc(struct device *dev, int ipu_id, + int ipu_di, enum crtc *crtc) +{ + int i = 0; + + for (; i < ARRAY_SIZE(ipu_di_crtc_maps); i++) + if (ipu_di_crtc_maps[i].ipu_id == ipu_id && + ipu_di_crtc_maps[i].ipu_di == ipu_di) { + *crtc = ipu_di_crtc_maps[i].crtc; + return 0; + } + + dev_err(dev, "failed to get valid ipu di crtc " + "ipu_id %d, ipu_di %d\n", ipu_id, ipu_di); + return -EINVAL; +} + +#endif diff --git a/drivers/video/fbdev/mxc/epdc_regs.h b/drivers/video/fbdev/mxc/epdc_regs.h new file mode 100644 index 000000000000..ae17655e60e3 --- /dev/null +++ b/drivers/video/fbdev/mxc/epdc_regs.h @@ -0,0 +1,429 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010-2013 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2019 NXP + */ +#ifndef __EPDC_REGS_INCLUDED__ +#define __EPDC_REGS_INCLUDED__ + +extern void __iomem *epdc_base; + +/************************************* + * Register addresses + **************************************/ + +#define EPDC_CTRL (epdc_base + 0x000) +#define EPDC_CTRL_SET (epdc_base + 0x004) +#define EPDC_CTRL_CLEAR (epdc_base + 0x008) +#define EPDC_CTRL_TOGGLE (epdc_base + 0x00C) +#define EPDC_WVADDR (epdc_base + 0x020) +#define EPDC_WB_ADDR (epdc_base + 0x030) +#define EPDC_RES (epdc_base + 0x040) +#define EPDC_FORMAT (epdc_base + 0x050) +#define EPDC_FORMAT_SET (epdc_base + 0x054) +#define EPDC_FORMAT_CLEAR (epdc_base + 0x058) +#define EPDC_FORMAT_TOGGLE (epdc_base + 0x05C) +#define EPDC_FIFOCTRL (epdc_base + 0x0A0) +#define EPDC_FIFOCTRL_SET (epdc_base + 0x0A4) +#define EPDC_FIFOCTRL_CLEAR (epdc_base + 0x0A8) +#define EPDC_FIFOCTRL_TOGGLE (epdc_base + 0x0AC) +#define EPDC_UPD_ADDR (epdc_base + 0x100) +#define EPDC_UPD_STRIDE (epdc_base + 0x110) +#define EPDC_UPD_CORD (epdc_base + 0x120) +#define EPDC_UPD_SIZE (epdc_base + 0x140) +#define EPDC_UPD_CTRL (epdc_base + 0x160) +#define EPDC_UPD_FIXED (epdc_base + 0x180) +#define EPDC_TEMP (epdc_base + 0x1A0) +#define EPDC_AUTOWV_LUT (epdc_base + 0x1C0) +#define EPDC_TCE_CTRL (epdc_base + 0x200) +#define EPDC_TCE_SDCFG (epdc_base + 0x220) +#define EPDC_TCE_GDCFG (epdc_base + 0x240) +#define EPDC_TCE_HSCAN1 (epdc_base + 0x260) +#define EPDC_TCE_HSCAN2 (epdc_base + 0x280) +#define EPDC_TCE_VSCAN (epdc_base + 0x2A0) +#define EPDC_TCE_OE (epdc_base + 0x2C0) +#define EPDC_TCE_POLARITY (epdc_base + 0x2E0) +#define EPDC_TCE_TIMING1 (epdc_base + 0x300) +#define EPDC_TCE_TIMING2 (epdc_base + 0x310) +#define EPDC_TCE_TIMING3 (epdc_base + 0x320) +#define EPDC_PIGEON_CTRL0 (epdc_base + 0x380) +#define EPDC_PIGEON_CTRL1 (epdc_base + 0x390) +#define EPDC_IRQ_MASK1 (epdc_base + 0x3C0) +#define EPDC_IRQ_MASK1_SET (epdc_base + 0x3C4) +#define EPDC_IRQ_MASK1_CLEAR (epdc_base + 0x3C8) +#define EPDC_IRQ_MASK1_TOGGLE (epdc_base + 0x3CC) +#define EPDC_IRQ_MASK2 (epdc_base + 0x3D0) +#define EPDC_IRQ_MASK2_SET (epdc_base + 0x3D4) +#define EPDC_IRQ_MASK2_CLEAR (epdc_base + 0x3D8) +#define EPDC_IRQ_MASK2_TOGGLE (epdc_base + 0x3DC) +#define EPDC_IRQ1 (epdc_base + 0x3E0) +#define EPDC_IRQ1_SET (epdc_base + 0x3E4) +#define EPDC_IRQ1_CLEAR (epdc_base + 0x3E8) +#define EPDC_IRQ1_TOGGLE (epdc_base + 0x3EC) +#define EPDC_IRQ2 (epdc_base + 0x3F0) +#define EPDC_IRQ2_SET (epdc_base + 0x3F4) +#define EPDC_IRQ2_CLEAR (epdc_base + 0x3F8) +#define EPDC_IRQ2_TOGGLE (epdc_base + 0x3FC) +#define EPDC_IRQ_MASK (epdc_base + 0x400) +#define EPDC_IRQ_MASK_SET (epdc_base + 0x404) +#define EPDC_IRQ_MASK_CLEAR (epdc_base + 0x408) +#define EPDC_IRQ_MASK_TOGGLE (epdc_base + 0x40C) +#define EPDC_IRQ (epdc_base + 0x420) +#define EPDC_IRQ_SET (epdc_base + 0x424) +#define EPDC_IRQ_CLEAR (epdc_base + 0x428) +#define EPDC_IRQ_TOGGLE (epdc_base + 0x42C) +#define EPDC_STATUS_LUTS (epdc_base + 0x440) +#define EPDC_STATUS_LUTS_SET (epdc_base + 0x444) +#define EPDC_STATUS_LUTS_CLEAR (epdc_base + 0x448) +#define EPDC_STATUS_LUTS_TOGGLE (epdc_base + 0x44C) +#define EPDC_STATUS_LUTS2 (epdc_base + 0x450) +#define EPDC_STATUS_LUTS2_SET (epdc_base + 0x454) +#define EPDC_STATUS_LUTS2_CLEAR (epdc_base + 0x458) +#define EPDC_STATUS_LUTS2_TOGGLE (epdc_base + 0x45C) +#define EPDC_STATUS_NEXTLUT (epdc_base + 0x460) +#define EPDC_STATUS_COL (epdc_base + 0x480) +#define EPDC_STATUS_COL2 (epdc_base + 0x490) +#define EPDC_STATUS (epdc_base + 0x4A0) +#define EPDC_STATUS_SET (epdc_base + 0x4A4) +#define EPDC_STATUS_CLEAR (epdc_base + 0x4A8) +#define EPDC_STATUS_TOGGLE (epdc_base + 0x4AC) +#define EPDC_UPD_COL_CORD (epdc_base + 0x4C0) +#define EPDC_UPD_COL_SIZE (epdc_base + 0x4E0) +#define EPDC_DEBUG (epdc_base + 0x500) +#define EPDC_DEBUG_LUT (epdc_base + 0x530) +#define EPDC_HIST1_PARAM (epdc_base + 0x600) +#define EPDC_HIST2_PARAM (epdc_base + 0x610) +#define EPDC_HIST4_PARAM (epdc_base + 0x620) +#define EPDC_HIST8_PARAM0 (epdc_base + 0x630) +#define EPDC_HIST8_PARAM1 (epdc_base + 0x640) +#define EPDC_HIST16_PARAM0 (epdc_base + 0x650) +#define EPDC_HIST16_PARAM1 (epdc_base + 0x660) +#define EPDC_HIST16_PARAM2 (epdc_base + 0x670) +#define EPDC_HIST16_PARAM3 (epdc_base + 0x680) +#define EPDC_GPIO (epdc_base + 0x700) +#define EPDC_VERSION (epdc_base + 0x7F0) +#define EPDC_PIGEON_0_0 (epdc_base + 0x800) +#define EPDC_PIGEON_0_1 (epdc_base + 0x810) +#define EPDC_PIGEON_0_2 (epdc_base + 0x820) +#define EPDC_PIGEON_1_0 (epdc_base + 0x840) +#define EPDC_PIGEON_1_1 (epdc_base + 0x850) +#define EPDC_PIGEON_1_2 (epdc_base + 0x860) +#define EPDC_PIGEON_2_0 (epdc_base + 0x880) +#define EPDC_PIGEON_2_1 (epdc_base + 0x890) +#define EPDC_PIGEON_2_2 (epdc_base + 0x8A0) +#define EPDC_PIGEON_3_0 (epdc_base + 0x8C0) +#define EPDC_PIGEON_3_1 (epdc_base + 0x8D0) +#define EPDC_PIGEON_3_2 (epdc_base + 0x8E0) +#define EPDC_PIGEON_4_0 (epdc_base + 0x900) +#define EPDC_PIGEON_4_1 (epdc_base + 0x910) +#define EPDC_PIGEON_4_2 (epdc_base + 0x920) +#define EPDC_PIGEON_5_0 (epdc_base + 0x940) +#define EPDC_PIGEON_5_1 (epdc_base + 0x950) +#define EPDC_PIGEON_5_2 (epdc_base + 0x960) +#define EPDC_PIGEON_6_0 (epdc_base + 0x980) +#define EPDC_PIGEON_6_1 (epdc_base + 0x990) +#define EPDC_PIGEON_6_2 (epdc_base + 0x9A0) +#define EPDC_PIGEON_7_0 (epdc_base + 0x9C0) +#define EPDC_PIGEON_7_1 (epdc_base + 0x9D0) +#define EPDC_PIGEON_7_2 (epdc_base + 0x9E0) +#define EPDC_PIGEON_8_0 (epdc_base + 0xA00) +#define EPDC_PIGEON_8_1 (epdc_base + 0xA10) +#define EPDC_PIGEON_8_2 (epdc_base + 0xA20) +#define EPDC_PIGEON_9_0 (epdc_base + 0xA40) +#define EPDC_PIGEON_9_1 (epdc_base + 0xA50) +#define EPDC_PIGEON_9_2 (epdc_base + 0xA60) +#define EPDC_PIGEON_10_0 (epdc_base + 0xA80) +#define EPDC_PIGEON_10_1 (epdc_base + 0xA90) +#define EPDC_PIGEON_10_2 (epdc_base + 0xAA0) +#define EPDC_PIGEON_11_0 (epdc_base + 0xAC0) +#define EPDC_PIGEON_11_1 (epdc_base + 0xAD0) +#define EPDC_PIGEON_11_2 (epdc_base + 0xAE0) +#define EPDC_PIGEON_12_0 (epdc_base + 0xB00) +#define EPDC_PIGEON_12_1 (epdc_base + 0xB10) +#define EPDC_PIGEON_12_2 (epdc_base + 0xB20) +#define EPDC_PIGEON_13_0 (epdc_base + 0xB40) +#define EPDC_PIGEON_13_1 (epdc_base + 0xB50) +#define EPDC_PIGEON_13_2 (epdc_base + 0xB60) +#define EPDC_PIGEON_14_0 (epdc_base + 0xB80) +#define EPDC_PIGEON_14_1 (epdc_base + 0xB90) +#define EPDC_PIGEON_14_2 (epdc_base + 0xBA0) +#define EPDC_PIGEON_15_0 (epdc_base + 0xBC0) +#define EPDC_PIGEON_15_1 (epdc_base + 0xBD0) +#define EPDC_PIGEON_15_2 (epdc_base + 0xBE0) +#define EPDC_WB_ADDR_TCE (epdc_base + 0xC10) + +/* + * Register field definitions + */ + +enum { +/* EPDC_CTRL field values */ + EPDC_CTRL_SFTRST = 0x80000000, + EPDC_CTRL_CLKGATE = 0x40000000, + EPDC_CTRL_SRAM_POWERDOWN = 0x100, + EPDC_CTRL_UPD_DATA_SWIZZLE_MASK = 0xC0, + EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP = 0, + EPDC_CTRL_UPD_DATA_SWIZZLE_ALL_BYTES_SWAP = 0x40, + EPDC_CTRL_UPD_DATA_SWIZZLE_HWD_SWAP = 0x80, + EPDC_CTRL_UPD_DATA_SWIZZLE_HWD_BYTE_SWAP = 0xC0, + EPDC_CTRL_LUT_DATA_SWIZZLE_MASK = 0x30, + EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP = 0, + EPDC_CTRL_LUT_DATA_SWIZZLE_ALL_BYTES_SWAP = 0x10, + EPDC_CTRL_LUT_DATA_SWIZZLE_HWD_SWAP = 0x20, + EPDC_CTRL_LUT_DATA_SWIZZLE_HWD_BYTE_SWAP = 0x30, + EPDC_CTRL_BURST_LEN_8_8 = 0x1, + EPDC_CTRL_BURST_LEN_8_16 = 0, + +/* EPDC_RES field values */ + EPDC_RES_VERTICAL_MASK = 0x1FFF0000, + EPDC_RES_VERTICAL_OFFSET = 16, + EPDC_RES_HORIZONTAL_MASK = 0x1FFF, + EPDC_RES_HORIZONTAL_OFFSET = 0, + +/* EPDC_FORMAT field values */ + EPDC_FORMAT_BUF_PIXEL_SCALE_ROUND = 0x1000000, + EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK = 0xFF0000, + EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET = 16, + EPDC_FORMAT_BUF_PIXEL_FORMAT_MASK = 0x700, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P2N = 0x200, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P3N = 0x300, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N = 0x400, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N = 0x500, + EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT = 0x0, + EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT_VCOM = 0x1, + EPDC_FORMAT_TFT_PIXEL_FORMAT_4BIT = 0x2, + EPDC_FORMAT_TFT_PIXEL_FORMAT_4BIT_VCOM = 0x3, + +/* EPDC_FIFOCTRL field values */ + EPDC_FIFOCTRL_ENABLE_PRIORITY = 0x80000000, + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK = 0xFF0000, + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET = 16, + EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK = 0xFF00, + EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET = 8, + EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK = 0xFF, + EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET = 0, + +/* EPDC_UPD_CORD field values */ + EPDC_UPD_CORD_YCORD_MASK = 0x1FFF0000, + EPDC_UPD_CORD_YCORD_OFFSET = 16, + EPDC_UPD_CORD_XCORD_MASK = 0x1FFF, + EPDC_UPD_CORD_XCORD_OFFSET = 0, + +/* EPDC_UPD_SIZE field values */ + EPDC_UPD_SIZE_HEIGHT_MASK = 0x1FFF0000, + EPDC_UPD_SIZE_HEIGHT_OFFSET = 16, + EPDC_UPD_SIZE_WIDTH_MASK = 0x1FFF, + EPDC_UPD_SIZE_WIDTH_OFFSET = 0, + +/* EPDC_UPD_CTRL field values */ + EPDC_UPD_CTRL_USE_FIXED = 0x80000000, + EPDC_UPD_CTRL_LUT_SEL_MASK = 0x3F0000, + EPDC_UPD_CTRL_LUT_SEL_OFFSET = 16, + EPDC_UPD_CTRL_WAVEFORM_MODE_MASK = 0xFF00, + EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET = 8, + EPDC_UPD_CTRL_AUTOWV_PAUSE = 0x8, + EPDC_UPD_CTRL_AUTOWV = 0x4, + EPDC_UPD_CTRL_DRY_RUN = 0x2, + EPDC_UPD_CTRL_UPDATE_MODE_FULL = 0x1, + +/* EPDC_UPD_FIXED field values */ + EPDC_UPD_FIXED_FIXNP_EN = 0x80000000, + EPDC_UPD_FIXED_FIXCP_EN = 0x40000000, + EPDC_UPD_FIXED_FIXNP_MASK = 0xFF00, + EPDC_UPD_FIXED_FIXNP_OFFSET = 8, + EPDC_UPD_FIXED_FIXCP_MASK = 0xFF, + EPDC_UPD_FIXED_FIXCP_OFFSET = 0, + +/* EPDC_AUTOWV_LUT field values */ + EPDC_AUTOWV_LUT_DATA_MASK = 0xFF0000, + EPDC_AUTOWV_LUT_DATA_OFFSET = 16, + EPDC_AUTOWV_LUT_ADDR_MASK = 0xFF, + EPDC_AUTOWV_LUT_ADDR_OFFSET = 0, + +/* EPDC_TCE_CTRL field values */ + EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK = 0x1FF0000, + EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET = 16, + EPDC_TCE_CTRL_VCOM_VAL_MASK = 0xC00, + EPDC_TCE_CTRL_VCOM_VAL_OFFSET = 10, + EPDC_TCE_CTRL_VCOM_MODE_AUTO = 0x200, + EPDC_TCE_CTRL_VCOM_MODE_MANUAL = 0x000, + EPDC_TCE_CTRL_DDR_MODE_ENABLE = 0x100, + EPDC_TCE_CTRL_LVDS_MODE_CE_ENABLE = 0x80, + EPDC_TCE_CTRL_LVDS_MODE_ENABLE = 0x40, + EPDC_TCE_CTRL_SCAN_DIR_1_UP = 0x20, + EPDC_TCE_CTRL_SCAN_DIR_0_UP = 0x10, + EPDC_TCE_CTRL_DUAL_SCAN_ENABLE = 0x8, + EPDC_TCE_CTRL_SDDO_WIDTH_16BIT = 0x4, + EPDC_TCE_CTRL_PIXELS_PER_SDCLK_2 = 1, + EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4 = 2, + EPDC_TCE_CTRL_PIXELS_PER_SDCLK_8 = 3, + +/* EPDC_TCE_SDCFG field values */ + EPDC_TCE_SDCFG_SDCLK_HOLD = 0x200000, + EPDC_TCE_SDCFG_SDSHR = 0x100000, + EPDC_TCE_SDCFG_NUM_CE_MASK = 0xF0000, + EPDC_TCE_SDCFG_NUM_CE_OFFSET = 16, + EPDC_TCE_SDCFG_SDDO_REFORMAT_STANDARD = 0, + EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS = 0x4000, + EPDC_TCE_SDCFG_SDDO_INVERT_ENABLE = 0x2000, + EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK = 0x1FFF, + EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET = 0, + +/* EPDC_TCE_GDCFG field values */ + EPDC_TCE_SDCFG_GDRL = 0x10, + EPDC_TCE_SDCFG_GDOE_MODE_DELAYED_GDCLK = 0x2, + EPDC_TCE_SDCFG_GDSP_MODE_FRAME_SYNC = 0x1, + EPDC_TCE_SDCFG_GDSP_MODE_ONE_LINE = 0x0, + +/* EPDC_TCE_HSCAN1 field values */ + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK = 0xFFF0000, + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET = 16, + EPDC_TCE_HSCAN1_LINE_SYNC_MASK = 0xFFF, + EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET = 0, + +/* EPDC_TCE_HSCAN2 field values */ + EPDC_TCE_HSCAN2_LINE_END_MASK = 0xFFF0000, + EPDC_TCE_HSCAN2_LINE_END_OFFSET = 16, + EPDC_TCE_HSCAN2_LINE_BEGIN_MASK = 0xFFF, + EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET = 0, + +/* EPDC_TCE_VSCAN field values */ + EPDC_TCE_VSCAN_FRAME_END_MASK = 0xFF0000, + EPDC_TCE_VSCAN_FRAME_END_OFFSET = 16, + EPDC_TCE_VSCAN_FRAME_BEGIN_MASK = 0xFF00, + EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET = 8, + EPDC_TCE_VSCAN_FRAME_SYNC_MASK = 0xFF, + EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET = 0, + +/* EPDC_TCE_OE field values */ + EPDC_TCE_OE_SDOED_WIDTH_MASK = 0xFF000000, + EPDC_TCE_OE_SDOED_WIDTH_OFFSET = 24, + EPDC_TCE_OE_SDOED_DLY_MASK = 0xFF0000, + EPDC_TCE_OE_SDOED_DLY_OFFSET = 16, + EPDC_TCE_OE_SDOEZ_WIDTH_MASK = 0xFF00, + EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET = 8, + EPDC_TCE_OE_SDOEZ_DLY_MASK = 0xFF, + EPDC_TCE_OE_SDOEZ_DLY_OFFSET = 0, + +/* EPDC_TCE_POLARITY field values */ + EPDC_TCE_POLARITY_GDSP_POL_ACTIVE_HIGH = 0x10, + EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH = 0x8, + EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH = 0x4, + EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH = 0x2, + EPDC_TCE_POLARITY_SDCE_POL_ACTIVE_HIGH = 0x1, + +/* EPDC_TCE_TIMING1 field values */ + EPDC_TCE_TIMING1_SDLE_SHIFT_NONE = 0x00, + EPDC_TCE_TIMING1_SDLE_SHIFT_1 = 0x10, + EPDC_TCE_TIMING1_SDLE_SHIFT_2 = 0x20, + EPDC_TCE_TIMING1_SDLE_SHIFT_3 = 0x30, + EPDC_TCE_TIMING1_SDCLK_INVERT = 0x8, + EPDC_TCE_TIMING1_SDCLK_SHIFT_NONE = 0, + EPDC_TCE_TIMING1_SDCLK_SHIFT_1CYCLE = 1, + EPDC_TCE_TIMING1_SDCLK_SHIFT_2CYCLES = 2, + EPDC_TCE_TIMING1_SDCLK_SHIFT_3CYCLES = 3, + +/* EPDC_TCE_TIMING2 field values */ + EPDC_TCE_TIMING2_GDCLK_HP_MASK = 0xFFFF0000, + EPDC_TCE_TIMING2_GDCLK_HP_OFFSET = 16, + EPDC_TCE_TIMING2_GDSP_OFFSET_MASK = 0xFFFF, + EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET = 0, + +/* EPDC_TCE_TIMING3 field values */ + EPDC_TCE_TIMING3_GDOE_OFFSET_MASK = 0xFFFF0000, + EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET = 16, + EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK = 0xFFFF, + EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET = 0, + +/* EPDC_IRQ_MASK/EPDC_IRQ field values */ + EPDC_IRQ_WB_CMPLT_IRQ = 0x10000, + EPDC_IRQ_LUT_COL_IRQ = 0x20000, + EPDC_IRQ_TCE_UNDERRUN_IRQ = 0x40000, + EPDC_IRQ_FRAME_END_IRQ = 0x80000, + EPDC_IRQ_BUS_ERROR_IRQ = 0x100000, + EPDC_IRQ_TCE_IDLE_IRQ = 0x200000, + EPDC_IRQ_UPD_DONE_IRQ = 0x400000, + EPDC_IRQ_PWR_IRQ = 0x800000, + +/* EPDC_STATUS_NEXTLUT field values */ + EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID = 0x100, + EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK = 0x3F, + EPDC_STATUS_NEXTLUT_NEXT_LUT_OFFSET = 0, + +/* EPDC_STATUS field values */ + EPDC_STATUS_HISTOGRAM_CP_MASK = 0x1F0000, + EPDC_STATUS_HISTOGRAM_CP_OFFSET = 16, + EPDC_STATUS_HISTOGRAM_NP_MASK = 0x1F00, + EPDC_STATUS_HISTOGRAM_NP_OFFSET = 8, + EPDC_STATUS_UPD_VOID = 0x8, + EPDC_STATUS_LUTS_UNDERRUN = 0x4, + EPDC_STATUS_LUTS_BUSY = 0x2, + EPDC_STATUS_WB_BUSY = 0x1, + +/* EPDC_UPD_COL_CORD field values */ + EPDC_UPD_COL_CORD_YCORD_MASK = 0x1FFF0000, + EPDC_UPD_COL_CORD_YCORD_OFFSET = 16, + EPDC_UPD_COL_CORD_XCORD_MASK = 0x1FFF, + EPDC_UPD_COL_CORD_XCORD_OFFSET = 0, + +/* EPDC_UPD_COL_SIZE field values */ + EPDC_UPD_COL_SIZE_HEIGHT_MASK = 0x1FFF0000, + EPDC_UPD_COL_SIZE_HEIGHT_OFFSET = 16, + EPDC_UPD_COL_SIZE_WIDTH_MASK = 0x1FFF, + EPDC_UPD_COL_SIZE_WIDTH_OFFSET = 0, + +/* EPDC_DEBUG field values */ + EPDC_DEBUG_UNDERRUN_RECOVER = 0x2, + EPDC_DEBUG_COLLISION_OFF = 0x1, + +/* EPDC_HISTx_PARAM field values */ + EPDC_HIST_PARAM_VALUE0_MASK = 0x1F, + EPDC_HIST_PARAM_VALUE0_OFFSET = 0, + EPDC_HIST_PARAM_VALUE1_MASK = 0x1F00, + EPDC_HIST_PARAM_VALUE1_OFFSET = 8, + EPDC_HIST_PARAM_VALUE2_MASK = 0x1F0000, + EPDC_HIST_PARAM_VALUE2_OFFSET = 16, + EPDC_HIST_PARAM_VALUE3_MASK = 0x1F000000, + EPDC_HIST_PARAM_VALUE3_OFFSET = 24, + EPDC_HIST_PARAM_VALUE4_MASK = 0x1F, + EPDC_HIST_PARAM_VALUE4_OFFSET = 0, + EPDC_HIST_PARAM_VALUE5_MASK = 0x1F00, + EPDC_HIST_PARAM_VALUE5_OFFSET = 8, + EPDC_HIST_PARAM_VALUE6_MASK = 0x1F0000, + EPDC_HIST_PARAM_VALUE6_OFFSET = 16, + EPDC_HIST_PARAM_VALUE7_MASK = 0x1F000000, + EPDC_HIST_PARAM_VALUE7_OFFSET = 24, + EPDC_HIST_PARAM_VALUE8_MASK = 0x1F, + EPDC_HIST_PARAM_VALUE8_OFFSET = 0, + EPDC_HIST_PARAM_VALUE9_MASK = 0x1F00, + EPDC_HIST_PARAM_VALUE9_OFFSET = 8, + EPDC_HIST_PARAM_VALUE10_MASK = 0x1F0000, + EPDC_HIST_PARAM_VALUE10_OFFSET = 16, + EPDC_HIST_PARAM_VALUE11_MASK = 0x1F000000, + EPDC_HIST_PARAM_VALUE11_OFFSET = 24, + EPDC_HIST_PARAM_VALUE12_MASK = 0x1F, + EPDC_HIST_PARAM_VALUE12_OFFSET = 0, + EPDC_HIST_PARAM_VALUE13_MASK = 0x1F00, + EPDC_HIST_PARAM_VALUE13_OFFSET = 8, + EPDC_HIST_PARAM_VALUE14_MASK = 0x1F0000, + EPDC_HIST_PARAM_VALUE14_OFFSET = 16, + EPDC_HIST_PARAM_VALUE15_MASK = 0x1F000000, + EPDC_HIST_PARAM_VALUE15_OFFSET = 24, + +/* EPDC_GPIO field values */ + EPDC_GPIO_PWRCOM = 0x40, + EPDC_GPIO_PWRCTRL_MASK = 0x3C, + EPDC_GPIO_PWRCTRL_OFFSET = 2, + EPDC_GPIO_BDR_MASK = 0x3, + EPDC_GPIO_BDR_OFFSET = 0, + +/* EPDC_VERSION field values */ + EPDC_VERSION_MAJOR_MASK = 0xFF000000, + EPDC_VERSION_MAJOR_OFFSET = 24, + EPDC_VERSION_MINOR_MASK = 0xFF0000, + EPDC_VERSION_MINOR_OFFSET = 16, + EPDC_VERSION_STEP_MASK = 0xFFFF, + EPDC_VERSION_STEP_OFFSET = 0, +}; + +#endif /* __EPDC_REGS_INCLUDED__ */ diff --git a/drivers/video/fbdev/mxc/epdc_v2_regs.h b/drivers/video/fbdev/mxc/epdc_v2_regs.h new file mode 100644 index 000000000000..b093059696bf --- /dev/null +++ b/drivers/video/fbdev/mxc/epdc_v2_regs.h @@ -0,0 +1,520 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2019 NXP + */ +#ifndef __EPDC_REGS_INCLUDED__ +#define __EPDC_REGS_INCLUDED__ + +extern void __iomem *epdc_v2_base; + +/************************************* + * Register addresses + **************************************/ + +#define EPDC_CTRL (epdc_v2_base + 0x000) +#define EPDC_CTRL_SET (epdc_v2_base + 0x004) +#define EPDC_CTRL_CLEAR (epdc_v2_base + 0x008) +#define EPDC_CTRL_TOGGLE (epdc_v2_base + 0x00C) +#define EPDC_WVADDR (epdc_v2_base + 0x020) +#define EPDC_WB_ADDR (epdc_v2_base + 0x030) +#define EPDC_RES (epdc_v2_base + 0x040) +#define EPDC_FORMAT (epdc_v2_base + 0x050) +#define EPDC_FORMAT_SET (epdc_v2_base + 0x054) +#define EPDC_FORMAT_CLEAR (epdc_v2_base + 0x058) +#define EPDC_FORMAT_TOGGLE (epdc_v2_base + 0x05C) +#define EPDC_WB_FIELD0 (epdc_v2_base + 0x060) +#define EPDC_WB_FIELD0_SET (epdc_v2_base + 0x064) +#define EPDC_WB_FIELD0_CLEAR (epdc_v2_base + 0x068) +#define EPDC_WB_FIELD0_TOGGLE (epdc_v2_base + 0x06C) +#define EPDC_WB_FIELD1 (epdc_v2_base + 0x070) +#define EPDC_WB_FIELD1_SET (epdc_v2_base + 0x074) +#define EPDC_WB_FIELD1_CLEAR (epdc_v2_base + 0x078) +#define EPDC_WB_FIELD1_TOGGLE (epdc_v2_base + 0x07C) +#define EPDC_WB_FIELD2 (epdc_v2_base + 0x080) +#define EPDC_WB_FIELD2_SET (epdc_v2_base + 0x084) +#define EPDC_WB_FIELD2_CLEAR (epdc_v2_base + 0x088) +#define EPDC_WB_FIELD2_TOGGLE (epdc_v2_base + 0x08C) +#define EPDC_WB_FIELD3 (epdc_v2_base + 0x090) +#define EPDC_WB_FIELD3_SET (epdc_v2_base + 0x094) +#define EPDC_WB_FIELD3_CLEAR (epdc_v2_base + 0x098) +#define EPDC_WB_FIELD3_TOGGLE (epdc_v2_base + 0x09C) +#define EPDC_FIFOCTRL (epdc_v2_base + 0x0A0) +#define EPDC_FIFOCTRL_SET (epdc_v2_base + 0x0A4) +#define EPDC_FIFOCTRL_CLEAR (epdc_v2_base + 0x0A8) +#define EPDC_FIFOCTRL_TOGGLE (epdc_v2_base + 0x0AC) +#define EPDC_UPD_ADDR (epdc_v2_base + 0x100) +#define EPDC_UPD_STRIDE (epdc_v2_base + 0x110) +#define EPDC_UPD_CORD (epdc_v2_base + 0x120) +#define EPDC_UPD_SIZE (epdc_v2_base + 0x140) +#define EPDC_UPD_CTRL (epdc_v2_base + 0x160) +#define EPDC_UPD_FIXED (epdc_v2_base + 0x180) +#define EPDC_TEMP (epdc_v2_base + 0x1A0) +#define EPDC_AUTOWV_LUT (epdc_v2_base + 0x1C0) +#define EPDC_LUT_STANDBY1 (epdc_v2_base + 0x1E0) +#define EPDC_LUT_STANDBY1_SET (epdc_v2_base + 0x1E4) +#define EPDC_LUT_STANDBY1_CLEAR (epdc_v2_base + 0x1E8) +#define EPDC_LUT_STANDBY1_TOGGLE (epdc_v2_base + 0x1EC) +#define EPDC_LUT_STANDBY2 (epdc_v2_base + 0x1F0) +#define EPDC_LUT_STANDBY2_SET (epdc_v2_base + 0x1F4) +#define EPDC_LUT_STANDBY2_CLEAR (epdc_v2_base + 0x1F8) +#define EPDC_LUT_STANDBY2_TOGGLE (epdc_v2_base + 0x1FC) +#define EPDC_TCE_CTRL (epdc_v2_base + 0x200) +#define EPDC_TCE_SDCFG (epdc_v2_base + 0x220) +#define EPDC_TCE_GDCFG (epdc_v2_base + 0x240) +#define EPDC_TCE_HSCAN1 (epdc_v2_base + 0x260) +#define EPDC_TCE_HSCAN2 (epdc_v2_base + 0x280) +#define EPDC_TCE_VSCAN (epdc_v2_base + 0x2A0) +#define EPDC_TCE_OE (epdc_v2_base + 0x2C0) +#define EPDC_TCE_POLARITY (epdc_v2_base + 0x2E0) +#define EPDC_TCE_TIMING1 (epdc_v2_base + 0x300) +#define EPDC_TCE_TIMING2 (epdc_v2_base + 0x310) +#define EPDC_TCE_TIMING3 (epdc_v2_base + 0x320) +#define EPDC_PIGEON_CTRL0 (epdc_v2_base + 0x380) +#define EPDC_PIGEON_CTRL1 (epdc_v2_base + 0x390) +#define EPDC_IRQ_MASK1 (epdc_v2_base + 0x3C0) +#define EPDC_IRQ_MASK1_SET (epdc_v2_base + 0x3C4) +#define EPDC_IRQ_MASK1_CLEAR (epdc_v2_base + 0x3C8) +#define EPDC_IRQ_MASK1_TOGGLE (epdc_v2_base + 0x3CC) +#define EPDC_IRQ_MASK2 (epdc_v2_base + 0x3D0) +#define EPDC_IRQ_MASK2_SET (epdc_v2_base + 0x3D4) +#define EPDC_IRQ_MASK2_CLEAR (epdc_v2_base + 0x3D8) +#define EPDC_IRQ_MASK2_TOGGLE (epdc_v2_base + 0x3DC) +#define EPDC_IRQ1 (epdc_v2_base + 0x3E0) +#define EPDC_IRQ1_SET (epdc_v2_base + 0x3E4) +#define EPDC_IRQ1_CLEAR (epdc_v2_base + 0x3E8) +#define EPDC_IRQ1_TOGGLE (epdc_v2_base + 0x3EC) +#define EPDC_IRQ2 (epdc_v2_base + 0x3F0) +#define EPDC_IRQ2_SET (epdc_v2_base + 0x3F4) +#define EPDC_IRQ2_CLEAR (epdc_v2_base + 0x3F8) +#define EPDC_IRQ2_TOGGLE (epdc_v2_base + 0x3FC) +#define EPDC_IRQ_MASK (epdc_v2_base + 0x400) +#define EPDC_IRQ_MASK_SET (epdc_v2_base + 0x404) +#define EPDC_IRQ_MASK_CLEAR (epdc_v2_base + 0x408) +#define EPDC_IRQ_MASK_TOGGLE (epdc_v2_base + 0x40C) +#define EPDC_IRQ (epdc_v2_base + 0x420) +#define EPDC_IRQ_SET (epdc_v2_base + 0x424) +#define EPDC_IRQ_CLEAR (epdc_v2_base + 0x428) +#define EPDC_IRQ_TOGGLE (epdc_v2_base + 0x42C) +#define EPDC_STATUS_LUTS (epdc_v2_base + 0x440) +#define EPDC_STATUS_LUTS_SET (epdc_v2_base + 0x444) +#define EPDC_STATUS_LUTS_CLEAR (epdc_v2_base + 0x448) +#define EPDC_STATUS_LUTS_TOGGLE (epdc_v2_base + 0x44C) +#define EPDC_STATUS_LUTS2 (epdc_v2_base + 0x450) +#define EPDC_STATUS_LUTS2_SET (epdc_v2_base + 0x454) +#define EPDC_STATUS_LUTS2_CLEAR (epdc_v2_base + 0x458) +#define EPDC_STATUS_LUTS2_TOGGLE (epdc_v2_base + 0x45C) +#define EPDC_STATUS_NEXTLUT (epdc_v2_base + 0x460) +#define EPDC_STATUS_COL (epdc_v2_base + 0x480) +#define EPDC_STATUS_COL2 (epdc_v2_base + 0x490) +#define EPDC_STATUS (epdc_v2_base + 0x4A0) +#define EPDC_STATUS_SET (epdc_v2_base + 0x4A4) +#define EPDC_STATUS_CLEAR (epdc_v2_base + 0x4A8) +#define EPDC_STATUS_TOGGLE (epdc_v2_base + 0x4AC) +#define EPDC_UPD_COL_CORD (epdc_v2_base + 0x4C0) +#define EPDC_UPD_COL_SIZE (epdc_v2_base + 0x4E0) +#define EPDC_DEBUG (epdc_v2_base + 0x500) +#define EPDC_DEBUG_LUT (epdc_v2_base + 0x530) +#define EPDC_HIST1_PARAM (epdc_v2_base + 0x600) +#define EPDC_HIST2_PARAM (epdc_v2_base + 0x610) +#define EPDC_HIST4_PARAM (epdc_v2_base + 0x620) +#define EPDC_HIST8_PARAM0 (epdc_v2_base + 0x630) +#define EPDC_HIST8_PARAM1 (epdc_v2_base + 0x640) +#define EPDC_HIST16_PARAM0 (epdc_v2_base + 0x650) +#define EPDC_HIST16_PARAM1 (epdc_v2_base + 0x660) +#define EPDC_HIST16_PARAM2 (epdc_v2_base + 0x670) +#define EPDC_HIST16_PARAM3 (epdc_v2_base + 0x680) +#define EPDC_GPIO (epdc_v2_base + 0x700) +#define EPDC_VERSION (epdc_v2_base + 0x7F0) +#define EPDC_PIGEON_0_0 (epdc_v2_base + 0x800) +#define EPDC_PIGEON_0_1 (epdc_v2_base + 0x810) +#define EPDC_PIGEON_0_2 (epdc_v2_base + 0x820) +#define EPDC_PIGEON_1_0 (epdc_v2_base + 0x840) +#define EPDC_PIGEON_1_1 (epdc_v2_base + 0x850) +#define EPDC_PIGEON_1_2 (epdc_v2_base + 0x860) +#define EPDC_PIGEON_2_0 (epdc_v2_base + 0x880) +#define EPDC_PIGEON_2_1 (epdc_v2_base + 0x890) +#define EPDC_PIGEON_2_2 (epdc_v2_base + 0x8A0) +#define EPDC_PIGEON_3_0 (epdc_v2_base + 0x8C0) +#define EPDC_PIGEON_3_1 (epdc_v2_base + 0x8D0) +#define EPDC_PIGEON_3_2 (epdc_v2_base + 0x8E0) +#define EPDC_PIGEON_4_0 (epdc_v2_base + 0x900) +#define EPDC_PIGEON_4_1 (epdc_v2_base + 0x910) +#define EPDC_PIGEON_4_2 (epdc_v2_base + 0x920) +#define EPDC_PIGEON_5_0 (epdc_v2_base + 0x940) +#define EPDC_PIGEON_5_1 (epdc_v2_base + 0x950) +#define EPDC_PIGEON_5_2 (epdc_v2_base + 0x960) +#define EPDC_PIGEON_6_0 (epdc_v2_base + 0x980) +#define EPDC_PIGEON_6_1 (epdc_v2_base + 0x990) +#define EPDC_PIGEON_6_2 (epdc_v2_base + 0x9A0) +#define EPDC_PIGEON_7_0 (epdc_v2_base + 0x9C0) +#define EPDC_PIGEON_7_1 (epdc_v2_base + 0x9D0) +#define EPDC_PIGEON_7_2 (epdc_v2_base + 0x9E0) +#define EPDC_PIGEON_8_0 (epdc_v2_base + 0xA00) +#define EPDC_PIGEON_8_1 (epdc_v2_base + 0xA10) +#define EPDC_PIGEON_8_2 (epdc_v2_base + 0xA20) +#define EPDC_PIGEON_9_0 (epdc_v2_base + 0xA40) +#define EPDC_PIGEON_9_1 (epdc_v2_base + 0xA50) +#define EPDC_PIGEON_9_2 (epdc_v2_base + 0xA60) +#define EPDC_PIGEON_10_0 (epdc_v2_base + 0xA80) +#define EPDC_PIGEON_10_1 (epdc_v2_base + 0xA90) +#define EPDC_PIGEON_10_2 (epdc_v2_base + 0xAA0) +#define EPDC_PIGEON_11_0 (epdc_v2_base + 0xAC0) +#define EPDC_PIGEON_11_1 (epdc_v2_base + 0xAD0) +#define EPDC_PIGEON_11_2 (epdc_v2_base + 0xAE0) +#define EPDC_PIGEON_12_0 (epdc_v2_base + 0xB00) +#define EPDC_PIGEON_12_1 (epdc_v2_base + 0xB10) +#define EPDC_PIGEON_12_2 (epdc_v2_base + 0xB20) +#define EPDC_PIGEON_13_0 (epdc_v2_base + 0xB40) +#define EPDC_PIGEON_13_1 (epdc_v2_base + 0xB50) +#define EPDC_PIGEON_13_2 (epdc_v2_base + 0xB60) +#define EPDC_PIGEON_14_0 (epdc_v2_base + 0xB80) +#define EPDC_PIGEON_14_1 (epdc_v2_base + 0xB90) +#define EPDC_PIGEON_14_2 (epdc_v2_base + 0xBA0) +#define EPDC_PIGEON_15_0 (epdc_v2_base + 0xBC0) +#define EPDC_PIGEON_15_1 (epdc_v2_base + 0xBD0) +#define EPDC_PIGEON_15_2 (epdc_v2_base + 0xBE0) +#define EPDC_PIGEON_16_0 (epdc_v2_base + 0xC00) +#define EPDC_PIGEON_16_1 (epdc_v2_base + 0xC10) +#define EPDC_PIGEON_16_2 (epdc_v2_base + 0xC20) +#define EPDC_WB_ADDR_TCE (epdc_v2_base + 0x010) + +/* + * Register field definitions + */ + +enum { +/* EPDC_CTRL field values */ + EPDC_CTRL_SFTRST = 0x80000000, + EPDC_CTRL_CLKGATE = 0x40000000, + EPDC_CTRL_SRAM_POWERDOWN = 0x100, + EPDC_CTRL_UPD_DATA_SWIZZLE_MASK = 0xC0, + EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP = 0, + EPDC_CTRL_UPD_DATA_SWIZZLE_ALL_BYTES_SWAP = 0x40, + EPDC_CTRL_UPD_DATA_SWIZZLE_HWD_SWAP = 0x80, + EPDC_CTRL_UPD_DATA_SWIZZLE_HWD_BYTE_SWAP = 0xC0, + EPDC_CTRL_LUT_DATA_SWIZZLE_MASK = 0x30, + EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP = 0, + EPDC_CTRL_LUT_DATA_SWIZZLE_ALL_BYTES_SWAP = 0x10, + EPDC_CTRL_LUT_DATA_SWIZZLE_HWD_SWAP = 0x20, + EPDC_CTRL_LUT_DATA_SWIZZLE_HWD_BYTE_SWAP = 0x30, + EPDC_CTRL_BURST_LEN_8_8 = 0x1, + EPDC_CTRL_BURST_LEN_8_16 = 0, + +/* EPDC_RES field values */ + EPDC_RES_VERTICAL_MASK = 0x1FFF0000, + EPDC_RES_VERTICAL_OFFSET = 16, + EPDC_RES_HORIZONTAL_MASK = 0x1FFF, + EPDC_RES_HORIZONTAL_OFFSET = 0, + +/* EPDC_FORMAT field values */ + EPDC_FORMAT_BUF_PIXEL_SCALE_ROUND = 0x1000000, + EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK = 0xFF0000, + EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET = 16, + EPDC_FORMAT_WB_ADDR_NO_COPY = 0x4000, + EPDC_FORMAT_WB_TYPE_MASK = 0x3000, + EPDC_FORMAT_WB_TYPE_OFFSET = 12, + EPDC_FORMAT_WB_TYPE_WB_INTERNAL = 0x0, + EPDC_FORMAT_WB_TYPE_WB_WAVEFORM = 0x1000, + EPDC_FORMAT_WB_TYPE_WB_EXTERNAL16 = 0x2000, + EPDC_FORMAT_WB_TYPE_WB_EXTERNAL32 = 0x3000, + EPDC_FORMAT_WB_COMPRESS = 0x800, + EPDC_FORMAT_BUF_PIXEL_FORMAT_MASK = 0x700, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P2N = 0x200, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P3N = 0x300, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N = 0x400, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N = 0x500, + EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT = 0x0, + EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT_VCOM = 0x1, + EPDC_FORMAT_TFT_PIXEL_FORMAT_4BIT = 0x2, + EPDC_FORMAT_TFT_PIXEL_FORMAT_4BIT_VCOM = 0x3, + +/* EPDC_WB_FIELD field values */ + EPDC_WB_FIELD_FIXED_MASK = 0xFF000000, + EPDC_WB_FIELD_FIXED_OFFSET = 24, + EPDC_WB_FIELD_USE_FIXED_MASK = 0x30000, + EPDC_WB_FIELD_USE_FIXED_OFFSET = 16, + EPDC_WB_FIELD_USE_FIXED_NO_FIXED = 0x0, + EPDC_WB_FIELD_USE_FIXED_USE_FIXED = 0x1, + EPDC_WB_FIELD_USE_FIXED_NE_FIXED = 0x2, + EPDC_WB_FIELD_USE_FIXED_EQ_FIXED = 0x3, + EPDC_WB_FIELD_USAGE_MASK = 0xE000, + EPDC_WB_FIELD_USAGE_OFFSET = 13, + EPDC_WB_FIELD_USAGE_NOT_USED = 0x0, + EPDC_WB_FIELD_USAGE_PARTIAL = 0x3, + EPDC_WB_FIELD_USAGE_LUT = 0x4, + EPDC_WB_FIELD_USAGE_CP = 0x5, + EPDC_WB_FIELD_USAGE_NP = 0x6, + EPDC_WB_FIELD_USAGE_PTS = 0x7, + EPDC_WB_FIELD_FROM_MASK = 0x1F00, + EPDC_WB_FIELD_FROM_OFFSET = 8, + EPDC_WB_FIELD_TO_MASK = 0xF0, + EPDC_WB_FIELD_TO_OFFSET = 4, + EPDC_WB_FIELD_LEN_MASK = 0xF, + EPDC_WB_FIELD_LEN_OFFSET = 0, + +/* EPDC_FIFOCTRL field values */ + EPDC_FIFOCTRL_ENABLE_PRIORITY = 0x80000000, + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK = 0xFF0000, + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET = 16, + EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK = 0xFF00, + EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET = 8, + EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK = 0xFF, + EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET = 0, + +/* EPDC_UPD_CORD field values */ + EPDC_UPD_CORD_YCORD_MASK = 0x1FFF0000, + EPDC_UPD_CORD_YCORD_OFFSET = 16, + EPDC_UPD_CORD_XCORD_MASK = 0x1FFF, + EPDC_UPD_CORD_XCORD_OFFSET = 0, + +/* EPDC_UPD_SIZE field values */ + EPDC_UPD_SIZE_HEIGHT_MASK = 0x1FFF0000, + EPDC_UPD_SIZE_HEIGHT_OFFSET = 16, + EPDC_UPD_SIZE_WIDTH_MASK = 0x1FFF, + EPDC_UPD_SIZE_WIDTH_OFFSET = 0, + +/* EPDC_UPD_CTRL field values */ + EPDC_UPD_CTRL_USE_FIXED = 0x80000000, + EPDC_UPD_CTRL_LUT_SEL_MASK = 0x3F0000, + EPDC_UPD_CTRL_LUT_SEL_OFFSET = 16, + EPDC_UPD_CTRL_WAVEFORM_MODE_MASK = 0xFF00, + EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET = 8, + EPDC_UPD_CTRL_NO_LUT_CANCEL = 0x10, + EPDC_UPD_CTRL_AUTOWV_PAUSE = 0x8, + EPDC_UPD_CTRL_AUTOWV = 0x4, + EPDC_UPD_CTRL_DRY_RUN = 0x2, + EPDC_UPD_CTRL_UPDATE_MODE_FULL = 0x1, + +/* EPDC_UPD_FIXED field values */ + EPDC_UPD_FIXED_FIXNP_EN = 0x80000000, + EPDC_UPD_FIXED_FIXCP_EN = 0x40000000, + EPDC_UPD_FIXED_FIXNP_MASK = 0xFF00, + EPDC_UPD_FIXED_FIXNP_OFFSET = 8, + EPDC_UPD_FIXED_FIXCP_MASK = 0xFF, + EPDC_UPD_FIXED_FIXCP_OFFSET = 0, + +/* EPDC_AUTOWV_LUT field values */ + EPDC_AUTOWV_LUT_DATA_MASK = 0xFF0000, + EPDC_AUTOWV_LUT_DATA_OFFSET = 16, + EPDC_AUTOWV_LUT_ADDR_MASK = 0x7, + EPDC_AUTOWV_LUT_ADDR_OFFSET = 0, + +/* EPDC_TCE_CTRL field values */ + EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK = 0x1FF0000, + EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET = 16, + EPDC_TCE_CTRL_VCOM_VAL_MASK = 0xC00, + EPDC_TCE_CTRL_VCOM_VAL_OFFSET = 10, + EPDC_TCE_CTRL_VCOM_MODE_AUTO = 0x200, + EPDC_TCE_CTRL_VCOM_MODE_MANUAL = 0x000, + EPDC_TCE_CTRL_DDR_MODE_ENABLE = 0x100, + EPDC_TCE_CTRL_LVDS_MODE_CE_ENABLE = 0x80, + EPDC_TCE_CTRL_LVDS_MODE_ENABLE = 0x40, + EPDC_TCE_CTRL_SCAN_DIR_1_UP = 0x20, + EPDC_TCE_CTRL_SCAN_DIR_0_UP = 0x10, + EPDC_TCE_CTRL_DUAL_SCAN_ENABLE = 0x8, + EPDC_TCE_CTRL_SDDO_WIDTH_16BIT = 0x4, + EPDC_TCE_CTRL_PIXELS_PER_SDCLK_2 = 1, + EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4 = 2, + EPDC_TCE_CTRL_PIXELS_PER_SDCLK_8 = 3, + +/* EPDC_TCE_SDCFG field values */ + EPDC_TCE_SDCFG_SDCLK_HOLD = 0x200000, + EPDC_TCE_SDCFG_SDSHR = 0x100000, + EPDC_TCE_SDCFG_NUM_CE_MASK = 0xF0000, + EPDC_TCE_SDCFG_NUM_CE_OFFSET = 16, + EPDC_TCE_SDCFG_SDDO_REFORMAT_STANDARD = 0, + EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS = 0x4000, + EPDC_TCE_SDCFG_SDDO_INVERT_ENABLE = 0x2000, + EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK = 0x1FFF, + EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET = 0, + +/* EPDC_TCE_GDCFG field values */ + EPDC_TCE_SDCFG_GDRL = 0x10, + EPDC_TCE_SDCFG_GDOE_MODE_DELAYED_GDCLK = 0x2, + EPDC_TCE_SDCFG_GDSP_MODE_FRAME_SYNC = 0x1, + EPDC_TCE_SDCFG_GDSP_MODE_ONE_LINE = 0x0, + +/* EPDC_TCE_HSCAN1 field values */ + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK = 0xFFF0000, + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET = 16, + EPDC_TCE_HSCAN1_LINE_SYNC_MASK = 0xFFF, + EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET = 0, + +/* EPDC_TCE_HSCAN2 field values */ + EPDC_TCE_HSCAN2_LINE_END_MASK = 0xFFF0000, + EPDC_TCE_HSCAN2_LINE_END_OFFSET = 16, + EPDC_TCE_HSCAN2_LINE_BEGIN_MASK = 0xFFF, + EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET = 0, + +/* EPDC_TCE_VSCAN field values */ + EPDC_TCE_VSCAN_FRAME_END_MASK = 0xFF0000, + EPDC_TCE_VSCAN_FRAME_END_OFFSET = 16, + EPDC_TCE_VSCAN_FRAME_BEGIN_MASK = 0xFF00, + EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET = 8, + EPDC_TCE_VSCAN_FRAME_SYNC_MASK = 0xFF, + EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET = 0, + +/* EPDC_TCE_OE field values */ + EPDC_TCE_OE_SDOED_WIDTH_MASK = 0xFF000000, + EPDC_TCE_OE_SDOED_WIDTH_OFFSET = 24, + EPDC_TCE_OE_SDOED_DLY_MASK = 0xFF0000, + EPDC_TCE_OE_SDOED_DLY_OFFSET = 16, + EPDC_TCE_OE_SDOEZ_WIDTH_MASK = 0xFF00, + EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET = 8, + EPDC_TCE_OE_SDOEZ_DLY_MASK = 0xFF, + EPDC_TCE_OE_SDOEZ_DLY_OFFSET = 0, + +/* EPDC_TCE_POLARITY field values */ + EPDC_TCE_POLARITY_GDSP_POL_ACTIVE_HIGH = 0x10, + EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH = 0x8, + EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH = 0x4, + EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH = 0x2, + EPDC_TCE_POLARITY_SDCE_POL_ACTIVE_HIGH = 0x1, + +/* EPDC_TCE_TIMING1 field values */ + EPDC_TCE_TIMING1_SDLE_SHIFT_NONE = 0x00, + EPDC_TCE_TIMING1_SDLE_SHIFT_1 = 0x10, + EPDC_TCE_TIMING1_SDLE_SHIFT_2 = 0x20, + EPDC_TCE_TIMING1_SDLE_SHIFT_3 = 0x30, + EPDC_TCE_TIMING1_SDCLK_INVERT = 0x8, + EPDC_TCE_TIMING1_SDCLK_SHIFT_NONE = 0, + EPDC_TCE_TIMING1_SDCLK_SHIFT_1CYCLE = 1, + EPDC_TCE_TIMING1_SDCLK_SHIFT_2CYCLES = 2, + EPDC_TCE_TIMING1_SDCLK_SHIFT_3CYCLES = 3, + +/* EPDC_TCE_TIMING2 field values */ + EPDC_TCE_TIMING2_GDCLK_HP_MASK = 0xFFFF0000, + EPDC_TCE_TIMING2_GDCLK_HP_OFFSET = 16, + EPDC_TCE_TIMING2_GDSP_OFFSET_MASK = 0xFFFF, + EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET = 0, + +/* EPDC_TCE_TIMING3 field values */ + EPDC_TCE_TIMING3_GDOE_OFFSET_MASK = 0xFFFF0000, + EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET = 16, + EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK = 0xFFFF, + EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET = 0, + +/* EPDC EPDC_PIGEON_CTRL0 field values */ + EPDC_PIGEON_CTRL0_LD_PERIOD_MASK = 0xFFF0000, + EPDC_PIGEON_CTRL0_LD_PERIOD_OFFSET = 16, + EPDC_PIGEON_CTRL0_FD_PERIOD_MASK = 0xFFF, + EPDC_PIGEON_CTRL0_FD_PERIOD_OFFSET = 0, + +/* EPDC EPDC_PIGEON_CTRL1 field values */ + EPDC_PIGEON_CTRL1_LD_PERIOD_MASK = 0xFFF0000, + EPDC_PIGEON_CTRL1_LD_PERIOD_OFFSET = 16, + EPDC_PIGEON_CTRL1_FD_PERIOD_MASK = 0xFFF, + EPDC_PIGEON_CTRL1_FD_PERIOD_OFFSET = 0, + +/* EPDC_IRQ_MASK/EPDC_IRQ field values */ + EPDC_IRQ_WB_CMPLT_IRQ = 0x10000, + EPDC_IRQ_LUT_COL_IRQ = 0x20000, + EPDC_IRQ_TCE_UNDERRUN_IRQ = 0x40000, + EPDC_IRQ_FRAME_END_IRQ = 0x80000, + EPDC_IRQ_BUS_ERROR_IRQ = 0x100000, + EPDC_IRQ_TCE_IDLE_IRQ = 0x200000, + EPDC_IRQ_UPD_DONE_IRQ = 0x400000, + EPDC_IRQ_PWR_IRQ = 0x800000, + +/* EPDC_STATUS_NEXTLUT field values */ + EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID = 0x100, + EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK = 0x3F, + EPDC_STATUS_NEXTLUT_NEXT_LUT_OFFSET = 0, + +/* EPDC_STATUS field values */ + EPDC_STATUS_HISTOGRAM_CP_MASK = 0x1F0000, + EPDC_STATUS_HISTOGRAM_CP_OFFSET = 16, + EPDC_STATUS_HISTOGRAM_NP_MASK = 0x1F00, + EPDC_STATUS_HISTOGRAM_NP_OFFSET = 8, + EPDC_STATUS_UPD_VOID = 0x8, + EPDC_STATUS_LUTS_UNDERRUN = 0x4, + EPDC_STATUS_LUTS_BUSY = 0x2, + EPDC_STATUS_WB_BUSY = 0x1, + +/* EPDC_UPD_COL_CORD field values */ + EPDC_UPD_COL_CORD_YCORD_MASK = 0x1FFF0000, + EPDC_UPD_COL_CORD_YCORD_OFFSET = 16, + EPDC_UPD_COL_CORD_XCORD_MASK = 0x1FFF, + EPDC_UPD_COL_CORD_XCORD_OFFSET = 0, + +/* EPDC_UPD_COL_SIZE field values */ + EPDC_UPD_COL_SIZE_HEIGHT_MASK = 0x1FFF0000, + EPDC_UPD_COL_SIZE_HEIGHT_OFFSET = 16, + EPDC_UPD_COL_SIZE_WIDTH_MASK = 0x1FFF, + EPDC_UPD_COL_SIZE_WIDTH_OFFSET = 0, + +/* EPDC_DEBUG field values */ + EPDC_DEBUG_DEBUG_LUT_SEL_MASK = 0x3F00000, + EPDC_DEBUG_DEBUG_LUT_SEL_OFFSET = 24, + EPDC_DEBUG_UBW_BURST_LEN_MASK = 0xF000, + EPDC_DEBUG_UBW_BURST_LEN_OFFSET = 12, + EPDC_DEBUG_UBR_BURST_LEN_MASK = 0xF00, + EPDC_DEBUG_UBR_BURST_LEN = 8, + EPDC_DEBUG_UPD_BURST_LEN_MASK = 0xF0, + EPDC_DEBUG_UPD_BURST_LEN_OFFSET = 4, + EPDC_DEBUG_UPDATE_SAME = 0x4, + EPDC_DEBUG_UNDERRUN_RECOVER = 0x2, + EPDC_DEBUG_COLLISION_OFF = 0x1, + +/* EPDC_DEBUG_LUT field values */ + EPDC_DEBUG_LUT_LUTADDR_MASK = 0x3FF0000, + EPDC_DEBUG_LUT_LUTADDR_OFFSET = 16, + EPDC_DEBUG_LUT_FRAME_MASK = 0x7FE0, + EPDC_DEBUG_LUT_FRAME_OFFSET = 5, + EPDC_DEBUG_LUT_STATEMACHINE_MASK = 0x1F, + EPDC_DEBUG_LUT_STATEMACHINE_OFFSET = 0, + +/* EPDC_HISTx_PARAM field values */ + EPDC_HIST_PARAM_VALUE0_MASK = 0x1F, + EPDC_HIST_PARAM_VALUE0_OFFSET = 0, + EPDC_HIST_PARAM_VALUE1_MASK = 0x1F00, + EPDC_HIST_PARAM_VALUE1_OFFSET = 8, + EPDC_HIST_PARAM_VALUE2_MASK = 0x1F0000, + EPDC_HIST_PARAM_VALUE2_OFFSET = 16, + EPDC_HIST_PARAM_VALUE3_MASK = 0x1F000000, + EPDC_HIST_PARAM_VALUE3_OFFSET = 24, + EPDC_HIST_PARAM_VALUE4_MASK = 0x1F, + EPDC_HIST_PARAM_VALUE4_OFFSET = 0, + EPDC_HIST_PARAM_VALUE5_MASK = 0x1F00, + EPDC_HIST_PARAM_VALUE5_OFFSET = 8, + EPDC_HIST_PARAM_VALUE6_MASK = 0x1F0000, + EPDC_HIST_PARAM_VALUE6_OFFSET = 16, + EPDC_HIST_PARAM_VALUE7_MASK = 0x1F000000, + EPDC_HIST_PARAM_VALUE7_OFFSET = 24, + EPDC_HIST_PARAM_VALUE8_MASK = 0x1F, + EPDC_HIST_PARAM_VALUE8_OFFSET = 0, + EPDC_HIST_PARAM_VALUE9_MASK = 0x1F00, + EPDC_HIST_PARAM_VALUE9_OFFSET = 8, + EPDC_HIST_PARAM_VALUE10_MASK = 0x1F0000, + EPDC_HIST_PARAM_VALUE10_OFFSET = 16, + EPDC_HIST_PARAM_VALUE11_MASK = 0x1F000000, + EPDC_HIST_PARAM_VALUE11_OFFSET = 24, + EPDC_HIST_PARAM_VALUE12_MASK = 0x1F, + EPDC_HIST_PARAM_VALUE12_OFFSET = 0, + EPDC_HIST_PARAM_VALUE13_MASK = 0x1F00, + EPDC_HIST_PARAM_VALUE13_OFFSET = 8, + EPDC_HIST_PARAM_VALUE14_MASK = 0x1F0000, + EPDC_HIST_PARAM_VALUE14_OFFSET = 16, + EPDC_HIST_PARAM_VALUE15_MASK = 0x1F000000, + EPDC_HIST_PARAM_VALUE15_OFFSET = 24, + +/* EPDC_GPIO field values */ + EPDC_GPIO_PWRSTAT = 0x100, + EPDC_GPIO_PWRWAKE = 0x80, + EPDC_GPIO_PWRCOM = 0x40, + EPDC_GPIO_PWRCTRL_MASK = 0x3C, + EPDC_GPIO_PWRCTRL_OFFSET = 2, + EPDC_GPIO_BDR_MASK = 0x3, + EPDC_GPIO_BDR_OFFSET = 0, + +/* EPDC_VERSION field values */ + EPDC_VERSION_MAJOR_MASK = 0xFF000000, + EPDC_VERSION_MAJOR_OFFSET = 24, + EPDC_VERSION_MINOR_MASK = 0xFF0000, + EPDC_VERSION_MINOR_OFFSET = 16, + EPDC_VERSION_STEP_MASK = 0xFFFF, + EPDC_VERSION_STEP_OFFSET = 0, +}; + +#endif /* __EPDC_REGS_INCLUDED__ */ diff --git a/drivers/video/fbdev/mxc/ldb.c b/drivers/video/fbdev/mxc/ldb.c new file mode 100644 index 000000000000..1d6c9e08590c --- /dev/null +++ b/drivers/video/fbdev/mxc/ldb.c @@ -0,0 +1,910 @@ +/* + * Copyright (C) 2012-2015 Freescale Semiconductor, Inc. All Rights Reserved. + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/types.h> +#include <video/of_videomode.h> +#include <video/videomode.h> +#include "mxc_dispdrv.h" + +#define DRIVER_NAME "ldb" + +#define LDB_BGREF_RMODE_INT (0x1 << 15) + +#define LDB_DI1_VS_POL_ACT_LOW (0x1 << 10) +#define LDB_DI0_VS_POL_ACT_LOW (0x1 << 9) + +#define LDB_BIT_MAP_CH1_JEIDA (0x1 << 8) +#define LDB_BIT_MAP_CH0_JEIDA (0x1 << 6) + +#define LDB_DATA_WIDTH_CH1_24 (0x1 << 7) +#define LDB_DATA_WIDTH_CH0_24 (0x1 << 5) + +#define LDB_CH1_MODE_MASK (0x3 << 2) +#define LDB_CH1_MODE_EN_TO_DI1 (0x3 << 2) +#define LDB_CH1_MODE_EN_TO_DI0 (0x1 << 2) +#define LDB_CH0_MODE_MASK (0x3 << 0) +#define LDB_CH0_MODE_EN_TO_DI1 (0x3 << 0) +#define LDB_CH0_MODE_EN_TO_DI0 (0x1 << 0) + +#define LDB_SPLIT_MODE_EN (0x1 << 4) + +#define INVALID_BUS_REG (~0UL) + +struct crtc_mux { + enum crtc crtc; + u32 val; +}; + +struct bus_mux { + int reg; + int shift; + int mask; + int crtc_mux_num; + const struct crtc_mux *crtcs; +}; + +struct ldb_info { + bool split_cap; + bool dual_cap; + bool ext_bgref_cap; + bool clk_fixup; + int ctrl_reg; + int bus_mux_num; + const struct bus_mux *buses; +}; + +struct ldb_data; + +struct ldb_chan { + struct ldb_data *ldb; + struct fb_info *fbi; + struct videomode vm; + enum crtc crtc; + int chno; + bool is_used; + bool online; +}; + +struct ldb_data { + struct regmap *regmap; + struct device *dev; + struct mxc_dispdrv_handle *mddh; + struct ldb_chan chan[2]; + int bus_mux_num; + const struct bus_mux *buses; + int primary_chno; + int ctrl_reg; + u32 ctrl; + bool spl_mode; + bool dual_mode; + bool clk_fixup; + struct clk *di_clk[4]; + struct clk *ldb_di_clk[2]; + struct clk *div_3_5_clk[2]; + struct clk *div_7_clk[2]; + struct clk *div_sel_clk[2]; +}; + +static const struct crtc_mux imx6q_lvds0_crtc_mux[] = { + { + .crtc = CRTC_IPU1_DI0, + .val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU1_DI0, + }, { + .crtc = CRTC_IPU1_DI1, + .val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU1_DI1, + }, { + .crtc = CRTC_IPU2_DI0, + .val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU2_DI0, + }, { + .crtc = CRTC_IPU2_DI1, + .val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU2_DI1, + } +}; + +static const struct crtc_mux imx6q_lvds1_crtc_mux[] = { + { + .crtc = CRTC_IPU1_DI0, + .val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU1_DI0, + }, { + .crtc = CRTC_IPU1_DI1, + .val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU1_DI1, + }, { + .crtc = CRTC_IPU2_DI0, + .val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU2_DI0, + }, { + .crtc = CRTC_IPU2_DI1, + .val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU2_DI1, + } +}; + +static const struct bus_mux imx6q_ldb_buses[] = { + { + .reg = IOMUXC_GPR3, + .shift = 6, + .mask = IMX6Q_GPR3_LVDS0_MUX_CTL_MASK, + .crtc_mux_num = ARRAY_SIZE(imx6q_lvds0_crtc_mux), + .crtcs = imx6q_lvds0_crtc_mux, + }, { + .reg = IOMUXC_GPR3, + .shift = 8, + .mask = IMX6Q_GPR3_LVDS1_MUX_CTL_MASK, + .crtc_mux_num = ARRAY_SIZE(imx6q_lvds1_crtc_mux), + .crtcs = imx6q_lvds1_crtc_mux, + } +}; + +static const struct ldb_info imx6q_ldb_info = { + .split_cap = true, + .dual_cap = true, + .ext_bgref_cap = false, + .clk_fixup = false, + .ctrl_reg = IOMUXC_GPR2, + .bus_mux_num = ARRAY_SIZE(imx6q_ldb_buses), + .buses = imx6q_ldb_buses, +}; + +static const struct ldb_info imx6qp_ldb_info = { + .split_cap = true, + .dual_cap = true, + .ext_bgref_cap = false, + .clk_fixup = true, + .ctrl_reg = IOMUXC_GPR2, + .bus_mux_num = ARRAY_SIZE(imx6q_ldb_buses), + .buses = imx6q_ldb_buses, +}; + +static const struct crtc_mux imx6dl_lvds0_crtc_mux[] = { + { + .crtc = CRTC_IPU1_DI0, + .val = IMX6DL_GPR3_LVDS0_MUX_CTL_IPU1_DI0, + }, { + .crtc = CRTC_IPU1_DI1, + .val = IMX6DL_GPR3_LVDS0_MUX_CTL_IPU1_DI1, + }, { + .crtc = CRTC_LCDIF1, + .val = IMX6DL_GPR3_LVDS0_MUX_CTL_LCDIF, + } +}; + +static const struct crtc_mux imx6dl_lvds1_crtc_mux[] = { + { + .crtc = CRTC_IPU1_DI0, + .val = IMX6DL_GPR3_LVDS1_MUX_CTL_IPU1_DI0, + }, { + .crtc = CRTC_IPU1_DI1, + .val = IMX6DL_GPR3_LVDS1_MUX_CTL_IPU1_DI1, + }, { + .crtc = CRTC_LCDIF1, + .val = IMX6DL_GPR3_LVDS1_MUX_CTL_LCDIF, + } +}; + +static const struct bus_mux imx6dl_ldb_buses[] = { + { + .reg = IOMUXC_GPR3, + .shift = 6, + .mask = IMX6DL_GPR3_LVDS0_MUX_CTL_MASK, + .crtc_mux_num = ARRAY_SIZE(imx6dl_lvds0_crtc_mux), + .crtcs = imx6dl_lvds0_crtc_mux, + }, { + .reg = IOMUXC_GPR3, + .shift = 8, + .mask = IMX6DL_GPR3_LVDS1_MUX_CTL_MASK, + .crtc_mux_num = ARRAY_SIZE(imx6dl_lvds1_crtc_mux), + .crtcs = imx6dl_lvds1_crtc_mux, + } +}; + +static const struct ldb_info imx6dl_ldb_info = { + .split_cap = true, + .dual_cap = true, + .ext_bgref_cap = false, + .clk_fixup = false, + .ctrl_reg = IOMUXC_GPR2, + .bus_mux_num = ARRAY_SIZE(imx6dl_ldb_buses), + .buses = imx6dl_ldb_buses, +}; + +static const struct crtc_mux imx6sx_lvds_crtc_mux[] = { + { + .crtc = CRTC_LCDIF1, + .val = IMX6SX_GPR5_DISP_MUX_LDB_CTRL_LCDIF1, + }, { + .crtc = CRTC_LCDIF2, + .val = IMX6SX_GPR5_DISP_MUX_LDB_CTRL_LCDIF2, + } +}; + +static const struct bus_mux imx6sx_ldb_buses[] = { + { + .reg = IOMUXC_GPR5, + .shift = 3, + .mask = IMX6SX_GPR5_DISP_MUX_LDB_CTRL_MASK, + .crtc_mux_num = ARRAY_SIZE(imx6sx_lvds_crtc_mux), + .crtcs = imx6sx_lvds_crtc_mux, + } +}; + +static const struct ldb_info imx6sx_ldb_info = { + .split_cap = false, + .dual_cap = false, + .ext_bgref_cap = false, + .clk_fixup = false, + .ctrl_reg = IOMUXC_GPR6, + .bus_mux_num = ARRAY_SIZE(imx6sx_ldb_buses), + .buses = imx6sx_ldb_buses, +}; + +static const struct crtc_mux imx53_lvds0_crtc_mux[] = { + { .crtc = CRTC_IPU1_DI0, }, +}; + +static const struct crtc_mux imx53_lvds1_crtc_mux[] = { + { .crtc = CRTC_IPU1_DI1, } +}; + +static const struct bus_mux imx53_ldb_buses[] = { + { + .reg = INVALID_BUS_REG, + .crtc_mux_num = ARRAY_SIZE(imx53_lvds0_crtc_mux), + .crtcs = imx53_lvds0_crtc_mux, + }, { + .reg = INVALID_BUS_REG, + .crtc_mux_num = ARRAY_SIZE(imx53_lvds1_crtc_mux), + .crtcs = imx53_lvds1_crtc_mux, + } +}; + +static const struct ldb_info imx53_ldb_info = { + .split_cap = true, + .dual_cap = false, + .ext_bgref_cap = true, + .clk_fixup = false, + .ctrl_reg = IOMUXC_GPR2, + .bus_mux_num = ARRAY_SIZE(imx53_ldb_buses), + .buses = imx53_ldb_buses, +}; + +static const struct of_device_id ldb_dt_ids[] = { + { .compatible = "fsl,imx6qp-ldb", .data = &imx6qp_ldb_info, }, + { .compatible = "fsl,imx6q-ldb", .data = &imx6q_ldb_info, }, + { .compatible = "fsl,imx6dl-ldb", .data = &imx6dl_ldb_info, }, + { .compatible = "fsl,imx6sx-ldb", .data = &imx6sx_ldb_info, }, + { .compatible = "fsl,imx53-ldb", .data = &imx53_ldb_info, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ldb_dt_ids); + +static int ldb_init(struct mxc_dispdrv_handle *mddh, + struct mxc_dispdrv_setting *setting) +{ + struct ldb_data *ldb = mxc_dispdrv_getdata(mddh); + struct device *dev = ldb->dev; + struct fb_info *fbi = setting->fbi; + struct ldb_chan *chan; + struct fb_videomode fb_vm; + int chno; + + chno = ldb->chan[ldb->primary_chno].is_used ? + !ldb->primary_chno : ldb->primary_chno; + + chan = &ldb->chan[chno]; + + if (chan->is_used) { + dev_err(dev, "LVDS channel%d is already used\n", chno); + return -EBUSY; + } + if (!chan->online) { + dev_err(dev, "LVDS channel%d is not online\n", chno); + return -ENODEV; + } + + chan->is_used = true; + + chan->fbi = fbi; + + fb_videomode_from_videomode(&chan->vm, &fb_vm); + fb_videomode_to_var(&fbi->var, &fb_vm); + + setting->crtc = chan->crtc; + + return 0; +} + +static int get_di_clk_id(struct ldb_chan chan, int *id) +{ + struct ldb_data *ldb = chan.ldb; + int i = 0, chno = chan.chno, mask, shift; + enum crtc crtc; + u32 val; + + /* no pre-muxing, such as mx53 */ + if (ldb->buses[chno].reg == INVALID_BUS_REG) { + *id = chno; + return 0; + } + + for (; i < ldb->buses[chno].crtc_mux_num; i++) { + crtc = ldb->buses[chno].crtcs[i].crtc; + val = ldb->buses[chno].crtcs[i].val; + mask = ldb->buses[chno].mask; + shift = ldb->buses[chno].shift; + if (chan.crtc == crtc) { + *id = (val & mask) >> shift; + return 0; + } + } + + return -EINVAL; +} + +static int get_mux_val(struct bus_mux bus_mux, enum crtc crtc, + u32 *mux_val) +{ + int i = 0; + + for (; i < bus_mux.crtc_mux_num; i++) + if (bus_mux.crtcs[i].crtc == crtc) { + *mux_val = bus_mux.crtcs[i].val; + return 0; + } + + return -EINVAL; +} + +static int find_ldb_chno(struct ldb_data *ldb, + struct fb_info *fbi, int *chno) +{ + struct device *dev = ldb->dev; + int i = 0; + + for (; i < 2; i++) + if (ldb->chan[i].fbi == fbi) { + *chno = ldb->chan[i].chno; + return 0; + } + dev_err(dev, "failed to find channel number\n"); + return -EINVAL; +} + +static void ldb_disable(struct mxc_dispdrv_handle *mddh, + struct fb_info *fbi); + +static int ldb_setup(struct mxc_dispdrv_handle *mddh, + struct fb_info *fbi) +{ + struct ldb_data *ldb = mxc_dispdrv_getdata(mddh); + struct ldb_chan chan; + struct device *dev = ldb->dev; + struct clk *ldb_di_parent, *ldb_di_sel, *ldb_di_sel_parent; + struct clk *other_ldb_di_sel = NULL; + struct bus_mux bus_mux; + int ret = 0, id = 0, chno, other_chno; + unsigned long serial_clk; + u32 mux_val; + + ret = find_ldb_chno(ldb, fbi, &chno); + if (ret < 0) + return ret; + + other_chno = chno ? 0 : 1; + + chan = ldb->chan[chno]; + + bus_mux = ldb->buses[chno]; + + ret = get_di_clk_id(chan, &id); + if (ret < 0) { + dev_err(dev, "failed to get ch%d di clk id\n", + chan.chno); + return ret; + } + + ret = get_mux_val(bus_mux, chan.crtc, &mux_val); + if (ret < 0) { + dev_err(dev, "failed to get ch%d mux val\n", + chan.chno); + return ret; + } + + + if (ldb->clk_fixup) { + /* + * ldb_di_sel_parent(plls) -> ldb_di_sel -> ldb_di[chno] -> + * + * -> div_3_5[chno] -> + * -> | |-> div_sel[chno] -> di[id] + * -> div_7[chno] -> + */ + clk_set_parent(ldb->di_clk[id], ldb->div_sel_clk[chno]); + } else { + /* + * ldb_di_sel_parent(plls) -> ldb_di_sel -> + * + * -> div_3_5[chno] -> + * -> | |-> div_sel[chno] -> + * -> div_7[chno] -> + * + * -> ldb_di[chno] -> di[id] + */ + clk_set_parent(ldb->di_clk[id], ldb->ldb_di_clk[chno]); + } + ldb_di_parent = ldb->spl_mode ? ldb->div_3_5_clk[chno] : + ldb->div_7_clk[chno]; + clk_set_parent(ldb->div_sel_clk[chno], ldb_di_parent); + ldb_di_sel = clk_get_parent(ldb_di_parent); + ldb_di_sel_parent = clk_get_parent(ldb_di_sel); + serial_clk = ldb->spl_mode ? chan.vm.pixelclock * 7 / 2 : + chan.vm.pixelclock * 7; + clk_set_rate(ldb_di_sel_parent, serial_clk); + + /* + * split mode or dual mode: + * clock tree for the other channel + */ + if (ldb->spl_mode) { + clk_set_parent(ldb->div_sel_clk[other_chno], + ldb->div_3_5_clk[other_chno]); + other_ldb_di_sel = + clk_get_parent(ldb->div_3_5_clk[other_chno]);; + } + + if (ldb->dual_mode) { + clk_set_parent(ldb->div_sel_clk[other_chno], + ldb->div_7_clk[other_chno]); + other_ldb_di_sel = + clk_get_parent(ldb->div_7_clk[other_chno]);; + } + + if (ldb->spl_mode || ldb->dual_mode) + clk_set_parent(other_ldb_di_sel, ldb_di_sel_parent); + + if (!(chan.fbi->var.sync & FB_SYNC_VERT_HIGH_ACT)) { + if (ldb->spl_mode && bus_mux.reg == INVALID_BUS_REG) + /* no pre-muxing, such as mx53 */ + ldb->ctrl |= (id == 0 ? LDB_DI0_VS_POL_ACT_LOW : + LDB_DI1_VS_POL_ACT_LOW); + else + ldb->ctrl |= (chno == 0 ? LDB_DI0_VS_POL_ACT_LOW : + LDB_DI1_VS_POL_ACT_LOW); + } + + if (bus_mux.reg != INVALID_BUS_REG) + regmap_update_bits(ldb->regmap, bus_mux.reg, + bus_mux.mask, mux_val); + + regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl); + + /* disable channel for correct sequence */ + ldb_disable(mddh, fbi); + + return ret; +} + +static int ldb_enable(struct mxc_dispdrv_handle *mddh, + struct fb_info *fbi) +{ + struct ldb_data *ldb = mxc_dispdrv_getdata(mddh); + struct ldb_chan chan; + struct device *dev = ldb->dev; + struct bus_mux bus_mux; + int ret = 0, id = 0, chno, other_chno; + + ret = find_ldb_chno(ldb, fbi, &chno); + if (ret < 0) + return ret; + + chan = ldb->chan[chno]; + + bus_mux = ldb->buses[chno]; + + if (ldb->spl_mode || ldb->dual_mode) { + other_chno = chno ? 0 : 1; + clk_prepare_enable(ldb->ldb_di_clk[other_chno]); + } + + if ((ldb->spl_mode || ldb->dual_mode) && + bus_mux.reg == INVALID_BUS_REG) { + /* no pre-muxing, such as mx53 */ + ret = get_di_clk_id(chan, &id); + if (ret < 0) { + dev_err(dev, "failed to get ch%d di clk id\n", + chan.chno); + return ret; + } + + ldb->ctrl |= id ? + (LDB_CH0_MODE_EN_TO_DI1 | LDB_CH1_MODE_EN_TO_DI1) : + (LDB_CH0_MODE_EN_TO_DI0 | LDB_CH1_MODE_EN_TO_DI0); + } else { + if (ldb->spl_mode || ldb->dual_mode) + ldb->ctrl |= LDB_CH0_MODE_EN_TO_DI0 | + LDB_CH1_MODE_EN_TO_DI0; + else + ldb->ctrl |= chno ? LDB_CH1_MODE_EN_TO_DI1 : + LDB_CH0_MODE_EN_TO_DI0; + } + + regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl); + return 0; +} + +static void ldb_disable(struct mxc_dispdrv_handle *mddh, + struct fb_info *fbi) +{ + struct ldb_data *ldb = mxc_dispdrv_getdata(mddh); + int ret, chno, other_chno; + + ret = find_ldb_chno(ldb, fbi, &chno); + if (ret < 0) + return; + + if (ldb->spl_mode || ldb->dual_mode) { + ldb->ctrl &= ~(LDB_CH1_MODE_MASK | LDB_CH0_MODE_MASK); + other_chno = chno ? 0 : 1; + clk_disable_unprepare(ldb->ldb_di_clk[other_chno]); + } else { + ldb->ctrl &= ~(chno ? LDB_CH1_MODE_MASK : + LDB_CH0_MODE_MASK); + } + + regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl); + return; +} + +static struct mxc_dispdrv_driver ldb_drv = { + .name = DRIVER_NAME, + .init = ldb_init, + .setup = ldb_setup, + .enable = ldb_enable, + .disable = ldb_disable +}; + +enum { + LVDS_BIT_MAP_SPWG, + LVDS_BIT_MAP_JEIDA, +}; + +static const char *ldb_bit_mappings[] = { + [LVDS_BIT_MAP_SPWG] = "spwg", + [LVDS_BIT_MAP_JEIDA] = "jeida", +}; + +static int of_get_data_mapping(struct device_node *np) +{ + const char *bm; + int ret, i; + + ret = of_property_read_string(np, "fsl,data-mapping", &bm); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(ldb_bit_mappings); i++) + if (!strcasecmp(bm, ldb_bit_mappings[i])) + return i; + + return -EINVAL; +} + +static const char *ldb_crtc_mappings[] = { + [CRTC_IPU_DI0] = "ipu-di0", + [CRTC_IPU_DI1] = "ipu-di1", + [CRTC_IPU1_DI0] = "ipu1-di0", + [CRTC_IPU1_DI1] = "ipu1-di1", + [CRTC_IPU2_DI0] = "ipu2-di0", + [CRTC_IPU2_DI1] = "ipu2-di1", + [CRTC_LCDIF] = "lcdif", + [CRTC_LCDIF1] = "lcdif1", + [CRTC_LCDIF2] = "lcdif2", +}; + +static enum crtc of_get_crtc_mapping(struct device_node *np) +{ + const char *cm; + enum crtc i; + int ret; + + ret = of_property_read_string(np, "crtc", &cm); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(ldb_crtc_mappings); i++) + if (!strcasecmp(cm, ldb_crtc_mappings[i])) { + switch (i) { + case CRTC_IPU_DI0: + i = CRTC_IPU1_DI0; + break; + case CRTC_IPU_DI1: + i = CRTC_IPU1_DI1; + break; + case CRTC_LCDIF: + i = CRTC_LCDIF1; + break; + default: + break; + } + return i; + } + + return -EINVAL; +} + +static int mux_count(struct ldb_data *ldb) +{ + int i, j, count = 0; + bool should_count[CRTC_MAX]; + enum crtc crtc; + + for (i = 0; i < CRTC_MAX; i++) + should_count[i] = true; + + for (i = 0; i < ldb->bus_mux_num; i++) { + for (j = 0; j < ldb->buses[i].crtc_mux_num; j++) { + crtc = ldb->buses[i].crtcs[j].crtc; + if (should_count[crtc]) { + count++; + should_count[crtc] = false; + } + } + } + + return count; +} + +static bool is_valid_crtc(struct ldb_data *ldb, enum crtc crtc, + int chno) +{ + int i = 0; + + if (chno > ldb->bus_mux_num - 1) + return false; + + for (; i < ldb->buses[chno].crtc_mux_num; i++) + if (ldb->buses[chno].crtcs[i].crtc == crtc) + return true; + + return false; +} + +static int ldb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id = + of_match_device(ldb_dt_ids, dev); + const struct ldb_info *ldb_info = + (const struct ldb_info *)of_id->data; + struct device_node *np = dev->of_node, *child; + struct ldb_data *ldb; + bool ext_ref; + int i, data_width, mapping, child_count = 0; + char clkname[16]; + + ldb = devm_kzalloc(dev, sizeof(*ldb), GFP_KERNEL); + if (!ldb) + return -ENOMEM; + + ldb->regmap = syscon_regmap_lookup_by_phandle(np, "gpr"); + if (IS_ERR(ldb->regmap)) { + dev_err(dev, "failed to get parent regmap\n"); + return PTR_ERR(ldb->regmap); + } + + ldb->dev = dev; + ldb->bus_mux_num = ldb_info->bus_mux_num; + ldb->buses = ldb_info->buses; + ldb->ctrl_reg = ldb_info->ctrl_reg; + ldb->clk_fixup = ldb_info->clk_fixup; + ldb->primary_chno = -1; + + ext_ref = of_property_read_bool(np, "ext-ref"); + if (!ext_ref && ldb_info->ext_bgref_cap) + ldb->ctrl |= LDB_BGREF_RMODE_INT; + + ldb->spl_mode = of_property_read_bool(np, "split-mode"); + if (ldb->spl_mode) { + if (ldb_info->split_cap) { + ldb->ctrl |= LDB_SPLIT_MODE_EN; + dev_info(dev, "split mode\n"); + } else { + dev_err(dev, "cannot support split mode\n"); + return -EINVAL; + } + } + + ldb->dual_mode = of_property_read_bool(np, "dual-mode"); + if (ldb->dual_mode) { + if (ldb_info->dual_cap) { + dev_info(dev, "dual mode\n"); + } else { + dev_err(dev, "cannot support dual mode\n"); + return -EINVAL; + } + } + + if (ldb->dual_mode && ldb->spl_mode) { + dev_err(dev, "cannot support dual mode and split mode " + "simultaneously\n"); + return -EINVAL; + } + + for (i = 0; i < mux_count(ldb); i++) { + sprintf(clkname, "di%d_sel", i); + ldb->di_clk[i] = devm_clk_get(dev, clkname); + if (IS_ERR(ldb->di_clk[i])) { + dev_err(dev, "failed to get clk %s\n", clkname); + return PTR_ERR(ldb->di_clk[i]); + } + } + + for_each_child_of_node(np, child) { + struct ldb_chan *chan; + enum crtc crtc; + bool is_primary; + int ret; + + ret = of_property_read_u32(child, "reg", &i); + if (ret || i < 0 || i > 1 || i >= ldb->bus_mux_num) { + dev_err(dev, "wrong LVDS channel number\n"); + return -EINVAL; + } + + if ((ldb->spl_mode || ldb->dual_mode) && i > 0) { + dev_warn(dev, "split mode or dual mode, ignoring " + "second output\n"); + continue; + } + + if (!of_device_is_available(child)) + continue; + + if (++child_count > ldb->bus_mux_num) { + dev_err(dev, "too many LVDS channels\n"); + return -EINVAL; + } + + chan = &ldb->chan[i]; + chan->chno = i; + chan->ldb = ldb; + chan->online = true; + + is_primary = of_property_read_bool(child, "primary"); + + if (ldb->bus_mux_num == 1 || (ldb->primary_chno == -1 && + (is_primary || ldb->spl_mode || ldb->dual_mode))) + ldb->primary_chno = chan->chno; + + ret = of_property_read_u32(child, "fsl,data-width", + &data_width); + if (ret || (data_width != 18 && data_width != 24)) { + dev_err(dev, "data width not specified or invalid\n"); + return -EINVAL; + } + + mapping = of_get_data_mapping(child); + switch (mapping) { + case LVDS_BIT_MAP_SPWG: + if (data_width == 24) { + if (i == 0 || ldb->spl_mode || ldb->dual_mode) + ldb->ctrl |= LDB_DATA_WIDTH_CH0_24; + if (i == 1 || ldb->spl_mode || ldb->dual_mode) + ldb->ctrl |= LDB_DATA_WIDTH_CH1_24; + } + break; + case LVDS_BIT_MAP_JEIDA: + if (data_width == 18) { + dev_err(dev, "JEIDA only support 24bit\n"); + return -EINVAL; + } + if (i == 0 || ldb->spl_mode || ldb->dual_mode) + ldb->ctrl |= LDB_DATA_WIDTH_CH0_24 | + LDB_BIT_MAP_CH0_JEIDA; + if (i == 1 || ldb->spl_mode || ldb->dual_mode) + ldb->ctrl |= LDB_DATA_WIDTH_CH1_24 | + LDB_BIT_MAP_CH1_JEIDA; + break; + default: + dev_err(dev, "data mapping not specified or invalid\n"); + return -EINVAL; + } + + crtc = of_get_crtc_mapping(child); + if (is_valid_crtc(ldb, crtc, chan->chno)) { + ldb->chan[i].crtc = crtc; + } else { + dev_err(dev, "crtc not specified or invalid\n"); + return -EINVAL; + } + + ret = of_get_videomode(child, &chan->vm, 0); + if (ret) + return -EINVAL; + + sprintf(clkname, "ldb_di%d", i); + ldb->ldb_di_clk[i] = devm_clk_get(dev, clkname); + if (IS_ERR(ldb->ldb_di_clk[i])) { + dev_err(dev, "failed to get clk %s\n", clkname); + return PTR_ERR(ldb->ldb_di_clk[i]); + } + + sprintf(clkname, "ldb_di%d_div_3_5", i); + ldb->div_3_5_clk[i] = devm_clk_get(dev, clkname); + if (IS_ERR(ldb->div_3_5_clk[i])) { + dev_err(dev, "failed to get clk %s\n", clkname); + return PTR_ERR(ldb->div_3_5_clk[i]); + } + + sprintf(clkname, "ldb_di%d_div_7", i); + ldb->div_7_clk[i] = devm_clk_get(dev, clkname); + if (IS_ERR(ldb->div_7_clk[i])) { + dev_err(dev, "failed to get clk %s\n", clkname); + return PTR_ERR(ldb->div_7_clk[i]); + } + + sprintf(clkname, "ldb_di%d_div_sel", i); + ldb->div_sel_clk[i] = devm_clk_get(dev, clkname); + if (IS_ERR(ldb->div_sel_clk[i])) { + dev_err(dev, "failed to get clk %s\n", clkname); + return PTR_ERR(ldb->div_sel_clk[i]); + } + } + + if (child_count == 0) { + dev_err(dev, "failed to find valid LVDS channel\n"); + return -EINVAL; + } + + if (ldb->primary_chno == -1) { + dev_err(dev, "failed to know primary channel\n"); + return -EINVAL; + } + + ldb->mddh = mxc_dispdrv_register(&ldb_drv); + mxc_dispdrv_setdata(ldb->mddh, ldb); + dev_set_drvdata(&pdev->dev, ldb); + + return 0; +} + +static int ldb_remove(struct platform_device *pdev) +{ + struct ldb_data *ldb = dev_get_drvdata(&pdev->dev); + + mxc_dispdrv_puthandle(ldb->mddh); + mxc_dispdrv_unregister(ldb->mddh); + return 0; +} + +static struct platform_driver ldb_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = ldb_dt_ids, + }, + .probe = ldb_probe, + .remove = ldb_remove, +}; + +module_platform_driver(ldb_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("LDB driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/video/fbdev/mxc/mipi_dsi.c b/drivers/video/fbdev/mxc/mipi_dsi.c new file mode 100644 index 000000000000..bf060cabf6aa --- /dev/null +++ b/drivers/video/fbdev/mxc/mipi_dsi.c @@ -0,0 +1,1040 @@ +/* + * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/ipu.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/mipi_dsi.h> +#include <linux/module.h> +#include <linux/mxcfb.h> +#include <linux/backlight.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <video/mipi_display.h> + +#include "mipi_dsi.h" + +#define DISPDRV_MIPI "mipi_dsi" +#define ROUND_UP(x) ((x)+1) +#define NS2PS_RATIO (1000) +#define NUMBER_OF_CHUNKS (0x8) +#define NULL_PKT_SIZE (0x8) +#define PHY_BTA_MAXTIME (0xd00) +#define PHY_LP2HS_MAXTIME (0x40) +#define PHY_HS2LP_MAXTIME (0x40) +#define PHY_STOP_WAIT_TIME (0x20) +#define DSI_CLKMGR_CFG_CLK_DIV (0x107) +#define DSI_GEN_PLD_DATA_BUF_ENTRY (0x10) +#define MIPI_MUX_CTRL(v) (((v) & 0x3) << 4) +#define MIPI_LCD_SLEEP_MODE_DELAY (120) +#define MIPI_DSI_REG_RW_TIMEOUT (20) +#define MIPI_DSI_PHY_TIMEOUT (10) + +static struct mipi_dsi_match_lcd mipi_dsi_lcd_db[] = { +#ifdef CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL + { + "TRULY-WVGA", + {mipid_hx8369_get_lcd_videomode, mipid_hx8369_lcd_setup} + }, +#endif + { + "", {NULL, NULL} + } +}; + +struct _mipi_dsi_phy_pll_clk { + u32 max_phy_clk; + u32 config; +}; + +/* configure data for DPHY PLL 27M reference clk out */ +static const struct _mipi_dsi_phy_pll_clk mipi_dsi_phy_pll_clk_table[] = { + {1000, 0x74}, /* 950-1000MHz */ + {950, 0x54}, /* 900-950Mhz */ + {900, 0x34}, /* 850-900Mhz */ + {850, 0x14}, /* 800-850MHz */ + {800, 0x32}, /* 750-800MHz */ + {750, 0x12}, /* 700-750Mhz */ + {700, 0x30}, /* 650-700Mhz */ + {650, 0x10}, /* 600-650MHz */ + {600, 0x2e}, /* 550-600MHz */ + {550, 0x0e}, /* 500-550Mhz */ + {500, 0x2c}, /* 450-500Mhz */ + {450, 0x0c}, /* 400-450MHz */ + {400, 0x4a}, /* 360-400MHz */ + {360, 0x2a}, /* 330-360Mhz */ + {330, 0x48}, /* 300-330Mhz */ + {300, 0x28}, /* 270-300MHz */ + {270, 0x08}, /* 250-270MHz */ + {250, 0x46}, /* 240-250Mhz */ + {240, 0x26}, /* 210-240Mhz */ + {210, 0x06}, /* 200-210MHz */ + {200, 0x44}, /* 180-200MHz */ + {180, 0x24}, /* 160-180MHz */ + {160, 0x04}, /* 150-160MHz */ +}; + +static int valid_mode(int pixel_fmt) +{ + return ((pixel_fmt == IPU_PIX_FMT_RGB24) || + (pixel_fmt == IPU_PIX_FMT_BGR24) || + (pixel_fmt == IPU_PIX_FMT_RGB666) || + (pixel_fmt == IPU_PIX_FMT_RGB565) || + (pixel_fmt == IPU_PIX_FMT_BGR666) || + (pixel_fmt == IPU_PIX_FMT_RGB332)); +} + +static inline void mipi_dsi_read_register(struct mipi_dsi_info *mipi_dsi, + u32 reg, u32 *val) +{ + *val = ioread32(mipi_dsi->mmio_base + reg); + dev_dbg(&mipi_dsi->pdev->dev, "read_reg:0x%02x, val:0x%08x.\n", + reg, *val); +} + +static inline void mipi_dsi_write_register(struct mipi_dsi_info *mipi_dsi, + u32 reg, u32 val) +{ + iowrite32(val, mipi_dsi->mmio_base + reg); + dev_dbg(&mipi_dsi->pdev->dev, "\t\twrite_reg:0x%02x, val:0x%08x.\n", + reg, val); +} + +static int mipi_dsi_pkt_write(struct mipi_dsi_info *mipi_dsi, + u8 data_type, const u32 *buf, int len) +{ + u32 val; + u32 status = 0; + int write_len = len; + uint32_t timeout = 0; + + if (len) { + /* generic long write command */ + while (len / DSI_GEN_PLD_DATA_BUF_SIZE) { + mipi_dsi_write_register(mipi_dsi, + MIPI_DSI_GEN_PLD_DATA, *buf); + buf++; + len -= DSI_GEN_PLD_DATA_BUF_SIZE; + mipi_dsi_read_register(mipi_dsi, + MIPI_DSI_CMD_PKT_STATUS, &status); + while ((status & DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) == + DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_REG_RW_TIMEOUT) + return -EIO; + mipi_dsi_read_register(mipi_dsi, + MIPI_DSI_CMD_PKT_STATUS, &status); + } + } + /* write the remainder bytes */ + if (len > 0) { + while ((status & DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) == + DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_REG_RW_TIMEOUT) + return -EIO; + mipi_dsi_read_register(mipi_dsi, + MIPI_DSI_CMD_PKT_STATUS, &status); + } + mipi_dsi_write_register(mipi_dsi, + MIPI_DSI_GEN_PLD_DATA, *buf); + } + + val = data_type | ((write_len & DSI_GEN_HDR_DATA_MASK) + << DSI_GEN_HDR_DATA_SHIFT); + } else { + /* generic short write command */ + val = data_type | ((*buf & DSI_GEN_HDR_DATA_MASK) + << DSI_GEN_HDR_DATA_SHIFT); + } + + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &status); + while ((status & DSI_CMD_PKT_STATUS_GEN_CMD_FULL) == + DSI_CMD_PKT_STATUS_GEN_CMD_FULL) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_REG_RW_TIMEOUT) + return -EIO; + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, + &status); + } + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_GEN_HDR, val); + + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &status); + while (!((status & DSI_CMD_PKT_STATUS_GEN_CMD_EMPTY) == + DSI_CMD_PKT_STATUS_GEN_CMD_EMPTY) || + !((status & DSI_CMD_PKT_STATUS_GEN_PLD_W_EMPTY) == + DSI_CMD_PKT_STATUS_GEN_PLD_W_EMPTY)) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_REG_RW_TIMEOUT) + return -EIO; + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, + &status); + } + + return 0; +} + +static int mipi_dsi_pkt_read(struct mipi_dsi_info *mipi_dsi, + u8 data_type, u32 *buf, int len) +{ + u32 val; + int read_len = 0; + uint32_t timeout = 0; + + if (!len) { + mipi_dbg("%s, len = 0 invalid error!\n", __func__); + return -EINVAL; + } + + val = data_type | ((*buf & DSI_GEN_HDR_DATA_MASK) + << DSI_GEN_HDR_DATA_SHIFT); + memset(buf, 0, len); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_GEN_HDR, val); + + /* wait for cmd to sent out */ + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &val); + while ((val & DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) != + DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_REG_RW_TIMEOUT) + return -EIO; + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, + &val); + } + /* wait for entire response stroed in FIFO */ + while ((val & DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) == + DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_REG_RW_TIMEOUT) + return -EIO; + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, + &val); + } + + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &val); + while (!(val & DSI_CMD_PKT_STATUS_GEN_PLD_R_EMPTY)) { + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_GEN_PLD_DATA, buf); + read_len += DSI_GEN_PLD_DATA_BUF_SIZE; + buf++; + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, + &val); + if (read_len == (DSI_GEN_PLD_DATA_BUF_ENTRY * + DSI_GEN_PLD_DATA_BUF_SIZE)) + break; + } + + if ((len <= read_len) && + ((len + DSI_GEN_PLD_DATA_BUF_SIZE) >= read_len)) + return 0; + else { + dev_err(&mipi_dsi->pdev->dev, + "actually read_len:%d != len:%d.\n", read_len, len); + return -ERANGE; + } +} + +static int mipi_dsi_dcs_cmd(struct mipi_dsi_info *mipi_dsi, + u8 cmd, const u32 *param, int num) +{ + int err = 0; + u32 buf[DSI_CMD_BUF_MAXSIZE]; + + switch (cmd) { + case MIPI_DCS_EXIT_SLEEP_MODE: + case MIPI_DCS_ENTER_SLEEP_MODE: + case MIPI_DCS_SET_DISPLAY_ON: + case MIPI_DCS_SET_DISPLAY_OFF: + buf[0] = cmd; + err = mipi_dsi_pkt_write(mipi_dsi, + MIPI_DSI_DCS_SHORT_WRITE, buf, 0); + break; + + default: + dev_err(&mipi_dsi->pdev->dev, + "MIPI DSI DCS Command:0x%x Not supported!\n", cmd); + break; + } + + return err; +} + +static void mipi_dsi_dphy_init(struct mipi_dsi_info *mipi_dsi, + u32 cmd, u32 data) +{ + u32 val; + u32 timeout = 0; + + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CTRL, + DSI_PHY_IF_CTRL_RESET); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, DSI_PWRUP_POWERUP); + + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 0); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL1, + (0x10000 | cmd)); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 2); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 0); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL1, (0 | data)); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 2); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 0); + val = DSI_PHY_RSTZ_EN_CLK | DSI_PHY_RSTZ_DISABLE_RST | + DSI_PHY_RSTZ_DISABLE_SHUTDOWN; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_RSTZ, val); + + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_PHY_STATUS, &val); + while ((val & DSI_PHY_STATUS_LOCK) != DSI_PHY_STATUS_LOCK) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_PHY_TIMEOUT) { + dev_err(&mipi_dsi->pdev->dev, + "Error: phy lock timeout!\n"); + break; + } + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_PHY_STATUS, &val); + } + timeout = 0; + while ((val & DSI_PHY_STATUS_STOPSTATE_CLK_LANE) != + DSI_PHY_STATUS_STOPSTATE_CLK_LANE) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_PHY_TIMEOUT) { + dev_err(&mipi_dsi->pdev->dev, + "Error: phy lock lane timeout!\n"); + break; + } + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_PHY_STATUS, &val); + } +} + +static void mipi_dsi_enable_controller(struct mipi_dsi_info *mipi_dsi, + bool init) +{ + u32 val = 0; + u32 lane_byte_clk_period; + struct fb_videomode *mode = mipi_dsi->mode; + struct mipi_lcd_config *lcd_config = mipi_dsi->lcd_config; + + if (init) { + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, + DSI_PWRUP_RESET); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_RSTZ, + DSI_PHY_RSTZ_RST); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CLKMGR_CFG, + DSI_CLKMGR_CFG_CLK_DIV); + + if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT)) + val = DSI_DPI_CFG_VSYNC_ACT_LOW; + if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT)) + val |= DSI_DPI_CFG_HSYNC_ACT_LOW; + if ((mode->sync & FB_SYNC_OE_LOW_ACT)) + val |= DSI_DPI_CFG_DATAEN_ACT_LOW; + if (MIPI_RGB666_LOOSELY == lcd_config->dpi_fmt) + val |= DSI_DPI_CFG_EN18LOOSELY; + val |= (lcd_config->dpi_fmt & DSI_DPI_CFG_COLORCODE_MASK) + << DSI_DPI_CFG_COLORCODE_SHIFT; + val |= (lcd_config->virtual_ch & DSI_DPI_CFG_VID_MASK) + << DSI_DPI_CFG_VID_SHIFT; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_DPI_CFG, val); + + val = DSI_PCKHDL_CFG_EN_BTA | + DSI_PCKHDL_CFG_EN_ECC_RX | + DSI_PCKHDL_CFG_EN_CRC_RX; + + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PCKHDL_CFG, val); + + val = (mode->xres & DSI_VID_PKT_CFG_VID_PKT_SZ_MASK) + << DSI_VID_PKT_CFG_VID_PKT_SZ_SHIFT; + val |= (NUMBER_OF_CHUNKS & DSI_VID_PKT_CFG_NUM_CHUNKS_MASK) + << DSI_VID_PKT_CFG_NUM_CHUNKS_SHIFT; + val |= (NULL_PKT_SIZE & DSI_VID_PKT_CFG_NULL_PKT_SZ_MASK) + << DSI_VID_PKT_CFG_NULL_PKT_SZ_SHIFT; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VID_PKT_CFG, val); + + /* enable LP mode when TX DCS cmd and enable DSI command mode */ + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, + MIPI_DSI_CMD_MODE_CFG_EN_LOWPOWER); + + /* mipi lane byte clk period in ns unit */ + lane_byte_clk_period = NS2PS_RATIO / + (lcd_config->max_phy_clk / BITS_PER_BYTE); + val = ROUND_UP(mode->hsync_len * mode->pixclock / + NS2PS_RATIO / lane_byte_clk_period) + << DSI_TME_LINE_CFG_HSA_TIME_SHIFT; + val |= ROUND_UP(mode->left_margin * mode->pixclock / + NS2PS_RATIO / lane_byte_clk_period) + << DSI_TME_LINE_CFG_HBP_TIME_SHIFT; + val |= ROUND_UP((mode->left_margin + mode->right_margin + + mode->hsync_len + mode->xres) * mode->pixclock + / NS2PS_RATIO / lane_byte_clk_period) + << DSI_TME_LINE_CFG_HLINE_TIME_SHIFT; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_TMR_LINE_CFG, val); + + val = ((mode->vsync_len & DSI_VTIMING_CFG_VSA_LINES_MASK) + << DSI_VTIMING_CFG_VSA_LINES_SHIFT); + val |= ((mode->upper_margin & DSI_VTIMING_CFG_VBP_LINES_MASK) + << DSI_VTIMING_CFG_VBP_LINES_SHIFT); + val |= ((mode->lower_margin & DSI_VTIMING_CFG_VFP_LINES_MASK) + << DSI_VTIMING_CFG_VFP_LINES_SHIFT); + val |= ((mode->yres & DSI_VTIMING_CFG_V_ACT_LINES_MASK) + << DSI_VTIMING_CFG_V_ACT_LINES_SHIFT); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VTIMING_CFG, val); + + val = ((PHY_BTA_MAXTIME & DSI_PHY_TMR_CFG_BTA_TIME_MASK) + << DSI_PHY_TMR_CFG_BTA_TIME_SHIFT); + val |= ((PHY_LP2HS_MAXTIME & DSI_PHY_TMR_CFG_LP2HS_TIME_MASK) + << DSI_PHY_TMR_CFG_LP2HS_TIME_SHIFT); + val |= ((PHY_HS2LP_MAXTIME & DSI_PHY_TMR_CFG_HS2LP_TIME_MASK) + << DSI_PHY_TMR_CFG_HS2LP_TIME_SHIFT); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TMR_CFG, val); + + val = (((lcd_config->data_lane_num - 1) & + DSI_PHY_IF_CFG_N_LANES_MASK) + << DSI_PHY_IF_CFG_N_LANES_SHIFT); + val |= ((PHY_STOP_WAIT_TIME & DSI_PHY_IF_CFG_WAIT_TIME_MASK) + << DSI_PHY_IF_CFG_WAIT_TIME_SHIFT); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CFG, val); + + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST0, &val); + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST1, &val); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_ERROR_MSK0, 0); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_ERROR_MSK1, 0); + + mipi_dsi_dphy_init(mipi_dsi, DSI_PHY_CLK_INIT_COMMAND, + mipi_dsi->dphy_pll_config); + } else { + mipi_dsi_dphy_init(mipi_dsi, DSI_PHY_CLK_INIT_COMMAND, + mipi_dsi->dphy_pll_config); + } +} + +static void mipi_dsi_disable_controller(struct mipi_dsi_info *mipi_dsi) +{ + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CTRL, + DSI_PHY_IF_CTRL_RESET); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, DSI_PWRUP_RESET); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_RSTZ, DSI_PHY_RSTZ_RST); +} + +static irqreturn_t mipi_dsi_irq_handler(int irq, void *data) +{ + u32 mask0; + u32 mask1; + u32 status0; + u32 status1; + struct mipi_dsi_info *mipi_dsi; + + mipi_dsi = (struct mipi_dsi_info *)data; + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST0, &status0); + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST1, &status1); + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_MSK0, &mask0); + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_MSK1, &mask1); + + if ((status0 & (~mask0)) || (status1 & (~mask1))) { + dev_err(&mipi_dsi->pdev->dev, + "mipi_dsi IRQ status0:0x%x, status1:0x%x!\n", + status0, status1); + } + + return IRQ_HANDLED; +} + +static inline void mipi_dsi_set_mode(struct mipi_dsi_info *mipi_dsi, + bool cmd_mode) +{ + u32 val; + + if (cmd_mode) { + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, + DSI_PWRUP_RESET); + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, &val); + val |= MIPI_DSI_CMD_MODE_CFG_EN_CMD_MODE; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, val); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VID_MODE_CFG, 0); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, + DSI_PWRUP_POWERUP); + } else { + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, + DSI_PWRUP_RESET); + /* Disable Command mode when tranfering video data */ + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, &val); + val &= ~MIPI_DSI_CMD_MODE_CFG_EN_CMD_MODE; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, val); + val = DSI_VID_MODE_CFG_EN | DSI_VID_MODE_CFG_EN_BURSTMODE | + DSI_VID_MODE_CFG_EN_LP_MODE; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VID_MODE_CFG, val); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, + DSI_PWRUP_POWERUP); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CTRL, + DSI_PHY_IF_CTRL_TX_REQ_CLK_HS); + } +} + +static int mipi_dsi_power_on(struct mxc_dispdrv_handle *disp) +{ + int err; + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + if (!mipi_dsi->dsi_power_on) { + clk_prepare_enable(mipi_dsi->dphy_clk); + clk_prepare_enable(mipi_dsi->cfg_clk); + mipi_dsi_enable_controller(mipi_dsi, false); + mipi_dsi_set_mode(mipi_dsi, false); + /* host send pclk/hsync/vsync for two frames before sleep-out */ + msleep((1000/mipi_dsi->mode->refresh + 1) << 1); + mipi_dsi_set_mode(mipi_dsi, true); + err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_EXIT_SLEEP_MODE, + NULL, 0); + if (err) { + dev_err(&mipi_dsi->pdev->dev, + "MIPI DSI DCS Command sleep-in error!\n"); + } + msleep(MIPI_LCD_SLEEP_MODE_DELAY); + mipi_dsi_set_mode(mipi_dsi, false); + mipi_dsi->dsi_power_on = 1; + } + + return 0; +} + +void mipi_dsi_power_off(struct mxc_dispdrv_handle *disp) +{ + int err; + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + if (mipi_dsi->dsi_power_on) { + mipi_dsi_set_mode(mipi_dsi, true); + err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_ENTER_SLEEP_MODE, + NULL, 0); + if (err) { + dev_err(&mipi_dsi->pdev->dev, + "MIPI DSI DCS Command display on error!\n"); + } + /* To allow time for the supply voltages + * and clock circuits to stabilize. + */ + msleep(5); + /* video stream timing on */ + mipi_dsi_set_mode(mipi_dsi, false); + msleep(MIPI_LCD_SLEEP_MODE_DELAY); + + mipi_dsi_set_mode(mipi_dsi, true); + mipi_dsi_disable_controller(mipi_dsi); + mipi_dsi->dsi_power_on = 0; + clk_disable_unprepare(mipi_dsi->dphy_clk); + clk_disable_unprepare(mipi_dsi->cfg_clk); + } +} + +static int mipi_dsi_lcd_init(struct mipi_dsi_info *mipi_dsi, + struct mxc_dispdrv_setting *setting) +{ + int err; + int size; + int i; + struct fb_videomode *mipi_lcd_modedb; + struct fb_videomode mode; + struct device *dev = &mipi_dsi->pdev->dev; + + for (i = 0; i < ARRAY_SIZE(mipi_dsi_lcd_db); i++) { + if (!strcmp(mipi_dsi->lcd_panel, + mipi_dsi_lcd_db[i].lcd_panel)) { + mipi_dsi->lcd_callback = + &mipi_dsi_lcd_db[i].lcd_callback; + break; + } + } + if (i == ARRAY_SIZE(mipi_dsi_lcd_db)) { + dev_err(dev, "failed to find supported lcd panel.\n"); + return -EINVAL; + } + /* get the videomode in the order: cmdline->platform data->driver */ + mipi_dsi->lcd_callback->get_mipi_lcd_videomode(&mipi_lcd_modedb, &size, + &mipi_dsi->lcd_config); + err = fb_find_mode(&setting->fbi->var, setting->fbi, + setting->dft_mode_str, + mipi_lcd_modedb, size, NULL, + setting->default_bpp); + if (err != 1) + fb_videomode_to_var(&setting->fbi->var, mipi_lcd_modedb); + + INIT_LIST_HEAD(&setting->fbi->modelist); + for (i = 0; i < size; i++) { + fb_var_to_videomode(&mode, &setting->fbi->var); + if (fb_mode_is_equal(&mode, mipi_lcd_modedb + i)) { + err = fb_add_videomode(mipi_lcd_modedb + i, + &setting->fbi->modelist); + /* Note: only support fb mode from driver */ + mipi_dsi->mode = mipi_lcd_modedb + i; + break; + } + } + if ((err < 0) || (size == i)) { + dev_err(dev, "failed to add videomode.\n"); + return err; + } + + for (i = 0; i < ARRAY_SIZE(mipi_dsi_phy_pll_clk_table); i++) { + if (mipi_dsi_phy_pll_clk_table[i].max_phy_clk < + mipi_dsi->lcd_config->max_phy_clk) + break; + } + if ((i == ARRAY_SIZE(mipi_dsi_phy_pll_clk_table)) || + (mipi_dsi->lcd_config->max_phy_clk > + mipi_dsi_phy_pll_clk_table[0].max_phy_clk)) { + dev_err(dev, "failed to find data in" + "mipi_dsi_phy_pll_clk_table.\n"); + return -EINVAL; + } + mipi_dsi->dphy_pll_config = mipi_dsi_phy_pll_clk_table[--i].config; + dev_dbg(dev, "dphy_pll_config:0x%x.\n", mipi_dsi->dphy_pll_config); + + return 0; +} + +static int mipi_dsi_enable(struct mxc_dispdrv_handle *disp, + struct fb_info *fbi) +{ + int err; + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + if (!mipi_dsi->lcd_inited) { + err = clk_prepare_enable(mipi_dsi->dphy_clk); + err |= clk_prepare_enable(mipi_dsi->cfg_clk); + if (err) + dev_err(&mipi_dsi->pdev->dev, + "clk enable error:%d!\n", err); + mipi_dsi_enable_controller(mipi_dsi, true); + err = mipi_dsi->lcd_callback->mipi_lcd_setup( + mipi_dsi); + if (err < 0) { + dev_err(&mipi_dsi->pdev->dev, + "failed to init mipi lcd."); + clk_disable_unprepare(mipi_dsi->dphy_clk); + clk_disable_unprepare(mipi_dsi->cfg_clk); + return err; + } + mipi_dsi_set_mode(mipi_dsi, false); + mipi_dsi->dsi_power_on = 1; + mipi_dsi->lcd_inited = 1; + } + mipi_dsi_power_on(mipi_dsi->disp_mipi); + + return 0; +} + +static void mipi_dsi_disable(struct mxc_dispdrv_handle *disp, + struct fb_info *fbi) +{ + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + mipi_dsi_power_off(mipi_dsi->disp_mipi); +} + +static int mipi_dsi_disp_init(struct mxc_dispdrv_handle *disp, + struct mxc_dispdrv_setting *setting) +{ + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + struct device *dev = &mipi_dsi->pdev->dev; + int ret = 0; + + if (!valid_mode(setting->if_fmt)) { + dev_warn(dev, "Input pixel format not valid" + "use default RGB24\n"); + setting->if_fmt = IPU_PIX_FMT_RGB24; + } + + ret = ipu_di_to_crtc(dev, mipi_dsi->dev_id, + mipi_dsi->disp_id, &setting->crtc); + if (ret < 0) + return ret; + + ret = mipi_dsi_lcd_init(mipi_dsi, setting); + if (ret) { + dev_err(dev, "failed to init mipi dsi lcd\n"); + return ret; + } + + dev_dbg(dev, "MIPI DSI dispdrv inited!\n"); + return ret; +} + +static void mipi_dsi_disp_deinit(struct mxc_dispdrv_handle *disp) +{ + struct mipi_dsi_info *mipi_dsi; + + mipi_dsi = mxc_dispdrv_getdata(disp); + + mipi_dsi_power_off(mipi_dsi->disp_mipi); + if (mipi_dsi->bl) + backlight_device_unregister(mipi_dsi->bl); +} + +static int mipi_dsi_setup(struct mxc_dispdrv_handle *disp, + struct fb_info *fbi) +{ + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + int xres_virtual = fbi->var.xres_virtual; + int yres_virtual = fbi->var.yres_virtual; + int xoffset = fbi->var.xoffset; + int yoffset = fbi->var.yoffset; + int pixclock = fbi->var.pixclock; + + if (!mipi_dsi->mode) + return 0; + + /* set the mode back to var in case userspace changes it */ + fb_videomode_to_var(&fbi->var, mipi_dsi->mode); + + /* restore some var entries cached */ + fbi->var.xres_virtual = xres_virtual; + fbi->var.yres_virtual = yres_virtual; + fbi->var.xoffset = xoffset; + fbi->var.yoffset = yoffset; + fbi->var.pixclock = pixclock; + return 0; +} + +static struct mxc_dispdrv_driver mipi_dsi_drv = { + .name = DISPDRV_MIPI, + .init = mipi_dsi_disp_init, + .deinit = mipi_dsi_disp_deinit, + .enable = mipi_dsi_enable, + .disable = mipi_dsi_disable, + .setup = mipi_dsi_setup, +}; + +static int device_reset(struct device *dev) +{ + struct device_node *np = dev->of_node; + enum of_gpio_flags flags; + unsigned long gpio_flags; + unsigned int gpio; + bool initially_in_reset; + bool active_low; + s32 delay_us; + int ret; + + gpio = of_get_named_gpio_flags(np, "reset-gpios", 0, &flags); + if (gpio == -EPROBE_DEFER) { + return gpio; + } else if (!gpio_is_valid(gpio)) { + dev_err(dev, "invalid reset gpio: %d\n", gpio); + return gpio; + } + + active_low = flags & OF_GPIO_ACTIVE_LOW; + + ret = of_property_read_u32(np, "reset-delay-us", &delay_us); + if (ret < 0 || delay_us < 0) { + dev_err(dev, "invalid reset delay\n"); + return -EINVAL; + } + + initially_in_reset = of_property_read_bool(np, "initially-in-reset"); + if (active_low ^ initially_in_reset) + gpio_flags = GPIOF_OUT_INIT_HIGH; + else + gpio_flags = GPIOF_OUT_INIT_LOW; + + ret = devm_gpio_request_one(dev, gpio, gpio_flags, NULL); + if (ret < 0) { + dev_err(dev, "failed to request gpio %d: %d\n", gpio, ret); + return ret; + } + + gpio_set_value_cansleep(gpio, active_low ? 0 : 1); + udelay(delay_us); + gpio_set_value_cansleep(gpio, active_low ? 1 : 0); + + return 0; +} + +static int imx6q_mipi_dsi_get_mux(int dev_id, int disp_id) +{ + if (dev_id > 1 || disp_id > 1) + return -EINVAL; + + return (dev_id << 5) | (disp_id << 4); +} + +static struct mipi_dsi_bus_mux imx6q_mipi_dsi_mux[] = { + { + .reg = IOMUXC_GPR3, + .mask = IMX6Q_GPR3_MIPI_MUX_CTL_MASK, + .get_mux = imx6q_mipi_dsi_get_mux, + }, +}; + +static int imx6dl_mipi_dsi_get_mux(int dev_id, int disp_id) +{ + if (dev_id > 1 || disp_id > 1) + return -EINVAL; + + /* MIPI DSI source is LCDIF */ + if (dev_id) + disp_id = 0; + + return (dev_id << 5) | (disp_id << 4); +} + +static struct mipi_dsi_bus_mux imx6dl_mipi_dsi_mux[] = { + { + .reg = IOMUXC_GPR3, + .mask = IMX6Q_GPR3_MIPI_MUX_CTL_MASK, + .get_mux = imx6dl_mipi_dsi_get_mux, + }, +}; + +static const struct of_device_id imx_mipi_dsi_dt_ids[] = { + { .compatible = "fsl,imx6q-mipi-dsi", .data = imx6q_mipi_dsi_mux, }, + { .compatible = "fsl,imx6dl-mipi-dsi", .data = imx6dl_mipi_dsi_mux, }, + { } +}; +MODULE_DEVICE_TABLE(of, imx_mipi_dsi_dt_ids); + +/** + * This function is called by the driver framework to initialize the MIPI DSI + * device. + * + * @param pdev The device structure for the MIPI DSI passed in by the + * driver framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int mipi_dsi_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id = + of_match_device(of_match_ptr(imx_mipi_dsi_dt_ids), + &pdev->dev); + struct mipi_dsi_info *mipi_dsi; + struct resource *res; + u32 dev_id, disp_id; + const char *lcd_panel; + int mux; + int ret = 0; + + if (!np) { + dev_err(&pdev->dev, "failed to find device tree node\n"); + return -ENODEV; + } + + mipi_dsi = devm_kzalloc(&pdev->dev, sizeof(*mipi_dsi), GFP_KERNEL); + if (!mipi_dsi) + return -ENOMEM; + + ret = of_property_read_string(np, "lcd_panel", &lcd_panel); + if (ret) { + dev_err(&pdev->dev, "failed to read of property lcd_panel\n"); + return ret; + } + + ret = of_property_read_u32(np, "dev_id", &dev_id); + if (ret) { + dev_err(&pdev->dev, "failed to read of property dev_id\n"); + return ret; + } + ret = of_property_read_u32(np, "disp_id", &disp_id); + if (ret) { + dev_err(&pdev->dev, "failed to read of property disp_id\n"); + return ret; + } + mipi_dsi->dev_id = dev_id; + mipi_dsi->disp_id = disp_id; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get platform resource 0\n"); + return -ENODEV; + } + + if (!devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), pdev->name)) + return -EBUSY; + + mipi_dsi->mmio_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!mipi_dsi->mmio_base) + return -EBUSY; + + mipi_dsi->irq = platform_get_irq(pdev, 0); + if (mipi_dsi->irq < 0) { + dev_err(&pdev->dev, "failed get device irq\n"); + return -ENODEV; + } + + ret = devm_request_irq(&pdev->dev, mipi_dsi->irq, + mipi_dsi_irq_handler, + 0, "mipi_dsi", mipi_dsi); + if (ret) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ret; + } + + mipi_dsi->dphy_clk = devm_clk_get(&pdev->dev, "mipi_pllref_clk"); + if (IS_ERR(mipi_dsi->dphy_clk)) { + dev_err(&pdev->dev, "failed to get dphy pll_ref_clk\n"); + return PTR_ERR(mipi_dsi->dphy_clk); + } + + mipi_dsi->cfg_clk = devm_clk_get(&pdev->dev, "mipi_cfg_clk"); + if (IS_ERR(mipi_dsi->cfg_clk)) { + dev_err(&pdev->dev, "failed to get cfg_clk\n"); + return PTR_ERR(mipi_dsi->cfg_clk); + } + + mipi_dsi->disp_power_on = devm_regulator_get(&pdev->dev, + "disp-power-on"); + if (!IS_ERR(mipi_dsi->disp_power_on)) { + ret = regulator_enable(mipi_dsi->disp_power_on); + if (ret) { + dev_err(&pdev->dev, "failed to enable display " + "power regulator, err=%d\n", ret); + return ret; + } + } else { + mipi_dsi->disp_power_on = NULL; + } + + ret = device_reset(&pdev->dev); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "failed to reset: %d\n", ret); + goto dev_reset_fail; + } + + if (of_id) + mipi_dsi->bus_mux = of_id->data; + + mipi_dsi->regmap = syscon_regmap_lookup_by_phandle(np, "gpr"); + if (IS_ERR(mipi_dsi->regmap)) { + dev_err(&pdev->dev, "failed to get parent regmap\n"); + ret = PTR_ERR(mipi_dsi->regmap); + goto get_parent_regmap_fail; + } + + mux = mipi_dsi->bus_mux->get_mux(dev_id, disp_id); + if (mux >= 0) + regmap_update_bits(mipi_dsi->regmap, mipi_dsi->bus_mux->reg, + mipi_dsi->bus_mux->mask, (unsigned int)mux); + else + dev_warn(&pdev->dev, "invalid dev_id or disp_id muxing\n"); + + mipi_dsi->lcd_panel = kstrdup(lcd_panel, GFP_KERNEL); + if (!mipi_dsi->lcd_panel) { + dev_err(&pdev->dev, "failed to allocate lcd panel name\n"); + ret = -ENOMEM; + goto kstrdup_fail; + } + + mipi_dsi->pdev = pdev; + mipi_dsi->disp_mipi = mxc_dispdrv_register(&mipi_dsi_drv); + if (IS_ERR(mipi_dsi->disp_mipi)) { + dev_err(&pdev->dev, "mxc_dispdrv_register error\n"); + ret = PTR_ERR(mipi_dsi->disp_mipi); + goto dispdrv_reg_fail; + } + + mipi_dsi->mipi_dsi_pkt_read = mipi_dsi_pkt_read; + mipi_dsi->mipi_dsi_pkt_write = mipi_dsi_pkt_write; + mipi_dsi->mipi_dsi_dcs_cmd = mipi_dsi_dcs_cmd; + + mxc_dispdrv_setdata(mipi_dsi->disp_mipi, mipi_dsi); + dev_set_drvdata(&pdev->dev, mipi_dsi); + + dev_info(&pdev->dev, "i.MX MIPI DSI driver probed\n"); + return ret; + +dispdrv_reg_fail: + kfree(mipi_dsi->lcd_panel); +kstrdup_fail: +get_parent_regmap_fail: +dev_reset_fail: + if (mipi_dsi->disp_power_on) + regulator_disable(mipi_dsi->disp_power_on); + return ret; +} + +static void mipi_dsi_shutdown(struct platform_device *pdev) +{ + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + mipi_dsi_power_off(mipi_dsi->disp_mipi); +} + +static int mipi_dsi_remove(struct platform_device *pdev) +{ + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + mxc_dispdrv_puthandle(mipi_dsi->disp_mipi); + mxc_dispdrv_unregister(mipi_dsi->disp_mipi); + + if (mipi_dsi->disp_power_on) + regulator_disable(mipi_dsi->disp_power_on); + + kfree(mipi_dsi->lcd_panel); + dev_set_drvdata(&pdev->dev, NULL); + + return 0; +} + +static struct platform_driver mipi_dsi_driver = { + .driver = { + .of_match_table = imx_mipi_dsi_dt_ids, + .name = "mxc_mipi_dsi", + }, + .probe = mipi_dsi_probe, + .remove = mipi_dsi_remove, + .shutdown = mipi_dsi_shutdown, +}; + +static int __init mipi_dsi_init(void) +{ + int err; + + err = platform_driver_register(&mipi_dsi_driver); + if (err) { + pr_err("mipi_dsi_driver register failed\n"); + return -ENODEV; + } + pr_debug("MIPI DSI driver module loaded: %s\n", mipi_dsi_driver.driver.name); + return 0; +} + +static void __exit mipi_dsi_cleanup(void) +{ + platform_driver_unregister(&mipi_dsi_driver); +} + +module_init(mipi_dsi_init); +module_exit(mipi_dsi_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX MIPI DSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/mxc/mipi_dsi.h b/drivers/video/fbdev/mxc/mipi_dsi.h new file mode 100644 index 000000000000..270259c2e218 --- /dev/null +++ b/drivers/video/fbdev/mxc/mipi_dsi.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2017 NXP. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MIPI_DSI_H__ +#define __MIPI_DSI_H__ + +#include <linux/regmap.h> +#include "mxc_dispdrv.h" + +#ifdef DEBUG +#define mipi_dbg(fmt, ...) printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) +#else +#define mipi_dbg(fmt, ...) +#endif + +#define DSI_CMD_BUF_MAXSIZE (128) + +#define DSI_NON_BURST_WITH_SYNC_PULSE 0 +#define DSI_NON_BURST_WITH_SYNC_EVENT 1 +#define DSI_BURST_MODE 2 + +#define DSI_HSA_PKT_OVERHEAD 10 +#define DSI_HFP_PKT_OVERHEAD 8 +#define DSI_HBP_PKT_OVERHEAD 14 + +/* DPI interface pixel color coding map */ +enum mipi_dsi_dpi_fmt { + MIPI_RGB565_PACKED = 0, + MIPI_RGB565_LOOSELY, + MIPI_RGB565_CONFIG3, + MIPI_RGB666_PACKED, + MIPI_RGB666_LOOSELY, + MIPI_RGB888, +}; + +struct mipi_lcd_config { + u32 virtual_ch; + u32 data_lane_num; + /* device max DPHY clock in MHz unit */ + u32 max_phy_clk; + enum mipi_dsi_dpi_fmt dpi_fmt; +}; + +struct mipi_dsi_info; +struct mipi_dsi_lcd_callback { + /* callback for lcd panel operation */ + void (*get_mipi_lcd_videomode)(struct fb_videomode **, int *, + struct mipi_lcd_config **); + int (*mipi_lcd_setup)(struct mipi_dsi_info *); + +}; + +struct mipi_dsi_match_lcd { + char *lcd_panel; + struct mipi_dsi_lcd_callback lcd_callback; +}; + +struct mipi_dsi_bus_mux { + int reg; + int mask; + int (*get_mux) (int dev_id, int disp_id); +}; + +/* driver private data */ +struct mipi_dsi_info { + struct platform_device *pdev; + void __iomem *mmio_base; + void __iomem *phy_base; + struct regmap *regmap; + struct regmap *mux_sel; + const struct mipi_dsi_bus_mux *bus_mux; + int dsi_power_on; + int lcd_inited; + int encoder; + int traffic_mode; + u32 dphy_pll_config; + int dev_id; + int disp_id; + char *lcd_panel; + int irq; + uint32_t phy_ref_clkfreq; +#ifdef CONFIG_FB_IMX64 + struct clk *core_clk; + struct clk *phy_ref_clk; + struct clk *dbi_clk; + struct clk *rxesc_clk; + struct clk *txesc_clk; +#else + struct clk *dphy_clk; + struct clk *cfg_clk; + struct clk *esc_clk; +#endif + struct mxc_dispdrv_handle *disp_mipi; + int vmode_index; + struct fb_videomode *mode; + struct regulator *disp_power_on; + struct mipi_lcd_config *lcd_config; + /* board related power control */ + struct backlight_device *bl; + /* callback for lcd panel operation */ + struct mipi_dsi_lcd_callback *lcd_callback; + + int (*mipi_dsi_pkt_read)(struct mipi_dsi_info *mipi, + u8 data_type, u32 *buf, int len); + int (*mipi_dsi_pkt_write)(struct mipi_dsi_info *mipi_dsi, + u8 data_type, const u32 *buf, int len); + int (*mipi_dsi_dcs_cmd)(struct mipi_dsi_info *mipi, + u8 cmd, const u32 *param, int num); +}; + +#ifdef CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL +void mipid_hx8369_get_lcd_videomode(struct fb_videomode **mode, int *size, + struct mipi_lcd_config **data); +int mipid_hx8369_lcd_setup(struct mipi_dsi_info *); +#endif +#ifdef CONFIG_FB_MXC_TRULY_PANEL_TFT3P5079E +void mipid_otm8018b_get_lcd_videomode(struct fb_videomode **mode, int *size, + struct mipi_lcd_config **data); +int mipid_otm8018b_lcd_setup(struct mipi_dsi_info *); +#endif +#ifdef CONFIG_FB_MXC_TRULY_PANEL_TFT3P5581E +void mipid_hx8363_get_lcd_videomode(struct fb_videomode **mode, int *size, + struct mipi_lcd_config **data); +int mipid_hx8363_lcd_setup(struct mipi_dsi_info *); +#endif +#ifdef CONFIG_FB_MXC_RK_PANEL_RK055AHD042 +void mipid_rm68200_get_lcd_videomode(struct fb_videomode **mode, int *size, + struct mipi_lcd_config **data); +int mipid_rm68200_lcd_setup(struct mipi_dsi_info *mipi_dsi); +#endif +#ifdef CONFIG_FB_MXC_RK_PANEL_RK055IQH042 +void mipid_rm68191_get_lcd_videomode(struct fb_videomode **mode, int *size, + struct mipi_lcd_config **data); +int mipid_rm68191_lcd_setup(struct mipi_dsi_info *mipi_dsi); +#endif + +#endif diff --git a/drivers/video/fbdev/mxc/mipi_dsi_northwest.c b/drivers/video/fbdev/mxc/mipi_dsi_northwest.c new file mode 100644 index 000000000000..e0cdda38552a --- /dev/null +++ b/drivers/video/fbdev/mxc/mipi_dsi_northwest.c @@ -0,0 +1,1556 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2017 NXP. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/bitops.h> +#include <linux/gcd.h> +#include <linux/mipi_dsi_northwest.h> +#include <linux/module.h> +#include <linux/mxcfb.h> +#include <linux/pm_runtime.h> +#include <linux/busfreq-imx.h> +#include <linux/backlight.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/of_graph.h> +#include <linux/pinctrl/consumer.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <video/mipi_display.h> +#include <video/mxc_edid.h> +#include <linux/mfd/syscon.h> + +#include "mipi_dsi.h" + +#define DISPDRV_MIPI "mipi_dsi_northwest" +#define ROUND_UP(x) ((x)+1) +#define NS2PS_RATIO (1000) +#define MIPI_LCD_SLEEP_MODE_DELAY (120) +#define MIPI_FIFO_TIMEOUT msecs_to_jiffies(250) +#define PICOS_PER_SEC (1000000000ULL) +#define PICOS2KHZ2(a, bpp) \ + DIV_ROUND_CLOSEST_ULL(PICOS_PER_SEC * (bpp), (a)) + +static struct mipi_dsi_match_lcd mipi_dsi_lcd_db[] = { +#ifdef CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL + { + "TRULY-WVGA", + {mipid_hx8369_get_lcd_videomode, mipid_hx8369_lcd_setup} + }, +#endif +#ifdef CONFIG_FB_MXC_TRULY_PANEL_TFT3P5079E + { + "TRULY-WVGA-TFT3P5079E", + {mipid_otm8018b_get_lcd_videomode, mipid_otm8018b_lcd_setup} + }, +#endif +#ifdef CONFIG_FB_MXC_TRULY_PANEL_TFT3P5581E + { + "TRULY-WVGA-TFT3P5581E", + {mipid_hx8363_get_lcd_videomode, mipid_hx8363_lcd_setup} + }, +#endif +#ifdef CONFIG_FB_MXC_RK_PANEL_RK055AHD042 + { + "ROCKTECH-WXGA-RK055AHD042", + {mipid_rm68200_get_lcd_videomode, mipid_rm68200_lcd_setup} + }, +#endif +#ifdef CONFIG_FB_MXC_RK_PANEL_RK055IQH042 + { + "ROCKTECH-QHD-RK055IQH042", + {mipid_rm68191_get_lcd_videomode, mipid_rm68191_lcd_setup} + }, +#endif + { + "", {NULL, NULL} + } +}; + +enum mipi_dsi_mode { + DSI_COMMAND_MODE, + DSI_VIDEO_MODE +}; + +#define DSI_LP_MODE 0 +#define DSI_HS_MODE 1 + +enum mipi_dsi_payload { + DSI_PAYLOAD_CMD, + DSI_PAYLOAD_VIDEO, +}; + +struct pll_divider { + unsigned int cm; /* multiplier */ + unsigned int cn; /* predivider */ + unsigned int co; /* outdivider */ +}; + +/** + * 'CM' value to 'CM' reigister config value map + * 'CM' = [16, 255]; + */ +static unsigned int cm_map_table[240] = { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, /* 16 ~ 23 */ + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, /* 24 ~ 31 */ + + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, /* 32 ~ 39 */ + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, /* 40 ~ 47 */ + + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, /* 48 ~ 55 */ + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, /* 56 ~ 63 */ + + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, /* 64 ~ 71 */ + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, /* 72 ~ 79 */ + + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, /* 80 ~ 87 */ + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, /* 88 ~ 95 */ + + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, /* 96 ~ 103 */ + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, /* 104 ~ 111 */ + + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, /* 112 ~ 119 */ + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, /* 120 ~ 127 */ + + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 128 ~ 135 */ + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, /* 136 ~ 143 */ + + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* 144 ~ 151 */ + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, /* 152 ~ 159 */ + + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, /* 160 ~ 167 */ + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, /* 168 ~ 175 */ + + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, /* 176 ~ 183 */ + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, /* 184 ~ 191 */ + + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, /* 192 ~ 199 */ + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, /* 200 ~ 207 */ + + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, /* 208 ~ 215 */ + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, /* 216 ~ 223 */ + + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, /* 224 ~ 231 */ + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, /* 232 ~ 239 */ + + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, /* 240 ~ 247 */ + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f /* 248 ~ 255 */ +}; + +/** + * map 'CN' value to 'CN' reigister config value + * 'CN' = [1, 32]; + */ +static unsigned int cn_map_table[32] = { + 0x1f, 0x00, 0x10, 0x18, 0x1c, 0x0e, 0x07, 0x13, /* 1 ~ 8 */ + 0x09, 0x04, 0x02, 0x11, 0x08, 0x14, 0x0a, 0x15, /* 9 ~ 16 */ + 0x1a, 0x1d, 0x1e, 0x0f, 0x17, 0x1b, 0x0d, 0x16, /* 17 ~ 24 */ + 0x0b, 0x05, 0x12, 0x19, 0x0c, 0x06, 0x03, 0x01 /* 25 ~ 32 */ +}; + +/** + * map 'CO' value to 'CO' reigister config value + * 'CO' = { 1, 2, 4, 8 }; + */ +static unsigned int co_map_table[4] = { + 0x0, 0x1, 0x2, 0x3 +}; + +static DECLARE_COMPLETION(dsi_rx_done); +static DECLARE_COMPLETION(dsi_tx_done); + +static void mipi_dsi_set_mode(struct mipi_dsi_info *mipi_dsi, + uint8_t mode); +static int mipi_dsi_dcs_cmd(struct mipi_dsi_info *mipi_dsi, + u8 cmd, const u32 *param, int num); + +static int mipi_dsi_lcd_init(struct mipi_dsi_info *mipi_dsi, + struct mxc_dispdrv_setting *setting) +{ + u32 data_lane_num, max_data_rate; + int i, size, err = 0; + struct fb_videomode *mipi_lcd_modedb; + struct fb_videomode mode; + struct device *dev = &mipi_dsi->pdev->dev; + + err = of_property_read_u32(dev->of_node, + "data-lanes-num", &data_lane_num); + if (err) { + dev_err(dev, "failed to get data lane num\n"); + goto err0; + } else if (data_lane_num > 4) { + dev_err(dev, "invalid data lane number\n"); + err = -EINVAL; + goto err0; + } + + err = of_property_read_u32(dev->of_node, + "max-data-rate", &max_data_rate); + if (err) { + dev_err(dev, "failed to get max data rate\n"); + goto err0; + } + + if (mipi_dsi->encoder) { + mipi_dsi->lcd_config->virtual_ch = 0; + mipi_dsi->lcd_config->data_lane_num = data_lane_num; + mipi_dsi->lcd_config->max_phy_clk = max_data_rate; + mipi_dsi->lcd_config->dpi_fmt = MIPI_RGB888; + setting->fbi->var.bits_per_pixel = 32; + + /* TODO Add bandwidth check */ + + if (setting->fbi->fbops->fb_check_var) + err = setting->fbi->fbops->fb_check_var(&setting->fbi->var, + setting->fbi); + if (err) + goto err0; + + err = fb_add_videomode(mipi_dsi->mode, + &setting->fbi->modelist); + if (err) + goto err0; + + fb_videomode_to_var(&setting->fbi->var, mipi_dsi->mode); + setting->fbi->mode = mipi_dsi->mode; +err0: + return err; + } + + for (i = 0; i < ARRAY_SIZE(mipi_dsi_lcd_db); i++) { + if (!strcmp(mipi_dsi->lcd_panel, + mipi_dsi_lcd_db[i].lcd_panel)) { + mipi_dsi->lcd_callback = + &mipi_dsi_lcd_db[i].lcd_callback; + break; + } + } + if (i == ARRAY_SIZE(mipi_dsi_lcd_db)) { + dev_err(dev, "failed to find supported lcd panel.\n"); + return -EINVAL; + } + + /* set default bpp to 32 if not set*/ + if (!setting->default_bpp) + setting->default_bpp = 32; + + mipi_dsi->lcd_callback->get_mipi_lcd_videomode(&mipi_lcd_modedb, &size, + &mipi_dsi->lcd_config); + + err = fb_find_mode(&setting->fbi->var, setting->fbi, + setting->dft_mode_str, + mipi_lcd_modedb, size, NULL, + setting->default_bpp); + if (err != 1) + fb_videomode_to_var(&setting->fbi->var, mipi_lcd_modedb); + + INIT_LIST_HEAD(&setting->fbi->modelist); + for (i = 0; i < size; i++) { + fb_var_to_videomode(&mode, &setting->fbi->var); + if (fb_mode_is_equal(&mode, mipi_lcd_modedb + i)) { + err = fb_add_videomode(mipi_lcd_modedb + i, + &setting->fbi->modelist); + mipi_dsi->mode = mipi_lcd_modedb + i; + break; + } + } + + if ((err < 0) || (size == i)) { + dev_err(dev, "failed to add videomode.\n"); + return err; + } + + setting->fbi->mode = mipi_dsi->mode; + + return 0; +} + +static int mipi_dsi_disp_init(struct mxc_dispdrv_handle *disp, + struct mxc_dispdrv_setting *setting) +{ + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + struct device *dev = &mipi_dsi->pdev->dev; + struct device_node *np = dev->of_node; + struct reset_control *reset = NULL; + int ret = 0; + + if (!mipi_dsi->encoder) { + reset = of_reset_control_get(np, NULL); + if (IS_ERR(reset)) + return PTR_ERR(reset); + } + + ret = mipi_dsi_lcd_init(mipi_dsi, setting); + if (ret) { + dev_err(dev, "failed to init mipi dsi lcd\n"); + goto out; + } + + dev_info(dev, "MIPI DSI dispdv inited\n"); + +out: + if (!mipi_dsi->encoder) + reset_control_put(reset); + + return ret; +} + +static void mipi_dsi_disp_deinit(struct mxc_dispdrv_handle *disp) +{ + struct mipi_dsi_info *mipi_dsi; + + mipi_dsi = mxc_dispdrv_getdata(disp); + + if (mipi_dsi->bl) + backlight_device_unregister(mipi_dsi->bl); +} + +static void mipi_dsi_set_mode(struct mipi_dsi_info *mipi_dsi, + uint8_t mode) +{ + uint32_t pkt_control; + + pkt_control = readl(mipi_dsi->mmio_base + HOST_PKT_CONTROL); + + switch (mode) { + case DSI_LP_MODE: + writel(0x1, mipi_dsi->mmio_base + HOST_CFG_NONCONTINUOUS_CLK); + break; + case DSI_HS_MODE: + writel(0x0, mipi_dsi->mmio_base + HOST_CFG_NONCONTINUOUS_CLK); + break; + default: + dev_err(&mipi_dsi->pdev->dev, + "invalid dsi mode\n"); + return; + } + + mdelay(1); +} + +static uint32_t fmt_to_bpp(enum mipi_dsi_dpi_fmt dpi_fmt) +{ + switch (dpi_fmt) { + case MIPI_RGB888: + return 24; + case MIPI_RGB565_PACKED: + return 16; + default: + return 0; + } +} + +/* +static void dphy_calc_dividers(int *cm, int *cn, int *co) +{ +} +*/ + +static int mipi_dsi_dphy_init(struct mipi_dsi_info *mipi_dsi) +{ + int i, best_div = -1; + int64_t delta; + uint64_t least_delta = ~0U; + uint32_t bpp, time_out = 100; + uint32_t lock; + uint32_t req_bit_clk; + uint64_t limit, div_result; + uint64_t denominator, numerator, divisor; + uint64_t norm_denom, norm_num, split_denom; + struct pll_divider div = { 0 }; + struct fb_videomode *mode = mipi_dsi->mode; + struct mipi_lcd_config *lcd_config = mipi_dsi->lcd_config; + +#ifndef CONFIG_FB_IMX64 + regmap_update_bits(mipi_dsi->regmap, SIM_SOPT1, + MIPI_ISO_DISABLE, MIPI_ISO_DISABLE); +#endif + + bpp = fmt_to_bpp(lcd_config->dpi_fmt); + req_bit_clk = PICOS2KHZ2(mode->pixclock, bpp) * 1000U; + + switch (lcd_config->data_lane_num) { + case 1: + break; + case 2: + req_bit_clk = req_bit_clk >> 1; + break; + case 4: + req_bit_clk = req_bit_clk >> 2; + break; + default: + dev_err(&mipi_dsi->pdev->dev, + "requested data lane num is invalid\n"); + return -EINVAL; + } + + if (mipi_dsi->encoder) { + if (req_bit_clk > lcd_config->max_phy_clk) + return -EINVAL; + } + + /* calc CM, CN and CO according to PHY PLL formula: + * + * 'PLL out bitclk = refclk * CM / (CN * CO);' + * + * Let: + * 'numerator = bitclk / divisor'; + * 'denominator = refclk / divisor'; + * Then: + * 'numerator / denominator = CM / (CN * CO)'; + * + * CM is in [16, 255] + * CN is in [1, 32] + * CO is in { 1, 2, 4, 8 }; + */ + divisor = gcd(mipi_dsi->phy_ref_clkfreq, req_bit_clk); + WARN_ON(divisor == 1); + + div_result = req_bit_clk; + do_div(div_result, divisor); + numerator = div_result; + + div_result = mipi_dsi->phy_ref_clkfreq; + do_div(div_result, divisor); + denominator = div_result; + + /* denominator & numerator out of range check */ + if (DIV_ROUND_CLOSEST_ULL(numerator, denominator) > 255 || + DIV_ROUND_CLOSEST_ULL(denominator, numerator) > 32 * 8) + return -EINVAL; + + /* Normalization: reduce or increase + * numerator to [16, 255] + * denominator to [1, 32 * 8] + * Reduce normalization result is 'approximiate' + * Increase nomralization result is 'precise' + */ + if (numerator > 255 || denominator > 32 * 8) { + /* approximate */ + if (likely(numerator > denominator)) { + /* 'numerator > 255'; + * 'limit' should meet below conditions: + * a. '(numerator / limit) >= 16' + * b. '(denominator / limit) >= 1' + */ + limit = min(denominator, + DIV_ROUND_CLOSEST_ULL(numerator, 16)); + + /* Let: + * norm_num = numerator / i; + * norm_denom = denominator / i; + * + * So: + * delta = numerator * norm_denom - + * denominator * norm_num + */ + for (i = 2; i <= limit; i++) { + norm_num = DIV_ROUND_CLOSEST_ULL(numerator, i); + if (norm_num > 255) + continue; + + norm_denom = DIV_ROUND_CLOSEST_ULL(denominator, i); + + /* 'norm_num <= 255' && 'norm_num > norm_denom' + * so, 'norm_denom < 256' + */ + delta = numerator * norm_denom - + denominator * norm_num; + delta = abs(delta); + if (delta < least_delta) { + least_delta = delta; + best_div = i; + } else if (delta == least_delta) { + /* choose better one IF: + * 'norm_denom' derived from last 'best_div' + * needs later split, i.e, 'norm_denom > 32'. + */ + if (DIV_ROUND_CLOSEST_ULL(denominator, best_div) > 32) { + least_delta = delta; + best_div = i; + } + } + } + } else { + /* 'denominator > 32 * 8'; + * 'limit' should meet below conditions: + * a. '(numerator / limit >= 16' + * b. '(denominator / limit >= 1': obviously. + */ + limit = DIV_ROUND_CLOSEST_ULL(numerator, 16); + if (!limit || + DIV_ROUND_CLOSEST_ULL(denominator, limit) > 32 * 8) + return -EINVAL; + + for (i = 2; i <= limit; i++) { + norm_denom = DIV_ROUND_CLOSEST_ULL(denominator, i); + if (norm_denom > 32 * 8) + continue; + + norm_num = DIV_ROUND_CLOSEST_ULL(numerator, i); + + /* 'norm_denom <= 256' && 'norm_num < norm_denom' + * so, 'norm_num <= 255' + */ + delta = numerator * norm_denom - + denominator * norm_num; + delta = abs(delta); + if (delta < least_delta) { + least_delta = delta; + best_div = i; + } else if (delta == least_delta) { + if (DIV_ROUND_CLOSEST_ULL(denominator, best_div) > 32) { + least_delta = delta; + best_div = i; + } + } + } + } + + numerator = DIV_ROUND_CLOSEST_ULL(numerator, best_div); + denominator = DIV_ROUND_CLOSEST_ULL(denominator, best_div); + } else if (numerator < 16) { + /* precise */ + + /* 'limit' should meet below conditions: + * a. 'denominator * limit <= 32 * 8' + * b. '16 <= numerator * limit <= 255' + * Choose 'limit' to be the least value + * which makes 'numerator * limit' to be + * in [16, 255]. + */ + limit = min(256 / (uint32_t)denominator, + 255 / (uint32_t)numerator); + if (limit == 1 || limit < DIV_ROUND_UP_ULL(16, numerator)) + return -EINVAL; + + /* choose the least available value for 'limit' */ + limit = DIV_ROUND_UP_ULL(16, numerator); + numerator = numerator * limit; + denominator = denominator * limit; + + WARN_ON(numerator < 16 || denominator > 32 * 8); + } + + div.cm = cm_map_table[numerator - 16]; + + /* split 'denominator' to 'CN' and 'CO' */ + if (denominator > 32) { + /* traverse four possible values of 'CO' + * there must be some value of 'CO' can be used + */ + least_delta = ~0U; + for (i = 0; i < 4; i++) { + split_denom = DIV_ROUND_CLOSEST_ULL(denominator, 1 << i); + if (split_denom > 32) + continue; + + /* calc deviation to choose the best one */ + delta = denominator - split_denom * (1 << i); + delta = abs(delta); + if (delta < least_delta) { + least_delta = delta; + div.co = co_map_table[i]; + div.cn = cn_map_table[split_denom - 1]; + } + } + } else { + div.co = co_map_table[1 >> 1]; + div.cn = cn_map_table[denominator - 1]; + } + + writel(div.cn, mipi_dsi->mmio_base + DPHY_CN); + writel(div.cm, mipi_dsi->mmio_base + DPHY_CM); + writel(div.co, mipi_dsi->mmio_base + DPHY_CO); + + writel(0x25, mipi_dsi->mmio_base + DPHY_TST); + writel(0x0, mipi_dsi->mmio_base + DPHY_PD_PLL); + + while(!(lock = readl(mipi_dsi->mmio_base + DPHY_LOCK))) { + udelay(10); + time_out--; + if (time_out == 0) { + dev_err(&mipi_dsi->pdev->dev, "cannot get the dphy lock = 0x%x\n", lock); + return -EINVAL; + } + } + + dev_dbg(&mipi_dsi->pdev->dev, "%s: dphy lock = 0x%x\n", __func__, lock); + + writel(0x0, mipi_dsi->mmio_base + DPHY_LOCK_BYP); + writel(0x1, mipi_dsi->mmio_base + DPHY_RTERM_SEL); + writel(0x0, mipi_dsi->mmio_base + DPHY_AUTO_PD_EN); + writel(0x1, mipi_dsi->mmio_base + DPHY_RXLPRP); + writel(0x1, mipi_dsi->mmio_base + DPHY_RXCDRP); + writel(0x0, mipi_dsi->mmio_base + DPHY_M_PRG_HS_PREPARE); + writel(0x0, mipi_dsi->mmio_base + DPHY_MC_PRG_HS_PREPARE); + writel(0x9, mipi_dsi->mmio_base + DPHY_M_PRG_HS_ZERO); + writel(0x20, mipi_dsi->mmio_base + DPHY_MC_PRG_HS_ZERO); + writel(0x5, mipi_dsi->mmio_base + DPHY_M_PRG_HS_TRAIL); + writel(0x5, mipi_dsi->mmio_base + DPHY_MC_PRG_HS_TRAIL); + writel(0x0, mipi_dsi->mmio_base + DPHY_PD_DPHY); + +#ifndef CONFIG_FB_IMX64 + regmap_update_bits(mipi_dsi->regmap, SIM_SOPT1CFG, + DSI_PLL_EN, DSI_PLL_EN); +#endif + + return 0; +} + +static int mipi_dsi_host_init(struct mipi_dsi_info *mipi_dsi) +{ + uint32_t lane_num; + struct mipi_lcd_config *lcd_config = mipi_dsi->lcd_config; + + switch (lcd_config->data_lane_num) { + case 1: + lane_num = 0x0; + break; + case 2: + lane_num = 0x1; + break; + case 4: + lane_num = 0x3; + break; + default: + /* Invalid lane num */ + return -EINVAL; + } + +#ifdef CONFIG_FB_IMX64_DEBUG + printk("%s: data_lane_num = %d\n", __func__, lcd_config->data_lane_num); +#endif + + writel(lane_num, mipi_dsi->mmio_base + HOST_CFG_NUM_LANES); + writel(mipi_dsi->encoder ? 0x0 : 0x1, + mipi_dsi->mmio_base + HOST_CFG_NONCONTINUOUS_CLK); + writel(0x1, mipi_dsi->mmio_base + HOST_CFG_T_PRE); + writel(52, mipi_dsi->mmio_base + HOST_CFG_T_POST); + writel(13, mipi_dsi->mmio_base + HOST_CFG_TX_GAP); + writel(mipi_dsi->encoder ? 0x0 : 0x1, + mipi_dsi->mmio_base + HOST_CFG_AUTOINSERT_EOTP); + writel(0x0, mipi_dsi->mmio_base + HOST_CFG_EXTRA_CMDS_AFTER_EOTP); + writel(0x0, mipi_dsi->mmio_base + HOST_CFG_HTX_TO_COUNT); + writel(0x0, mipi_dsi->mmio_base + HOST_CFG_LRX_H_TO_COUNT); + writel(0x0, mipi_dsi->mmio_base + HOST_CFG_BTA_H_TO_COUNT); + writel(0x3A98, mipi_dsi->mmio_base + HOST_CFG_TWAKEUP); + + return 0; +} + +static int mipi_dsi_dpi_init(struct mipi_dsi_info *mipi_dsi) +{ + uint32_t bpp, color_coding, pixel_fmt; + uint32_t pixel_fifo_level, hfp_period, hbp_period, hsa_period; + struct fb_videomode *mode = mipi_dsi->mode; + struct mipi_lcd_config *lcd_config = mipi_dsi->lcd_config; + + bpp = fmt_to_bpp(lcd_config->dpi_fmt); + + writel(mode->xres, mipi_dsi->mmio_base + DPI_PIXEL_PAYLOAD_SIZE); + + switch (mipi_dsi->traffic_mode) { + case DSI_NON_BURST_WITH_SYNC_PULSE: +#ifdef CONFIG_FB_IMX64 + pixel_fifo_level = 8; + hfp_period = mode->right_margin - DSI_HFP_PKT_OVERHEAD; + hbp_period = mode->left_margin - DSI_HBP_PKT_OVERHEAD; + hsa_period = mode->hsync_len - DSI_HSA_PKT_OVERHEAD; +#else + pixel_fifo_level = mode->xres; + hfp_period = 0x10; + hbp_period = 0x60; + hsa_period = 0xf0; +#endif + break; + case DSI_BURST_MODE: + pixel_fifo_level = mode->xres; +#ifdef CONFIG_FB_IMX64 + hfp_period = mode->right_margin; + hbp_period = mode->left_margin; + hsa_period = mode->hsync_len; +#else + hfp_period = mode->right_margin * (bpp >> 3); + hbp_period = mode->left_margin * (bpp >> 3); + hsa_period = mode->hsync_len * (bpp >> 3); +#endif + break; + default: + pr_debug("unsupport traffic mode: %d\n", + mipi_dsi->traffic_mode); + return -EINVAL; + } + writel(pixel_fifo_level, mipi_dsi->mmio_base + DPI_PIXEL_FIFO_SEND_LEVEL); + + switch (bpp) { + case 24: + color_coding = 5; + pixel_fmt = 3; + break; + case 16: + case 18: + default: + break; + } + writel(color_coding, mipi_dsi->mmio_base + DPI_INTERFACE_COLOR_CODING); + writel(pixel_fmt, mipi_dsi->mmio_base + DPI_PIXEL_FORMAT); +#ifdef CONFIG_FB_IMX64 + writel(0x1, mipi_dsi->mmio_base + DPI_VSYNC_POLARITY); + writel(0x1, mipi_dsi->mmio_base + DPI_HSYNC_POLARITY); +#else + writel(0x0, mipi_dsi->mmio_base + DPI_VSYNC_POLARITY); + writel(0x0, mipi_dsi->mmio_base + DPI_HSYNC_POLARITY); +#endif + writel(mipi_dsi->traffic_mode, + mipi_dsi->mmio_base + DPI_VIDEO_MODE); + + writel(hfp_period, mipi_dsi->mmio_base + DPI_HFP); + writel(hbp_period, mipi_dsi->mmio_base + DPI_HBP); + writel(hsa_period, mipi_dsi->mmio_base + DPI_HSA); + + writel(0x0, mipi_dsi->mmio_base + DPI_ENABLE_MULT_PKTS); + + writel(mode->upper_margin, mipi_dsi->mmio_base + DPI_VBP); + writel(mode->lower_margin, mipi_dsi->mmio_base + DPI_VFP); + writel(0x1, mipi_dsi->mmio_base + DPI_BLLP_MODE); + writel(0x0, mipi_dsi->mmio_base + DPI_USE_NULL_PKT_BLLP); + + writel(mode->yres - 1, mipi_dsi->mmio_base + DPI_VACTIVE); + + writel(0x0, mipi_dsi->mmio_base + DPI_VC); + + return 0; +} + +static void mipi_dsi_init_interrupt(struct mipi_dsi_info *mipi_dsi) +{ + uint32_t irqs_enable; + + /* disable all the irqs */ + writel(0xffffffff, mipi_dsi->mmio_base + HOST_IRQ_MASK); + writel(0x7, mipi_dsi->mmio_base + HOST_IRQ_MASK2); + + irqs_enable = ~(HOST_IRQ_MASK_TX_PKT_DONE_MASK | + HOST_IRQ_MASK_RX_PKT_HDR_RCVD_MASK); + + writel(irqs_enable, mipi_dsi->mmio_base + HOST_IRQ_MASK); +} + +static int mipi_display_enter_sleep(struct mxc_dispdrv_handle *disp) +{ + int err; + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_SET_DISPLAY_OFF, + NULL, 0); + if (err) + return -EINVAL; + msleep(50); + + err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_ENTER_SLEEP_MODE, + NULL, 0); + if (err) { + dev_err(&mipi_dsi->pdev->dev, + "MIPI DSI DCS Command sleep in error!\n"); + } + msleep(MIPI_LCD_SLEEP_MODE_DELAY); + + return err; +} + +static int mipi_display_exit_sleep(struct mxc_dispdrv_handle *disp) +{ + int err; + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_EXIT_SLEEP_MODE, + NULL, 0); + if (err) { + dev_err(&mipi_dsi->pdev->dev, + "MIPI DSI DCS Command sleep-out error!\n"); + return err; + } + msleep(MIPI_LCD_SLEEP_MODE_DELAY); + + err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_SET_DISPLAY_ON, + NULL, 0); + msleep(20); + + return err; +} + +static void reset_dsi_domains(struct mipi_dsi_info *mipi_dsi, bool reset) +{ +#ifdef CONFIG_FB_IMX64 + /* pclk domain */ + regmap_update_bits(mipi_dsi->regmap, SRC_MIPIPHY_RCR, + MIPI_DSI_PCLK_RESET_N, (reset ? 0 : MIPI_DSI_PCLK_RESET_N)); + /* escape domain */ + regmap_update_bits(mipi_dsi->regmap, SRC_MIPIPHY_RCR, + MIPI_DSI_ESC_RESET_N, (reset ? 0 : MIPI_DSI_ESC_RESET_N)); + /* byte domain */ + regmap_update_bits(mipi_dsi->regmap, SRC_MIPIPHY_RCR, + MIPI_DSI_RESET_BYTE_N, (reset ? 0 : MIPI_DSI_RESET_BYTE_N)); + /* dpi domain */ + regmap_update_bits(mipi_dsi->regmap, SRC_MIPIPHY_RCR, + MIPI_DSI_DPI_RESET_N, (reset ? 0 : MIPI_DSI_DPI_RESET_N)); +#else + /* escape domain */ + regmap_update_bits(mipi_dsi->regmap, SIM_SOPT1CFG, + DSI_RST_ESC_N, (reset ? 0 : DSI_RST_ESC_N)); + /* byte domain */ + regmap_update_bits(mipi_dsi->regmap, SIM_SOPT1CFG, + DSI_RST_BYTE_N, (reset ? 0 : DSI_RST_BYTE_N)); + + /* dpi domain */ + regmap_update_bits(mipi_dsi->regmap, SIM_SOPT1CFG, + DSI_RST_DPI_N, (reset ? 0 : DSI_RST_DPI_N)); +#endif +} + +static int mipi_dsi_enable(struct mxc_dispdrv_handle *disp, + struct fb_info *fbi) +{ + int ret; + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + +#ifndef CONFIG_FB_IMX64 + if (!mipi_dsi->dsi_power_on) + pm_runtime_get_sync(&mipi_dsi->pdev->dev); +#endif + + if (!mipi_dsi->lcd_inited) { +#ifdef CONFIG_FB_IMX64 + reset_dsi_domains(mipi_dsi, 0); +#else + ret = clk_set_rate(mipi_dsi->esc_clk, 80000000); + if (ret) { + dev_err(&mipi_dsi->pdev->dev, + "clk enable error: %d!\n", ret); + return ret; + } + + ret = clk_prepare_enable(mipi_dsi->esc_clk); + if (ret) { + dev_err(&mipi_dsi->pdev->dev, + "clk enable error: %d!\n", ret); + return -EINVAL; + } +#endif + + if ((ret = mipi_dsi_dphy_init(mipi_dsi)) < 0) + return ret; + + if ((ret = mipi_dsi_host_init(mipi_dsi)) < 0) + return ret; + + mipi_dsi_dpi_init(mipi_dsi); + +#ifndef CONFIG_FB_IMX64 + reset_dsi_domains(mipi_dsi, 0); + + /* display_en */ + regmap_update_bits(mipi_dsi->regmap, SIM_SOPT1CFG, + DSI_SD, 0x0); + /* normal cm */ + regmap_update_bits(mipi_dsi->regmap, SIM_SOPT1CFG, + DSI_CM, 0x0); +#endif + msleep(20); + + if (!mipi_dsi->encoder) { + ret = device_reset(&mipi_dsi->pdev->dev); + if (ret) { + dev_err(&mipi_dsi->pdev->dev, + "failed to reset device: %d\n", ret); + return -EINVAL; + } + msleep(60); + + mipi_dsi_init_interrupt(mipi_dsi); + + ret = mipi_dsi->lcd_callback->mipi_lcd_setup(mipi_dsi); + if (ret < 0) { + dev_err(&mipi_dsi->pdev->dev, + "failed to init mipi lcd.\n"); + return ret; + } + mipi_dsi_set_mode(mipi_dsi, DSI_HS_MODE); + } + + mipi_dsi->lcd_inited = 1; + } else { +#ifndef CONFIG_FB_IMX64 + ret = clk_prepare_enable(mipi_dsi->esc_clk); + if (ret) { + dev_err(&mipi_dsi->pdev->dev, + "clk enable error: %d!\n", ret); + return -EINVAL; + } +#endif + + reset_dsi_domains(mipi_dsi, 0); + + if (!mipi_dsi->encoder) { + ret = mipi_display_exit_sleep(mipi_dsi->disp_mipi); + if (ret) { + dev_err(&mipi_dsi->pdev->dev, "exit sleep failed\n"); + return -EINVAL; + } + } + } + + return 0; +} + +static void mipi_dsi_wr_tx_header(struct mipi_dsi_info *mipi_dsi, + u8 di, u8 data0, u8 data1, u8 mode, u8 need_bta) +{ + uint32_t pkt_control = 0; + uint16_t word_count = 0; + + word_count = data0 | (data1 << 8); + pkt_control = HOST_PKT_CONTROL_WC(word_count) | + HOST_PKT_CONTROL_VC(0) | + HOST_PKT_CONTROL_DT(di) | + HOST_PKT_CONTROL_HS_SEL(mode) | + HOST_PKT_CONTROL_BTA_TX(need_bta); + + dev_dbg(&mipi_dsi->pdev->dev, "pkt_control = %x\n", pkt_control); + writel(pkt_control, mipi_dsi->mmio_base + HOST_PKT_CONTROL); +} + +static void mipi_dsi_wr_tx_data(struct mipi_dsi_info *mipi_dsi, + uint32_t tx_data) +{ + writel(tx_data, mipi_dsi->mmio_base + HOST_TX_PAYLOAD); +} + +static void mipi_dsi_long_data_wr(struct mipi_dsi_info *mipi_dsi, + const uint8_t *data0, uint32_t data_size) +{ + uint32_t data_cnt = 0, payload = 0; + + /* in case that data count is more than 4 */ + for (data_cnt = 0; data_cnt < data_size; data_cnt += 4) { + /* + * after sending 4bytes per one time, + * send remainder data less then 4. + */ + if ((data_size - data_cnt) < 4) { + if ((data_size - data_cnt) == 3) { + payload = data0[data_cnt] | + (data0[data_cnt + 1] << 8) | + (data0[data_cnt + 2] << 16); + dev_dbg(&mipi_dsi->pdev->dev, "count = 3 payload = %x, %x %x %x\n", + payload, data0[data_cnt], data0[data_cnt + 1], data0[data_cnt + 2]); + } else if ((data_size - data_cnt) == 2) { + payload = data0[data_cnt] | + (data0[data_cnt + 1] << 8); + dev_dbg(&mipi_dsi->pdev->dev, "count = 2 payload = %x, %x %x\n", + payload, data0[data_cnt], data0[data_cnt + 1]); + } else if ((data_size - data_cnt) == 1) { + payload = data0[data_cnt]; + dev_dbg(&mipi_dsi->pdev->dev, "count = 1 payload = %x, %x\n", + payload, data0[data_cnt]); + } + + mipi_dsi_wr_tx_data(mipi_dsi, payload); + } else { + payload = data0[data_cnt] | + (data0[data_cnt + 1] << 8) | + (data0[data_cnt + 2] << 16) | + (data0[data_cnt + 3] << 24); + + dev_dbg(&mipi_dsi->pdev->dev, + "count = 4 payload = %x, %x %x %x %x\n", + payload, *(u8 *)(data0 + data_cnt), + data0[data_cnt + 1], + data0[data_cnt + 2], + data0[data_cnt + 3]); + + mipi_dsi_wr_tx_data(mipi_dsi, payload); + } + } +} + +static int mipi_dsi_pkt_write(struct mipi_dsi_info *mipi_dsi, + u8 data_type, const u32 *buf, int len) +{ + int ret = 0; + struct platform_device *pdev = mipi_dsi->pdev; + const uint8_t *data = (const uint8_t *)buf; + + if (len == 0) + /* handle generic long write command */ + mipi_dsi_wr_tx_header(mipi_dsi, data_type, data[0], data[1], DSI_LP_MODE, 0); + else { + reinit_completion(&dsi_tx_done); + + /* handle generic long write command */ + mipi_dsi_long_data_wr(mipi_dsi, data, len); + mipi_dsi_wr_tx_header(mipi_dsi, data_type, len & 0xff, + (len & 0xff00) >> 8, DSI_LP_MODE, 0); + } + + /* send packet */ + writel(0x1, mipi_dsi->mmio_base + HOST_SEND_PACKET); + ret = wait_for_completion_timeout(&dsi_tx_done, MIPI_FIFO_TIMEOUT); + + if (!ret) { + dev_err(&pdev->dev, "wait tx done timeout!\n"); + return -ETIMEDOUT; + } + mdelay(10); + + return 0; +} + +static uint32_t mipi_dsi_rd_rx_header(struct mipi_dsi_info *mipi_dsi) +{ + return readl(mipi_dsi->mmio_base + HOST_PKT_RX_PKT_HEADER); +} + +static int mipi_dsi_pkt_read(struct mipi_dsi_info *mipi_dsi, + uint8_t data_type, uint32_t *buf, int len) +{ + int ret; + uint32_t rx_hdr; + struct platform_device *pdev = mipi_dsi->pdev; + const uint8_t *data = (const uint8_t *)buf; + + if (len <= 4) { + reinit_completion(&dsi_rx_done); + + mipi_dsi_wr_tx_header(mipi_dsi, data_type, data[0], data[1], DSI_LP_MODE, 1); + writel(0x1, mipi_dsi->mmio_base + HOST_SEND_PACKET); + + ret = wait_for_completion_timeout(&dsi_rx_done, MIPI_FIFO_TIMEOUT); + if (!ret) { + dev_err(&pdev->dev, "wait rx done timeout!\n"); + return -ETIMEDOUT; + } + + rx_hdr = mipi_dsi_rd_rx_header(mipi_dsi); + dev_dbg(&pdev->dev, "rx: rx_hdr = 0x%x, data type = 0x%x, word_count = 0x%x\n", + rx_hdr, (rx_hdr >> 16) & 0x3f, rx_hdr & 0xffff); + + buf[0] = rx_hdr & 0xff; + } else { + /* TODO: add support later */ + } + + return 0; +} + +static int mipi_dsi_dcs_cmd(struct mipi_dsi_info *mipi_dsi, + u8 cmd, const u32 *param, int num) +{ + int err = 0; + u32 buf[DSI_CMD_BUF_MAXSIZE]; + + switch (cmd) { + case MIPI_DCS_EXIT_SLEEP_MODE: + case MIPI_DCS_ENTER_SLEEP_MODE: + case MIPI_DCS_SET_DISPLAY_ON: + case MIPI_DCS_SET_DISPLAY_OFF: + buf[0] = cmd; + buf[1] = 0x0; + err = mipi_dsi_pkt_write(mipi_dsi, + MIPI_DSI_DCS_SHORT_WRITE, buf, 0); + break; + + default: + dev_err(&mipi_dsi->pdev->dev, + "MIPI DSI DCS Command:0x%x Not supported!\n", cmd); + break; + } + + return err; +} + +static void mipi_dsi_disable(struct mxc_dispdrv_handle *disp, + struct fb_info *fbi) +{ + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + if (!mipi_dsi->encoder) + mipi_display_enter_sleep(mipi_dsi->disp_mipi); + +#ifndef CONFIG_FB_IMX64 + clk_disable_unprepare(mipi_dsi->esc_clk); +#endif + reset_dsi_domains(mipi_dsi, 1); +#ifdef CONFIG_FB_IMX64 + regmap_update_bits(mipi_dsi->regmap, SRC_MIPIPHY_RCR, + MIPI_DSI_PCLK_RESET_N, 0x0); +#else + regmap_update_bits(mipi_dsi->regmap, SIM_SOPT1CFG, + DSI_PLL_EN, 0x0); +#endif +} + +static int mipi_dsi_setup(struct mxc_dispdrv_handle *disp, + struct fb_info *fbi) +{ + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + int xres_virtual = fbi->var.xres_virtual; + int yres_virtual = fbi->var.yres_virtual; + int xoffset = fbi->var.xoffset; + int yoffset = fbi->var.yoffset; + int pixclock = fbi->var.pixclock; + + if (!mipi_dsi->mode) + return 0; + + /* set the mode back to var in case userspace changes it */ + fb_videomode_to_var(&fbi->var, mipi_dsi->mode); + + /* restore some var entries cached */ + fbi->var.xres_virtual = xres_virtual; + fbi->var.yres_virtual = yres_virtual; + fbi->var.xoffset = xoffset; + fbi->var.yoffset = yoffset; + fbi->var.pixclock = pixclock; + + return 0; +} + +static struct mxc_dispdrv_driver mipi_dsi_drv = { + .name = DISPDRV_MIPI, + .init = mipi_dsi_disp_init, + .deinit = mipi_dsi_disp_deinit, + .enable = mipi_dsi_enable, + .disable = mipi_dsi_disable, + .setup = mipi_dsi_setup, +}; + +static irqreturn_t mipi_dsi_irq_handler(int irq, void *data) +{ + uint32_t irq_status; + struct mipi_dsi_info *mipi_dsi = data; + struct platform_device *pdev = mipi_dsi->pdev; + + irq_status = readl(mipi_dsi->mmio_base + HOST_IRQ_STATUS); + + dev_dbg(&pdev->dev, "irq_status = 0x%x\n", irq_status); + + if (irq_status & HOST_IRQ_STATUS_TX_PKT_DONE) { + dev_dbg(&pdev->dev, "payload tx finished\n"); + complete(&dsi_tx_done); + } + + if (irq_status & HOST_IRQ_STATUS_RX_PKT_HDR_RCVD) { + dev_dbg(&pdev->dev, "rx data finished\n"); + complete(&dsi_rx_done); + } + + return IRQ_HANDLED; +} + +#ifdef CONFIG_FB_IMX64 +static int dsi_clks_init(struct mipi_dsi_info *minfo) +{ + int ret = 0; + struct platform_device *pdev = minfo->pdev; + struct device_node *np = pdev->dev.of_node; + + minfo->core_clk = devm_clk_get(&pdev->dev, "core"); + BUG_ON(IS_ERR(minfo->core_clk)); + + minfo->phy_ref_clk = devm_clk_get(&pdev->dev, "phy_ref"); + BUG_ON(IS_ERR(minfo->phy_ref_clk)); + + minfo->rxesc_clk = devm_clk_get(&pdev->dev, "rxesc"); + BUG_ON(IS_ERR(minfo->rxesc_clk)); + + minfo->txesc_clk = devm_clk_get(&pdev->dev, "txesc"); + BUG_ON(IS_ERR(minfo->txesc_clk)); + + minfo->dbi_clk = devm_clk_get(&pdev->dev, "dbi"); + BUG_ON(IS_ERR(minfo->dbi_clk)); + + + ret = clk_set_rate(minfo->phy_ref_clk, minfo->phy_ref_clkfreq); + if (ret < 0) { + dev_err(&pdev->dev, "set phy_ref clock rate failed\n"); + goto out; + } + + ret = clk_set_rate(minfo->rxesc_clk, 80000000); + if (ret < 0) { + dev_err(&pdev->dev, "set rxesc clock rate failed\n"); + goto out; + } + + ret = clk_set_rate(minfo->txesc_clk, 20000000); + if (ret < 0) { + dev_err(&pdev->dev, "set txesc clock rate failed\n"); + goto out; + } + + clk_prepare_enable(minfo->core_clk); + clk_prepare_enable(minfo->phy_ref_clk); + clk_prepare_enable(minfo->rxesc_clk); + clk_prepare_enable(minfo->txesc_clk); + /* TODO: dbi clk is not used yet */ + +out: + return ret; +} +#endif + +/** + * This function is called by the driver framework to initialize the MIPI DSI + * device. + * + * @param pdev The device structure for the MIPI DSI passed in by the + * driver framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int mipi_dsi_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mipi_dsi_info *mipi_dsi; + struct device_node *endpoint = NULL, *remote; + struct resource *res; + const char *lcd_panel; + int ret = 0; + u32 vmode_index; + uint32_t phy_ref_clkfreq; + + mipi_dsi = devm_kzalloc(&pdev->dev, sizeof(*mipi_dsi), GFP_KERNEL); + if (!mipi_dsi) + return -ENOMEM; + mipi_dsi->pdev = pdev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get platform mem resource\n"); + return -ENOMEM; + } + + mipi_dsi->mmio_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mipi_dsi->mmio_base)) + return -ENODEV; + + mipi_dsi->irq = platform_get_irq(pdev, 0); + if (mipi_dsi->irq < 0) { + dev_err(&pdev->dev, "failed to get device irq\n"); + return -EINVAL; + } + + ret = devm_request_irq(&pdev->dev, mipi_dsi->irq, + mipi_dsi_irq_handler, + 0, "mipi_dsi_northwest", mipi_dsi); + if (ret) { + dev_err(&pdev->dev, "failed to request mipi dsi irq\n"); + return ret; + } + + ret = of_property_read_u32(np, "phy-ref-clkfreq", + &phy_ref_clkfreq); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get phy reference clock rate\n"); + return -EINVAL; + } + + if (phy_ref_clkfreq < 24000000 || phy_ref_clkfreq > 200000000) { + dev_err(&pdev->dev, "invalid phy reference clock rate\n"); + return -EINVAL; + } + mipi_dsi->phy_ref_clkfreq = phy_ref_clkfreq; + +#ifdef CONFIG_FB_IMX64 + dsi_clks_init(mipi_dsi); + + mipi_dsi->regmap = syscon_regmap_lookup_by_phandle(np, "reset"); + if (IS_ERR(mipi_dsi->regmap)) { + dev_err(&pdev->dev, "failed to get reset regmap\n"); + return -EINVAL; + } + + mipi_dsi->mux_sel = syscon_regmap_lookup_by_phandle(np, "mux-sel"); + if (IS_ERR(mipi_dsi->mux_sel)) { + dev_err(&pdev->dev, "failed to get mux_sel regmap\n"); + return -EINVAL; + } + + /* TODO: use lcdif for source */ + regmap_update_bits(mipi_dsi->mux_sel, IOMUXC_GPR_GPR13, + GPR_MIPI_MUX_SEL, 0x0); + +#else + mipi_dsi->esc_clk = devm_clk_get(&pdev->dev, "mipi_dsi_clk"); + if (IS_ERR(mipi_dsi->esc_clk)) { + dev_err(&pdev->dev, "failed to get esc clk\n"); + return PTR_ERR(mipi_dsi->esc_clk); + } + + mipi_dsi->regmap = syscon_regmap_lookup_by_phandle(np, "sim"); + if (IS_ERR(mipi_dsi->regmap)) { + dev_err(&pdev->dev, "failed to get parent regmap\n"); + return -EINVAL; + } +#endif + /* check whether an encoder exists */ + endpoint = of_graph_get_next_endpoint(np, NULL); + if (endpoint) { + remote = of_graph_get_remote_port_parent(endpoint); + if (!remote) + return -EINVAL; + + ret = of_property_read_u32(remote, "video-mode", &vmode_index); + if ((ret < 0) || (vmode_index >= ARRAY_SIZE(mxc_cea_mode))) + return -EINVAL; + mipi_dsi->vmode_index = vmode_index; + + mipi_dsi->mode = devm_kzalloc(&pdev->dev, + sizeof(struct fb_videomode), + GFP_KERNEL); + if (!mipi_dsi->mode) + return -ENOMEM; + + memcpy(mipi_dsi->mode, &mxc_cea_mode[vmode_index], + sizeof(struct fb_videomode)); + + ret = of_property_read_u32(remote, "dsi-traffic-mode", + &mipi_dsi->traffic_mode); + if (ret < 0 || mipi_dsi->traffic_mode > 2) { + devm_kfree(&pdev->dev, mipi_dsi->mode); + return -EINVAL; + } + + mipi_dsi->lcd_config = devm_kzalloc(&pdev->dev, + sizeof(struct mipi_lcd_config), + GFP_KERNEL); + if (!mipi_dsi->lcd_config) { + kfree(mipi_dsi->mode); + return -ENOMEM; + } + + mipi_dsi->encoder = 1; + } else { + /* Default, using 'BURST-MODE' for mipi panel */ + mipi_dsi->traffic_mode = 2; + + ret = of_property_read_string(np, "lcd_panel", &lcd_panel); + if (ret) { + dev_err(&pdev->dev, "failed to read lcd_panel property\n"); + return ret; + } + + /* mipi VDDA is sw1 in PMIC which is always on */ + + mipi_dsi->lcd_panel = kstrdup(lcd_panel, GFP_KERNEL); + if (!mipi_dsi->lcd_panel) { + dev_err(&pdev->dev, "failed to allocate lcd panel\n"); + ret = -ENOMEM; + } + } + + mipi_dsi->disp_mipi = mxc_dispdrv_register(&mipi_dsi_drv); + if (IS_ERR(mipi_dsi->disp_mipi)) { + dev_err(&pdev->dev, "mxc_dispdrv_register error\n"); + ret = PTR_ERR(mipi_dsi->disp_mipi); + goto dispdrv_reg_fail; + } + + mipi_dsi->mipi_dsi_pkt_read = mipi_dsi_pkt_read; + mipi_dsi->mipi_dsi_pkt_write = mipi_dsi_pkt_write; + mipi_dsi->mipi_dsi_dcs_cmd = mipi_dsi_dcs_cmd; + +#ifndef CONFIG_FB_IMX64 + pm_runtime_enable(&pdev->dev); +#endif + mxc_dispdrv_setdata(mipi_dsi->disp_mipi, mipi_dsi); + dev_set_drvdata(&pdev->dev, mipi_dsi); + + dev_info(&pdev->dev, "i.MX MIPI DSI driver probed\n"); + return ret; + +dispdrv_reg_fail: + if (mipi_dsi->lcd_panel) + kfree(mipi_dsi->lcd_panel); + return ret; +} + +static int mipi_dsi_remove(struct platform_device *pdev) +{ + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + mxc_dispdrv_puthandle(mipi_dsi->disp_mipi); + mxc_dispdrv_unregister(mipi_dsi->disp_mipi); + + kfree(mipi_dsi->lcd_panel); + dev_set_drvdata(&pdev->dev, NULL); + + return 0; +} + +static void mipi_dsi_shutdown(struct platform_device *pdev) +{ + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + if (mipi_dsi->lcd_inited) { +#ifndef CONFIG_FB_IMX64 + clk_prepare_enable(mipi_dsi->esc_clk); +#endif + if (!mipi_dsi->encoder) + mipi_display_enter_sleep(mipi_dsi->disp_mipi); + + writel(0x1, mipi_dsi->mmio_base + DPHY_PD_PLL); + writel(0x1, mipi_dsi->mmio_base + DPHY_PD_DPHY); +#ifndef CONFIG_FB_IMX64 + clk_disable_unprepare(mipi_dsi->esc_clk); +#endif + mipi_dsi->lcd_inited = 0; + } + + reset_dsi_domains(mipi_dsi, 1); + +#ifdef CONFIG_FB_IMX64 + regmap_update_bits(mipi_dsi->regmap, SRC_MIPIPHY_RCR, + MIPI_DSI_PCLK_RESET_N, 0x0); +#else + regmap_update_bits(mipi_dsi->regmap, SIM_SOPT1CFG, + DSI_PLL_EN, 0x0); +#endif +} + +static const struct of_device_id imx_mipi_dsi_dt_ids[] = { + { .compatible = "fsl,imx7ulp-mipi-dsi", .data = NULL, }, + { .compatible = "fsl,imx8mq-mipi-dsi", .data = NULL, }, + { } +}; +MODULE_DEVICE_TABLE(of, imx_mipi_dsi_dt_ids); + +static int mipi_dsi_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + mipi_dsi->dsi_power_on = 0; + + return 0; +} + +static int mipi_dsi_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + if (!mipi_dsi->dsi_power_on) { + request_bus_freq(BUS_FREQ_HIGH); + dev_dbg(dev, "mipi dsi busfreq high request.\n"); + + mipi_dsi->dsi_power_on = 1; + } + + return 0; +} + +static int mipi_dsi_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + if (unlikely(mipi_dsi->lcd_inited)) { +#ifndef CONFIG_FB_IMX64 + clk_prepare_enable(mipi_dsi->esc_clk); +#endif + + writel(0x1, mipi_dsi->mmio_base + DPHY_PD_PLL); + writel(0x1, mipi_dsi->mmio_base + DPHY_PD_DPHY); + +#ifndef CONFIG_FB_IMX64 + clk_disable_unprepare(mipi_dsi->esc_clk); +#endif + mipi_dsi->lcd_inited = 0; + } + + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static int mipi_dsi_resume(struct device *dev) +{ + pinctrl_pm_select_default_state(dev); + + return 0; +} + +static const struct dev_pm_ops mipi_dsi_pm_ops = { + .suspend = mipi_dsi_suspend, + .resume = mipi_dsi_resume, + .runtime_suspend = mipi_dsi_runtime_suspend, + .runtime_resume = mipi_dsi_runtime_resume, + .runtime_idle = NULL, +}; + +static struct platform_driver mipi_dsi_driver = { + .driver = { + .of_match_table = imx_mipi_dsi_dt_ids, + .name = "mipi_dsi_northwest", + .pm = &mipi_dsi_pm_ops, + }, + .probe = mipi_dsi_probe, + .remove = mipi_dsi_remove, + .shutdown = mipi_dsi_shutdown, +}; + +static int __init mipi_dsi_init(void) +{ + int err; + + err = platform_driver_register(&mipi_dsi_driver); + if (err) { + pr_err("mipi_dsi_driver register failed\n"); + return err; + } + + pr_debug("MIPI DSI driver module loaded: %s\n", mipi_dsi_driver.driver.name); + + return 0; +} + +static void __exit mipi_dsi_exit(void) +{ + platform_driver_unregister(&mipi_dsi_driver); +} + +module_init(mipi_dsi_init); +module_exit(mipi_dsi_exit); + +MODULE_AUTHOR("NXP Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX MIPI DSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/mxc/mipi_dsi_samsung.c b/drivers/video/fbdev/mxc/mipi_dsi_samsung.c new file mode 100644 index 000000000000..e5caebb5e355 --- /dev/null +++ b/drivers/video/fbdev/mxc/mipi_dsi_samsung.c @@ -0,0 +1,923 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2017 NXP. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/bitops.h> +#include <linux/mipi_dsi_samsung.h> +#include <linux/module.h> +#include <linux/mxcfb.h> +#include <linux/pm_runtime.h> +#include <linux/busfreq-imx.h> +#include <linux/backlight.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <video/mipi_display.h> +#include <linux/mfd/syscon.h> + +#include "mipi_dsi.h" + +#define DISPDRV_MIPI "mipi_dsi_samsung" +#define ROUND_UP(x) ((x)+1) +#define NS2PS_RATIO (1000) +#define MIPI_LCD_SLEEP_MODE_DELAY (120) +#define MIPI_FIFO_TIMEOUT msecs_to_jiffies(250) + +static struct mipi_dsi_match_lcd mipi_dsi_lcd_db[] = { +#ifdef CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL + { + "TRULY-WVGA", + {mipid_hx8369_get_lcd_videomode, mipid_hx8369_lcd_setup} + }, +#endif +#ifdef CONFIG_FB_MXC_TRULY_PANEL_TFT3P5079E + { + "TRULY-WVGA-TFT3P5079E", + {mipid_otm8018b_get_lcd_videomode, mipid_otm8018b_lcd_setup} + }, +#endif +#ifdef CONFIG_FB_MXC_TRULY_PANEL_TFT3P5581E + { + "TRULY-WVGA-TFT3P5581E", + {mipid_hx8363_get_lcd_videomode, mipid_hx8363_lcd_setup} + }, +#endif + { + "", {NULL, NULL} + } +}; + +enum mipi_dsi_mode { + DSI_COMMAND_MODE, + DSI_VIDEO_MODE +}; + +enum mipi_dsi_trans_mode { + DSI_LP_MODE, + DSI_HS_MODE +}; + +static DECLARE_COMPLETION(dsi_rx_done); +static DECLARE_COMPLETION(dsi_tx_done); + +static void mipi_dsi_set_mode(struct mipi_dsi_info *mipi_dsi, + enum mipi_dsi_trans_mode mode); + +static int mipi_dsi_lcd_init(struct mipi_dsi_info *mipi_dsi, + struct mxc_dispdrv_setting *setting) +{ + int i, size, err; + struct fb_videomode *mipi_lcd_modedb; + struct fb_videomode mode; + struct device *dev = &mipi_dsi->pdev->dev; + + for (i = 0; i < ARRAY_SIZE(mipi_dsi_lcd_db); i++) { + if (!strcmp(mipi_dsi->lcd_panel, + mipi_dsi_lcd_db[i].lcd_panel)) { + mipi_dsi->lcd_callback = + &mipi_dsi_lcd_db[i].lcd_callback; + break; + } + } + if (i == ARRAY_SIZE(mipi_dsi_lcd_db)) { + dev_err(dev, "failed to find supported lcd panel.\n"); + return -EINVAL; + } + + /* set default bpp to 32 if not set*/ + if (!setting->default_bpp) + setting->default_bpp = 32; + + mipi_dsi->lcd_callback->get_mipi_lcd_videomode(&mipi_lcd_modedb, &size, + &mipi_dsi->lcd_config); + + err = fb_find_mode(&setting->fbi->var, setting->fbi, + setting->dft_mode_str, + mipi_lcd_modedb, size, NULL, + setting->default_bpp); + if (err != 1) + fb_videomode_to_var(&setting->fbi->var, mipi_lcd_modedb); + + INIT_LIST_HEAD(&setting->fbi->modelist); + for (i = 0; i < size; i++) { + fb_var_to_videomode(&mode, &setting->fbi->var); + if (fb_mode_is_equal(&mode, mipi_lcd_modedb + i)) { + err = fb_add_videomode(mipi_lcd_modedb + i, + &setting->fbi->modelist); + mipi_dsi->mode = mipi_lcd_modedb + i; + break; + } + } + + if ((err < 0) || (size == i)) { + dev_err(dev, "failed to add videomode.\n"); + return err; + } + + return 0; +} + +static void mipi_dsi_wr_tx_header(struct mipi_dsi_info *mipi_dsi, + u8 di, u8 data0, u8 data1) +{ + unsigned int reg; + + reg = (data1 << 16) | (data0 << 8) | ((di & 0x3f) << 0); + + writel(reg, mipi_dsi->mmio_base + MIPI_DSI_PKTHDR); +} + +static void mipi_dsi_wr_tx_data(struct mipi_dsi_info *mipi_dsi, + unsigned int tx_data) +{ + writel(tx_data, mipi_dsi->mmio_base + MIPI_DSI_PAYLOAD); +} + +static void mipi_dsi_long_data_wr(struct mipi_dsi_info *mipi_dsi, + const unsigned char *data0, unsigned int data_size) +{ + unsigned int data_cnt = 0, payload = 0; + + /* in case that data count is more then 4 */ + for (data_cnt = 0; data_cnt < data_size; data_cnt += 4) { + /* + * after sending 4bytes per one time, + * send remainder data less then 4. + */ + if ((data_size - data_cnt) < 4) { + if ((data_size - data_cnt) == 3) { + payload = data0[data_cnt] | + data0[data_cnt + 1] << 8 | + data0[data_cnt + 2] << 16; + dev_dbg(&mipi_dsi->pdev->dev, "count = 3 payload = %x, %x %x %x\n", + payload, data0[data_cnt], + data0[data_cnt + 1], + data0[data_cnt + 2]); + } else if ((data_size - data_cnt) == 2) { + payload = data0[data_cnt] | + data0[data_cnt + 1] << 8; + dev_dbg(&mipi_dsi->pdev->dev, + "count = 2 payload = %x, %x %x\n", payload, + data0[data_cnt], + data0[data_cnt + 1]); + } else if ((data_size - data_cnt) == 1) { + payload = data0[data_cnt]; + } + + mipi_dsi_wr_tx_data(mipi_dsi, payload); + /* send 4bytes per one time. */ + } else { + payload = data0[data_cnt] | + data0[data_cnt + 1] << 8 | + data0[data_cnt + 2] << 16 | + data0[data_cnt + 3] << 24; + + dev_dbg(&mipi_dsi->pdev->dev, + "count = 4 payload = %x, %x %x %x %x\n", + payload, *(u8 *)(data0 + data_cnt), + data0[data_cnt + 1], + data0[data_cnt + 2], + data0[data_cnt + 3]); + + mipi_dsi_wr_tx_data(mipi_dsi, payload); + } + } +} + +static int mipi_dsi_pkt_write(struct mipi_dsi_info *mipi_dsi, + u8 data_type, const u32 *buf, int len) +{ + int ret = 0; + struct platform_device *pdev = mipi_dsi->pdev; + const unsigned char *data = (const unsigned char*)buf; + + if (len == 0) + /* handle generic short write command */ + mipi_dsi_wr_tx_header(mipi_dsi, data_type, data[0], data[1]); + else { + reinit_completion(&dsi_tx_done); + + /* handle generic long write command */ + mipi_dsi_long_data_wr(mipi_dsi, data, len); + mipi_dsi_wr_tx_header(mipi_dsi, data_type, len & 0xff, (len & 0xff00) >> 8); + + ret = wait_for_completion_timeout(&dsi_tx_done, MIPI_FIFO_TIMEOUT); + if (!ret) { + dev_err(&pdev->dev, "wait tx done timeout!\n"); + return -ETIMEDOUT; + } + } + mdelay(10); + + return 0; +} + +static void mipi_dsi_rd_tx_header(struct mipi_dsi_info *mipi_dsi, + u8 data_type, u8 data0) +{ + unsigned int reg = (data_type << 0) | (data0 << 8); + + writel(reg, mipi_dsi->mmio_base + MIPI_DSI_PKTHDR); +} + +static unsigned int mipi_dsi_rd_rx_fifo(struct mipi_dsi_info *mipi_dsi) +{ + return readl(mipi_dsi->mmio_base + MIPI_DSI_RXFIFO); +} + +static int mipi_dsi_pkt_read(struct mipi_dsi_info *mipi_dsi, + u8 data_type, u32 *buf, int len) +{ + int ret; + struct platform_device *pdev = mipi_dsi->pdev; + + if (len <= 4) { + reinit_completion(&dsi_rx_done); + + mipi_dsi_rd_tx_header(mipi_dsi, data_type, buf[0]); + + ret = wait_for_completion_timeout(&dsi_rx_done, MIPI_FIFO_TIMEOUT); + if (!ret) { + dev_err(&pdev->dev, "wait rx done timeout!\n"); + return -ETIMEDOUT; + } + + buf[0] = mipi_dsi_rd_rx_fifo(mipi_dsi); + buf[0] = buf[0] >> 8; + } + else { + /* TODO: add support later */ + } + + return 0; +} + +int mipi_dsi_dcs_cmd(struct mipi_dsi_info *mipi_dsi, + u8 cmd, const u32 *param, int num) +{ + int err = 0; + u32 buf[DSI_CMD_BUF_MAXSIZE]; + + switch (cmd) { + case MIPI_DCS_EXIT_SLEEP_MODE: + case MIPI_DCS_ENTER_SLEEP_MODE: + case MIPI_DCS_SET_DISPLAY_ON: + case MIPI_DCS_SET_DISPLAY_OFF: + buf[0] = cmd; + err = mipi_dsi_pkt_write(mipi_dsi, + MIPI_DSI_DCS_SHORT_WRITE, buf, 0); + break; + + default: + dev_err(&mipi_dsi->pdev->dev, + "MIPI DSI DCS Command:0x%x Not supported!\n", cmd); + break; + } + + return err; +} + +static void mipi_dsi_set_main_standby(struct mipi_dsi_info *mipi_dsi, + unsigned int enable) +{ + unsigned int reg; + + reg = readl(mipi_dsi->mmio_base + MIPI_DSI_MDRESOL); + + reg &= ~MIPI_DSI_MAIN_STANDBY(1); + + if (enable) + reg |= MIPI_DSI_MAIN_STANDBY(1); + + writel(reg, mipi_dsi->mmio_base + MIPI_DSI_MDRESOL); +} + +static void mipi_dsi_power_off(struct mxc_dispdrv_handle *disp) +{ + int err; + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_ENTER_SLEEP_MODE, + NULL, 0); + if (err) { + dev_err(&mipi_dsi->pdev->dev, + "MIPI DSI DCS Command display on error!\n"); + } + msleep(MIPI_LCD_SLEEP_MODE_DELAY); + + mipi_dsi_set_main_standby(mipi_dsi, 0); + + clk_disable_unprepare(mipi_dsi->dphy_clk); + clk_disable_unprepare(mipi_dsi->cfg_clk); +} + +static int mipi_dsi_lane_stop_state(struct mipi_dsi_info *mipi_dsi) +{ + unsigned int reg; + + reg = readl(mipi_dsi->mmio_base + MIPI_DSI_STATUS); + + if (((reg & MIPI_DSI_STOP_STATE_DAT(0x3)) == 0x3) && + ((reg & MIPI_DSI_STOP_STATE_CLK(0x1)) || + (reg & MIPI_DSI_TX_READY_HS_CLK(0x1)))) + return 1; + + return 0; +} + +static void mipi_dsi_init_interrupt(struct mipi_dsi_info *mipi_dsi) +{ + unsigned int intsrc, intmsk; + + intsrc = (INTSRC_SFR_PL_FIFO_EMPTY | INTSRC_RX_DATA_DONE); + writel(intsrc, mipi_dsi->mmio_base + MIPI_DSI_INTSRC); + + intmsk = ~(INTMSK_SFR_PL_FIFO_EMPTY | INTMSK_RX_DATA_DONE); + writel(intmsk, mipi_dsi->mmio_base + MIPI_DSI_INTMSK); +} + +static int mipi_dsi_master_init(struct mipi_dsi_info *mipi_dsi, + bool init) +{ + unsigned int time_out = 100; + unsigned int reg, byte_clk, esc_div; + struct fb_videomode *mode = mipi_dsi->mode; + struct device *dev = &mipi_dsi->pdev->dev; + + /* configure DPHY PLL clock */ + writel(MIPI_DSI_TX_REQUEST_HSCLK(0) | + MIPI_DSI_DPHY_SEL(0) | + MIPI_DSI_PLL_BYPASS(0) | + MIPI_DSI_BYTE_CLK_SRC(0), + mipi_dsi->mmio_base + MIPI_DSI_CLKCTRL); + if (!strcmp(mipi_dsi->lcd_panel, "TRULY-WVGA-TFT3P5581E")) + writel(MIPI_DSI_PLL_EN(1) | MIPI_DSI_PMS(0x3141), + mipi_dsi->mmio_base + MIPI_DSI_PLLCTRL); + else + writel(MIPI_DSI_PLL_EN(1) | MIPI_DSI_PMS(0x4190), + mipi_dsi->mmio_base + MIPI_DSI_PLLCTRL); + + /* set PLLTMR: stable time */ + writel(33024, mipi_dsi->mmio_base + MIPI_DSI_PLLTMR); + udelay(300); + + /* configure byte clock */ + reg = readl(mipi_dsi->mmio_base + MIPI_DSI_CLKCTRL); + reg |= MIPI_DSI_BYTE_CLK_EN(1); + byte_clk = 1500000000 / 8; + esc_div = DIV_ROUND_UP(byte_clk, 20 * 1000000); + reg |= (esc_div & 0xffff); + /* enable escape clock for clock lane and data lane0 and lane1 */ + reg |= MIPI_DSI_LANE_ESC_CLK_EN(0x7); + reg |= MIPI_DSI_ESC_CLK_EN(1); + writel(reg, mipi_dsi->mmio_base + MIPI_DSI_CLKCTRL); + + /* set main display resolution */ + writel(MIPI_DSI_MAIN_HRESOL(mode->xres) | + MIPI_DSI_MAIN_VRESOL(mode->yres) | + MIPI_DSI_MAIN_STANDBY(0), + mipi_dsi->mmio_base + MIPI_DSI_MDRESOL); + + /* set config register */ + writel(MIPI_DSI_MFLUSH_VS(1) | + MIPI_DSI_SYNC_IN_FORM(0) | + MIPI_DSI_BURST_MODE(1) | + MIPI_DSI_VIDEO_MODE(1) | + MIPI_DSI_AUTO_MODE(0) | + MIPI_DSI_HSE_DISABLE_MODE(0) | + MIPI_DSI_HFP_DISABLE_MODE(0) | + MIPI_DSI_HBP_DISABLE_MODE(0) | + MIPI_DSI_HSA_DISABLE_MODE(0) | + MIPI_DSI_MAIN_VC(0) | + MIPI_DSI_SUB_VC(1) | + MIPI_DSI_MAIN_PIX_FORMAT(0x7) | + MIPI_DSI_SUB_PIX_FORMAT(0x7) | + MIPI_DSI_NUM_OF_DATALANE(0x1) | + MIPI_DSI_LANE_EN(0x7), /* enable data lane 0 and 1 */ + mipi_dsi->mmio_base + MIPI_DSI_CONFIG); + + /* set main display vporch */ + writel(MIPI_DSI_CMDALLOW(0xf) | + MIPI_DSI_STABLE_VFP(mode->lower_margin) | + MIPI_DSI_MAIN_VBP(mode->upper_margin), + mipi_dsi->mmio_base + MIPI_DSI_MVPORCH); + /* set main display hporch */ + writel(MIPI_DSI_MAIN_HFP(mode->right_margin) | + MIPI_DSI_MAIN_HBP(mode->left_margin), + mipi_dsi->mmio_base + MIPI_DSI_MHPORCH); + /* set main display sync */ + writel(MIPI_DSI_MAIN_VSA(mode->vsync_len) | + MIPI_DSI_MAIN_HSA(mode->hsync_len), + mipi_dsi->mmio_base + MIPI_DSI_MSYNC); + + /* configure d-phy timings */ + if (!strcmp(mipi_dsi->lcd_panel, "TRULY-WVGA-TFT3P5581E")) { + writel(MIPI_DSI_M_TLPXCTL(2) | MIPI_DSI_M_THSEXITCTL(4), + mipi_dsi->mmio_base + MIPI_DSI_PHYTIMING); + writel(MIPI_DSI_M_TCLKPRPRCTL(5) | + MIPI_DSI_M_TCLKZEROCTL(14) | + MIPI_DSI_M_TCLKPOSTCTL(8) | + MIPI_DSI_M_TCLKTRAILCTL(3), + mipi_dsi->mmio_base + MIPI_DSI_PHYTIMING1); + writel(MIPI_DSI_M_THSPRPRCTL(3) | + MIPI_DSI_M_THSZEROCTL(3) | + MIPI_DSI_M_THSTRAILCTL(3), + mipi_dsi->mmio_base + MIPI_DSI_PHYTIMING2); + } else { + writel(MIPI_DSI_M_TLPXCTL(11) | MIPI_DSI_M_THSEXITCTL(18), + mipi_dsi->mmio_base + MIPI_DSI_PHYTIMING); + writel(MIPI_DSI_M_TCLKPRPRCTL(13) | + MIPI_DSI_M_TCLKZEROCTL(65) | + MIPI_DSI_M_TCLKPOSTCTL(17) | + MIPI_DSI_M_TCLKTRAILCTL(13), + mipi_dsi->mmio_base + MIPI_DSI_PHYTIMING1); + writel(MIPI_DSI_M_THSPRPRCTL(16) | + MIPI_DSI_M_THSZEROCTL(24) | + MIPI_DSI_M_THSTRAILCTL(16), + mipi_dsi->mmio_base + MIPI_DSI_PHYTIMING2); + } + + writel(0xf000f, mipi_dsi->mmio_base + MIPI_DSI_TIMEOUT); + + /* Init FIFO */ + writel(0x0, mipi_dsi->mmio_base + MIPI_DSI_FIFOCTRL); + udelay(300); + writel(0x1f, mipi_dsi->mmio_base + MIPI_DSI_FIFOCTRL); + + /* check clock and data lanes are in stop state + * which means dphy is in low power mode + */ + while (!mipi_dsi_lane_stop_state(mipi_dsi)) { + time_out--; + if (time_out == 0) { + dev_err(dev, "MIPI DSI is not stop state.\n"); + return -EINVAL; + } + } + + /* transfer commands always in lp mode */ + writel(MIPI_DSI_CMD_LPDT, mipi_dsi->mmio_base + MIPI_DSI_ESCMODE); + + mipi_dsi_init_interrupt(mipi_dsi); + + return 0; +} + +static int mipi_dsi_disp_init(struct mxc_dispdrv_handle *disp, + struct mxc_dispdrv_setting *setting) +{ + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + struct device *dev = &mipi_dsi->pdev->dev; + struct device_node *np = dev->of_node; + struct reset_control *reset = NULL; + int ret = 0; + + reset = of_reset_control_get(np, NULL); + if (IS_ERR(reset)) + return PTR_ERR(reset); + + ret = mipi_dsi_lcd_init(mipi_dsi, setting); + if (ret) { + dev_err(dev, "failed to init mipi dsi lcd\n"); + goto out; + } + + dev_info(dev, "MIPI DSI dispdrv inited!\n"); + +out: + reset_control_put(reset); + return ret; +} + +static void mipi_dsi_disp_deinit(struct mxc_dispdrv_handle *disp) +{ + struct mipi_dsi_info *mipi_dsi; + + mipi_dsi = mxc_dispdrv_getdata(disp); + + mipi_dsi_power_off(mipi_dsi->disp_mipi); + if (mipi_dsi->bl) + backlight_device_unregister(mipi_dsi->bl); +} + +static void mipi_dsi_set_mode(struct mipi_dsi_info *mipi_dsi, + enum mipi_dsi_trans_mode mode) +{ + unsigned int dsi_clkctrl; + + dsi_clkctrl = readl(mipi_dsi->mmio_base + MIPI_DSI_CLKCTRL); + + switch (mode) { + case DSI_LP_MODE: + dsi_clkctrl &= ~MIPI_DSI_TX_REQUEST_HSCLK(1); + break; + case DSI_HS_MODE: + dsi_clkctrl |= MIPI_DSI_TX_REQUEST_HSCLK(1); + break; + default: + dev_err(&mipi_dsi->pdev->dev, + "invalid dsi mode\n"); + return; + } + + writel(dsi_clkctrl, mipi_dsi->mmio_base + MIPI_DSI_CLKCTRL); + mdelay(1); +} + +static int mipi_dsi_enable(struct mxc_dispdrv_handle *disp, + struct fb_info *fbi) +{ + int ret; + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + if (fbi->state == FBINFO_STATE_SUSPENDED) { + if (mipi_dsi->disp_power_on) { + ret = regulator_enable(mipi_dsi->disp_power_on); + if (ret) { + dev_err(&mipi_dsi->pdev->dev, "failed to enable display " + "power regulator, err = %d\n", ret); + return ret; + } + } + } + + if (!mipi_dsi->dsi_power_on) + pm_runtime_get_sync(&mipi_dsi->pdev->dev); + + ret = clk_prepare_enable(mipi_dsi->dphy_clk); + ret |= clk_prepare_enable(mipi_dsi->cfg_clk); + if (ret) { + dev_err(&mipi_dsi->pdev->dev, + "clk enable error:%d!\n", ret); + return -EINVAL; + } + + if (!mipi_dsi->lcd_inited) { + ret = mipi_dsi_master_init(mipi_dsi, true); + if (ret) + return -EINVAL; + + msleep(20); + ret = device_reset(&mipi_dsi->pdev->dev); + if (ret) { + dev_err(&mipi_dsi->pdev->dev, "failed to reset device: %d\n", ret); + return -EINVAL; + } + msleep(120); + + /* the panel should be config under LP mode */ + ret = mipi_dsi->lcd_callback->mipi_lcd_setup(mipi_dsi); + if (ret < 0) { + dev_err(&mipi_dsi->pdev->dev, + "failed to init mipi lcd.\n"); + return ret ; + } + mipi_dsi->lcd_inited = 1; + + /* change to HS mode for panel display */ + mipi_dsi_set_mode(mipi_dsi, DSI_HS_MODE); + } else { + ret = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_EXIT_SLEEP_MODE, + NULL, 0); + if (ret) { + dev_err(&mipi_dsi->pdev->dev, + "MIPI DSI DCS Command sleep-in error!\n"); + } + msleep(MIPI_LCD_SLEEP_MODE_DELAY); + } + + mipi_dsi_set_main_standby(mipi_dsi, 1); + + return 0; +} + +static void mipi_dsi_disable(struct mxc_dispdrv_handle *disp, + struct fb_info *fbi) +{ + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + mipi_dsi_power_off(mipi_dsi->disp_mipi); + + if (fbi->state == FBINFO_STATE_SUSPENDED) { + if (mipi_dsi->dsi_power_on) { + pm_runtime_put_noidle(&mipi_dsi->pdev->dev); + pm_runtime_put_sync_suspend(&mipi_dsi->pdev->dev); + pm_runtime_get_noresume(&mipi_dsi->pdev->dev); + } + + if (mipi_dsi->disp_power_on) + regulator_disable(mipi_dsi->disp_power_on); + + mipi_dsi->lcd_inited = 0; + } +} + +static int mipi_dsi_setup(struct mxc_dispdrv_handle *disp, + struct fb_info *fbi) +{ + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + int xres_virtual = fbi->var.xres_virtual; + int yres_virtual = fbi->var.yres_virtual; + int xoffset = fbi->var.xoffset; + int yoffset = fbi->var.yoffset; + int pixclock = fbi->var.pixclock; + + if (!mipi_dsi->mode) + return 0; + + /* set the mode back to var in case userspace changes it */ + fb_videomode_to_var(&fbi->var, mipi_dsi->mode); + + /* restore some var entries cached */ + fbi->var.xres_virtual = xres_virtual; + fbi->var.yres_virtual = yres_virtual; + fbi->var.xoffset = xoffset; + fbi->var.yoffset = yoffset; + fbi->var.pixclock = pixclock; + + return 0; +} + +static struct mxc_dispdrv_driver mipi_dsi_drv = { + .name = DISPDRV_MIPI, + .init = mipi_dsi_disp_init, + .deinit = mipi_dsi_disp_deinit, + .enable = mipi_dsi_enable, + .disable = mipi_dsi_disable, + .setup = mipi_dsi_setup, +}; + +static const struct of_device_id imx_mipi_dsi_dt_ids[] = { + { .compatible = "fsl,imx7d-mipi-dsi", .data = NULL, }, + { } +}; +MODULE_DEVICE_TABLE(of, imx_mipi_dsi_dt_ids); + +static irqreturn_t mipi_dsi_irq_handler(int irq, void *data) +{ + unsigned int intsrc, intclr; + struct mipi_dsi_info *mipi_dsi = data; + struct platform_device *pdev = mipi_dsi->pdev; + + intclr = 0; + intsrc = readl(mipi_dsi->mmio_base + MIPI_DSI_INTSRC); + + dev_dbg(&pdev->dev, "intsrc = 0x%x\n", intsrc); + + if (intsrc & INTSRC_SFR_PL_FIFO_EMPTY) { + dev_dbg(&pdev->dev, "playload tx finished\n"); + intclr |= INTSRC_SFR_PL_FIFO_EMPTY; + complete(&dsi_tx_done); + } + + if(intsrc & INTSRC_RX_DATA_DONE) { + dev_dbg(&pdev->dev, "rx data finished\n"); + intclr |= INTSRC_RX_DATA_DONE; + complete(&dsi_rx_done); + } + + /* clear the interrupts */ + if (intclr) + writel(intclr, mipi_dsi->mmio_base + MIPI_DSI_INTSRC); + + return IRQ_HANDLED; +} + +/** + * This function is called by the driver framework to initialize the MIPI DSI + * device. + * + * @param pdev The device structure for the MIPI DSI passed in by the + * driver framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int mipi_dsi_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mipi_dsi_info *mipi_dsi; + struct resource *res; + const char *lcd_panel; + int ret = 0; + + mipi_dsi = devm_kzalloc(&pdev->dev, sizeof(*mipi_dsi), GFP_KERNEL); + if (!mipi_dsi) + return -ENOMEM; + mipi_dsi->pdev = pdev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get platform resource mem\n"); + return -ENODEV; + } + + if (!devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), pdev->name)) + return -EBUSY; + + mipi_dsi->mmio_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!mipi_dsi->mmio_base) + return -ENOMEM; + + mipi_dsi->irq = platform_get_irq(pdev, 0); + if (mipi_dsi->irq < 0) { + dev_err(&pdev->dev, "failed to get device irq\n"); + return -EINVAL; + } + + ret = devm_request_irq(&pdev->dev, mipi_dsi->irq, + mipi_dsi_irq_handler, + 0, "mipi_dsi_samsung", mipi_dsi); + if (ret) { + dev_err(&pdev->dev, "failed to request mipi dsi irq\n"); + return ret; + } + + mipi_dsi->dphy_clk = devm_clk_get(&pdev->dev, "mipi_pllref_clk"); + if (IS_ERR(mipi_dsi->dphy_clk)) { + dev_err(&pdev->dev, "failed to get dphy pll_ref_clk\n"); + return PTR_ERR(mipi_dsi->dphy_clk); + } + + mipi_dsi->cfg_clk = devm_clk_get(&pdev->dev, "mipi_cfg_clk"); + if (IS_ERR(mipi_dsi->cfg_clk)) { + dev_err(&pdev->dev, "failed to get cfg_clk\n"); + return PTR_ERR(mipi_dsi->cfg_clk); + } + + ret = of_property_read_string(np, "lcd_panel", &lcd_panel); + if (ret) { + dev_err(&pdev->dev, "failed to read lcd_panel property\n"); + return ret; + } + + mipi_dsi->disp_power_on = devm_regulator_get(&pdev->dev, + "disp-power-on"); + if (!IS_ERR(mipi_dsi->disp_power_on)) { + ret = regulator_enable(mipi_dsi->disp_power_on); + if (ret) { + dev_err(&pdev->dev, "failed to enable display " + "power regulator, err = %d\n", ret); + return ret; + } + } + + mipi_dsi->lcd_panel = kstrdup(lcd_panel, GFP_KERNEL); + if (!mipi_dsi->lcd_panel) { + dev_err(&pdev->dev, "failed to allocate lcd panel name\n"); + ret = -ENOMEM; + goto kstrdup_fail; + } + + mipi_dsi->disp_mipi = mxc_dispdrv_register(&mipi_dsi_drv); + if (IS_ERR(mipi_dsi->disp_mipi)) { + dev_err(&pdev->dev, "mxc_dispdrv_register error\n"); + ret = PTR_ERR(mipi_dsi->disp_mipi); + goto dispdrv_reg_fail; + } + + mipi_dsi->mipi_dsi_pkt_read = mipi_dsi_pkt_read; + mipi_dsi->mipi_dsi_pkt_write = mipi_dsi_pkt_write; + mipi_dsi->mipi_dsi_dcs_cmd = mipi_dsi_dcs_cmd; + + pm_runtime_enable(&pdev->dev); + + mxc_dispdrv_setdata(mipi_dsi->disp_mipi, mipi_dsi); + dev_set_drvdata(&pdev->dev, mipi_dsi); + + dev_info(&pdev->dev, "i.MX MIPI DSI driver probed\n"); + return ret; + +dispdrv_reg_fail: + kfree(mipi_dsi->lcd_panel); +kstrdup_fail: + if (mipi_dsi->disp_power_on) + regulator_disable(mipi_dsi->disp_power_on); + + return ret; +} + +static void mipi_dsi_shutdown(struct platform_device *pdev) +{ + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + mipi_dsi_power_off(mipi_dsi->disp_mipi); +} + +static int mipi_dsi_remove(struct platform_device *pdev) +{ + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + mxc_dispdrv_puthandle(mipi_dsi->disp_mipi); + mxc_dispdrv_unregister(mipi_dsi->disp_mipi); + + if (mipi_dsi->disp_power_on) + regulator_disable(mipi_dsi->disp_power_on); + + kfree(mipi_dsi->lcd_panel); + dev_set_drvdata(&pdev->dev, NULL); + + return 0; +} + +static int mipi_dsi_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + if (mipi_dsi->dsi_power_on) { + release_bus_freq(BUS_FREQ_HIGH); + dev_dbg(dev, "mipi dsi busfreq high release.\n"); + + mipi_dsi->dsi_power_on = 0; + } + + return 0; +} + +static int mipi_dsi_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + if (!mipi_dsi->dsi_power_on) { + request_bus_freq(BUS_FREQ_HIGH); + dev_dbg(dev, "mipi dsi busfreq high request.\n"); + + mipi_dsi->dsi_power_on = 1; + } + + return 0; +} + +static const struct dev_pm_ops mipi_dsi_pm_ops = { + .runtime_suspend = mipi_dsi_runtime_suspend, + .runtime_resume = mipi_dsi_runtime_resume, + .runtime_idle = NULL, +}; + +static struct platform_driver mipi_dsi_driver = { + .driver = { + .of_match_table = imx_mipi_dsi_dt_ids, + .name = "mxc_mipi_dsi_samsung", + .pm = &mipi_dsi_pm_ops, + }, + .probe = mipi_dsi_probe, + .remove = mipi_dsi_remove, + .shutdown = mipi_dsi_shutdown, +}; + +static int __init mipi_dsi_init(void) +{ + int err; + + err = platform_driver_register(&mipi_dsi_driver); + if (err) { + pr_err("mipi_dsi_driver register failed\n"); + return err; + } + + pr_debug("MIPI DSI driver module loaded: %s\n", mipi_dsi_driver.driver.name); + + return 0; +} + +static void __exit mipi_dsi_cleanup(void) +{ + platform_driver_unregister(&mipi_dsi_driver); +} + +module_init(mipi_dsi_init); +module_exit(mipi_dsi_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX MIPI DSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/mxc/mxc_dcic.c b/drivers/video/fbdev/mxc/mxc_dcic.c new file mode 100644 index 000000000000..cab75e276896 --- /dev/null +++ b/drivers/video/fbdev/mxc/mxc_dcic.c @@ -0,0 +1,593 @@ +/* + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/clk.h> +#include <linux/cdev.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/fs.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ioctl.h> +#include <linux/interrupt.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/module.h> +#include <linux/mxc_dcic.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <video/videomode.h> +#include <video/of_videomode.h> + +#define DRIVER_NAME "mxc_dcic" + +#define DCIC_IPU1_DI0 "dcic-ipu1-di0" +#define DCIC_IPU1_DI1 "dcic-ipu1-di1" +#define DCIC_IPU2_DI0 "dcic-ipu2-di0" +#define DCIC_IPU2_DI1 "dcic-ipu2-di1" +#define DCIC_LCDIF "dcic-lcdif" +#define DCIC_LCDIF1 "dcic-lcdif1" +#define DCIC_LCDIF2 "dcic-lcdif2" +#define DCIC_LVDS "dcic-lvds" +#define DCIC_LVDS0 "dcic-lvds0" +#define DCIC_LVDS1 "dcic-lvds1" +#define DCIC_HDMI "dcic-hdmi" + +#define DCIC0_DEV_NAME "mxc_dcic0" +#define DCIC1_DEV_NAME "mxc_dcic1" + +#define FB_SYNC_OE_LOW_ACT 0x80000000 +#define FB_SYNC_CLK_LAT_FALL 0x40000000 + +static const struct dcic_mux imx6q_dcic0_mux[] = { + { + .dcic = DCIC_IPU1_DI0, + .val = IMX6Q_GPR10_DCIC1_MUX_CTL_IPU1_DI0, + }, { + .dcic = DCIC_LVDS0, + .val = IMX6Q_GPR10_DCIC1_MUX_CTL_LVDS0, + }, { + .dcic = DCIC_LVDS1, + .val = IMX6Q_GPR10_DCIC1_MUX_CTL_LVDS1, + }, { + .dcic = DCIC_HDMI, + .val = IMX6Q_GPR10_DCIC1_MUX_CTL_HDMI, + } +}; + +static const struct dcic_mux imx6q_dcic1_mux[] = { + { + .dcic = DCIC_IPU1_DI1, + .val = IMX6Q_GPR10_DCIC2_MUX_CTL_IPU1_DI1, + }, { + .dcic = DCIC_LVDS0, + .val = IMX6Q_GPR10_DCIC2_MUX_CTL_LVDS0, + }, { + .dcic = DCIC_LVDS1, + .val = IMX6Q_GPR10_DCIC2_MUX_CTL_LVDS1, + }, { + .dcic = DCIC_HDMI, + .val = IMX6Q_GPR10_DCIC2_MUX_CTL_MIPI, + } +}; + +static const struct bus_mux imx6q_dcic_buses[] = { + { + .name = DCIC0_DEV_NAME, + .reg = IOMUXC_GPR10, + .shift = 0, + .mask = IMX6Q_GPR10_DCIC1_MUX_CTL_MASK, + .dcic_mux_num = ARRAY_SIZE(imx6q_dcic0_mux), + .dcics = imx6q_dcic0_mux, + }, { + .name = DCIC1_DEV_NAME, + .reg = IOMUXC_GPR10, + .shift = 2, + .mask = IMX6Q_GPR10_DCIC2_MUX_CTL_MASK, + .dcic_mux_num = ARRAY_SIZE(imx6q_dcic1_mux), + .dcics = imx6q_dcic1_mux, + } +}; + +static const struct dcic_info imx6q_dcic_info = { + .bus_mux_num = ARRAY_SIZE(imx6q_dcic_buses), + .buses = imx6q_dcic_buses, +}; + +static const struct dcic_mux imx6sx_dcic0_mux[] = { + { + .dcic = DCIC_LCDIF1, + .val = IMX6SX_GPR5_DISP_MUX_DCIC1_LCDIF1, + }, { + .dcic = DCIC_LVDS, + .val = IMX6SX_GPR5_DISP_MUX_DCIC1_LVDS, + } +}; + +static const struct dcic_mux imx6sx_dcic1_mux[] = { + { + .dcic = DCIC_LCDIF2, + .val = IMX6SX_GPR5_DISP_MUX_DCIC2_LCDIF2, + }, { + .dcic = DCIC_LVDS, + .val = IMX6SX_GPR5_DISP_MUX_DCIC2_LVDS, + } +}; + +static const struct bus_mux imx6sx_dcic_buses[] = { + { + .name = DCIC0_DEV_NAME, + .reg = IOMUXC_GPR5, + .shift = 1, + .mask = IMX6SX_GPR5_DISP_MUX_DCIC1_MASK, + .dcic_mux_num = ARRAY_SIZE(imx6sx_dcic0_mux), + .dcics = imx6sx_dcic0_mux, + }, { + .name = DCIC1_DEV_NAME, + .reg = IOMUXC_GPR5, + .shift = 2, + .mask = IMX6SX_GPR5_DISP_MUX_DCIC2_MASK, + .dcic_mux_num = ARRAY_SIZE(imx6sx_dcic1_mux), + .dcics = imx6sx_dcic1_mux, + } +}; + +static const struct dcic_info imx6sx_dcic_info = { + .bus_mux_num = ARRAY_SIZE(imx6sx_dcic_buses), + .buses = imx6sx_dcic_buses, +}; + +static const struct of_device_id dcic_dt_ids[] = { + { .compatible = "fsl,imx6q-dcic", .data = &imx6q_dcic_info, }, + { .compatible = "fsl,imx6sx-dcic", .data = &imx6sx_dcic_info, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dcic_dt_ids); + +static int of_get_dcic_val(struct device_node *np, struct dcic_data *dcic) +{ + const char *mux; + int ret; + u32 i, dcic_id; + + ret = of_property_read_string(np, "dcic_mux", &mux); + if (ret < 0) { + dev_err(dcic->dev, "Can not get dcic_mux\n"); + return ret; + } + ret = of_property_read_u32(np, "dcic_id", &dcic_id); + if (ret < 0) { + dev_err(dcic->dev, "Can not get dcic_id\n"); + return ret; + } + + dcic->bus_n = dcic_id; + + for (i = 0; i < dcic->buses[dcic_id].dcic_mux_num; i++) + if (!strcmp(mux, dcic->buses[dcic_id].dcics[i].dcic)) { + dcic->mux_n = i; + return dcic->buses[dcic_id].dcics[i].val; + } + + return -EINVAL; +} + +static void dcic_enable(struct dcic_data *dcic) +{ + u32 val; + + val = readl(&dcic->regs->dcicc); + val |= DCICC_IC_ENABLE; + writel(val, &dcic->regs->dcicc); +} + +void dcic_disable(struct dcic_data *dcic) +{ + u32 val; + + val = readl(&dcic->regs->dcicc); + val &= ~DCICC_IC_MASK; + val |= DCICC_IC_DISABLE; + writel(val, &dcic->regs->dcicc); +} + +static void roi_enable(struct dcic_data *dcic, struct roi_params *roi_param) +{ + u32 val; + u32 roi_n = roi_param->roi_n; + + val = readl(&dcic->regs->ROI[roi_n].dcicrc); + val |= DCICRC_ROI_ENABLE; + if (roi_param->freeze) + val |= DCICRC_ROI_FROZEN; + writel(val, &dcic->regs->ROI[roi_n].dcicrc); +} + +static void roi_disable(struct dcic_data *dcic, u32 roi_n) +{ + u32 val; + + val = readl(&dcic->regs->ROI[roi_n].dcicrc); + val &= ~DCICRC_ROI_ENABLE; + writel(val, &dcic->regs->ROI[roi_n].dcicrc); +} + +static bool roi_configure(struct dcic_data *dcic, struct roi_params *roi_param) +{ + struct roi_regs *roi_reg; + u32 val; + + if (roi_param->roi_n >= 16) { + printk(KERN_ERR "Error, Wrong ROI number %d\n", roi_param->roi_n); + return false; + } + + if (roi_param->end_x <= roi_param->start_x || + roi_param->end_y <= roi_param->start_y) { + printk(KERN_ERR "Error, Wrong ROI\n"); + return false; + } + + roi_reg = (struct roi_regs *) &dcic->regs->ROI[roi_param->roi_n]; + + /* init roi block size */ + val = roi_param->start_y << 16 | roi_param->start_x; + writel(val, &roi_reg->dcicrc); + + val = roi_param->end_y << 16 | roi_param->end_x; + writel(val, &roi_reg->dcicrs); + + writel(roi_param->ref_sig, &roi_reg->dcicrrs); + + roi_enable(dcic, roi_param); + return true; +} + +static void dcic_int_enable(struct dcic_data *dcic) +{ + u32 val; + + /* Clean pending interrupt before enable int */ + writel(DCICS_FI_STAT_PENDING, &dcic->regs->dcics); + writel(0xffffffff, &dcic->regs->dcics); + + /* Enable function interrupt */ + val = readl(&dcic->regs->dcicic); + val &= ~DCICIC_FUN_INT_MASK; + val |= DCICIC_FUN_INT_ENABLE; + writel(val, &dcic->regs->dcicic); +} + +static void dcic_int_disable(struct dcic_data *dcic) +{ + u32 val; + + /* Disable both function and error interrupt */ + val = readl(&dcic->regs->dcicic); + val = DCICIC_ERROR_INT_DISABLE | DCICIC_FUN_INT_DISABLE; + writel(val, &dcic->regs->dcicic); +} + +static irqreturn_t dcic_irq_handler(int irq, void *data) +{ + u32 i; + + struct dcic_data *dcic = data; + u32 dcics = readl(&dcic->regs->dcics); + + dcic->result = dcics & 0xffff; + + dcic_int_disable(dcic); + + /* clean dcic interrupt state */ + writel(DCICS_FI_STAT_PENDING, &dcic->regs->dcics); + writel(dcics, &dcic->regs->dcics); + + for (i = 0; i < 16; i++) { + printk(KERN_INFO "ROI=%d,crcRS=0x%x, crcCS=0x%x\n", i, + readl(&dcic->regs->ROI[i].dcicrrs), + readl(&dcic->regs->ROI[i].dcicrcs)); + } + complete(&dcic->roi_crc_comp); + + return 0; +} + +static int dcic_configure(struct dcic_data *dcic, unsigned int sync) +{ + u32 val; + val = 0; + + /* vsync, hsync, DE, clk_pol */ + if (!(sync & FB_SYNC_HOR_HIGH_ACT)) + val |= DCICC_HSYNC_POL_ACTIVE_LOW; + if (!(sync & FB_SYNC_VERT_HIGH_ACT)) + val |= DCICC_VSYNC_POL_ACTIVE_LOW; + if (sync & FB_SYNC_OE_LOW_ACT) + val |= DCICC_DE_ACTIVE_LOW; + if (sync & FB_SYNC_CLK_LAT_FALL) + val |= DCICC_CLK_POL_INVERTED; + + writel(val, &dcic->regs->dcicc); + return 0; +} + +static int dcic_open(struct inode *inode, struct file *file) +{ + struct dcic_data *dcic; + + dcic = container_of(inode->i_cdev, struct dcic_data, cdev); + + mutex_lock(&dcic->lock); + + clk_prepare_enable(dcic->disp_axi_clk); + clk_prepare_enable(dcic->dcic_clk); + + file->private_data = dcic; + mutex_unlock(&dcic->lock); + return 0; +} + +static int dcic_release(struct inode *inode, struct file *file) +{ + struct dcic_data *dcic = file->private_data; + u32 i; + + mutex_lock(&dcic->lock); + + for (i = 0; i < 16; i++) + roi_disable(dcic, i); + + clk_disable_unprepare(dcic->dcic_clk); + clk_disable_unprepare(dcic->disp_axi_clk); + + mutex_unlock(&dcic->lock); + return 0; +} + +static int dcic_init(struct device_node *np, struct dcic_data *dcic) +{ + int val; + u32 bus; + + val = of_get_dcic_val(np, dcic); + if (val < 0) { + printk(KERN_ERR "Error incorrect\n"); + return -1; + } + + bus = dcic->bus_n; + + regmap_update_bits(dcic->regmap, dcic->buses[bus].reg, + dcic->buses[bus].mask, val); + + return 0; +} + +static long dcic_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int __user *argp = (void __user *)arg; + struct dcic_data *dcic = file->private_data; + struct roi_params roi_param; + unsigned int sync; + int ret = 0; + + switch (cmd) { + case DCIC_IOC_CONFIG_DCIC: + if (!copy_from_user(&sync, argp, sizeof(unsigned int))) + dcic_configure(dcic, sync); + break; + case DCIC_IOC_CONFIG_ROI: + if (copy_from_user(&roi_param, argp, sizeof(roi_param))) + return -EFAULT; + else + if (!roi_configure(dcic, &roi_param)) + return -EINVAL; + break; + case DCIC_IOC_GET_RESULT: + init_completion(&dcic->roi_crc_comp); + + dcic_enable(dcic); + + dcic->result = 0; + msleep(25); + + dcic_int_enable(dcic); + + ret = wait_for_completion_interruptible_timeout( + &dcic->roi_crc_comp, 1 * HZ); + if (ret == 0) { + dev_err(dcic->dev, + "dcic wait for roi crc cal timeout\n"); + ret = -ETIME; + } else if (ret > 0) { + if (copy_to_user(argp, &dcic->result, sizeof(dcic->result))) + return -EFAULT; + ret = 0; + } + dcic_disable(dcic); + break; + default: + printk(KERN_ERR "%s, Unsupport cmd %d\n", __func__, cmd); + break; + } + return ret; +} + + +static const struct file_operations mxc_dcic_fops = { + .owner = THIS_MODULE, + .open = dcic_open, + .release = dcic_release, + .unlocked_ioctl = dcic_ioctl, +}; + +static int dcic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id = + of_match_device(dcic_dt_ids, dev); + const struct dcic_info *dcic_info = + (const struct dcic_info *)of_id->data; + struct device_node *np = dev->of_node; + struct dcic_data *dcic; + struct resource *res; + const char *name; + dev_t devt; + int ret = 0; + int irq; + + dcic = devm_kzalloc(&pdev->dev, + sizeof(struct dcic_data), + GFP_KERNEL); + if (!dcic) { + dev_err(&pdev->dev, "Cannot allocate device data\n"); + ret = -ENOMEM; + goto ealloc; + } + + platform_set_drvdata(pdev, dcic); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "No dcic base address found.\n"); + ret = -ENODEV; + goto ealloc; + } + + dcic->regs = (struct dcic_regs *) devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!dcic->regs) { + dev_err(&pdev->dev, "ioremap failed with dcic base\n"); + ret = -ENOMEM; + goto ealloc; + } + + dcic->dev = dev; + dcic->buses = dcic_info->buses; + + dcic->regmap = syscon_regmap_lookup_by_phandle(np, "gpr"); + if (IS_ERR(dcic->regmap)) { + dev_err(dev, "failed to get parent regmap\n"); + ret = PTR_ERR(dcic->regmap); + goto ealloc; + } + + /* clock */ + dcic->disp_axi_clk = devm_clk_get(&pdev->dev, "disp-axi"); + if (IS_ERR(dcic->disp_axi_clk)) { + dev_err(&pdev->dev, "get disp-axi clock failed\n"); + ret = PTR_ERR(dcic->disp_axi_clk); + goto ealloc; + } + + dcic->dcic_clk = devm_clk_get(&pdev->dev, "dcic"); + if (IS_ERR(dcic->dcic_clk)) { + dev_err(&pdev->dev, "get dcic clk failed\n"); + ret = PTR_ERR(dcic->dcic_clk); + goto ealloc; + } + + mutex_init(&dcic->lock); + ret = dcic_init(np, dcic); + if (ret < 0) { + printk(KERN_ERR "Failed init dcic\n"); + goto ealloc; + } + + /* register device */ + name = dcic->buses[dcic->bus_n].name; + dcic->major = register_chrdev(0, name, &mxc_dcic_fops); + if (dcic->major < 0) { + printk(KERN_ERR "DCIC: unable to get a major for dcic\n"); + ret = -EBUSY; + goto ealloc; + } + + dcic->class = class_create(THIS_MODULE, name); + if (IS_ERR(dcic->class)) { + ret = PTR_ERR(dcic->class); + goto err_out_chrdev; + } + + /* create char device */ + devt = MKDEV(dcic->major, 0); + dcic->devt = devt; + + cdev_init(&dcic->cdev, &mxc_dcic_fops); + dcic->cdev.owner = THIS_MODULE; + ret = cdev_add(&dcic->cdev, devt, 1); + if (ret) + goto err_out_class; + + device_create(dcic->class, NULL, devt, + NULL, name); + + /* IRQ */ + irq = platform_get_irq(pdev, 0); + + ret = devm_request_irq(&pdev->dev, irq, dcic_irq_handler, 0, + dev_name(&pdev->dev), dcic); + if (ret) { + dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", + irq, ret); + goto err_out_cdev; + } + + return 0; + +err_out_cdev: + cdev_del(&dcic->cdev); +err_out_class: + device_destroy(dcic->class, devt); + class_destroy(dcic->class); +err_out_chrdev: + unregister_chrdev(dcic->major, name); +ealloc: + return ret; +} + +static int dcic_remove(struct platform_device *pdev) +{ + struct dcic_data *dcic = platform_get_drvdata(pdev); + const char *name; + + name = dcic->buses[dcic->bus_n].name; + + device_destroy(dcic->class, dcic->devt); + cdev_del(&dcic->cdev); + class_destroy(dcic->class); + unregister_chrdev(dcic->major, name); + mutex_destroy(&dcic->lock); + + return 0; +} + +static struct platform_driver dcic_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = dcic_dt_ids, + }, + .probe = dcic_probe, + .remove = dcic_remove, +}; + +module_platform_driver(dcic_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC DCIC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/video/fbdev/mxc/mxc_dispdrv.c b/drivers/video/fbdev/mxc/mxc_dispdrv.c new file mode 100644 index 000000000000..e8b9b6afd0e5 --- /dev/null +++ b/drivers/video/fbdev/mxc/mxc_dispdrv.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_dispdrv.c + * @brief mxc display driver framework. + * + * A display device driver could call mxc_dispdrv_register(drv) in its + * dev_probe() function. + * Move all dev_probe() things into mxc_dispdrv_driver->init(), init() function + * should init and feedback setting; + * Move all dev_remove() things into mxc_dispdrv_driver->deinit(); + * Move all dev_suspend() things into fb_notifier for SUSPEND, if there is; + * Move all dev_resume() things into fb_notifier for RESUME, if there is; + * + * mxc fb driver could call mxc_dispdrv_gethandle(name, setting) before a fb + * need be added, with fbi param passing by setting, after + * mxc_dispdrv_gethandle() return, FB driver should get the basic setting + * about fbi info and crtc. + * + * @ingroup Framebuffer + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/string.h> +#include "mxc_dispdrv.h" + +static LIST_HEAD(dispdrv_list); +static DEFINE_MUTEX(dispdrv_lock); + +struct mxc_dispdrv_entry { + /* Note: drv always the first element */ + struct mxc_dispdrv_driver *drv; + bool active; + void *priv; + struct list_head list; +}; + +struct mxc_dispdrv_handle *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv) +{ + struct mxc_dispdrv_entry *new; + + mutex_lock(&dispdrv_lock); + + new = kzalloc(sizeof(struct mxc_dispdrv_entry), GFP_KERNEL); + if (!new) { + mutex_unlock(&dispdrv_lock); + return ERR_PTR(-ENOMEM); + } + + new->drv = drv; + list_add_tail(&new->list, &dispdrv_list); + + mutex_unlock(&dispdrv_lock); + + return (struct mxc_dispdrv_handle *)new; +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_register); + +int mxc_dispdrv_unregister(struct mxc_dispdrv_handle *handle) +{ + struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle; + + if (entry) { + mutex_lock(&dispdrv_lock); + list_del(&entry->list); + mutex_unlock(&dispdrv_lock); + kfree(entry); + return 0; + } else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_unregister); + +struct mxc_dispdrv_handle *mxc_dispdrv_gethandle(char *name, + struct mxc_dispdrv_setting *setting) +{ + int ret = -ENODEV, found = 0; + struct mxc_dispdrv_entry *entry; + + mutex_lock(&dispdrv_lock); + list_for_each_entry(entry, &dispdrv_list, list) { + if (!strcmp(entry->drv->name, name) && (entry->drv->init)) { + ret = entry->drv->init((struct mxc_dispdrv_handle *) + entry, setting); + if (ret >= 0) { + entry->active = true; + found = 1; + break; + } + } + } + mutex_unlock(&dispdrv_lock); + + return found ? (struct mxc_dispdrv_handle *)entry:ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_gethandle); + +void mxc_dispdrv_puthandle(struct mxc_dispdrv_handle *handle) +{ + struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle; + + mutex_lock(&dispdrv_lock); + if (entry && entry->active && entry->drv->deinit) { + entry->drv->deinit(handle); + entry->active = false; + } + mutex_unlock(&dispdrv_lock); + +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_puthandle); + +int mxc_dispdrv_setdata(struct mxc_dispdrv_handle *handle, void *data) +{ + struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle; + + if (entry) { + entry->priv = data; + return 0; + } else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_setdata); + +void *mxc_dispdrv_getdata(struct mxc_dispdrv_handle *handle) +{ + struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle; + + if (entry) { + return entry->priv; + } else + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_getdata); diff --git a/drivers/video/fbdev/mxc/mxc_dispdrv.h b/drivers/video/fbdev/mxc/mxc_dispdrv.h new file mode 100644 index 000000000000..58d8a07d3380 --- /dev/null +++ b/drivers/video/fbdev/mxc/mxc_dispdrv.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MXC_DISPDRV_H__ +#define __MXC_DISPDRV_H__ +#include <linux/fb.h> +#include "crtc.h" + +struct mxc_dispdrv_handle { + struct mxc_dispdrv_driver *drv; +}; + +struct mxc_dispdrv_setting { + /*input-feedback parameter*/ + struct fb_info *fbi; + int if_fmt; + int default_bpp; + char *dft_mode_str; + + /* feedback parameter */ + enum crtc crtc; +}; + +struct mxc_dispdrv_driver { + const char *name; + int (*init) (struct mxc_dispdrv_handle *, struct mxc_dispdrv_setting *); + void (*deinit) (struct mxc_dispdrv_handle *); + /* display driver enable function for extension */ + int (*enable) (struct mxc_dispdrv_handle *, struct fb_info *); + /* display driver disable function, called at early part of fb_blank */ + void (*disable) (struct mxc_dispdrv_handle *, struct fb_info *); + /* display driver setup function, called at early part of fb_set_par */ + int (*setup) (struct mxc_dispdrv_handle *, struct fb_info *fbi); +}; + +struct mxc_dispdrv_handle *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv); +int mxc_dispdrv_unregister(struct mxc_dispdrv_handle *handle); +struct mxc_dispdrv_handle *mxc_dispdrv_gethandle(char *name, + struct mxc_dispdrv_setting *setting); +void mxc_dispdrv_puthandle(struct mxc_dispdrv_handle *handle); +int mxc_dispdrv_setdata(struct mxc_dispdrv_handle *handle, void *data); +void *mxc_dispdrv_getdata(struct mxc_dispdrv_handle *handle); +#endif diff --git a/drivers/video/fbdev/mxc/mxc_edid.c b/drivers/video/fbdev/mxc/mxc_edid.c new file mode 100644 index 000000000000..02563d8781cc --- /dev/null +++ b/drivers/video/fbdev/mxc/mxc_edid.c @@ -0,0 +1,771 @@ +/* + * Copyright 2009-2015 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxc_edid.c + * + * @brief MXC EDID driver + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/i2c.h> +#include <linux/fb.h> +#include <video/mxc_edid.h> +#include "../edid.h" + +#undef DEBUG /* define this for verbose EDID parsing output */ +#ifdef DEBUG +#define DPRINTK(fmt, args...) printk(fmt, ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +const struct fb_videomode mxc_cea_mode[64] = { + /* #1: 640x480p@59.94/60Hz 4:3 */ + [1] = { + NULL, 60, 640, 480, 39683, 48, 16, 33, 10, 96, 2, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #2: 720x480p@59.94/60Hz 4:3 */ + [2] = { + NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #3: 720x480p@59.94/60Hz 16:9 */ + [3] = { + NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #4: 1280x720p@59.94/60Hz 16:9 */ + [4] = { + NULL, 60, 1280, 720, 13468, 220, 110, 20, 5, 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0 + }, + /* #5: 1920x1080i@59.94/60Hz 16:9 */ + [5] = { + NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #6: 720(1440)x480iH@59.94/60Hz 4:3 */ + [6] = { + NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0, + FB_VMODE_INTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #7: 720(1440)x480iH@59.94/60Hz 16:9 */ + [7] = { + NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0, + FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #8: 720(1440)x240pH@59.94/60Hz 4:3 */ + [8] = { + NULL, 60, 1440, 240, 37108, 114, 38, 15, 4, 124, 3, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #9: 720(1440)x240pH@59.94/60Hz 16:9 */ + [9] = { + NULL, 60, 1440, 240, 37108, 114, 38, 15, 4, 124, 3, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #14: 1440x480p@59.94/60Hz 4:3 */ + [14] = { + NULL, 60, 1440, 480, 18500, 120, 32, 30, 9, 124, 6, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #15: 1440x480p@59.94/60Hz 16:9 */ + [15] = { + NULL, 60, 1440, 480, 18500, 120, 32, 30, 9, 124, 6, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #16: 1920x1080p@60Hz 16:9 */ + [16] = { + NULL, 60, 1920, 1080, 6734, 148, 88, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #17: 720x576pH@50Hz 4:3 */ + [17] = { + NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #18: 720x576pH@50Hz 16:9 */ + [18] = { + NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #19: 1280x720p@50Hz */ + [19] = { + NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #20: 1920x1080i@50Hz */ + [20] = { + NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #23: 720(1440)x288pH@50Hz 4:3 */ + [23] = { + NULL, 50, 1440, 288, 37037, 138, 24, 19, 2, 126, 3, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #24: 720(1440)x288pH@50Hz 16:9 */ + [24] = { + NULL, 50, 1440, 288, 37037, 138, 24, 19, 2, 126, 3, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #29: 720(1440)x576pH@50Hz 4:3 */ + [29] = { + NULL, 50, 1440, 576, 18518, 136, 24, 39, 5, 128, 5, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #30: 720(1440)x576pH@50Hz 16:9 */ + [30] = { + NULL, 50, 1440, 576, 18518, 136, 24, 39, 5, 128, 5, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #31: 1920x1080p@50Hz */ + [31] = { + NULL, 50, 1920, 1080, 6734, 148, 528, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #32: 1920x1080p@23.98/24Hz */ + [32] = { + NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #33: 1920x1080p@25Hz */ + [33] = { + NULL, 25, 1920, 1080, 13468, 148, 528, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #34: 1920x1080p@30Hz */ + [34] = { + NULL, 30, 1920, 1080, 13468, 148, 88, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #41: 1280x720p@100Hz 16:9 */ + [41] = { + NULL, 100, 1280, 720, 6734, 220, 440, 20, 5, 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0 + }, + /* #47: 1280x720p@119.88/120Hz 16:9 */ + [47] = { + NULL, 120, 1280, 720, 6734, 220, 110, 20, 5, 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0 + }, +}; + +/* + * We have a special version of fb_mode_is_equal that ignores + * pixclock, since for many CEA modes, 2 frequencies are supported + * e.g. 640x480 @ 60Hz or 59.94Hz + */ +int mxc_edid_fb_mode_is_equal(bool use_aspect, + const struct fb_videomode *mode1, + const struct fb_videomode *mode2) +{ + u32 mask; + + if (use_aspect) + mask = ~0; + else + mask = ~FB_VMODE_ASPECT_MASK; + + return (mode1->xres == mode2->xres && + mode1->yres == mode2->yres && + mode1->hsync_len == mode2->hsync_len && + mode1->vsync_len == mode2->vsync_len && + mode1->left_margin == mode2->left_margin && + mode1->right_margin == mode2->right_margin && + mode1->upper_margin == mode2->upper_margin && + mode1->lower_margin == mode2->lower_margin && + mode1->sync == mode2->sync && + /* refresh check, 59.94Hz and 60Hz have the same parameter + * in struct of mxc_cea_mode */ + abs(mode1->refresh - mode2->refresh) <= 1 && + (mode1->vmode & mask) == (mode2->vmode & mask)); +} + +static void get_detailed_timing(unsigned char *block, + struct fb_videomode *mode) +{ + mode->xres = H_ACTIVE; + mode->yres = V_ACTIVE; + mode->pixclock = PIXEL_CLOCK; + mode->pixclock /= 1000; + mode->pixclock = KHZ2PICOS(mode->pixclock); + mode->right_margin = H_SYNC_OFFSET; + mode->left_margin = (H_ACTIVE + H_BLANKING) - + (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH); + mode->upper_margin = V_BLANKING - V_SYNC_OFFSET - + V_SYNC_WIDTH; + mode->lower_margin = V_SYNC_OFFSET; + mode->hsync_len = H_SYNC_WIDTH; + mode->vsync_len = V_SYNC_WIDTH; + if (HSYNC_POSITIVE) + mode->sync |= FB_SYNC_HOR_HIGH_ACT; + if (VSYNC_POSITIVE) + mode->sync |= FB_SYNC_VERT_HIGH_ACT; + mode->refresh = PIXEL_CLOCK/((H_ACTIVE + H_BLANKING) * + (V_ACTIVE + V_BLANKING)); + if (INTERLACED) { + mode->yres *= 2; + mode->upper_margin *= 2; + mode->lower_margin *= 2; + mode->vsync_len *= 2; + mode->vmode |= FB_VMODE_INTERLACED; + } + mode->flag = FB_MODE_IS_DETAILED; + + if ((H_SIZE / 16) == (V_SIZE / 9)) + mode->vmode |= FB_VMODE_ASPECT_16_9; + else if ((H_SIZE / 4) == (V_SIZE / 3)) + mode->vmode |= FB_VMODE_ASPECT_4_3; + else if ((mode->xres / 16) == (mode->yres / 9)) + mode->vmode |= FB_VMODE_ASPECT_16_9; + else if ((mode->xres / 4) == (mode->yres / 3)) + mode->vmode |= FB_VMODE_ASPECT_4_3; + + if (mode->vmode & FB_VMODE_ASPECT_16_9) + DPRINTK("Aspect ratio: 16:9\n"); + if (mode->vmode & FB_VMODE_ASPECT_4_3) + DPRINTK("Aspect ratio: 4:3\n"); + DPRINTK(" %d MHz ", PIXEL_CLOCK/1000000); + DPRINTK("%d %d %d %d ", H_ACTIVE, H_ACTIVE + H_SYNC_OFFSET, + H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH, H_ACTIVE + H_BLANKING); + DPRINTK("%d %d %d %d ", V_ACTIVE, V_ACTIVE + V_SYNC_OFFSET, + V_ACTIVE + V_SYNC_OFFSET + V_SYNC_WIDTH, V_ACTIVE + V_BLANKING); + DPRINTK("%sHSync %sVSync\n\n", (HSYNC_POSITIVE) ? "+" : "-", + (VSYNC_POSITIVE) ? "+" : "-"); +} + +int mxc_edid_parse_ext_blk(unsigned char *edid, + struct mxc_edid_cfg *cfg, + struct fb_monspecs *specs) +{ + char detail_timing_desc_offset; + struct fb_videomode *mode, *m; + unsigned char index = 0x0; + unsigned char *block; + int i, num = 0, revision; + + if (edid[index++] != 0x2) /* only support cea ext block now */ + return 0; + revision = edid[index++]; + DPRINTK("cea extent revision %d\n", revision); + mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL); + if (mode == NULL) + return -1; + + detail_timing_desc_offset = edid[index++]; + + if (revision >= 2) { + cfg->cea_underscan = (edid[index] >> 7) & 0x1; + cfg->cea_basicaudio = (edid[index] >> 6) & 0x1; + cfg->cea_ycbcr444 = (edid[index] >> 5) & 0x1; + cfg->cea_ycbcr422 = (edid[index] >> 4) & 0x1; + + DPRINTK("CEA underscan %d\n", cfg->cea_underscan); + DPRINTK("CEA basicaudio %d\n", cfg->cea_basicaudio); + DPRINTK("CEA ycbcr444 %d\n", cfg->cea_ycbcr444); + DPRINTK("CEA ycbcr422 %d\n", cfg->cea_ycbcr422); + } + + if (revision >= 3) { + /* short desc */ + DPRINTK("CEA Short desc timmings\n"); + index++; + while (index < detail_timing_desc_offset) { + unsigned char tagcode, blklen; + + tagcode = (edid[index] >> 5) & 0x7; + blklen = (edid[index]) & 0x1f; + + DPRINTK("Tagcode %x Len %d\n", tagcode, blklen); + + switch (tagcode) { + case 0x2: /*Video data block*/ + { + int cea_idx; + i = 0; + while (i < blklen) { + index++; + cea_idx = edid[index] & 0x7f; + if (cea_idx < ARRAY_SIZE(mxc_cea_mode) && + (mxc_cea_mode[cea_idx].xres)) { + DPRINTK("Support CEA Format #%d\n", cea_idx); + mode[num] = mxc_cea_mode[cea_idx]; + mode[num].flag |= FB_MODE_IS_STANDARD; + num++; + } + i++; + } + break; + } + case 0x3: /*Vendor specific data*/ + { + unsigned char IEEE_reg_iden[3]; + unsigned char deep_color; + unsigned char latency_present; + unsigned char I_latency_present; + unsigned char hdmi_video_present; + unsigned char hdmi_3d_present; + unsigned char hdmi_3d_multi_present; + unsigned char hdmi_vic_len; + unsigned char hdmi_3d_len; + unsigned char index_inc = 0; + unsigned char vsd_end; + + vsd_end = index + blklen; + + IEEE_reg_iden[0] = edid[index+1]; + IEEE_reg_iden[1] = edid[index+2]; + IEEE_reg_iden[2] = edid[index+3]; + cfg->physical_address[0] = (edid[index+4] & 0xf0) >> 4; + cfg->physical_address[1] = (edid[index+4] & 0x0f); + cfg->physical_address[2] = (edid[index+5] & 0xf0) >> 4; + cfg->physical_address[3] = (edid[index+5] & 0x0f); + + if ((IEEE_reg_iden[0] == 0x03) && + (IEEE_reg_iden[1] == 0x0c) && + (IEEE_reg_iden[2] == 0x00)) + cfg->hdmi_cap = 1; + + if (blklen > 5) { + deep_color = edid[index+6]; + if (deep_color & 0x80) + cfg->vsd_support_ai = true; + if (deep_color & 0x40) + cfg->vsd_dc_48bit = true; + if (deep_color & 0x20) + cfg->vsd_dc_36bit = true; + if (deep_color & 0x10) + cfg->vsd_dc_30bit = true; + if (deep_color & 0x08) + cfg->vsd_dc_y444 = true; + if (deep_color & 0x01) + cfg->vsd_dvi_dual = true; + } + + DPRINTK("VSD hdmi capability %d\n", cfg->hdmi_cap); + DPRINTK("VSD support ai %d\n", cfg->vsd_support_ai); + DPRINTK("VSD support deep color 48bit %d\n", cfg->vsd_dc_48bit); + DPRINTK("VSD support deep color 36bit %d\n", cfg->vsd_dc_36bit); + DPRINTK("VSD support deep color 30bit %d\n", cfg->vsd_dc_30bit); + DPRINTK("VSD support deep color y444 %d\n", cfg->vsd_dc_y444); + DPRINTK("VSD support dvi dual %d\n", cfg->vsd_dvi_dual); + + if (blklen > 6) + cfg->vsd_max_tmdsclk_rate = edid[index+7] * 5; + DPRINTK("VSD MAX TMDS CLOCK RATE %d\n", cfg->vsd_max_tmdsclk_rate); + + if (blklen > 7) { + latency_present = edid[index+8] >> 7; + I_latency_present = (edid[index+8] & 0x40) >> 6; + hdmi_video_present = (edid[index+8] & 0x20) >> 5; + cfg->vsd_cnc3 = (edid[index+8] & 0x8) >> 3; + cfg->vsd_cnc2 = (edid[index+8] & 0x4) >> 2; + cfg->vsd_cnc1 = (edid[index+8] & 0x2) >> 1; + cfg->vsd_cnc0 = edid[index+8] & 0x1; + + DPRINTK("VSD cnc0 %d\n", cfg->vsd_cnc0); + DPRINTK("VSD cnc1 %d\n", cfg->vsd_cnc1); + DPRINTK("VSD cnc2 %d\n", cfg->vsd_cnc2); + DPRINTK("VSD cnc3 %d\n", cfg->vsd_cnc3); + DPRINTK("latency_present %d\n", latency_present); + DPRINTK("I_latency_present %d\n", I_latency_present); + DPRINTK("hdmi_video_present %d\n", hdmi_video_present); + + } else { + index += blklen; + break; + } + + index += 9; + + /*latency present */ + if (latency_present) { + cfg->vsd_video_latency = edid[index++]; + cfg->vsd_audio_latency = edid[index++]; + + if (I_latency_present) { + cfg->vsd_I_video_latency = edid[index++]; + cfg->vsd_I_audio_latency = edid[index++]; + } else { + cfg->vsd_I_video_latency = cfg->vsd_video_latency; + cfg->vsd_I_audio_latency = cfg->vsd_audio_latency; + } + + DPRINTK("VSD latency video_latency %d\n", cfg->vsd_video_latency); + DPRINTK("VSD latency audio_latency %d\n", cfg->vsd_audio_latency); + DPRINTK("VSD latency I_video_latency %d\n", cfg->vsd_I_video_latency); + DPRINTK("VSD latency I_audio_latency %d\n", cfg->vsd_I_audio_latency); + } + + if (hdmi_video_present) { + hdmi_3d_present = edid[index] >> 7; + hdmi_3d_multi_present = (edid[index] & 0x60) >> 5; + index++; + hdmi_vic_len = (edid[index] & 0xe0) >> 5; + hdmi_3d_len = edid[index] & 0x1f; + index++; + + DPRINTK("hdmi_3d_present %d\n", hdmi_3d_present); + DPRINTK("hdmi_3d_multi_present %d\n", hdmi_3d_multi_present); + DPRINTK("hdmi_vic_len %d\n", hdmi_vic_len); + DPRINTK("hdmi_3d_len %d\n", hdmi_3d_len); + + if (hdmi_vic_len > 0) { + for (i = 0; i < hdmi_vic_len; i++) { + cfg->hdmi_vic[i] = edid[index++]; + DPRINTK("HDMI_vic=%d\n", cfg->hdmi_vic[i]); + } + } + + if (hdmi_3d_len > 0) { + if (hdmi_3d_present) { + if (hdmi_3d_multi_present == 0x1) { + cfg->hdmi_3d_struct_all = (edid[index] << 8) | edid[index+1]; + index_inc = 2; + } else if (hdmi_3d_multi_present == 0x2) { + cfg->hdmi_3d_struct_all = (edid[index] << 8) | edid[index+1]; + cfg->hdmi_3d_mask_all = (edid[index+2] << 8) | edid[index+3]; + index_inc = 4; + } else + index_inc = 0; + } + + DPRINTK("HDMI 3d struct all =0x%x\n", cfg->hdmi_3d_struct_all); + DPRINTK("HDMI 3d mask all =0x%x\n", cfg->hdmi_3d_mask_all); + + /* Read 2D vic 3D_struct */ + if ((hdmi_3d_len - index_inc) > 0) { + DPRINTK("Support 3D video format\n"); + i = 0; + while ((hdmi_3d_len - index_inc) > 0) { + + cfg->hdmi_3d_format[i].vic_order_2d = edid[index+index_inc] >> 4; + cfg->hdmi_3d_format[i].struct_3d = edid[index+index_inc] & 0x0f; + index_inc++; + + if (cfg->hdmi_3d_format[i].struct_3d == 8) { + cfg->hdmi_3d_format[i].detail_3d = edid[index+index_inc] >> 4; + index_inc++; + } else if (cfg->hdmi_3d_format[i].struct_3d > 8) { + cfg->hdmi_3d_format[i].detail_3d = 0; + index_inc++; + } + + DPRINTK("vic_order_2d=%d, 3d_struct=%d, 3d_detail=0x%x\n", + cfg->hdmi_3d_format[i].vic_order_2d, + cfg->hdmi_3d_format[i].struct_3d, + cfg->hdmi_3d_format[i].detail_3d); + i++; + } + } + index += index_inc; + } + } + + index = vsd_end; + + break; + } + case 0x1: /*Audio data block*/ + { + u8 audio_format, max_ch, byte1, byte2, byte3; + + i = 0; + cfg->max_channels = 0; + cfg->sample_rates = 0; + cfg->sample_sizes = 0; + + while (i < blklen) { + byte1 = edid[index + 1]; + byte2 = edid[index + 2]; + byte3 = edid[index + 3]; + index += 3; + i += 3; + + audio_format = byte1 >> 3; + max_ch = (byte1 & 0x07) + 1; + + DPRINTK("Audio Format Descriptor : %2d\n", audio_format); + DPRINTK("Max Number of Channels : %2d\n", max_ch); + DPRINTK("Sample Rates : %02x\n", byte2); + + /* ALSA can't specify specific compressed + * formats, so only care about PCM for now. */ + if (audio_format == AUDIO_CODING_TYPE_LPCM) { + if (max_ch > cfg->max_channels) + cfg->max_channels = max_ch; + + cfg->sample_rates |= byte2; + cfg->sample_sizes |= byte3 & 0x7; + DPRINTK("Sample Sizes : %02x\n", + byte3 & 0x7); + } + } + break; + } + case 0x4: /*Speaker allocation block*/ + { + i = 0; + while (i < blklen) { + cfg->speaker_alloc = edid[index + 1]; + index += 3; + i += 3; + DPRINTK("Speaker Alloc : %02x\n", cfg->speaker_alloc); + } + break; + } + case 0x7: /*User extended block*/ + default: + /* skip */ + DPRINTK("Not handle block, tagcode = 0x%x\n", tagcode); + index += blklen; + break; + } + + index++; + } + } + + /* long desc */ + DPRINTK("CEA long desc timmings\n"); + index = detail_timing_desc_offset; + block = edid + index; + while (index < (EDID_LENGTH - DETAILED_TIMING_DESCRIPTION_SIZE)) { + if (!(block[0] == 0x00 && block[1] == 0x00)) { + get_detailed_timing(block, &mode[num]); + num++; + } + block += DETAILED_TIMING_DESCRIPTION_SIZE; + index += DETAILED_TIMING_DESCRIPTION_SIZE; + } + + if (!num) { + kfree(mode); + return 0; + } + + m = kmalloc((num + specs->modedb_len) * + sizeof(struct fb_videomode), GFP_KERNEL); + if (!m) { + kfree(mode); + return 0; + } + + if (specs->modedb_len) { + memmove(m, specs->modedb, + specs->modedb_len * sizeof(struct fb_videomode)); + kfree(specs->modedb); + } + memmove(m+specs->modedb_len, mode, + num * sizeof(struct fb_videomode)); + kfree(mode); + + specs->modedb_len += num; + specs->modedb = m; + + return 0; +} +EXPORT_SYMBOL(mxc_edid_parse_ext_blk); + +static int mxc_edid_readblk(struct i2c_adapter *adp, + unsigned short addr, unsigned char *edid) +{ + int ret = 0, extblknum = 0; + unsigned char regaddr = 0x0; + struct i2c_msg msg[2] = { + { + .addr = addr, + .flags = 0, + .len = 1, + .buf = ®addr, + }, { + .addr = addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = edid, + }, + }; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; + } + + if (edid[1] == 0x00) + return -ENOENT; + + extblknum = edid[0x7E]; + + if (extblknum) { + regaddr = 128; + msg[1].buf = edid + EDID_LENGTH; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID ext block\n"); + return -EIO; + } + } + + return extblknum; +} + +static int mxc_edid_readsegblk(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, int seg_num) +{ + int ret = 0; + unsigned char segment = 0x1, regaddr = 0; + struct i2c_msg msg[3] = { + { + .addr = 0x30, + .flags = 0, + .len = 1, + .buf = &segment, + }, { + .addr = addr, + .flags = 0, + .len = 1, + .buf = ®addr, + }, { + .addr = addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = edid, + }, + }; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; + } + + if (seg_num == 2) { + regaddr = 128; + msg[2].buf = edid + EDID_LENGTH; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; + } + } + + return ret; +} + +int mxc_edid_var_to_vic(struct fb_var_screeninfo *var) +{ + int i; + struct fb_videomode m; + + for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) { + fb_var_to_videomode(&m, var); + if (mxc_edid_fb_mode_is_equal(false, &m, &mxc_cea_mode[i])) + break; + } + + if (i == ARRAY_SIZE(mxc_cea_mode)) + return 0; + + return i; +} +EXPORT_SYMBOL(mxc_edid_var_to_vic); + +int mxc_edid_mode_to_vic(const struct fb_videomode *mode) +{ + int i; + bool use_aspect = (mode->vmode & FB_VMODE_ASPECT_MASK); + + for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) { + if (mxc_edid_fb_mode_is_equal(use_aspect, mode, &mxc_cea_mode[i])) + break; + } + + if (i == ARRAY_SIZE(mxc_cea_mode)) + return 0; + + return i; +} +EXPORT_SYMBOL(mxc_edid_mode_to_vic); + +/* make sure edid has 512 bytes*/ +int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, struct mxc_edid_cfg *cfg, struct fb_info *fbi) +{ + int ret = 0, extblknum; + if (!adp || !edid || !cfg || !fbi) + return -EINVAL; + + memset(edid, 0, EDID_LENGTH*4); + memset(cfg, 0, sizeof(struct mxc_edid_cfg)); + + extblknum = mxc_edid_readblk(adp, addr, edid); + if (extblknum < 0) + return extblknum; + + /* edid first block parsing */ + memset(&fbi->monspecs, 0, sizeof(fbi->monspecs)); + fb_edid_to_monspecs(edid, &fbi->monspecs); + + if (extblknum) { + int i; + + /* FIXME: mxc_edid_readsegblk() won't read more than 2 blocks + * and the for-loop will read past the end of the buffer! :-( */ + if (extblknum > 3) { + WARN_ON(true); + return -EINVAL; + } + + /* need read segment block? */ + if (extblknum > 1) { + ret = mxc_edid_readsegblk(adp, addr, + edid + EDID_LENGTH*2, extblknum - 1); + if (ret < 0) + return ret; + } + + for (i = 1; i <= extblknum; i++) + /* edid ext block parsing */ + mxc_edid_parse_ext_blk(edid + i*EDID_LENGTH, + cfg, &fbi->monspecs); + } + + return 0; +} +EXPORT_SYMBOL(mxc_edid_read); + diff --git a/drivers/video/fbdev/mxc/mxc_epdc_fb.c b/drivers/video/fbdev/mxc/mxc_epdc_fb.c new file mode 100644 index 000000000000..fa7aa9fb4b5f --- /dev/null +++ b/drivers/video/fbdev/mxc/mxc_epdc_fb.c @@ -0,0 +1,5589 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2017-2019 NXP + */ +/* + * Based on STMP378X LCDIF + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +#include <linux/busfreq-imx.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/uaccess.h> +#include <linux/cpufreq.h> +#include <linux/firmware.h> +#include <linux/kthread.h> +#include <linux/dmaengine.h> +#include <linux/pxp_dma.h> +#include <linux/pm_runtime.h> +#include <linux/mxcfb.h> +#include <linux/mxcfb_epdc.h> +#include <linux/gpio.h> +#include <linux/regulator/driver.h> +#include <linux/fsl_devices.h> +#include <linux/bitops.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_data/dma-imx.h> +#include <asm/cacheflush.h> + +#include "epdc_regs.h" + +/* + * Enable this define to have a default panel + * loaded during driver initialization + */ +/*#define DEFAULT_PANEL_HW_INIT*/ + +#define NUM_SCREENS_MIN 2 + +#define EPDC_V1_NUM_LUTS 16 +#define EPDC_V1_MAX_NUM_UPDATES 20 +#define EPDC_V2_NUM_LUTS 64 +#define EPDC_V2_MAX_NUM_UPDATES 64 +#define EPDC_MAX_NUM_BUFFERS 2 +#define INVALID_LUT (-1) +#define DRY_RUN_NO_LUT 100 + +/* Maximum update buffer image width due to v2.0 and v2.1 errata ERR005313. */ +#define EPDC_V2_MAX_UPDATE_WIDTH 2047 +#define EPDC_V2_ROTATION_ALIGNMENT 8 + +#define DEFAULT_TEMP_INDEX 0 +#define DEFAULT_TEMP 20 /* room temp in deg Celsius */ + +#define INIT_UPDATE_MARKER 0x12345678 +#define PAN_UPDATE_MARKER 0x12345679 + +#define POWER_STATE_OFF 0 +#define POWER_STATE_ON 1 + +#define MERGE_OK 0 +#define MERGE_FAIL 1 +#define MERGE_BLOCK 2 + +static unsigned long default_bpp = 16; +static DEFINE_MUTEX(hard_lock); + +struct update_marker_data { + struct list_head full_list; + struct list_head upd_list; + u32 update_marker; + struct completion update_completion; + int lut_num; + bool collision_test; + bool waiting; +}; + +struct update_desc_list { + struct list_head list; + struct mxcfb_update_data upd_data;/* Update parameters */ + u32 epdc_offs; /* Added to buffer ptr to resolve alignment */ + u32 epdc_stride; /* Depends on rotation & whether we skip PxP */ + struct list_head upd_marker_list; /* List of markers for this update */ + u32 update_order; /* Numeric ordering value for update */ +}; + +/* This structure represents a list node containing both + * a memory region allocated as an output buffer for the PxP + * update processing task, and the update description (mode, region, etc.) */ +struct update_data_list { + struct list_head list; + dma_addr_t phys_addr; /* Pointer to phys address of processed Y buf */ + void *virt_addr; + struct update_desc_list *update_desc; + int lut_num; /* Assigned before update is processed into working buffer */ + u64 collision_mask; /* Set when update creates collision */ + /* Mask of the LUTs the update collides with */ +}; + +struct mxc_epdc_fb_data { + struct fb_info info; + struct fb_var_screeninfo epdc_fb_var; /* Internal copy of screeninfo + so we can sync changes to it */ + u32 pseudo_palette[16]; + char fw_str[24]; + struct list_head list; + struct imx_epdc_fb_mode *cur_mode; + struct imx_epdc_fb_platform_data *pdata; + int blank; + u32 max_pix_size; + ssize_t map_size; + dma_addr_t phys_start; + u32 fb_offset; + int default_bpp; + int native_width; + int native_height; + int num_screens; + int epdc_irq; + struct device *dev; + int power_state; + int wait_for_powerdown; + struct completion powerdown_compl; + struct clk *epdc_clk_axi; + struct clk *epdc_clk_pix; + struct regulator *display_regulator; + struct regulator *vcom_regulator; + struct regulator *v3p3_regulator; + bool fw_default_load; + int rev; + + /* FB elements related to EPDC updates */ + int num_luts; + int max_num_updates; + bool in_init; + bool hw_ready; + bool hw_initializing; + bool waiting_for_idle; + u32 auto_mode; + u32 upd_scheme; + struct list_head upd_pending_list; + struct list_head upd_buf_queue; + struct list_head upd_buf_free_list; + struct list_head upd_buf_collision_list; + struct update_data_list *cur_update; + struct mutex queue_mutex; + int trt_entries; + int temp_index; + u8 *temp_range_bounds; + struct mxcfb_waveform_modes wv_modes; + bool wv_modes_update; + u32 *waveform_buffer_virt; + u32 waveform_buffer_phys; + u32 waveform_buffer_size; + u32 *working_buffer_virt; + u32 working_buffer_phys; + u32 working_buffer_size; + dma_addr_t *phys_addr_updbuf; + void **virt_addr_updbuf; + u32 upd_buffer_num; + u32 max_num_buffers; + dma_addr_t phys_addr_copybuf; /* Phys address of copied update data */ + void *virt_addr_copybuf; /* Used for PxP SW workaround */ + u32 order_cnt; + struct list_head full_marker_list; + u32 *lut_update_order; /* Array size = number of luts */ + u64 epdc_colliding_luts; + u64 luts_complete_wb; + struct completion updates_done; + struct delayed_work epdc_done_work; + struct workqueue_struct *epdc_submit_workqueue; + struct work_struct epdc_submit_work; + struct workqueue_struct *epdc_intr_workqueue; + struct work_struct epdc_intr_work; + bool waiting_for_wb; + bool waiting_for_lut; + bool waiting_for_lut15; + struct completion update_res_free; + struct completion lut15_free; + struct completion eof_event; + int eof_sync_period; + struct mutex power_mutex; + bool powering_down; + bool updates_active; + int pwrdown_delay; + unsigned long tce_prevent; + bool restrict_width; /* work around rev >=2.0 width and + stride restriction */ + + /* FB elements related to PxP DMA */ + struct completion pxp_tx_cmpl; + struct pxp_channel *pxp_chan; + struct pxp_config_data pxp_conf; + struct dma_async_tx_descriptor *txd; + dma_cookie_t cookie; + struct scatterlist sg[2]; + struct mutex pxp_mutex; /* protects access to PxP */ +}; + +struct waveform_data_header { + unsigned int wi0; + unsigned int wi1; + unsigned int wi2; + unsigned int wi3; + unsigned int wi4; + unsigned int wi5; + unsigned int wi6; + unsigned int xwia:24; + unsigned int cs1:8; + unsigned int wmta:24; + unsigned int fvsn:8; + unsigned int luts:8; + unsigned int mc:8; + unsigned int trc:8; + unsigned int reserved0_0:8; + unsigned int eb:8; + unsigned int sb:8; + unsigned int reserved0_1:8; + unsigned int reserved0_2:8; + unsigned int reserved0_3:8; + unsigned int reserved0_4:8; + unsigned int reserved0_5:8; + unsigned int cs2:8; +}; + +struct mxcfb_waveform_data_file { + struct waveform_data_header wdh; + u32 *data; /* Temperature Range Table + Waveform Data */ +}; + +static struct fb_videomode e60_v110_mode = { + .name = "E60_V110", + .refresh = 50, + .xres = 800, + .yres = 600, + .pixclock = 18604700, + .left_margin = 8, + .right_margin = 178, + .upper_margin = 4, + .lower_margin = 10, + .hsync_len = 20, + .vsync_len = 4, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, +}; + +static struct fb_videomode e60_v220_mode = { + .name = "E60_V220", + .refresh = 85, + .xres = 800, + .yres = 600, + .pixclock = 30000000, + .left_margin = 8, + .right_margin = 164, + .upper_margin = 4, + .lower_margin = 8, + .hsync_len = 4, + .vsync_len = 1, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, +}; + +static struct fb_videomode e060scm_mode = { + .name = "E060SCM", + .refresh = 85, + .xres = 800, + .yres = 600, + .pixclock = 26666667, + .left_margin = 8, + .right_margin = 100, + .upper_margin = 4, + .lower_margin = 8, + .hsync_len = 4, + .vsync_len = 1, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, +}; + +static struct fb_videomode e97_v110_mode = { + .name = "E97_V110", + .refresh = 50, + .xres = 1200, + .yres = 825, + .pixclock = 32000000, + .left_margin = 12, + .right_margin = 128, + .upper_margin = 4, + .lower_margin = 10, + .hsync_len = 20, + .vsync_len = 4, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, +}; + +static struct imx_epdc_fb_mode panel_modes[] = { + { + &e60_v110_mode, + 4, /* vscan_holdoff */ + 10, /* sdoed_width */ + 20, /* sdoed_delay */ + 10, /* sdoez_width */ + 20, /* sdoez_delay */ + 428, /* gdclk_hp_offs */ + 20, /* gdsp_offs */ + 0, /* gdoe_offs */ + 1, /* gdclk_offs */ + 1, /* num_ce */ + }, + { + &e60_v220_mode, + 4, /* vscan_holdoff */ + 10, /* sdoed_width */ + 20, /* sdoed_delay */ + 10, /* sdoez_width */ + 20, /* sdoez_delay */ + 465, /* gdclk_hp_offs */ + 20, /* gdsp_offs */ + 0, /* gdoe_offs */ + 9, /* gdclk_offs */ + 1, /* num_ce */ + }, + { + &e060scm_mode, + 4, /* vscan_holdoff */ + 10, /* sdoed_width */ + 20, /* sdoed_delay */ + 10, /* sdoez_width */ + 20, /* sdoez_delay */ + 419, /* gdclk_hp_offs */ + 20, /* gdsp_offs */ + 0, /* gdoe_offs */ + 5, /* gdclk_offs */ + 1, /* num_ce */ + }, + { + &e97_v110_mode, + 8, /* vscan_holdoff */ + 10, /* sdoed_width */ + 20, /* sdoed_delay */ + 10, /* sdoez_width */ + 20, /* sdoez_delay */ + 632, /* gdclk_hp_offs */ + 20, /* gdsp_offs */ + 0, /* gdoe_offs */ + 1, /* gdclk_offs */ + 3, /* num_ce */ + } +}; + +static struct imx_epdc_fb_platform_data epdc_data = { + .epdc_mode = panel_modes, + .num_modes = ARRAY_SIZE(panel_modes), +}; + +void __iomem *epdc_base; + +struct mxc_epdc_fb_data *g_fb_data; + +/* forward declaration */ +static int mxc_epdc_fb_get_temp_index(struct mxc_epdc_fb_data *fb_data, + int temp); +static void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data); +static int mxc_epdc_fb_blank(int blank, struct fb_info *info); +static int mxc_epdc_fb_init_hw(struct fb_info *info); +static int pxp_process_update(struct mxc_epdc_fb_data *fb_data, + u32 src_width, u32 src_height, + struct mxcfb_rect *update_region); +static int pxp_complete_update(struct mxc_epdc_fb_data *fb_data, u32 *hist_stat); + +static void draw_mode0(struct mxc_epdc_fb_data *fb_data); +static bool is_free_list_full(struct mxc_epdc_fb_data *fb_data); + +static void do_dithering_processing_Y1_v1_0( + unsigned char *update_region_virt_ptr, + dma_addr_t update_region_phys_ptr, + struct mxcfb_rect *update_region, + unsigned long update_region_stride, + int *err_dist); +static void do_dithering_processing_Y4_v1_0( + unsigned char *update_region_virt_ptr, + dma_addr_t update_region_phys_ptr, + struct mxcfb_rect *update_region, + unsigned long update_region_stride, + int *err_dist); + +#ifdef DEBUG +static void dump_pxp_config(struct mxc_epdc_fb_data *fb_data, + struct pxp_config_data *pxp_conf) +{ + dev_info(fb_data->dev, "S0 fmt 0x%x", + pxp_conf->s0_param.pixel_fmt); + dev_info(fb_data->dev, "S0 width 0x%x", + pxp_conf->s0_param.width); + dev_info(fb_data->dev, "S0 height 0x%x", + pxp_conf->s0_param.height); + dev_info(fb_data->dev, "S0 ckey 0x%x", + pxp_conf->s0_param.color_key); + dev_info(fb_data->dev, "S0 ckey en 0x%x", + pxp_conf->s0_param.color_key_enable); + + dev_info(fb_data->dev, "OL0 combine en 0x%x", + pxp_conf->ol_param[0].combine_enable); + dev_info(fb_data->dev, "OL0 fmt 0x%x", + pxp_conf->ol_param[0].pixel_fmt); + dev_info(fb_data->dev, "OL0 width 0x%x", + pxp_conf->ol_param[0].width); + dev_info(fb_data->dev, "OL0 height 0x%x", + pxp_conf->ol_param[0].height); + dev_info(fb_data->dev, "OL0 ckey 0x%x", + pxp_conf->ol_param[0].color_key); + dev_info(fb_data->dev, "OL0 ckey en 0x%x", + pxp_conf->ol_param[0].color_key_enable); + dev_info(fb_data->dev, "OL0 alpha 0x%x", + pxp_conf->ol_param[0].global_alpha); + dev_info(fb_data->dev, "OL0 alpha en 0x%x", + pxp_conf->ol_param[0].global_alpha_enable); + dev_info(fb_data->dev, "OL0 local alpha en 0x%x", + pxp_conf->ol_param[0].local_alpha_enable); + + dev_info(fb_data->dev, "Out fmt 0x%x", + pxp_conf->out_param.pixel_fmt); + dev_info(fb_data->dev, "Out width 0x%x", + pxp_conf->out_param.width); + dev_info(fb_data->dev, "Out height 0x%x", + pxp_conf->out_param.height); + + dev_info(fb_data->dev, + "drect left 0x%x right 0x%x width 0x%x height 0x%x", + pxp_conf->proc_data.drect.left, pxp_conf->proc_data.drect.top, + pxp_conf->proc_data.drect.width, + pxp_conf->proc_data.drect.height); + dev_info(fb_data->dev, + "srect left 0x%x right 0x%x width 0x%x height 0x%x", + pxp_conf->proc_data.srect.left, pxp_conf->proc_data.srect.top, + pxp_conf->proc_data.srect.width, + pxp_conf->proc_data.srect.height); + dev_info(fb_data->dev, "Scaling en 0x%x", pxp_conf->proc_data.scaling); + dev_info(fb_data->dev, "HFlip en 0x%x", pxp_conf->proc_data.hflip); + dev_info(fb_data->dev, "VFlip en 0x%x", pxp_conf->proc_data.vflip); + dev_info(fb_data->dev, "Rotation 0x%x", pxp_conf->proc_data.rotate); + dev_info(fb_data->dev, "BG Color 0x%x", pxp_conf->proc_data.bgcolor); +} + +static void dump_epdc_reg(void) +{ + printk(KERN_DEBUG "\n\n"); + printk(KERN_DEBUG "EPDC_CTRL 0x%x\n", __raw_readl(EPDC_CTRL)); + printk(KERN_DEBUG "EPDC_WVADDR 0x%x\n", __raw_readl(EPDC_WVADDR)); + printk(KERN_DEBUG "EPDC_WB_ADDR 0x%x\n", __raw_readl(EPDC_WB_ADDR)); + printk(KERN_DEBUG "EPDC_RES 0x%x\n", __raw_readl(EPDC_RES)); + printk(KERN_DEBUG "EPDC_FORMAT 0x%x\n", __raw_readl(EPDC_FORMAT)); + printk(KERN_DEBUG "EPDC_FIFOCTRL 0x%x\n", __raw_readl(EPDC_FIFOCTRL)); + printk(KERN_DEBUG "EPDC_UPD_ADDR 0x%x\n", __raw_readl(EPDC_UPD_ADDR)); + printk(KERN_DEBUG "EPDC_UPD_STRIDE 0x%x\n", __raw_readl(EPDC_UPD_STRIDE)); + printk(KERN_DEBUG "EPDC_UPD_FIXED 0x%x\n", __raw_readl(EPDC_UPD_FIXED)); + printk(KERN_DEBUG "EPDC_UPD_CORD 0x%x\n", __raw_readl(EPDC_UPD_CORD)); + printk(KERN_DEBUG "EPDC_UPD_SIZE 0x%x\n", __raw_readl(EPDC_UPD_SIZE)); + printk(KERN_DEBUG "EPDC_UPD_CTRL 0x%x\n", __raw_readl(EPDC_UPD_CTRL)); + printk(KERN_DEBUG "EPDC_TEMP 0x%x\n", __raw_readl(EPDC_TEMP)); + printk(KERN_DEBUG "EPDC_AUTOWV_LUT 0x%x\n", __raw_readl(EPDC_AUTOWV_LUT)); + printk(KERN_DEBUG "EPDC_TCE_CTRL 0x%x\n", __raw_readl(EPDC_TCE_CTRL)); + printk(KERN_DEBUG "EPDC_TCE_SDCFG 0x%x\n", __raw_readl(EPDC_TCE_SDCFG)); + printk(KERN_DEBUG "EPDC_TCE_GDCFG 0x%x\n", __raw_readl(EPDC_TCE_GDCFG)); + printk(KERN_DEBUG "EPDC_TCE_HSCAN1 0x%x\n", __raw_readl(EPDC_TCE_HSCAN1)); + printk(KERN_DEBUG "EPDC_TCE_HSCAN2 0x%x\n", __raw_readl(EPDC_TCE_HSCAN2)); + printk(KERN_DEBUG "EPDC_TCE_VSCAN 0x%x\n", __raw_readl(EPDC_TCE_VSCAN)); + printk(KERN_DEBUG "EPDC_TCE_OE 0x%x\n", __raw_readl(EPDC_TCE_OE)); + printk(KERN_DEBUG "EPDC_TCE_POLARITY 0x%x\n", __raw_readl(EPDC_TCE_POLARITY)); + printk(KERN_DEBUG "EPDC_TCE_TIMING1 0x%x\n", __raw_readl(EPDC_TCE_TIMING1)); + printk(KERN_DEBUG "EPDC_TCE_TIMING2 0x%x\n", __raw_readl(EPDC_TCE_TIMING2)); + printk(KERN_DEBUG "EPDC_TCE_TIMING3 0x%x\n", __raw_readl(EPDC_TCE_TIMING3)); + printk(KERN_DEBUG "EPDC_PIGEON_CTRL0 0x%x\n", __raw_readl(EPDC_PIGEON_CTRL0)); + printk(KERN_DEBUG "EPDC_PIGEON_CTRL1 0x%x\n", __raw_readl(EPDC_PIGEON_CTRL1)); + printk(KERN_DEBUG "EPDC_IRQ_MASK1 0x%x\n", __raw_readl(EPDC_IRQ_MASK1)); + printk(KERN_DEBUG "EPDC_IRQ_MASK2 0x%x\n", __raw_readl(EPDC_IRQ_MASK2)); + printk(KERN_DEBUG "EPDC_IRQ1 0x%x\n", __raw_readl(EPDC_IRQ1)); + printk(KERN_DEBUG "EPDC_IRQ2 0x%x\n", __raw_readl(EPDC_IRQ2)); + printk(KERN_DEBUG "EPDC_IRQ_MASK 0x%x\n", __raw_readl(EPDC_IRQ_MASK)); + printk(KERN_DEBUG "EPDC_IRQ 0x%x\n", __raw_readl(EPDC_IRQ)); + printk(KERN_DEBUG "EPDC_STATUS_LUTS 0x%x\n", __raw_readl(EPDC_STATUS_LUTS)); + printk(KERN_DEBUG "EPDC_STATUS_LUTS2 0x%x\n", __raw_readl(EPDC_STATUS_LUTS2)); + printk(KERN_DEBUG "EPDC_STATUS_NEXTLUT 0x%x\n", __raw_readl(EPDC_STATUS_NEXTLUT)); + printk(KERN_DEBUG "EPDC_STATUS_COL1 0x%x\n", __raw_readl(EPDC_STATUS_COL)); + printk(KERN_DEBUG "EPDC_STATUS_COL2 0x%x\n", __raw_readl(EPDC_STATUS_COL2)); + printk(KERN_DEBUG "EPDC_STATUS 0x%x\n", __raw_readl(EPDC_STATUS)); + printk(KERN_DEBUG "EPDC_UPD_COL_CORD 0x%x\n", __raw_readl(EPDC_UPD_COL_CORD)); + printk(KERN_DEBUG "EPDC_UPD_COL_SIZE 0x%x\n", __raw_readl(EPDC_UPD_COL_SIZE)); + printk(KERN_DEBUG "EPDC_DEBUG 0x%x\n", __raw_readl(EPDC_DEBUG)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT 0x%x\n", __raw_readl(EPDC_DEBUG_LUT)); + printk(KERN_DEBUG "EPDC_HIST1_PARAM 0x%x\n", __raw_readl(EPDC_HIST1_PARAM)); + printk(KERN_DEBUG "EPDC_HIST2_PARAM 0x%x\n", __raw_readl(EPDC_HIST2_PARAM)); + printk(KERN_DEBUG "EPDC_HIST4_PARAM 0x%x\n", __raw_readl(EPDC_HIST4_PARAM)); + printk(KERN_DEBUG "EPDC_HIST8_PARAM0 0x%x\n", __raw_readl(EPDC_HIST8_PARAM0)); + printk(KERN_DEBUG "EPDC_HIST8_PARAM1 0x%x\n", __raw_readl(EPDC_HIST8_PARAM1)); + printk(KERN_DEBUG "EPDC_HIST16_PARAM0 0x%x\n", __raw_readl(EPDC_HIST16_PARAM0)); + printk(KERN_DEBUG "EPDC_HIST16_PARAM1 0x%x\n", __raw_readl(EPDC_HIST16_PARAM1)); + printk(KERN_DEBUG "EPDC_HIST16_PARAM2 0x%x\n", __raw_readl(EPDC_HIST16_PARAM2)); + printk(KERN_DEBUG "EPDC_HIST16_PARAM3 0x%x\n", __raw_readl(EPDC_HIST16_PARAM3)); + printk(KERN_DEBUG "EPDC_GPIO 0x%x\n", __raw_readl(EPDC_GPIO)); + printk(KERN_DEBUG "EPDC_VERSION 0x%x\n", __raw_readl(EPDC_VERSION)); + printk(KERN_DEBUG "\n\n"); +} + +static void dump_update_data(struct device *dev, + struct update_data_list *upd_data_list) +{ + dev_info(dev, + "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, " + "LUT = %d, Coll Mask = 0x%llx, order = %d\n", + upd_data_list->update_desc->upd_data.update_region.left, + upd_data_list->update_desc->upd_data.update_region.top, + upd_data_list->update_desc->upd_data.update_region.width, + upd_data_list->update_desc->upd_data.update_region.height, + upd_data_list->update_desc->upd_data.waveform_mode, + upd_data_list->lut_num, + upd_data_list->collision_mask, + upd_data_list->update_desc->update_order); +} + +static void dump_collision_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_info(fb_data->dev, "Collision List:\n"); + if (list_empty(&fb_data->upd_buf_collision_list)) + dev_info(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_collision_list, list) { + dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); + dump_update_data(fb_data->dev, plist); + } +} + +static void dump_free_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_info(fb_data->dev, "Free List:\n"); + if (list_empty(&fb_data->upd_buf_free_list)) + dev_info(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) + dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); +} + +static void dump_queue(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_info(fb_data->dev, "Queue:\n"); + if (list_empty(&fb_data->upd_buf_queue)) + dev_info(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_queue, list) { + dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); + dump_update_data(fb_data->dev, plist); + } +} + +static void dump_desc_data(struct device *dev, + struct update_desc_list *upd_desc_list) +{ + dev_info(dev, + "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, " + "order = %d\n", + upd_desc_list->upd_data.update_region.left, + upd_desc_list->upd_data.update_region.top, + upd_desc_list->upd_data.update_region.width, + upd_desc_list->upd_data.update_region.height, + upd_desc_list->upd_data.waveform_mode, + upd_desc_list->update_order); +} + +static void dump_pending_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_desc_list *plist; + + dev_info(fb_data->dev, "Queue:\n"); + if (list_empty(&fb_data->upd_pending_list)) + dev_info(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_pending_list, list) + dump_desc_data(fb_data->dev, plist); +} + +static void dump_all_updates(struct mxc_epdc_fb_data *fb_data) +{ + dump_free_list(fb_data); + dump_queue(fb_data); + dump_collision_list(fb_data); + dev_info(fb_data->dev, "Current update being processed:\n"); + if (fb_data->cur_update == NULL) + dev_info(fb_data->dev, "No current update\n"); + else + dump_update_data(fb_data->dev, fb_data->cur_update); +} +#else +static inline void dump_pxp_config(struct mxc_epdc_fb_data *fb_data, + struct pxp_config_data *pxp_conf) {} +static inline void dump_epdc_reg(void) {} +static inline void dump_update_data(struct device *dev, + struct update_data_list *upd_data_list) {} +static inline void dump_collision_list(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_free_list(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_queue(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_all_updates(struct mxc_epdc_fb_data *fb_data) {} + +#endif + + +/******************************************************** + * Start Low-Level EPDC Functions + ********************************************************/ + +static inline void epdc_lut_complete_intr(int rev, u32 lut_num, bool enable) +{ + if (rev < 20) { + if (enable) + __raw_writel(1 << lut_num, EPDC_IRQ_MASK_SET); + else + __raw_writel(1 << lut_num, EPDC_IRQ_MASK_CLEAR); + } else { + if (enable) { + if (lut_num < 32) + __raw_writel(1 << lut_num, EPDC_IRQ_MASK1_SET); + else + __raw_writel(1 << (lut_num - 32), + EPDC_IRQ_MASK2_SET); + } else { + if (lut_num < 32) + __raw_writel(1 << lut_num, + EPDC_IRQ_MASK1_CLEAR); + else + __raw_writel(1 << (lut_num - 32), + EPDC_IRQ_MASK2_CLEAR); + } + } +} + +static inline void epdc_working_buf_intr(bool enable) +{ + if (enable) + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ, EPDC_IRQ_MASK_SET); + else + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ, EPDC_IRQ_MASK_CLEAR); +} + +static inline void epdc_clear_working_buf_irq(void) +{ + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ | EPDC_IRQ_LUT_COL_IRQ, + EPDC_IRQ_CLEAR); +} + +static inline void epdc_eof_intr(bool enable) +{ + if (enable) + __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_MASK_SET); + else + __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_MASK_CLEAR); +} + +static inline void epdc_clear_eof_irq(void) +{ + __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_CLEAR); +} + +static inline bool epdc_signal_eof(void) +{ + return (__raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ) + & EPDC_IRQ_FRAME_END_IRQ) ? true : false; +} + +static inline void epdc_set_temp(u32 temp) +{ + __raw_writel(temp, EPDC_TEMP); +} + +static inline void epdc_set_screen_res(u32 width, u32 height) +{ + u32 val = (height << EPDC_RES_VERTICAL_OFFSET) | width; + __raw_writel(val, EPDC_RES); +} + +static inline void epdc_set_update_addr(u32 addr) +{ + __raw_writel(addr, EPDC_UPD_ADDR); +} + +static inline void epdc_set_update_coord(u32 x, u32 y) +{ + u32 val = (y << EPDC_UPD_CORD_YCORD_OFFSET) | x; + __raw_writel(val, EPDC_UPD_CORD); +} + +static inline void epdc_set_update_dimensions(u32 width, u32 height) +{ + u32 val = (height << EPDC_UPD_SIZE_HEIGHT_OFFSET) | width; + __raw_writel(val, EPDC_UPD_SIZE); +} + +static void epdc_set_update_waveform(struct mxcfb_waveform_modes *wv_modes) +{ + u32 val; + + /* Configure the auto-waveform look-up table based on waveform modes */ + + /* Entry 1 = DU, 2 = GC4, 3 = GC8, etc. */ + val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (0 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (1 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_gc4 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (2 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_gc8 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (3 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_gc16 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (4 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_gc32 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (5 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); +} + +static void epdc_set_update_stride(u32 stride) +{ + __raw_writel(stride, EPDC_UPD_STRIDE); +} + +static void epdc_submit_update(u32 lut_num, u32 waveform_mode, u32 update_mode, + bool use_dry_run, bool use_test_mode, u32 np_val) +{ + u32 reg_val = 0; + + if (use_test_mode) { + reg_val |= + ((np_val << EPDC_UPD_FIXED_FIXNP_OFFSET) & + EPDC_UPD_FIXED_FIXNP_MASK) | EPDC_UPD_FIXED_FIXNP_EN; + + __raw_writel(reg_val, EPDC_UPD_FIXED); + + reg_val = EPDC_UPD_CTRL_USE_FIXED; + } else { + __raw_writel(reg_val, EPDC_UPD_FIXED); + } + + if (waveform_mode == WAVEFORM_MODE_AUTO) + reg_val |= EPDC_UPD_CTRL_AUTOWV; + else + reg_val |= ((waveform_mode << + EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET) & + EPDC_UPD_CTRL_WAVEFORM_MODE_MASK); + + reg_val |= (use_dry_run ? EPDC_UPD_CTRL_DRY_RUN : 0) | + ((lut_num << EPDC_UPD_CTRL_LUT_SEL_OFFSET) & + EPDC_UPD_CTRL_LUT_SEL_MASK) | + update_mode; + + __raw_writel(reg_val, EPDC_UPD_CTRL); +} + +static inline bool epdc_is_lut_complete(int rev, u32 lut_num) +{ + u32 val; + bool is_compl; + if (rev < 20) { + val = __raw_readl(EPDC_IRQ); + is_compl = val & (1 << lut_num) ? true : false; + } else if (lut_num < 32) { + val = __raw_readl(EPDC_IRQ1); + is_compl = val & (1 << lut_num) ? true : false; + } else { + val = __raw_readl(EPDC_IRQ2); + is_compl = val & (1 << (lut_num - 32)) ? true : false; + } + + return is_compl; +} + +static inline void epdc_clear_lut_complete_irq(int rev, u32 lut_num) +{ + if (rev < 20) + __raw_writel(1 << lut_num, EPDC_IRQ_CLEAR); + else if (lut_num < 32) + __raw_writel(1 << lut_num, EPDC_IRQ1_CLEAR); + else + __raw_writel(1 << (lut_num - 32), EPDC_IRQ2_CLEAR); +} + +static inline bool epdc_is_lut_active(u32 lut_num) +{ + u32 val; + bool is_active; + + if (lut_num < 32) { + val = __raw_readl(EPDC_STATUS_LUTS); + is_active = val & (1 << lut_num) ? true : false; + } else { + val = __raw_readl(EPDC_STATUS_LUTS2); + is_active = val & (1 << (lut_num - 32)) ? true : false; + } + + return is_active; +} + +static inline bool epdc_any_luts_active(int rev) +{ + bool any_active; + + if (rev < 20) + any_active = __raw_readl(EPDC_STATUS_LUTS) ? true : false; + else + any_active = (__raw_readl(EPDC_STATUS_LUTS) | + __raw_readl(EPDC_STATUS_LUTS2)) ? true : false; + + return any_active; +} + +static inline bool epdc_any_luts_available(void) +{ + bool luts_available = + (__raw_readl(EPDC_STATUS_NEXTLUT) & + EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID) ? true : false; + return luts_available; +} + +static inline int epdc_get_next_lut(void) +{ + u32 val = + __raw_readl(EPDC_STATUS_NEXTLUT) & + EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK; + return val; +} + +static int epdc_choose_next_lut(int rev, int *next_lut) +{ + u64 luts_status, unprocessed_luts, used_luts; + /* Available LUTs are reduced to 16 in 5-bit waveform mode */ + bool format_p5n = ((__raw_readl(EPDC_FORMAT) & + EPDC_FORMAT_BUF_PIXEL_FORMAT_MASK) == + EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N); + + luts_status = __raw_readl(EPDC_STATUS_LUTS); + if ((rev < 20) || format_p5n) + luts_status &= 0xFFFF; + else + luts_status |= ((u64)__raw_readl(EPDC_STATUS_LUTS2) << 32); + + if (rev < 20) { + unprocessed_luts = __raw_readl(EPDC_IRQ) & 0xFFFF; + } else { + unprocessed_luts = __raw_readl(EPDC_IRQ1) | + ((u64)__raw_readl(EPDC_IRQ2) << 32); + if (format_p5n) + unprocessed_luts &= 0xFFFF; + } + + /* + * Note on unprocessed_luts: There is a race condition + * where a LUT completes, but has not been processed by + * IRQ handler workqueue, and then a new update request + * attempts to use that LUT. We prevent that here by + * ensuring that the LUT we choose doesn't have its IRQ + * bit set (indicating it has completed but not yet been + * processed). + */ + used_luts = luts_status | unprocessed_luts; + + /* + * Selecting a LUT to minimize incidence of TCE Underrun Error + * -------------------------------------------------------- + * We want to find the lowest order LUT that is of greater + * order than all other active LUTs. If highest order LUT + * is active, then we want to choose the lowest order + * available LUT. + * + * NOTE: For EPDC version 2.0 and later, TCE Underrun error + * bug is fixed, so it doesn't matter which LUT is used. + */ + + if ((rev < 20) || format_p5n) { + *next_lut = fls64(used_luts); + if (*next_lut > 15) + *next_lut = ffz(used_luts); + } else { + if ((u32)used_luts != ~0UL) + *next_lut = ffz((u32)used_luts); + else if ((u32)(used_luts >> 32) != ~0UL) + *next_lut = ffz((u32)(used_luts >> 32)) + 32; + else + *next_lut = INVALID_LUT; + } + + if (used_luts & 0x8000) + return 1; + else + return 0; +} + +static inline bool epdc_is_working_buffer_busy(void) +{ + u32 val = __raw_readl(EPDC_STATUS); + bool is_busy = (val & EPDC_STATUS_WB_BUSY) ? true : false; + + return is_busy; +} + +static inline bool epdc_is_working_buffer_complete(void) +{ + u32 val = __raw_readl(EPDC_IRQ); + bool is_compl = (val & EPDC_IRQ_WB_CMPLT_IRQ) ? true : false; + + return is_compl; +} + +static inline bool epdc_is_lut_cancelled(void) +{ + u32 val = __raw_readl(EPDC_STATUS); + bool is_void = (val & EPDC_STATUS_UPD_VOID) ? true : false; + + return is_void; +} + +static inline bool epdc_is_collision(void) +{ + u32 val = __raw_readl(EPDC_IRQ); + return (val & EPDC_IRQ_LUT_COL_IRQ) ? true : false; +} + +static inline u64 epdc_get_colliding_luts(int rev) +{ + u32 val = __raw_readl(EPDC_STATUS_COL); + if (rev >= 20) + val |= (u64)__raw_readl(EPDC_STATUS_COL2) << 32; + return val; +} + +static void epdc_set_horizontal_timing(u32 horiz_start, u32 horiz_end, + u32 hsync_width, u32 hsync_line_length) +{ + u32 reg_val = + ((hsync_width << EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET) & + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK) + | ((hsync_line_length << EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET) & + EPDC_TCE_HSCAN1_LINE_SYNC_MASK); + __raw_writel(reg_val, EPDC_TCE_HSCAN1); + + reg_val = + ((horiz_start << EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET) & + EPDC_TCE_HSCAN2_LINE_BEGIN_MASK) + | ((horiz_end << EPDC_TCE_HSCAN2_LINE_END_OFFSET) & + EPDC_TCE_HSCAN2_LINE_END_MASK); + __raw_writel(reg_val, EPDC_TCE_HSCAN2); +} + +static void epdc_set_vertical_timing(u32 vert_start, u32 vert_end, + u32 vsync_width) +{ + u32 reg_val = + ((vert_start << EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET) & + EPDC_TCE_VSCAN_FRAME_BEGIN_MASK) + | ((vert_end << EPDC_TCE_VSCAN_FRAME_END_OFFSET) & + EPDC_TCE_VSCAN_FRAME_END_MASK) + | ((vsync_width << EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET) & + EPDC_TCE_VSCAN_FRAME_SYNC_MASK); + __raw_writel(reg_val, EPDC_TCE_VSCAN); +} + +static void epdc_init_settings(struct mxc_epdc_fb_data *fb_data) +{ + struct imx_epdc_fb_mode *epdc_mode = fb_data->cur_mode; + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 reg_val; + int num_ce; + int i; + + /* Enable clocks to access EPDC regs */ + clk_prepare_enable(fb_data->epdc_clk_axi); + clk_prepare_enable(fb_data->epdc_clk_pix); + + /* Reset */ + __raw_writel(EPDC_CTRL_SFTRST, EPDC_CTRL_SET); + while (!(__raw_readl(EPDC_CTRL) & EPDC_CTRL_CLKGATE)) + ; + __raw_writel(EPDC_CTRL_SFTRST, EPDC_CTRL_CLEAR); + + /* Enable clock gating (clear to enable) */ + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR); + while (__raw_readl(EPDC_CTRL) & (EPDC_CTRL_SFTRST | EPDC_CTRL_CLKGATE)) + ; + + /* EPDC_CTRL */ + reg_val = __raw_readl(EPDC_CTRL); + reg_val &= ~EPDC_CTRL_UPD_DATA_SWIZZLE_MASK; + reg_val |= EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP; + reg_val &= ~EPDC_CTRL_LUT_DATA_SWIZZLE_MASK; + reg_val |= EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP; + __raw_writel(reg_val, EPDC_CTRL_SET); + + /* EPDC_FORMAT - 2bit TFT and 4bit Buf pixel format */ + reg_val = EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT + | EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N + | ((0x0 << EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET) & + EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK); + __raw_writel(reg_val, EPDC_FORMAT); + + /* EPDC_FIFOCTRL (disabled) */ + reg_val = + ((100 << EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK) + | ((200 << EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK) + | ((100 << EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK); + __raw_writel(reg_val, EPDC_FIFOCTRL); + + /* EPDC_TEMP - Use default temp to get index */ + epdc_set_temp(mxc_epdc_fb_get_temp_index(fb_data, DEFAULT_TEMP)); + + /* EPDC_RES */ + epdc_set_screen_res(epdc_mode->vmode->xres, epdc_mode->vmode->yres); + + /* EPDC_AUTOWV_LUT */ + /* Initialize all auto-wavefrom look-up values to 2 - GC16 */ + for (i = 0; i < 8; i++) + __raw_writel((2 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (i << EPDC_AUTOWV_LUT_ADDR_OFFSET), EPDC_AUTOWV_LUT); + + /* + * EPDC_TCE_CTRL + * VSCAN_HOLDOFF = 4 + * VCOM_MODE = MANUAL + * VCOM_VAL = 0 + * DDR_MODE = DISABLED + * LVDS_MODE_CE = DISABLED + * LVDS_MODE = DISABLED + * DUAL_SCAN = DISABLED + * SDDO_WIDTH = 8bit + * PIXELS_PER_SDCLK = 4 + */ + reg_val = + ((epdc_mode->vscan_holdoff << EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET) & + EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK) + | EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4; + __raw_writel(reg_val, EPDC_TCE_CTRL); + + /* EPDC_TCE_HSCAN */ + epdc_set_horizontal_timing(screeninfo->left_margin, + screeninfo->right_margin, + screeninfo->hsync_len, + screeninfo->hsync_len); + + /* EPDC_TCE_VSCAN */ + epdc_set_vertical_timing(screeninfo->upper_margin, + screeninfo->lower_margin, + screeninfo->vsync_len); + + /* EPDC_TCE_OE */ + reg_val = + ((epdc_mode->sdoed_width << EPDC_TCE_OE_SDOED_WIDTH_OFFSET) & + EPDC_TCE_OE_SDOED_WIDTH_MASK) + | ((epdc_mode->sdoed_delay << EPDC_TCE_OE_SDOED_DLY_OFFSET) & + EPDC_TCE_OE_SDOED_DLY_MASK) + | ((epdc_mode->sdoez_width << EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET) & + EPDC_TCE_OE_SDOEZ_WIDTH_MASK) + | ((epdc_mode->sdoez_delay << EPDC_TCE_OE_SDOEZ_DLY_OFFSET) & + EPDC_TCE_OE_SDOEZ_DLY_MASK); + __raw_writel(reg_val, EPDC_TCE_OE); + + /* EPDC_TCE_TIMING1 */ + __raw_writel(0x0, EPDC_TCE_TIMING1); + + /* EPDC_TCE_TIMING2 */ + reg_val = + ((epdc_mode->gdclk_hp_offs << EPDC_TCE_TIMING2_GDCLK_HP_OFFSET) & + EPDC_TCE_TIMING2_GDCLK_HP_MASK) + | ((epdc_mode->gdsp_offs << EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET) & + EPDC_TCE_TIMING2_GDSP_OFFSET_MASK); + __raw_writel(reg_val, EPDC_TCE_TIMING2); + + /* EPDC_TCE_TIMING3 */ + reg_val = + ((epdc_mode->gdoe_offs << EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET) & + EPDC_TCE_TIMING3_GDOE_OFFSET_MASK) + | ((epdc_mode->gdclk_offs << EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET) & + EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK); + __raw_writel(reg_val, EPDC_TCE_TIMING3); + + /* + * EPDC_TCE_SDCFG + * SDCLK_HOLD = 1 + * SDSHR = 1 + * NUM_CE = 1 + * SDDO_REFORMAT = FLIP_PIXELS + * SDDO_INVERT = DISABLED + * PIXELS_PER_CE = display horizontal resolution + */ + num_ce = epdc_mode->num_ce; + if (num_ce == 0) + num_ce = 1; + reg_val = EPDC_TCE_SDCFG_SDCLK_HOLD | EPDC_TCE_SDCFG_SDSHR + | ((num_ce << EPDC_TCE_SDCFG_NUM_CE_OFFSET) & + EPDC_TCE_SDCFG_NUM_CE_MASK) + | EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS + | ((epdc_mode->vmode->xres/num_ce << EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET) & + EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK); + __raw_writel(reg_val, EPDC_TCE_SDCFG); + + /* + * EPDC_TCE_GDCFG + * GDRL = 1 + * GDOE_MODE = 0; + * GDSP_MODE = 0; + */ + reg_val = EPDC_TCE_SDCFG_GDRL; + __raw_writel(reg_val, EPDC_TCE_GDCFG); + + /* + * EPDC_TCE_POLARITY + * SDCE_POL = ACTIVE LOW + * SDLE_POL = ACTIVE HIGH + * SDOE_POL = ACTIVE HIGH + * GDOE_POL = ACTIVE HIGH + * GDSP_POL = ACTIVE LOW + */ + reg_val = EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH + | EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH + | EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH; + __raw_writel(reg_val, EPDC_TCE_POLARITY); + + /* EPDC_IRQ_MASK */ + __raw_writel(EPDC_IRQ_TCE_UNDERRUN_IRQ, EPDC_IRQ_MASK); + + /* + * EPDC_GPIO + * PWRCOM = ? + * PWRCTRL = ? + * BDR = ? + */ + reg_val = ((0 << EPDC_GPIO_PWRCTRL_OFFSET) & EPDC_GPIO_PWRCTRL_MASK) + | ((0 << EPDC_GPIO_BDR_OFFSET) & EPDC_GPIO_BDR_MASK); + __raw_writel(reg_val, EPDC_GPIO); + + __raw_writel(fb_data->waveform_buffer_phys, EPDC_WVADDR); + __raw_writel(fb_data->working_buffer_phys, EPDC_WB_ADDR); + __raw_writel(fb_data->working_buffer_phys, EPDC_WB_ADDR_TCE); + + /* Disable clock */ + clk_disable_unprepare(fb_data->epdc_clk_axi); + clk_disable_unprepare(fb_data->epdc_clk_pix); +} + +static void epdc_powerup(struct mxc_epdc_fb_data *fb_data) +{ + int ret = 0; + mutex_lock(&fb_data->power_mutex); + + /* + * If power down request is pending, clear + * powering_down to cancel the request. + */ + if (fb_data->powering_down) + fb_data->powering_down = false; + + if (fb_data->power_state == POWER_STATE_ON) { + mutex_unlock(&fb_data->power_mutex); + return; + } + + dev_dbg(fb_data->dev, "EPDC Powerup\n"); + + fb_data->updates_active = true; + + /* Enable the v3p3 regulator */ + ret = regulator_enable(fb_data->v3p3_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable V3P3 regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + return; + } + + msleep(1); + + pm_runtime_get_sync(fb_data->dev); + + /* Enable clocks to EPDC */ + clk_prepare_enable(fb_data->epdc_clk_axi); + clk_prepare_enable(fb_data->epdc_clk_pix); + + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR); + + /* Enable power to the EPD panel */ + ret = regulator_enable(fb_data->display_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable DISPLAY regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + return; + } + ret = regulator_enable(fb_data->vcom_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable VCOM regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + return; + } + + fb_data->power_state = POWER_STATE_ON; + + mutex_unlock(&fb_data->power_mutex); +} + +static void epdc_powerdown(struct mxc_epdc_fb_data *fb_data) +{ + mutex_lock(&fb_data->power_mutex); + + /* If powering_down has been cleared, a powerup + * request is pre-empting this powerdown request. + */ + if (!fb_data->powering_down + || (fb_data->power_state == POWER_STATE_OFF)) { + mutex_unlock(&fb_data->power_mutex); + return; + } + + dev_dbg(fb_data->dev, "EPDC Powerdown\n"); + + /* Disable power to the EPD panel */ + regulator_disable(fb_data->vcom_regulator); + regulator_disable(fb_data->display_regulator); + + /* Disable clocks to EPDC */ + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_SET); + clk_disable_unprepare(fb_data->epdc_clk_pix); + clk_disable_unprepare(fb_data->epdc_clk_axi); + + pm_runtime_put_sync_suspend(fb_data->dev); + + /* turn off the V3p3 */ + regulator_disable(fb_data->v3p3_regulator); + + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + + if (fb_data->wait_for_powerdown) { + fb_data->wait_for_powerdown = false; + complete(&fb_data->powerdown_compl); + } + + mutex_unlock(&fb_data->power_mutex); +} + +static void epdc_init_sequence(struct mxc_epdc_fb_data *fb_data) +{ + /* Initialize EPDC, passing pointer to EPDC registers */ + epdc_init_settings(fb_data); + fb_data->in_init = true; + epdc_powerup(fb_data); + draw_mode0(fb_data); + /* Force power down event */ + fb_data->powering_down = true; + epdc_powerdown(fb_data); + fb_data->updates_active = false; +} + +static int mxc_epdc_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + + if (offset < info->fix.smem_len) { + /* mapping framebuffer memory */ + len = info->fix.smem_len - offset; + vma->vm_pgoff = (info->fix.smem_start + offset) >> PAGE_SHIFT; + } else + return -EINVAL; + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) + return -EINVAL; + + /* make buffers bufferable */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(info->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + } + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxc_epdc_fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, struct fb_info *info) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + val = _chan_to_field(red, &info->var.red); + val |= _chan_to_field(green, &info->var.green); + val |= _chan_to_field(blue, &info->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +static int mxc_epdc_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info) +{ + int count, index, r; + u16 *red, *green, *blue, *transp; + u16 trans = 0xffff; + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + int i; + + dev_dbg(fb_data->dev, "setcmap\n"); + + if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) { + /* Only support an 8-bit, 256 entry lookup */ + if (cmap->len != 256) + return 1; + + mxc_epdc_fb_flush_updates(fb_data); + + mutex_lock(&fb_data->pxp_mutex); + /* + * Store colormap in pxp_conf structure for later transmit + * to PxP during update process to convert gray pixels. + * + * Since red=blue=green for pseudocolor visuals, we can + * just use red values. + */ + for (i = 0; i < 256; i++) + fb_data->pxp_conf.proc_data.lut_map[i] = cmap->red[i] & 0xFF; + + fb_data->pxp_conf.proc_data.lut_map_updated = true; + + mutex_unlock(&fb_data->pxp_mutex); + } else { + red = cmap->red; + green = cmap->green; + blue = cmap->blue; + transp = cmap->transp; + index = cmap->start; + + for (count = 0; count < cmap->len; count++) { + if (transp) + trans = *transp++; + r = mxc_epdc_fb_setcolreg(index++, *red++, *green++, *blue++, + trans, info); + if (r != 0) + return r; + } + } + + return 0; +} + +static void adjust_coordinates(u32 xres, u32 yres, u32 rotation, + struct mxcfb_rect *update_region, struct mxcfb_rect *adj_update_region) +{ + u32 temp; + + /* If adj_update_region == NULL, pass result back in update_region */ + /* If adj_update_region == valid, use it to pass back result */ + if (adj_update_region) + switch (rotation) { + case FB_ROTATE_UR: + adj_update_region->top = update_region->top; + adj_update_region->left = update_region->left; + adj_update_region->width = update_region->width; + adj_update_region->height = update_region->height; + break; + case FB_ROTATE_CW: + adj_update_region->top = update_region->left; + adj_update_region->left = yres - + (update_region->top + update_region->height); + adj_update_region->width = update_region->height; + adj_update_region->height = update_region->width; + break; + case FB_ROTATE_UD: + adj_update_region->width = update_region->width; + adj_update_region->height = update_region->height; + adj_update_region->top = yres - + (update_region->top + update_region->height); + adj_update_region->left = xres - + (update_region->left + update_region->width); + break; + case FB_ROTATE_CCW: + adj_update_region->left = update_region->top; + adj_update_region->top = xres - + (update_region->left + update_region->width); + adj_update_region->width = update_region->height; + adj_update_region->height = update_region->width; + break; + } + else + switch (rotation) { + case FB_ROTATE_UR: + /* No adjustment needed */ + break; + case FB_ROTATE_CW: + temp = update_region->top; + update_region->top = update_region->left; + update_region->left = yres - + (temp + update_region->height); + temp = update_region->width; + update_region->width = update_region->height; + update_region->height = temp; + break; + case FB_ROTATE_UD: + update_region->top = yres - + (update_region->top + update_region->height); + update_region->left = xres - + (update_region->left + update_region->width); + break; + case FB_ROTATE_CCW: + temp = update_region->left; + update_region->left = update_region->top; + update_region->top = xres - + (temp + update_region->width); + temp = update_region->width; + update_region->width = update_region->height; + update_region->height = temp; + break; + } +} + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxc_epdc_fb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + if (var->grayscale) + fix->visual = FB_VISUAL_STATIC_PSEUDOCOLOR; + else + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +/* + * This routine actually sets the video mode. It's in here where we + * the hardware state info->par and fix which can be affected by the + * change in par. For this driver it doesn't do much. + * + */ +static int mxc_epdc_fb_set_par(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &pxp_conf->proc_data; + struct fb_var_screeninfo *screeninfo = &fb_data->info.var; + struct imx_epdc_fb_mode *epdc_modes = fb_data->pdata->epdc_mode; + int i; + int ret; + __u32 xoffset_old, yoffset_old; + + /* + * Can't change the FB parameters until current updates have completed. + * This function returns when all active updates are done. + */ + mxc_epdc_fb_flush_updates(fb_data); + + mutex_lock(&fb_data->queue_mutex); + /* + * Set all screeninfo except for xoffset/yoffset + * Subsequent call to pan_display will handle those. + */ + xoffset_old = fb_data->epdc_fb_var.xoffset; + yoffset_old = fb_data->epdc_fb_var.yoffset; + fb_data->epdc_fb_var = *screeninfo; + fb_data->epdc_fb_var.xoffset = xoffset_old; + fb_data->epdc_fb_var.yoffset = yoffset_old; + mutex_unlock(&fb_data->queue_mutex); + + mutex_lock(&fb_data->pxp_mutex); + + /* + * Update PxP config data (used to process FB regions for updates) + * based on FB info and processing tasks required + */ + + /* Initialize non-channel-specific PxP parameters */ + proc_data->drect.left = proc_data->srect.left = 0; + proc_data->drect.top = proc_data->srect.top = 0; + proc_data->drect.width = proc_data->srect.width = screeninfo->xres; + proc_data->drect.height = proc_data->srect.height = screeninfo->yres; + proc_data->scaling = 0; + proc_data->hflip = 0; + proc_data->vflip = 0; + proc_data->rotate = screeninfo->rotate; + proc_data->bgcolor = 0; + proc_data->overlay_state = 0; + proc_data->lut_transform = PXP_LUT_NONE; + + /* + * configure S0 channel parameters + * Parameters should match FB format/width/height + */ + if (screeninfo->grayscale) + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_GREY; + else { + switch (screeninfo->bits_per_pixel) { + case 16: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + break; + case 24: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB24; + break; + case 32: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_XRGB32; + break; + default: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + break; + } + } + pxp_conf->s0_param.width = screeninfo->xres_virtual; + pxp_conf->s0_param.height = screeninfo->yres; + pxp_conf->s0_param.color_key = -1; + pxp_conf->s0_param.color_key_enable = false; + + /* + * Initialize Output channel parameters + * Output is Y-only greyscale + * Output width/height will vary based on update region size + */ + pxp_conf->out_param.width = screeninfo->xres; + pxp_conf->out_param.height = screeninfo->yres; + pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GREY; + + mutex_unlock(&fb_data->pxp_mutex); + + /* + * If HW not yet initialized, check to see if we are being sent + * an initialization request. + */ + if (!fb_data->hw_ready) { + struct fb_videomode mode; + u32 xres_temp; + + fb_var_to_videomode(&mode, screeninfo); + + /* When comparing requested fb mode, + we need to use unrotated dimensions */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres_temp = mode.xres; + mode.xres = mode.yres; + mode.yres = xres_temp; + } + + /* + * If requested video mode does not match current video + * mode, search for a matching panel. + */ + if (fb_data->cur_mode && + !fb_mode_is_equal(fb_data->cur_mode->vmode, + &mode)) { + bool found_match = false; + + /* Match videomode against epdc modes */ + for (i = 0; i < fb_data->pdata->num_modes; i++) { + if (!fb_mode_is_equal(epdc_modes[i].vmode, + &mode)) + continue; + fb_data->cur_mode = &epdc_modes[i]; + found_match = true; + break; + } + + if (!found_match) { + dev_err(fb_data->dev, + "Failed to match requested " + "video mode\n"); + return EINVAL; + } + } + + /* Found a match - Grab timing params */ + screeninfo->left_margin = mode.left_margin; + screeninfo->right_margin = mode.right_margin; + screeninfo->upper_margin = mode.upper_margin; + screeninfo->lower_margin = mode.lower_margin; + screeninfo->hsync_len = mode.hsync_len; + screeninfo->vsync_len = mode.vsync_len; + + fb_data->hw_initializing = true; + + /* Initialize EPDC settings and init panel */ + ret = + mxc_epdc_fb_init_hw((struct fb_info *)fb_data); + if (ret) { + dev_err(fb_data->dev, + "Failed to load panel waveform data\n"); + return ret; + } + } + + /* + * EOF sync delay (in us) should be equal to the vscan holdoff time + * VSCAN_HOLDOFF time = (VSCAN_HOLDOFF value + 1) * Vertical lines + * Add 25us for additional margin + */ + fb_data->eof_sync_period = (fb_data->cur_mode->vscan_holdoff + 1) * + 1000000/(fb_data->cur_mode->vmode->refresh * + (fb_data->cur_mode->vmode->upper_margin + + fb_data->cur_mode->vmode->yres + + fb_data->cur_mode->vmode->lower_margin + + fb_data->cur_mode->vmode->vsync_len)) + 25; + + mxc_epdc_fb_set_fix(info); + + return 0; +} + +static int mxc_epdc_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + + if (!var->xres) + var->xres = 1; + if (!var->yres) + var->yres = 1; + + if (var->xres_virtual < var->xoffset + var->xres) + var->xres_virtual = var->xoffset + var->xres; + if (var->yres_virtual < var->yoffset + var->yres) + var->yres_virtual = var->yoffset + var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16) && (var->bits_per_pixel != 8)) + var->bits_per_pixel = default_bpp; + + switch (var->bits_per_pixel) { + case 8: + if (var->grayscale != 0) { + /* + * For 8-bit grayscale, R, G, and B offset are equal. + * + */ + var->red.length = 8; + var->red.offset = 0; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 0; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + } else { + var->red.length = 3; + var->red.offset = 5; + var->red.msb_right = 0; + + var->green.length = 3; + var->green.offset = 2; + var->green.msb_right = 0; + + var->blue.length = 2; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + } + break; + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + switch (var->rotate) { + case FB_ROTATE_UR: + case FB_ROTATE_UD: + var->xres = fb_data->native_width; + var->yres = fb_data->native_height; + break; + case FB_ROTATE_CW: + case FB_ROTATE_CCW: + var->xres = fb_data->native_height; + var->yres = fb_data->native_width; + break; + default: + /* Invalid rotation value */ + var->rotate = 0; + dev_dbg(fb_data->dev, "Invalid rotation request\n"); + return -EINVAL; + } + + var->xres_virtual = ALIGN(var->xres, 32); + var->yres_virtual = ALIGN(var->yres, 128) * fb_data->num_screens; + + var->height = -1; + var->width = -1; + + return 0; +} + +void mxc_epdc_fb_set_waveform_modes(struct mxcfb_waveform_modes *modes, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + mutex_lock(&fb_data->queue_mutex); + + memcpy(&fb_data->wv_modes, modes, sizeof(struct mxcfb_waveform_modes)); + + /* Set flag to ensure that new waveform modes + * are programmed into EPDC before next update */ + fb_data->wv_modes_update = true; + + mutex_unlock(&fb_data->queue_mutex); +} +EXPORT_SYMBOL(mxc_epdc_fb_set_waveform_modes); + +static int mxc_epdc_fb_get_temp_index(struct mxc_epdc_fb_data *fb_data, int temp) +{ + int i; + int index = -1; + + if (fb_data->trt_entries == 0) { + dev_err(fb_data->dev, + "No TRT exists...using default temp index\n"); + return DEFAULT_TEMP_INDEX; + } + + /* Search temperature ranges for a match */ + for (i = 0; i < fb_data->trt_entries - 1; i++) { + if ((temp >= fb_data->temp_range_bounds[i]) + && (temp < fb_data->temp_range_bounds[i+1])) { + index = i; + break; + } + } + + if (index < 0) { + dev_err(fb_data->dev, + "No TRT index match...using default temp index\n"); + return DEFAULT_TEMP_INDEX; + } + + dev_dbg(fb_data->dev, "Using temperature index %d\n", index); + + return index; +} + +int mxc_epdc_fb_set_temperature(int temperature, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + /* Store temp index. Used later when configuring updates. */ + mutex_lock(&fb_data->queue_mutex); + fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, temperature); + mutex_unlock(&fb_data->queue_mutex); + + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_temperature); + +int mxc_epdc_fb_set_auto_update(u32 auto_mode, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + dev_dbg(fb_data->dev, "Setting auto update mode to %d\n", auto_mode); + + if ((auto_mode == AUTO_UPDATE_MODE_AUTOMATIC_MODE) + || (auto_mode == AUTO_UPDATE_MODE_REGION_MODE)) + fb_data->auto_mode = auto_mode; + else { + dev_err(fb_data->dev, "Invalid auto update mode parameter.\n"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_auto_update); + +int mxc_epdc_fb_set_upd_scheme(u32 upd_scheme, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + dev_dbg(fb_data->dev, "Setting optimization level to %d\n", upd_scheme); + + /* + * Can't change the scheme until current updates have completed. + * This function returns when all active updates are done. + */ + mxc_epdc_fb_flush_updates(fb_data); + + if ((upd_scheme == UPDATE_SCHEME_SNAPSHOT) + || (upd_scheme == UPDATE_SCHEME_QUEUE) + || (upd_scheme == UPDATE_SCHEME_QUEUE_AND_MERGE)) + fb_data->upd_scheme = upd_scheme; + else { + dev_err(fb_data->dev, "Invalid update scheme specified.\n"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_upd_scheme); + +static void copy_before_process(struct mxc_epdc_fb_data *fb_data, + struct update_data_list *upd_data_list) +{ + struct mxcfb_update_data *upd_data = + &upd_data_list->update_desc->upd_data; + int i; + unsigned char *temp_buf_ptr = fb_data->virt_addr_copybuf; + unsigned char *src_ptr; + struct mxcfb_rect *src_upd_region; + int temp_buf_stride; + int src_stride; + int bpp = fb_data->epdc_fb_var.bits_per_pixel; + int left_offs, right_offs; + int x_trailing_bytes, y_trailing_bytes; + int alt_buf_offset; + + /* Set source buf pointer based on input source, panning, etc. */ + if (upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) { + src_upd_region = &upd_data->alt_buffer_data.alt_update_region; + src_stride = + upd_data->alt_buffer_data.width * bpp/8; + alt_buf_offset = upd_data->alt_buffer_data.phys_addr - + fb_data->info.fix.smem_start; + src_ptr = fb_data->info.screen_base + alt_buf_offset + + src_upd_region->top * src_stride; + } else { + src_upd_region = &upd_data->update_region; + src_stride = fb_data->epdc_fb_var.xres_virtual * bpp/8; + src_ptr = fb_data->info.screen_base + fb_data->fb_offset + + src_upd_region->top * src_stride; + } + + temp_buf_stride = ALIGN(src_upd_region->width, 8) * bpp/8; + left_offs = src_upd_region->left * bpp/8; + right_offs = src_upd_region->width * bpp/8; + x_trailing_bytes = (ALIGN(src_upd_region->width, 8) + - src_upd_region->width) * bpp/8; + + for (i = 0; i < src_upd_region->height; i++) { + /* Copy the full line */ + memcpy(temp_buf_ptr, src_ptr + left_offs, + src_upd_region->width * bpp/8); + + /* Clear any unwanted pixels at the end of each line */ + if (src_upd_region->width & 0x7) { + memset(temp_buf_ptr + right_offs, 0x0, + x_trailing_bytes); + } + + temp_buf_ptr += temp_buf_stride; + src_ptr += src_stride; + } + + /* Clear any unwanted pixels at the bottom of the end of each line */ + if (src_upd_region->height & 0x7) { + y_trailing_bytes = (ALIGN(src_upd_region->height, 8) + - src_upd_region->height) * + ALIGN(src_upd_region->width, 8) * bpp/8; + memset(temp_buf_ptr, 0x0, y_trailing_bytes); + } +} + +static int epdc_process_update(struct update_data_list *upd_data_list, + struct mxc_epdc_fb_data *fb_data) +{ + struct mxcfb_rect *src_upd_region; /* Region of src buffer for update */ + struct mxcfb_rect pxp_upd_region; + u32 src_width, src_height; + u32 offset_from_4, bytes_per_pixel; + u32 post_rotation_xcoord, post_rotation_ycoord, width_pxp_blocks; + u32 pxp_input_offs, pxp_output_offs, pxp_output_shift; + u32 hist_stat = 0; + int width_unaligned, height_unaligned; + bool input_unaligned = false; + bool line_overflow = false; + int pix_per_line_added; + bool use_temp_buf = false; + struct mxcfb_rect temp_buf_upd_region; + struct update_desc_list *upd_desc_list = upd_data_list->update_desc; + + int ret; + + /* + * Gotta do a whole bunch of buffer ptr manipulation to + * work around HW restrictions for PxP & EPDC + * Note: Applies to pre-2.0 versions of EPDC/PxP + */ + + /* + * Are we using FB or an alternate (overlay) + * buffer for source of update? + */ + if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) { + src_width = upd_desc_list->upd_data.alt_buffer_data.width; + src_height = upd_desc_list->upd_data.alt_buffer_data.height; + src_upd_region = &upd_desc_list->upd_data.alt_buffer_data.alt_update_region; + } else { + src_width = fb_data->epdc_fb_var.xres_virtual; + src_height = fb_data->epdc_fb_var.yres; + src_upd_region = &upd_desc_list->upd_data.update_region; + } + + bytes_per_pixel = fb_data->epdc_fb_var.bits_per_pixel/8; + + /* + * SW workaround for PxP limitation (for pre-v2.0 HW) + * + * There are 3 cases where we cannot process the update data + * directly from the input buffer: + * + * 1) PxP must process 8x8 pixel blocks, and all pixels in each block + * are considered for auto-waveform mode selection. If the + * update region is not 8x8 aligned, additional unwanted pixels + * will be considered in auto-waveform mode selection. + * + * 2) PxP input must be 32-bit aligned, so any update + * address not 32-bit aligned must be shifted to meet the + * 32-bit alignment. The PxP will thus end up processing pixels + * outside of the update region to satisfy this alignment restriction, + * which can affect auto-waveform mode selection. + * + * 3) If input fails 32-bit alignment, and the resulting expansion + * of the processed region would add at least 8 pixels more per + * line than the original update line width, the EPDC would + * cause screen artifacts by incorrectly handling the 8+ pixels + * at the end of each line. + * + * Workaround is to copy from source buffer into a temporary + * buffer, which we pad with zeros to match the 8x8 alignment + * requirement. This temp buffer becomes the input to the PxP. + */ + width_unaligned = src_upd_region->width & 0x7; + height_unaligned = src_upd_region->height & 0x7; + + offset_from_4 = src_upd_region->left & 0x3; + input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ? + true : false; + + pix_per_line_added = (offset_from_4 * bytes_per_pixel % 4) + / bytes_per_pixel; + if ((((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) || + fb_data->epdc_fb_var.rotate == FB_ROTATE_UD)) && + (ALIGN(src_upd_region->width, 8) < + ALIGN(src_upd_region->width + pix_per_line_added, 8))) + line_overflow = true; + + /* Grab pxp_mutex here so that we protect access + * to copybuf in addition to the PxP structures */ + mutex_lock(&fb_data->pxp_mutex); + + if (((((width_unaligned || height_unaligned || input_unaligned) && + (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) + || line_overflow) && (fb_data->rev < 20)) || + fb_data->restrict_width) { + dev_dbg(fb_data->dev, "Copying update before processing.\n"); + + /* Update to reflect what the new source buffer will be */ + src_width = ALIGN(src_upd_region->width, 8); + src_height = ALIGN(src_upd_region->height, 8); + + copy_before_process(fb_data, upd_data_list); + + /* + * src_upd_region should now describe + * the new update buffer attributes. + */ + temp_buf_upd_region.left = 0; + temp_buf_upd_region.top = 0; + temp_buf_upd_region.width = src_upd_region->width; + temp_buf_upd_region.height = src_upd_region->height; + src_upd_region = &temp_buf_upd_region; + + use_temp_buf = true; + } + + /* + * For pre-2.0 HW, input address must be 32-bit aligned + * Compute buffer offset to account for this PxP limitation + */ + offset_from_4 = src_upd_region->left & 0x3; + input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ? + true : false; + if ((fb_data->rev < 20) && input_unaligned) { + /* Leave a gap between PxP input addr and update region pixels */ + pxp_input_offs = + (src_upd_region->top * src_width + src_upd_region->left) + * bytes_per_pixel & 0xFFFFFFFC; + /* Update region left changes to reflect relative position to input ptr */ + pxp_upd_region.left = (offset_from_4 * bytes_per_pixel % 4) + / bytes_per_pixel; + } else { + pxp_input_offs = + (src_upd_region->top * src_width + src_upd_region->left) + * bytes_per_pixel; + pxp_upd_region.left = 0; + } + + pxp_upd_region.top = 0; + + /* + * For version 2.0 and later of EPDC & PxP, if no rotation, we don't + * need to align width & height (rotation always requires 8-pixel + * width & height alignment, per PxP limitations) + */ + if ((fb_data->epdc_fb_var.rotate == 0) && (fb_data->rev >= 20)) { + pxp_upd_region.width = src_upd_region->width; + pxp_upd_region.height = src_upd_region->height; + } else { + /* Update region dimensions to meet 8x8 pixel requirement */ + pxp_upd_region.width = ALIGN(src_upd_region->width + pxp_upd_region.left, 8); + pxp_upd_region.height = ALIGN(src_upd_region->height, 8); + } + + switch (fb_data->epdc_fb_var.rotate) { + case FB_ROTATE_UR: + default: + post_rotation_xcoord = pxp_upd_region.left; + post_rotation_ycoord = pxp_upd_region.top; + width_pxp_blocks = pxp_upd_region.width; + break; + case FB_ROTATE_CW: + width_pxp_blocks = pxp_upd_region.height; + post_rotation_xcoord = width_pxp_blocks - src_upd_region->height; + post_rotation_ycoord = pxp_upd_region.left; + break; + case FB_ROTATE_UD: + width_pxp_blocks = pxp_upd_region.width; + post_rotation_xcoord = width_pxp_blocks - src_upd_region->width - pxp_upd_region.left; + post_rotation_ycoord = pxp_upd_region.height - src_upd_region->height - pxp_upd_region.top; + break; + case FB_ROTATE_CCW: + width_pxp_blocks = pxp_upd_region.height; + post_rotation_xcoord = pxp_upd_region.top; + post_rotation_ycoord = pxp_upd_region.width - src_upd_region->width - pxp_upd_region.left; + break; + } + + /* Update region start coord to force PxP to process full 8x8 regions */ + pxp_upd_region.top &= ~0x7; + pxp_upd_region.left &= ~0x7; + + if (fb_data->rev < 20) { + pxp_output_shift = ALIGN(post_rotation_xcoord, 8) + - post_rotation_xcoord; + + pxp_output_offs = post_rotation_ycoord * width_pxp_blocks + + pxp_output_shift; + + upd_desc_list->epdc_offs = ALIGN(pxp_output_offs, 8); + } else { + pxp_output_shift = 0; + pxp_output_offs = post_rotation_ycoord * width_pxp_blocks + + post_rotation_xcoord; + + upd_desc_list->epdc_offs = pxp_output_offs; + } + + upd_desc_list->epdc_stride = width_pxp_blocks; + + /* Source address either comes from alternate buffer + provided in update data, or from the framebuffer. */ + if (use_temp_buf) + sg_dma_address(&fb_data->sg[0]) = + fb_data->phys_addr_copybuf; + else if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) + sg_dma_address(&fb_data->sg[0]) = + upd_desc_list->upd_data.alt_buffer_data.phys_addr + + pxp_input_offs; + else { + sg_dma_address(&fb_data->sg[0]) = + fb_data->info.fix.smem_start + fb_data->fb_offset + + pxp_input_offs; + sg_set_page(&fb_data->sg[0], + virt_to_page(fb_data->info.screen_base), + fb_data->info.fix.smem_len, + offset_in_page(fb_data->info.screen_base)); + } + + /* Update sg[1] to point to output of PxP proc task */ + sg_dma_address(&fb_data->sg[1]) = upd_data_list->phys_addr + + pxp_output_shift; + sg_set_page(&fb_data->sg[1], virt_to_page(upd_data_list->virt_addr), + fb_data->max_pix_size, + offset_in_page(upd_data_list->virt_addr)); + + /* + * Set PxP LUT transform type based on update flags. + */ + fb_data->pxp_conf.proc_data.lut_transform = 0; + if (upd_desc_list->upd_data.flags & EPDC_FLAG_ENABLE_INVERSION) + fb_data->pxp_conf.proc_data.lut_transform |= PXP_LUT_INVERT; + if (upd_desc_list->upd_data.flags & EPDC_FLAG_FORCE_MONOCHROME) + fb_data->pxp_conf.proc_data.lut_transform |= + PXP_LUT_BLACK_WHITE; + if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_CMAP) + fb_data->pxp_conf.proc_data.lut_transform |= + PXP_LUT_USE_CMAP; + + /* + * Toggle inversion processing if 8-bit + * inverted is the current pixel format. + */ + if (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT_INVERTED) + fb_data->pxp_conf.proc_data.lut_transform ^= PXP_LUT_INVERT; + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_process_update(fb_data, src_width, src_height, + &pxp_upd_region); + if (ret) { + dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) { + epdc_powerup(fb_data); + } + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_complete_update(fb_data, &hist_stat); + if (ret) { + dev_err(fb_data->dev, "Unable to complete PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + mutex_unlock(&fb_data->pxp_mutex); + + /* Update waveform mode from PxP histogram results */ + if ((fb_data->rev <= 20) && + (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) { + if (hist_stat & 0x1) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_du; + else if (hist_stat & 0x2) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc4; + else if (hist_stat & 0x4) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc8; + else if (hist_stat & 0x8) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc16; + else + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc32; + + dev_dbg(fb_data->dev, "hist_stat = 0x%x, new waveform = 0x%x\n", + hist_stat, upd_desc_list->upd_data.waveform_mode); + } + + return 0; +} + +static int epdc_submit_merge(struct update_desc_list *upd_desc_list, + struct update_desc_list *update_to_merge, + struct mxc_epdc_fb_data *fb_data) +{ + struct mxcfb_update_data *a, *b; + struct mxcfb_rect *arect, *brect; + struct mxcfb_rect combine; + bool use_flags = false; + + a = &upd_desc_list->upd_data; + b = &update_to_merge->upd_data; + arect = &upd_desc_list->upd_data.update_region; + brect = &update_to_merge->upd_data.update_region; + + /* Do not merge a dry-run collision test update */ + if ((a->flags & EPDC_FLAG_TEST_COLLISION) || + (b->flags & EPDC_FLAG_TEST_COLLISION)) + return MERGE_BLOCK; + + /* + * Updates with different flags must be executed sequentially. + * Halt the merge process to ensure this. + */ + if (a->flags != b->flags) { + /* + * Special exception: if update regions are identical, + * we may be able to merge them. + */ + if ((arect->left != brect->left) || + (arect->top != brect->top) || + (arect->width != brect->width) || + (arect->height != brect->height)) + return MERGE_BLOCK; + + use_flags = true; + } + + if (a->update_mode != b->update_mode) + a->update_mode = UPDATE_MODE_FULL; + + if (a->waveform_mode != b->waveform_mode) + a->waveform_mode = WAVEFORM_MODE_AUTO; + + if (arect->left > (brect->left + brect->width) || + brect->left > (arect->left + arect->width) || + arect->top > (brect->top + brect->height) || + brect->top > (arect->top + arect->height)) + return MERGE_FAIL; + + combine.left = arect->left < brect->left ? arect->left : brect->left; + combine.top = arect->top < brect->top ? arect->top : brect->top; + combine.width = (arect->left + arect->width) > + (brect->left + brect->width) ? + (arect->left + arect->width - combine.left) : + (brect->left + brect->width - combine.left); + combine.height = (arect->top + arect->height) > + (brect->top + brect->height) ? + (arect->top + arect->height - combine.top) : + (brect->top + brect->height - combine.top); + + /* Don't merge if combined width exceeds max width */ + if (fb_data->restrict_width) { + u32 max_width = EPDC_V2_MAX_UPDATE_WIDTH; + u32 combined_width = combine.width; + if (fb_data->epdc_fb_var.rotate != FB_ROTATE_UR) + max_width -= EPDC_V2_ROTATION_ALIGNMENT; + if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_CW) || + (fb_data->epdc_fb_var.rotate == FB_ROTATE_CCW)) + combined_width = combine.height; + if (combined_width > max_width) + return MERGE_FAIL; + } + + *arect = combine; + + /* Use flags of the later update */ + if (use_flags) + a->flags = b->flags; + + /* Merge markers */ + list_splice_tail(&update_to_merge->upd_marker_list, + &upd_desc_list->upd_marker_list); + + /* Merged update should take on the earliest order */ + upd_desc_list->update_order = + (upd_desc_list->update_order > update_to_merge->update_order) ? + upd_desc_list->update_order : update_to_merge->update_order; + + return MERGE_OK; +} + +static void epdc_submit_work_func(struct work_struct *work) +{ + int temp_index; + struct update_data_list *next_update, *temp_update; + struct update_desc_list *next_desc, *temp_desc; + struct update_marker_data *next_marker, *temp_marker; + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, epdc_submit_work); + struct update_data_list *upd_data_list = NULL; + struct mxcfb_rect adj_update_region, *upd_region; + bool end_merge = false; + bool is_transform; + u32 update_addr; + int *err_dist; + int ret; + + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + /* + * Are any of our collision updates able to go now? + * Go through all updates in the collision list and check to see + * if the collision mask has been fully cleared + */ + list_for_each_entry_safe(next_update, temp_update, + &fb_data->upd_buf_collision_list, list) { + + if (next_update->collision_mask != 0) + continue; + + dev_dbg(fb_data->dev, "A collision update is ready to go!\n"); + + /* Force waveform mode to auto for resubmitted collisions */ + next_update->update_desc->upd_data.waveform_mode = + WAVEFORM_MODE_AUTO; + + /* + * We have a collision cleared, so select it for resubmission. + * If an update is already selected, attempt to merge. + */ + if (!upd_data_list) { + upd_data_list = next_update; + list_del_init(&next_update->list); + if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) + /* If not merging, we have our update */ + break; + } else { + switch (epdc_submit_merge(upd_data_list->update_desc, + next_update->update_desc, + fb_data)) { + case MERGE_OK: + dev_dbg(fb_data->dev, + "Update merged [collision]\n"); + list_del_init(&next_update->update_desc->list); + kfree(next_update->update_desc); + next_update->update_desc = NULL; + list_del_init(&next_update->list); + /* Add to free buffer list */ + list_add_tail(&next_update->list, + &fb_data->upd_buf_free_list); + break; + case MERGE_FAIL: + dev_dbg(fb_data->dev, + "Update not merged [collision]\n"); + break; + case MERGE_BLOCK: + dev_dbg(fb_data->dev, + "Merge blocked [collision]\n"); + end_merge = true; + break; + } + + if (end_merge) { + end_merge = false; + break; + } + } + } + + /* + * Skip pending update list only if we found a collision + * update and we are not merging + */ + if (!((fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) && + upd_data_list)) { + /* + * If we didn't find a collision update ready to go, we + * need to get a free buffer and match it to a pending update. + */ + + /* + * Can't proceed if there are no free buffers (and we don't + * already have a collision update selected) + */ + if (!upd_data_list && + list_empty(&fb_data->upd_buf_free_list)) { + mutex_unlock(&fb_data->queue_mutex); + return; + } + + list_for_each_entry_safe(next_desc, temp_desc, + &fb_data->upd_pending_list, list) { + + dev_dbg(fb_data->dev, "Found a pending update!\n"); + + if (!upd_data_list) { + if (list_empty(&fb_data->upd_buf_free_list)) + break; + upd_data_list = + list_entry(fb_data->upd_buf_free_list.next, + struct update_data_list, list); + list_del_init(&upd_data_list->list); + upd_data_list->update_desc = next_desc; + list_del_init(&next_desc->list); + if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) + /* If not merging, we have an update */ + break; + } else { + switch (epdc_submit_merge(upd_data_list->update_desc, + next_desc, fb_data)) { + case MERGE_OK: + dev_dbg(fb_data->dev, + "Update merged [queue]\n"); + list_del_init(&next_desc->list); + kfree(next_desc); + break; + case MERGE_FAIL: + dev_dbg(fb_data->dev, + "Update not merged [queue]\n"); + break; + case MERGE_BLOCK: + dev_dbg(fb_data->dev, + "Merge blocked [collision]\n"); + end_merge = true; + break; + } + + if (end_merge) + break; + } + } + } + + /* Is update list empty? */ + if (!upd_data_list) { + mutex_unlock(&fb_data->queue_mutex); + return; + } + + /* + * If no processing required, skip update processing + * No processing means: + * - FB unrotated + * - FB pixel format = 8-bit grayscale + * - No look-up transformations (inversion, posterization, etc.) + * + * Note: A bug with EPDC stride prevents us from skipping + * PxP in versions 2.0 and earlier of EPDC. + */ + is_transform = upd_data_list->update_desc->upd_data.flags & + (EPDC_FLAG_ENABLE_INVERSION | EPDC_FLAG_USE_DITHERING_Y1 | + EPDC_FLAG_USE_DITHERING_Y4 | EPDC_FLAG_FORCE_MONOCHROME | + EPDC_FLAG_USE_CMAP) ? true : false; + + if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) && + (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT) && + !is_transform && (fb_data->rev > 20) && + !fb_data->restrict_width) { + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) + epdc_powerup(fb_data); + + /* + * Set update buffer pointer to the start of + * the update region in the frame buffer. + */ + upd_region = &upd_data_list->update_desc->upd_data.update_region; + update_addr = fb_data->info.fix.smem_start + + ((upd_region->top * fb_data->info.var.xres_virtual) + + upd_region->left) * fb_data->info.var.bits_per_pixel/8; + upd_data_list->update_desc->epdc_stride = + fb_data->info.var.xres_virtual * + fb_data->info.var.bits_per_pixel/8; + } else { + /* Select from PxP output buffers */ + upd_data_list->phys_addr = + fb_data->phys_addr_updbuf[fb_data->upd_buffer_num]; + upd_data_list->virt_addr = + fb_data->virt_addr_updbuf[fb_data->upd_buffer_num]; + fb_data->upd_buffer_num++; + if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1) + fb_data->upd_buffer_num = 0; + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + + /* Perform PXP processing - EPDC power will also be enabled */ + if (epdc_process_update(upd_data_list, fb_data)) { + dev_dbg(fb_data->dev, "PXP processing error.\n"); + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + list_del_init(&upd_data_list->update_desc->list); + kfree(upd_data_list->update_desc); + upd_data_list->update_desc = NULL; + /* Add to free buffer list */ + list_add_tail(&upd_data_list->list, + &fb_data->upd_buf_free_list); + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + return; + } + + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + update_addr = upd_data_list->phys_addr + + upd_data_list->update_desc->epdc_offs; + } + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data->epdc_fb_var.xres, + fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate, + &upd_data_list->update_desc->upd_data.update_region, + &adj_update_region); + + /* + * Is the working buffer idle? + * If the working buffer is busy, we must wait for the resource + * to become free. The IST will signal this event. + */ + if (fb_data->cur_update != NULL) { + dev_dbg(fb_data->dev, "working buf busy!\n"); + + /* Initialize event signalling an update resource is free */ + init_completion(&fb_data->update_res_free); + + fb_data->waiting_for_wb = true; + + /* Leave spinlock while waiting for WB to complete */ + mutex_unlock(&fb_data->queue_mutex); + wait_for_completion(&fb_data->update_res_free); + mutex_lock(&fb_data->queue_mutex); + } + + /* + * Dithering Processing (Version 1.0 - for i.MX508 and i.MX6SL) + */ + if (upd_data_list->update_desc->upd_data.flags & + EPDC_FLAG_USE_DITHERING_Y1) { + + err_dist = kzalloc((fb_data->info.var.xres_virtual + 3) * 3 + * sizeof(int), GFP_KERNEL); + + /* Dithering Y8 -> Y1 */ + do_dithering_processing_Y1_v1_0( + (uint8_t *)(upd_data_list->virt_addr + + upd_data_list->update_desc->epdc_offs), + upd_data_list->phys_addr + + upd_data_list->update_desc->epdc_offs, + &adj_update_region, + (fb_data->rev < 20) ? + ALIGN(adj_update_region.width, 8) : + adj_update_region.width, + err_dist); + + kfree(err_dist); + } else if (upd_data_list->update_desc->upd_data.flags & + EPDC_FLAG_USE_DITHERING_Y4) { + + err_dist = kzalloc((fb_data->info.var.xres_virtual + 3) * 3 + * sizeof(int), GFP_KERNEL); + + /* Dithering Y8 -> Y1 */ + do_dithering_processing_Y4_v1_0( + (uint8_t *)(upd_data_list->virt_addr + + upd_data_list->update_desc->epdc_offs), + upd_data_list->phys_addr + + upd_data_list->update_desc->epdc_offs, + &adj_update_region, + (fb_data->rev < 20) ? + ALIGN(adj_update_region.width, 8) : + adj_update_region.width, + err_dist); + + kfree(err_dist); + } + + /* + * If there are no LUTs available, + * then we must wait for the resource to become free. + * The IST will signal this event. + */ + if (!epdc_any_luts_available()) { + dev_dbg(fb_data->dev, "no luts available!\n"); + + /* Initialize event signalling an update resource is free */ + init_completion(&fb_data->update_res_free); + + fb_data->waiting_for_lut = true; + + /* Leave spinlock while waiting for LUT to free up */ + mutex_unlock(&fb_data->queue_mutex); + wait_for_completion(&fb_data->update_res_free); + mutex_lock(&fb_data->queue_mutex); + } + + ret = epdc_choose_next_lut(fb_data->rev, &upd_data_list->lut_num); + /* + * If LUT15 is in use (for pre-EPDC v2.0 hardware): + * - Wait for LUT15 to complete is if TCE underrun prevent is enabled + * - If we go ahead with update, sync update submission with EOF + */ + if (ret && fb_data->tce_prevent && (fb_data->rev < 20)) { + dev_dbg(fb_data->dev, "Waiting for LUT15\n"); + + /* Initialize event signalling that lut15 is free */ + init_completion(&fb_data->lut15_free); + + fb_data->waiting_for_lut15 = true; + + /* Leave spinlock while waiting for LUT to free up */ + mutex_unlock(&fb_data->queue_mutex); + wait_for_completion(&fb_data->lut15_free); + mutex_lock(&fb_data->queue_mutex); + + epdc_choose_next_lut(fb_data->rev, &upd_data_list->lut_num); + } else if (ret && (fb_data->rev < 20)) { + /* Synchronize update submission time to reduce + chances of TCE underrun */ + init_completion(&fb_data->eof_event); + + epdc_eof_intr(true); + + /* Leave spinlock while waiting for EOF event */ + mutex_unlock(&fb_data->queue_mutex); + ret = wait_for_completion_timeout(&fb_data->eof_event, + msecs_to_jiffies(1000)); + if (!ret) { + dev_err(fb_data->dev, "Missed EOF event!\n"); + epdc_eof_intr(false); + } + udelay(fb_data->eof_sync_period); + mutex_lock(&fb_data->queue_mutex); + + } + + /* LUTs are available, so we get one here */ + fb_data->cur_update = upd_data_list; + + /* Reset mask for LUTS that have completed during WB processing */ + fb_data->luts_complete_wb = 0; + + /* If we are just testing for collision, we don't assign a LUT, + * so we don't need to update LUT-related resources. */ + if (!(upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION)) { + /* Associate LUT with update marker */ + list_for_each_entry_safe(next_marker, temp_marker, + &upd_data_list->update_desc->upd_marker_list, upd_list) + next_marker->lut_num = fb_data->cur_update->lut_num; + + /* Mark LUT with order */ + fb_data->lut_update_order[upd_data_list->lut_num] = + upd_data_list->update_desc->update_order; + + epdc_lut_complete_intr(fb_data->rev, upd_data_list->lut_num, + true); + } + + /* Enable Collision and WB complete IRQs */ + epdc_working_buf_intr(true); + + /* Program EPDC update to process buffer */ + if (upd_data_list->update_desc->upd_data.temp != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, + upd_data_list->update_desc->upd_data.temp); + epdc_set_temp(temp_index); + } else + epdc_set_temp(fb_data->temp_index); + epdc_set_update_addr(update_addr); + epdc_set_update_coord(adj_update_region.left, adj_update_region.top); + epdc_set_update_dimensions(adj_update_region.width, + adj_update_region.height); + if (fb_data->rev > 20) + epdc_set_update_stride(upd_data_list->update_desc->epdc_stride); + if (fb_data->wv_modes_update && + (upd_data_list->update_desc->upd_data.waveform_mode + == WAVEFORM_MODE_AUTO)) { + epdc_set_update_waveform(&fb_data->wv_modes); + fb_data->wv_modes_update = false; + } + + epdc_submit_update(upd_data_list->lut_num, + upd_data_list->update_desc->upd_data.waveform_mode, + upd_data_list->update_desc->upd_data.update_mode, + (upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION) ? true : false, + false, 0); + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); +} + +static int mxc_epdc_fb_send_single_update(struct mxcfb_update_data *upd_data, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + struct update_data_list *upd_data_list = NULL; + struct mxcfb_rect *screen_upd_region; /* Region on screen to update */ + int temp_index; + int ret; + struct update_desc_list *upd_desc; + struct update_marker_data *marker_data, *next_marker, *temp_marker; + + /* Has EPDC HW been initialized? */ + if (!fb_data->hw_ready) { + /* Throw message if we are not mid-initialization */ + if (!fb_data->hw_initializing) + dev_err(fb_data->dev, "Display HW not properly" + "initialized. Aborting update.\n"); + return -EPERM; + } + + /* Check validity of update params */ + if ((upd_data->update_mode != UPDATE_MODE_PARTIAL) && + (upd_data->update_mode != UPDATE_MODE_FULL)) { + dev_err(fb_data->dev, + "Update mode 0x%x is invalid. Aborting update.\n", + upd_data->update_mode); + return -EINVAL; + } + if ((upd_data->waveform_mode > 255) && + (upd_data->waveform_mode != WAVEFORM_MODE_AUTO)) { + dev_err(fb_data->dev, + "Update waveform mode 0x%x is invalid." + " Aborting update.\n", + upd_data->waveform_mode); + return -EINVAL; + } + + mutex_lock(&fb_data->queue_mutex); + if ((upd_data->update_region.left + upd_data->update_region.width > fb_data->epdc_fb_var.xres) || + (upd_data->update_region.top + upd_data->update_region.height > fb_data->epdc_fb_var.yres)) { + mutex_unlock(&fb_data->queue_mutex); + dev_err(fb_data->dev, + "Update region is outside bounds of framebuffer." + "Aborting update.\n"); + return -EINVAL; + } + mutex_unlock(&fb_data->queue_mutex); + + if (upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) { + if ((upd_data->update_region.width != + upd_data->alt_buffer_data.alt_update_region.width) || + (upd_data->update_region.height != + upd_data->alt_buffer_data.alt_update_region.height)) { + dev_err(fb_data->dev, + "Alternate update region dimensions must " + "match screen update region dimensions.\n"); + return -EINVAL; + } + /* Validate physical address parameter */ + if ((upd_data->alt_buffer_data.phys_addr < + fb_data->info.fix.smem_start) || + (upd_data->alt_buffer_data.phys_addr > + fb_data->info.fix.smem_start + fb_data->map_size)) { + dev_err(fb_data->dev, + "Invalid physical address for alternate " + "buffer. Aborting update...\n"); + return -EINVAL; + } + } + + mutex_lock(&fb_data->queue_mutex); + + /* + * If we are waiting to go into suspend, or the FB is blanked, + * we do not accept new updates + */ + if ((fb_data->waiting_for_idle) || + (fb_data->blank != FB_BLANK_UNBLANK)) { + dev_dbg(fb_data->dev, "EPDC not active." + "Update request abort.\n"); + mutex_unlock(&fb_data->queue_mutex); + return -EPERM; + } + + if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) { + int count = 0; + struct update_data_list *plist; + + /* + * If next update is a FULL mode update, then we must + * ensure that all pending & active updates are complete + * before submitting the update. Otherwise, the FULL + * mode update may cause an endless collision loop with + * other updates. Block here until updates are flushed. + */ + if (upd_data->update_mode == UPDATE_MODE_FULL) { + mutex_unlock(&fb_data->queue_mutex); + mxc_epdc_fb_flush_updates(fb_data); + mutex_lock(&fb_data->queue_mutex); + } + + /* Count buffers in free buffer list */ + list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) + count++; + + /* Use count to determine if we have enough + * free buffers to handle this update request */ + if (count + fb_data->max_num_buffers + <= fb_data->max_num_updates) { + dev_err(fb_data->dev, + "No free intermediate buffers available.\n"); + mutex_unlock(&fb_data->queue_mutex); + return -ENOMEM; + } + + /* Grab first available buffer and delete from the free list */ + upd_data_list = + list_entry(fb_data->upd_buf_free_list.next, + struct update_data_list, list); + + list_del_init(&upd_data_list->list); + } + + /* + * Create new update data structure, fill it with new update + * data and add it to the list of pending updates + */ + upd_desc = kzalloc(sizeof(struct update_desc_list), GFP_KERNEL); + if (!upd_desc) { + dev_err(fb_data->dev, + "Insufficient system memory for update! Aborting.\n"); + if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) { + list_add(&upd_data_list->list, + &fb_data->upd_buf_free_list); + } + mutex_unlock(&fb_data->queue_mutex); + return -EPERM; + } + /* Initialize per-update marker list */ + INIT_LIST_HEAD(&upd_desc->upd_marker_list); + upd_desc->upd_data = *upd_data; + upd_desc->update_order = fb_data->order_cnt++; + list_add_tail(&upd_desc->list, &fb_data->upd_pending_list); + + /* If marker specified, associate it with a completion */ + if (upd_data->update_marker != 0) { + /* Allocate new update marker and set it up */ + marker_data = kzalloc(sizeof(struct update_marker_data), + GFP_KERNEL); + if (!marker_data) { + dev_err(fb_data->dev, "No memory for marker!\n"); + mutex_unlock(&fb_data->queue_mutex); + return -ENOMEM; + } + list_add_tail(&marker_data->upd_list, + &upd_desc->upd_marker_list); + marker_data->update_marker = upd_data->update_marker; + if (upd_desc->upd_data.flags & EPDC_FLAG_TEST_COLLISION) + marker_data->lut_num = DRY_RUN_NO_LUT; + else + marker_data->lut_num = INVALID_LUT; + init_completion(&marker_data->update_completion); + /* Add marker to master marker list */ + list_add_tail(&marker_data->full_list, + &fb_data->full_marker_list); + } + + if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { + /* Queued update scheme processing */ + + mutex_unlock(&fb_data->queue_mutex); + + /* Signal workqueue to handle new update */ + queue_work(fb_data->epdc_submit_workqueue, + &fb_data->epdc_submit_work); + + return 0; + } + + /* Snapshot update scheme processing */ + + /* Set descriptor for current update, delete from pending list */ + upd_data_list->update_desc = upd_desc; + list_del_init(&upd_desc->list); + + mutex_unlock(&fb_data->queue_mutex); + + /* + * Hold on to original screen update region, which we + * will ultimately use when telling EPDC where to update on panel + */ + screen_upd_region = &upd_desc->upd_data.update_region; + + /* Select from PxP output buffers */ + upd_data_list->phys_addr = + fb_data->phys_addr_updbuf[fb_data->upd_buffer_num]; + upd_data_list->virt_addr = + fb_data->virt_addr_updbuf[fb_data->upd_buffer_num]; + fb_data->upd_buffer_num++; + if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1) + fb_data->upd_buffer_num = 0; + + ret = epdc_process_update(upd_data_list, fb_data); + if (ret) { + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + /* Pass selected waveform mode back to user */ + upd_data->waveform_mode = upd_desc->upd_data.waveform_mode; + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data->epdc_fb_var.xres, + fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate, + &upd_desc->upd_data.update_region, NULL); + + /* Grab lock for queue manipulation and update submission */ + mutex_lock(&fb_data->queue_mutex); + + /* + * Is the working buffer idle? + * If either the working buffer is busy, or there are no LUTs available, + * then we return and let the ISR handle the update later + */ + if ((fb_data->cur_update != NULL) || !epdc_any_luts_available()) { + /* Add processed Y buffer to update list */ + list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue); + + /* Return and allow the update to be submitted by the ISR. */ + mutex_unlock(&fb_data->queue_mutex); + return 0; + } + + /* LUTs are available, so we get one here */ + ret = epdc_choose_next_lut(fb_data->rev, &upd_data_list->lut_num); + if (ret && fb_data->tce_prevent && (fb_data->rev < 20)) { + dev_dbg(fb_data->dev, "Must wait for LUT15\n"); + /* Add processed Y buffer to update list */ + list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue); + + /* Return and allow the update to be submitted by the ISR. */ + mutex_unlock(&fb_data->queue_mutex); + return 0; + } + + if (!(upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION)) { + + /* Save current update */ + fb_data->cur_update = upd_data_list; + + /* Reset mask for LUTS that have completed during WB processing */ + fb_data->luts_complete_wb = 0; + + /* Associate LUT with update marker */ + list_for_each_entry_safe(next_marker, temp_marker, + &upd_data_list->update_desc->upd_marker_list, upd_list) + next_marker->lut_num = upd_data_list->lut_num; + + /* Mark LUT as containing new update */ + fb_data->lut_update_order[upd_data_list->lut_num] = + upd_desc->update_order; + + epdc_lut_complete_intr(fb_data->rev, upd_data_list->lut_num, + true); + } + + /* Clear status and Enable LUT complete and WB complete IRQs */ + epdc_working_buf_intr(true); + + /* Program EPDC update to process buffer */ + epdc_set_update_addr(upd_data_list->phys_addr + upd_desc->epdc_offs); + epdc_set_update_coord(screen_upd_region->left, screen_upd_region->top); + epdc_set_update_dimensions(screen_upd_region->width, + screen_upd_region->height); + if (fb_data->rev > 20) + epdc_set_update_stride(upd_desc->epdc_stride); + if (upd_desc->upd_data.temp != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, + upd_desc->upd_data.temp); + epdc_set_temp(temp_index); + } else + epdc_set_temp(fb_data->temp_index); + if (fb_data->wv_modes_update && + (upd_desc->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) { + epdc_set_update_waveform(&fb_data->wv_modes); + fb_data->wv_modes_update = false; + } + + epdc_submit_update(upd_data_list->lut_num, + upd_desc->upd_data.waveform_mode, + upd_desc->upd_data.update_mode, + (upd_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION) ? true : false, + false, 0); + + mutex_unlock(&fb_data->queue_mutex); + return 0; +} + +int mxc_epdc_fb_send_update(struct mxcfb_update_data *upd_data, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + if (!fb_data->restrict_width) { + /* No width restriction, send entire update region */ + return mxc_epdc_fb_send_single_update(upd_data, info); + } else { + int ret; + __u32 width, left; + __u32 marker; + __u32 *region_width, *region_left; + u32 max_upd_width = EPDC_V2_MAX_UPDATE_WIDTH; + + /* Further restrict max width due to pxp rotation + * alignment requirement + */ + if (fb_data->epdc_fb_var.rotate != FB_ROTATE_UR) + max_upd_width -= EPDC_V2_ROTATION_ALIGNMENT; + + /* Select split of width or height based on rotation */ + if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) || + (fb_data->epdc_fb_var.rotate == FB_ROTATE_UD)) { + region_width = &upd_data->update_region.width; + region_left = &upd_data->update_region.left; + } else { + region_width = &upd_data->update_region.height; + region_left = &upd_data->update_region.top; + } + + if (*region_width <= max_upd_width) + return mxc_epdc_fb_send_single_update(upd_data, info); + + width = *region_width; + left = *region_left; + marker = upd_data->update_marker; + upd_data->update_marker = 0; + + do { + *region_width = max_upd_width; + *region_left = left; + ret = mxc_epdc_fb_send_single_update(upd_data, info); + if (ret) + return ret; + width -= max_upd_width; + left += max_upd_width; + } while (width > max_upd_width); + + *region_width = width; + *region_left = left; + upd_data->update_marker = marker; + return mxc_epdc_fb_send_single_update(upd_data, info); + } +} +EXPORT_SYMBOL(mxc_epdc_fb_send_update); + +int mxc_epdc_fb_wait_update_complete(struct mxcfb_update_marker_data *marker_data, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + struct update_marker_data *next_marker; + struct update_marker_data *temp; + bool marker_found = false; + int ret = 0; + + /* 0 is an invalid update_marker value */ + if (marker_data->update_marker == 0) + return -EINVAL; + + /* + * Find completion associated with update_marker requested. + * Note: If update completed already, marker will have been + * cleared, it won't be found, and function will just return. + */ + + /* Grab queue lock to protect access to marker list */ + mutex_lock(&fb_data->queue_mutex); + + list_for_each_entry_safe(next_marker, temp, + &fb_data->full_marker_list, full_list) { + if (next_marker->update_marker == marker_data->update_marker) { + dev_dbg(fb_data->dev, "Waiting for marker %d\n", + marker_data->update_marker); + next_marker->waiting = true; + marker_found = true; + break; + } + } + + mutex_unlock(&fb_data->queue_mutex); + + /* + * If marker not found, it has either been signalled already + * or the update request failed. In either case, just return. + */ + if (!marker_found) + return ret; + + ret = wait_for_completion_timeout(&next_marker->update_completion, + msecs_to_jiffies(5000)); + if (!ret) { + dev_err(fb_data->dev, + "Timed out waiting for update completion\n"); + return -ETIMEDOUT; + } + + marker_data->collision_test = next_marker->collision_test; + + /* Free update marker object */ + kfree(next_marker); + + return ret; +} +EXPORT_SYMBOL(mxc_epdc_fb_wait_update_complete); + +int mxc_epdc_fb_set_pwrdown_delay(u32 pwrdown_delay, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + fb_data->pwrdown_delay = pwrdown_delay; + + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_pwrdown_delay); + +int mxc_epdc_get_pwrdown_delay(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + return fb_data->pwrdown_delay; +} +EXPORT_SYMBOL(mxc_epdc_get_pwrdown_delay); + +static int mxc_epdc_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int ret = -EINVAL; + + switch (cmd) { + case MXCFB_SET_WAVEFORM_MODES: + { + struct mxcfb_waveform_modes modes; + if (!copy_from_user(&modes, argp, sizeof(modes))) { + mxc_epdc_fb_set_waveform_modes(&modes, info); + ret = 0; + } + break; + } + case MXCFB_SET_TEMPERATURE: + { + int temperature; + if (!get_user(temperature, (int32_t __user *) arg)) + ret = mxc_epdc_fb_set_temperature(temperature, + info); + break; + } + case MXCFB_SET_AUTO_UPDATE_MODE: + { + u32 auto_mode = 0; + if (!get_user(auto_mode, (__u32 __user *) arg)) + ret = mxc_epdc_fb_set_auto_update(auto_mode, + info); + break; + } + case MXCFB_SET_UPDATE_SCHEME: + { + u32 upd_scheme = 0; + if (!get_user(upd_scheme, (__u32 __user *) arg)) + ret = mxc_epdc_fb_set_upd_scheme(upd_scheme, + info); + break; + } + case MXCFB_SEND_UPDATE: + { + struct mxcfb_update_data upd_data; + + if (mutex_lock_interruptible(&hard_lock) < 0) + return -ERESTARTSYS; + + if (!copy_from_user(&upd_data, argp, + sizeof(upd_data))) { + ret = mxc_epdc_fb_send_update(&upd_data, info); + if (ret == 0 && copy_to_user(argp, &upd_data, + sizeof(upd_data))) + ret = -EFAULT; + } else { + ret = -EFAULT; + } + + mutex_unlock(&hard_lock); + + break; + } + case MXCFB_WAIT_FOR_UPDATE_COMPLETE: + { + struct mxcfb_update_marker_data upd_marker_data; + if (!copy_from_user(&upd_marker_data, argp, + sizeof(upd_marker_data))) { + ret = mxc_epdc_fb_wait_update_complete( + &upd_marker_data, info); + if (copy_to_user(argp, &upd_marker_data, + sizeof(upd_marker_data))) + ret = -EFAULT; + } else { + ret = -EFAULT; + } + + break; + } + + case MXCFB_SET_PWRDOWN_DELAY: + { + int delay = 0; + if (!get_user(delay, (__u32 __user *) arg)) + ret = + mxc_epdc_fb_set_pwrdown_delay(delay, info); + break; + } + + case MXCFB_GET_PWRDOWN_DELAY: + { + int pwrdown_delay = mxc_epdc_get_pwrdown_delay(info); + if (put_user(pwrdown_delay, + (int __user *)argp)) + ret = -EFAULT; + ret = 0; + break; + } + + case MXCFB_GET_WORK_BUFFER: + { + /* copy the epdc working buffer to the user space */ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + flush_cache_all(); + outer_flush_range(fb_data->working_buffer_phys, + fb_data->working_buffer_phys + + fb_data->working_buffer_size); + if (copy_to_user((void __user *)arg, + (const void *) fb_data->working_buffer_virt, + fb_data->working_buffer_size)) + ret = -EFAULT; + else + ret = 0; + flush_cache_all(); + outer_flush_range(fb_data->working_buffer_phys, + fb_data->working_buffer_phys + + fb_data->working_buffer_size); + break; + } + + case MXCFB_DISABLE_EPDC_ACCESS: + { + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + mxc_epdc_fb_flush_updates(fb_data); + /* disable handling any user update request */ + mutex_lock(&hard_lock); + ret = 0; + break; + } + + case MXCFB_ENABLE_EPDC_ACCESS: + { + /* enable user update handling again */ + mutex_unlock(&hard_lock); + ret = 0; + break; + } + + default: + break; + } + return ret; +} + +static void mxc_epdc_fb_update_pages(struct mxc_epdc_fb_data *fb_data, + u16 y1, u16 y2) +{ + struct mxcfb_update_data update; + + /* Do partial screen update, Update full horizontal lines */ + update.update_region.left = 0; + update.update_region.width = fb_data->epdc_fb_var.xres; + update.update_region.top = y1; + update.update_region.height = y2 - y1; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_mode = UPDATE_MODE_FULL; + update.update_marker = 0; + update.temp = TEMP_USE_AMBIENT; + update.flags = 0; + + mxc_epdc_fb_send_update(&update, &fb_data->info); +} + +/* this is called back from the deferred io workqueue */ +static void mxc_epdc_fb_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + struct page *page; + unsigned long beg, end; + int y1, y2, miny, maxy; + + if (fb_data->auto_mode != AUTO_UPDATE_MODE_AUTOMATIC_MODE) + return; + + miny = INT_MAX; + maxy = 0; + list_for_each_entry(page, pagelist, lru) { + beg = page->index << PAGE_SHIFT; + end = beg + PAGE_SIZE - 1; + y1 = beg / info->fix.line_length; + y2 = end / info->fix.line_length; + if (y2 >= fb_data->epdc_fb_var.yres) + y2 = fb_data->epdc_fb_var.yres - 1; + if (miny > y1) + miny = y1; + if (maxy < y2) + maxy = y2; + } + + mxc_epdc_fb_update_pages(fb_data, miny, maxy); +} + +void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data) +{ + int ret; + + if (fb_data->in_init) + return; + + /* Grab queue lock to prevent any new updates from being submitted */ + mutex_lock(&fb_data->queue_mutex); + + /* + * 3 places to check for updates that are active or pending: + * 1) Updates in the pending list + * 2) Update buffers in use (e.g., PxP processing) + * 3) Active updates to panel - We can key off of EPDC + * power state to know if we have active updates. + */ + if (!list_empty(&fb_data->upd_pending_list) || + !is_free_list_full(fb_data) || + (fb_data->updates_active == true)) { + /* Initialize event signalling updates are done */ + init_completion(&fb_data->updates_done); + fb_data->waiting_for_idle = true; + + mutex_unlock(&fb_data->queue_mutex); + /* Wait for any currently active updates to complete */ + ret = wait_for_completion_timeout(&fb_data->updates_done, + msecs_to_jiffies(8000)); + if (!ret) + dev_err(fb_data->dev, + "Flush updates timeout! ret = 0x%x\n", ret); + + mutex_lock(&fb_data->queue_mutex); + fb_data->waiting_for_idle = false; + } + + mutex_unlock(&fb_data->queue_mutex); +} + +static int mxc_epdc_fb_blank(int blank, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + int ret; + + dev_dbg(fb_data->dev, "blank = %d\n", blank); + + if (fb_data->blank == blank) + return 0; + + fb_data->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + mxc_epdc_fb_flush_updates(fb_data); + /* Wait for powerdown */ + mutex_lock(&fb_data->power_mutex); + if ((fb_data->power_state == POWER_STATE_ON) && + (fb_data->pwrdown_delay == FB_POWERDOWN_DISABLE)) { + + /* Powerdown disabled, so we disable EPDC manually */ + int count = 0; + int sleep_ms = 10; + + mutex_unlock(&fb_data->power_mutex); + + /* If any active updates, wait for them to complete */ + while (fb_data->updates_active) { + /* Timeout after 1 sec */ + if ((count * sleep_ms) > 1000) + break; + msleep(sleep_ms); + count++; + } + + fb_data->powering_down = true; + epdc_powerdown(fb_data); + } else if (fb_data->power_state != POWER_STATE_OFF) { + fb_data->wait_for_powerdown = true; + init_completion(&fb_data->powerdown_compl); + mutex_unlock(&fb_data->power_mutex); + ret = wait_for_completion_timeout(&fb_data->powerdown_compl, + msecs_to_jiffies(5000)); + if (!ret) { + dev_err(fb_data->dev, + "No powerdown received!\n"); + return -ETIMEDOUT; + } + } else + mutex_unlock(&fb_data->power_mutex); + break; + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + mxc_epdc_fb_flush_updates(fb_data); + break; + } + return 0; +} + +static int mxc_epdc_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + u_int y_bottom; + + dev_dbg(info->device, "%s: var->yoffset %d, info->var.yoffset %d\n", + __func__, var->yoffset, info->var.yoffset); + /* check if var is valid; also, xpan is not supported */ + if (!var || (var->xoffset != info->var.xoffset) || + (var->yoffset + var->yres > var->yres_virtual)) { + dev_dbg(info->device, "x panning not supported\n"); + return -EINVAL; + } + + if ((fb_data->epdc_fb_var.xoffset == var->xoffset) && + (fb_data->epdc_fb_var.yoffset == var->yoffset)) + return 0; /* No change, do nothing */ + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += var->yres; + + if (y_bottom > info->var.yres_virtual) + return -EINVAL; + + mutex_lock(&fb_data->queue_mutex); + + fb_data->fb_offset = (var->yoffset * var->xres_virtual + var->xoffset) + * (var->bits_per_pixel) / 8; + + fb_data->epdc_fb_var.xoffset = var->xoffset; + fb_data->epdc_fb_var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + + mutex_unlock(&fb_data->queue_mutex); + + return 0; +} + +static struct fb_ops mxc_epdc_fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mxc_epdc_fb_check_var, + .fb_set_par = mxc_epdc_fb_set_par, + .fb_setcmap = mxc_epdc_fb_setcmap, + .fb_setcolreg = mxc_epdc_fb_setcolreg, + .fb_pan_display = mxc_epdc_fb_pan_display, + .fb_ioctl = mxc_epdc_fb_ioctl, + .fb_mmap = mxc_epdc_fb_mmap, + .fb_blank = mxc_epdc_fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static struct fb_deferred_io mxc_epdc_fb_defio = { + .delay = HZ, + .deferred_io = mxc_epdc_fb_deferred_io, +}; + +static void epdc_done_work_func(struct work_struct *work) +{ + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, + epdc_done_work.work); + epdc_powerdown(fb_data); +} + +static bool is_free_list_full(struct mxc_epdc_fb_data *fb_data) +{ + int count = 0; + struct update_data_list *plist; + + /* Count buffers in free buffer list */ + list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) + count++; + + /* Check to see if all buffers are in this list */ + if (count == fb_data->max_num_updates) + return true; + else + return false; +} + +static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id) +{ + struct mxc_epdc_fb_data *fb_data = dev_id; + u32 ints_fired, luts1_ints_fired, luts2_ints_fired; + + /* + * If we just completed one-time panel init, bypass + * queue handling, clear interrupt and return + */ + if (fb_data->in_init) { + if (epdc_is_working_buffer_complete()) { + epdc_working_buf_intr(false); + epdc_clear_working_buf_irq(); + dev_dbg(fb_data->dev, "Cleared WB for init update\n"); + } + + if (epdc_is_lut_complete(fb_data->rev, 0)) { + epdc_lut_complete_intr(fb_data->rev, 0, false); + epdc_clear_lut_complete_irq(fb_data->rev, 0); + fb_data->in_init = false; + dev_dbg(fb_data->dev, "Cleared LUT complete for init update\n"); + } + + return IRQ_HANDLED; + } + + ints_fired = __raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ); + if (fb_data->rev < 20) { + luts1_ints_fired = 0; + luts2_ints_fired = 0; + } else { + luts1_ints_fired = __raw_readl(EPDC_IRQ_MASK1) & __raw_readl(EPDC_IRQ1); + luts2_ints_fired = __raw_readl(EPDC_IRQ_MASK2) & __raw_readl(EPDC_IRQ2); + } + + if (!(ints_fired || luts1_ints_fired || luts2_ints_fired)) + return IRQ_HANDLED; + + if (__raw_readl(EPDC_IRQ) & EPDC_IRQ_TCE_UNDERRUN_IRQ) { + dev_err(fb_data->dev, + "TCE underrun! Will continue to update panel\n"); + /* Clear TCE underrun IRQ */ + __raw_writel(EPDC_IRQ_TCE_UNDERRUN_IRQ, EPDC_IRQ_CLEAR); + } + + /* Check if we are waiting on EOF to sync a new update submission */ + if (epdc_signal_eof()) { + epdc_eof_intr(false); + epdc_clear_eof_irq(); + complete(&fb_data->eof_event); + } + + /* + * Workaround for EPDC v2.0/v2.1 errata: Must read collision status + * before clearing IRQ, or else collision status for bits 16:63 + * will be automatically cleared. So we read it here, and there is + * no conflict with using it in epdc_intr_work_func since the + * working buffer processing flow is strictly sequential (i.e., + * only one WB processing done at a time, so the data grabbed + * here should be up-to-date and accurate when the WB processing + * completes. Also, note that there is no impact to other versions + * of EPDC by reading LUT status here. + */ + if (fb_data->cur_update != NULL) + fb_data->epdc_colliding_luts = epdc_get_colliding_luts(fb_data->rev); + + /* Clear the interrupt mask for any interrupts signalled */ + __raw_writel(ints_fired, EPDC_IRQ_MASK_CLEAR); + __raw_writel(luts1_ints_fired, EPDC_IRQ_MASK1_CLEAR); + __raw_writel(luts2_ints_fired, EPDC_IRQ_MASK2_CLEAR); + + dev_dbg(fb_data->dev, "EPDC interrupts fired = 0x%x, " + "LUTS1 fired = 0x%x, LUTS2 fired = 0x%x\n", + ints_fired, luts1_ints_fired, luts2_ints_fired); + + queue_work(fb_data->epdc_intr_workqueue, + &fb_data->epdc_intr_work); + + return IRQ_HANDLED; +} + +static void epdc_intr_work_func(struct work_struct *work) +{ + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, epdc_intr_work); + struct update_data_list *collision_update; + struct mxcfb_rect *next_upd_region; + struct update_marker_data *next_marker; + struct update_marker_data *temp; + int temp_index; + u64 temp_mask; + u32 lut; + bool ignore_collision = false; + int i; + bool wb_lut_done = false; + bool free_update = true; + int next_lut, epdc_next_lut_15; + u32 epdc_luts_active, epdc_wb_busy, epdc_luts_avail, epdc_lut_cancelled; + u32 epdc_collision; + u64 epdc_irq_stat; + bool epdc_waiting_on_wb; + u32 coll_coord, coll_size; + struct mxcfb_rect coll_region; + + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + /* Capture EPDC status one time to limit exposure to race conditions */ + epdc_luts_active = epdc_any_luts_active(fb_data->rev); + epdc_wb_busy = epdc_is_working_buffer_busy(); + epdc_lut_cancelled = epdc_is_lut_cancelled(); + epdc_luts_avail = epdc_any_luts_available(); + epdc_collision = epdc_is_collision(); + if (fb_data->rev < 20) + epdc_irq_stat = __raw_readl(EPDC_IRQ); + else + epdc_irq_stat = (u64)__raw_readl(EPDC_IRQ1) | + ((u64)__raw_readl(EPDC_IRQ2) << 32); + epdc_waiting_on_wb = (fb_data->cur_update != NULL) ? true : false; + + /* Free any LUTs that have completed */ + for (i = 0; i < fb_data->num_luts; i++) { + if ((epdc_irq_stat & (1ULL << i)) == 0) + continue; + + dev_dbg(fb_data->dev, "LUT %d completed\n", i); + + /* Disable IRQ for completed LUT */ + epdc_lut_complete_intr(fb_data->rev, i, false); + + /* + * Go through all updates in the collision list and + * unmask any updates that were colliding with + * the completed LUT. + */ + list_for_each_entry(collision_update, + &fb_data->upd_buf_collision_list, list) { + collision_update->collision_mask = + collision_update->collision_mask & ~(1 << i); + } + + epdc_clear_lut_complete_irq(fb_data->rev, i); + + fb_data->luts_complete_wb |= 1ULL << i; + + fb_data->lut_update_order[i] = 0; + + /* Signal completion if submit workqueue needs a LUT */ + if (fb_data->waiting_for_lut) { + complete(&fb_data->update_res_free); + fb_data->waiting_for_lut = false; + } + + /* Signal completion if LUT15 free and is needed */ + if (fb_data->waiting_for_lut15 && (i == 15)) { + complete(&fb_data->lut15_free); + fb_data->waiting_for_lut15 = false; + } + + /* Detect race condition where WB and its LUT complete + (i.e. full update completes) in one swoop */ + if (epdc_waiting_on_wb && + (i == fb_data->cur_update->lut_num)) + wb_lut_done = true; + + /* Signal completion if anyone waiting on this LUT */ + if (!wb_lut_done) + list_for_each_entry_safe(next_marker, temp, + &fb_data->full_marker_list, + full_list) { + if (next_marker->lut_num != i) + continue; + + /* Found marker to signal - remove from list */ + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, "Signaling marker %d\n", + next_marker->update_marker); + if (next_marker->waiting) + complete(&next_marker->update_completion); + else + kfree(next_marker); + } + } + + /* Check to see if all updates have completed */ + if (list_empty(&fb_data->upd_pending_list) && + is_free_list_full(fb_data) && + !epdc_waiting_on_wb && + !epdc_luts_active) { + + fb_data->updates_active = false; + + if (fb_data->pwrdown_delay != FB_POWERDOWN_DISABLE) { + /* + * Set variable to prevent overlapping + * enable/disable requests + */ + fb_data->powering_down = true; + + /* Schedule task to disable EPDC HW until next update */ + schedule_delayed_work(&fb_data->epdc_done_work, + msecs_to_jiffies(fb_data->pwrdown_delay)); + + /* Reset counter to reduce chance of overflow */ + fb_data->order_cnt = 0; + } + + if (fb_data->waiting_for_idle) + complete(&fb_data->updates_done); + } + + /* Is Working Buffer busy? */ + if (epdc_wb_busy) { + /* Can't submit another update until WB is done */ + mutex_unlock(&fb_data->queue_mutex); + return; + } + + /* + * Were we waiting on working buffer? + * If so, update queues and check for collisions + */ + if (epdc_waiting_on_wb) { + dev_dbg(fb_data->dev, "\nWorking buffer completed\n"); + + /* Signal completion if submit workqueue was waiting on WB */ + if (fb_data->waiting_for_wb) { + complete(&fb_data->update_res_free); + fb_data->waiting_for_wb = false; + } + + if (fb_data->cur_update->update_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION) { + /* This was a dry run to test for collision */ + + /* Signal marker */ + list_for_each_entry_safe(next_marker, temp, + &fb_data->full_marker_list, + full_list) { + if (next_marker->lut_num != DRY_RUN_NO_LUT) + continue; + + if (epdc_collision) + next_marker->collision_test = true; + else + next_marker->collision_test = false; + + dev_dbg(fb_data->dev, + "In IRQ, collision_test = %d\n", + next_marker->collision_test); + + /* Found marker to signal - remove from list */ + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, "Signaling marker " + "for dry-run - %d\n", + next_marker->update_marker); + complete(&next_marker->update_completion); + } + } else if (epdc_lut_cancelled && !epdc_collision) { + /* + * Note: The update may be cancelled (void) if all + * pixels collided. In that case we handle it as a + * collision, not a cancel. + */ + + /* Clear LUT status (might be set if no AUTOWV used) */ + + /* + * Disable and clear IRQ for the LUT used. + * Even though LUT is cancelled in HW, the LUT + * complete bit may be set if AUTOWV not used. + */ + epdc_lut_complete_intr(fb_data->rev, + fb_data->cur_update->lut_num, false); + epdc_clear_lut_complete_irq(fb_data->rev, + fb_data->cur_update->lut_num); + + fb_data->lut_update_order[fb_data->cur_update->lut_num] = 0; + + /* Signal completion if submit workqueue needs a LUT */ + if (fb_data->waiting_for_lut) { + complete(&fb_data->update_res_free); + fb_data->waiting_for_lut = false; + } + + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, + upd_list) { + + /* Del from per-update & full list */ + list_del_init(&next_marker->upd_list); + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, + "Signaling marker (cancelled) %d\n", + next_marker->update_marker); + if (next_marker->waiting) + complete(&next_marker->update_completion); + else + kfree(next_marker); + } + } else if (epdc_collision) { + /* Real update (no dry-run), collision occurred */ + + /* Check list of colliding LUTs, and add to our collision mask */ + fb_data->cur_update->collision_mask = + fb_data->epdc_colliding_luts; + + /* Clear collisions that completed since WB began */ + fb_data->cur_update->collision_mask &= + ~fb_data->luts_complete_wb; + + dev_dbg(fb_data->dev, "Collision mask = 0x%llx\n", + fb_data->epdc_colliding_luts); + + /* For EPDC 2.0 and later, minimum collision bounds + are provided by HW. Recompute new bounds here. */ + if ((fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) + && (fb_data->rev >= 20)) { + u32 xres, yres, rotate; + struct mxcfb_rect *cur_upd_rect = + &fb_data->cur_update->update_desc->upd_data.update_region; + + /* Get collision region coords from EPDC */ + coll_coord = __raw_readl(EPDC_UPD_COL_CORD); + coll_size = __raw_readl(EPDC_UPD_COL_SIZE); + coll_region.left = + (coll_coord & EPDC_UPD_COL_CORD_XCORD_MASK) + >> EPDC_UPD_COL_CORD_XCORD_OFFSET; + coll_region.top = + (coll_coord & EPDC_UPD_COL_CORD_YCORD_MASK) + >> EPDC_UPD_COL_CORD_YCORD_OFFSET; + coll_region.width = + (coll_size & EPDC_UPD_COL_SIZE_WIDTH_MASK) + >> EPDC_UPD_COL_SIZE_WIDTH_OFFSET; + coll_region.height = + (coll_size & EPDC_UPD_COL_SIZE_HEIGHT_MASK) + >> EPDC_UPD_COL_SIZE_HEIGHT_OFFSET; + dev_dbg(fb_data->dev, "Coll region: l = %d, " + "t = %d, w = %d, h = %d\n", + coll_region.left, coll_region.top, + coll_region.width, coll_region.height); + + /* Convert coords back to orig orientation */ + switch (fb_data->epdc_fb_var.rotate) { + case FB_ROTATE_CW: + xres = fb_data->epdc_fb_var.yres; + yres = fb_data->epdc_fb_var.xres; + rotate = FB_ROTATE_CCW; + break; + case FB_ROTATE_UD: + xres = fb_data->epdc_fb_var.xres; + yres = fb_data->epdc_fb_var.yres; + rotate = FB_ROTATE_UD; + break; + case FB_ROTATE_CCW: + xres = fb_data->epdc_fb_var.yres; + yres = fb_data->epdc_fb_var.xres; + rotate = FB_ROTATE_CW; + break; + default: + xres = fb_data->epdc_fb_var.xres; + yres = fb_data->epdc_fb_var.yres; + rotate = FB_ROTATE_UR; + break; + } + adjust_coordinates(xres, yres, rotate, + &coll_region, cur_upd_rect); + + dev_dbg(fb_data->dev, "Adj coll region: l = %d, " + "t = %d, w = %d, h = %d\n", + cur_upd_rect->left, cur_upd_rect->top, + cur_upd_rect->width, + cur_upd_rect->height); + } + + /* + * If we collide with newer updates, then + * we don't need to re-submit the update. The + * idea is that the newer updates should take + * precedence anyways, so we don't want to + * overwrite them. + */ + for (temp_mask = fb_data->cur_update->collision_mask, lut = 0; + temp_mask != 0; + lut++, temp_mask = temp_mask >> 1) { + if (!(temp_mask & 0x1)) + continue; + + if (fb_data->lut_update_order[lut] >= + fb_data->cur_update->update_desc->update_order) { + dev_dbg(fb_data->dev, + "Ignoring collision with" + "newer update.\n"); + ignore_collision = true; + break; + } + } + + if (!ignore_collision) { + free_update = false; + /* + * If update has markers, clear the LUTs to + * avoid signalling that they have completed. + */ + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, + upd_list) + next_marker->lut_num = INVALID_LUT; + + /* Move to collision list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_collision_list); + } + } + + /* Do we need to free the current update descriptor? */ + if (free_update) { + /* Handle condition where WB & LUT are both complete */ + if (wb_lut_done) + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, + upd_list) { + + /* Del from per-update & full list */ + list_del_init(&next_marker->upd_list); + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, + "Signaling marker (wb) %d\n", + next_marker->update_marker); + if (next_marker->waiting) + complete(&next_marker->update_completion); + else + kfree(next_marker); + } + + /* Free marker list and update descriptor */ + kfree(fb_data->cur_update->update_desc); + + /* Add to free buffer list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_free_list); + + /* Check to see if all updates have completed */ + if (list_empty(&fb_data->upd_pending_list) && + is_free_list_full(fb_data) && + !epdc_luts_active) { + + fb_data->updates_active = false; + + if (fb_data->pwrdown_delay != + FB_POWERDOWN_DISABLE) { + /* + * Set variable to prevent overlapping + * enable/disable requests + */ + fb_data->powering_down = true; + + /* Schedule EPDC disable */ + schedule_delayed_work(&fb_data->epdc_done_work, + msecs_to_jiffies(fb_data->pwrdown_delay)); + + /* Reset counter to reduce chance of overflow */ + fb_data->order_cnt = 0; + } + + if (fb_data->waiting_for_idle) + complete(&fb_data->updates_done); + } + } + + /* Clear current update */ + fb_data->cur_update = NULL; + + /* Clear IRQ for working buffer */ + epdc_working_buf_intr(false); + epdc_clear_working_buf_irq(); + } + + if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { + /* Queued update scheme processing */ + + /* Schedule task to submit collision and pending update */ + if (!fb_data->powering_down) + queue_work(fb_data->epdc_submit_workqueue, + &fb_data->epdc_submit_work); + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + + return; + } + + /* Snapshot update scheme processing */ + + /* Check to see if any LUTs are free */ + if (!epdc_luts_avail) { + dev_dbg(fb_data->dev, "No luts available.\n"); + mutex_unlock(&fb_data->queue_mutex); + return; + } + + epdc_next_lut_15 = epdc_choose_next_lut(fb_data->rev, &next_lut); + /* Check to see if there is a valid LUT to use */ + if (epdc_next_lut_15 && fb_data->tce_prevent && (fb_data->rev < 20)) { + dev_dbg(fb_data->dev, "Must wait for LUT15\n"); + mutex_unlock(&fb_data->queue_mutex); + return; + } + + /* + * Are any of our collision updates able to go now? + * Go through all updates in the collision list and check to see + * if the collision mask has been fully cleared + */ + list_for_each_entry(collision_update, + &fb_data->upd_buf_collision_list, list) { + + if (collision_update->collision_mask != 0) + continue; + + dev_dbg(fb_data->dev, "A collision update is ready to go!\n"); + /* + * We have a collision cleared, so select it + * and we will retry the update + */ + fb_data->cur_update = collision_update; + list_del_init(&fb_data->cur_update->list); + break; + } + + /* + * If we didn't find a collision update ready to go, + * we try to grab one from the update queue + */ + if (fb_data->cur_update == NULL) { + /* Is update list empty? */ + if (list_empty(&fb_data->upd_buf_queue)) { + dev_dbg(fb_data->dev, "No pending updates.\n"); + + /* No updates pending, so we are done */ + mutex_unlock(&fb_data->queue_mutex); + return; + } else { + dev_dbg(fb_data->dev, "Found a pending update!\n"); + + /* Process next item in update list */ + fb_data->cur_update = + list_entry(fb_data->upd_buf_queue.next, + struct update_data_list, list); + list_del_init(&fb_data->cur_update->list); + } + } + + /* Use LUT selected above */ + fb_data->cur_update->lut_num = next_lut; + + /* Associate LUT with update markers */ + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, upd_list) + next_marker->lut_num = fb_data->cur_update->lut_num; + + /* Mark LUT as containing new update */ + fb_data->lut_update_order[fb_data->cur_update->lut_num] = + fb_data->cur_update->update_desc->update_order; + + /* Enable Collision and WB complete IRQs */ + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->rev, fb_data->cur_update->lut_num, true); + + /* Program EPDC update to process buffer */ + next_upd_region = + &fb_data->cur_update->update_desc->upd_data.update_region; + if (fb_data->cur_update->update_desc->upd_data.temp + != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, + fb_data->cur_update->update_desc->upd_data.temp); + epdc_set_temp(temp_index); + } else + epdc_set_temp(fb_data->temp_index); + epdc_set_update_addr(fb_data->cur_update->phys_addr + + fb_data->cur_update->update_desc->epdc_offs); + epdc_set_update_coord(next_upd_region->left, next_upd_region->top); + epdc_set_update_dimensions(next_upd_region->width, + next_upd_region->height); + if (fb_data->rev > 20) + epdc_set_update_stride(fb_data->cur_update->update_desc->epdc_stride); + if (fb_data->wv_modes_update && + (fb_data->cur_update->update_desc->upd_data.waveform_mode + == WAVEFORM_MODE_AUTO)) { + epdc_set_update_waveform(&fb_data->wv_modes); + fb_data->wv_modes_update = false; + } + + epdc_submit_update(fb_data->cur_update->lut_num, + fb_data->cur_update->update_desc->upd_data.waveform_mode, + fb_data->cur_update->update_desc->upd_data.update_mode, + false, false, 0); + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + + return; +} + +static void draw_mode0(struct mxc_epdc_fb_data *fb_data) +{ + u32 *upd_buf_ptr; + int i; + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 xres, yres; + + upd_buf_ptr = (u32 *)fb_data->info.screen_base; + + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->rev, 0, true); + + /* Use unrotated (native) width/height */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres = screeninfo->yres; + yres = screeninfo->xres; + } else { + xres = screeninfo->xres; + yres = screeninfo->yres; + } + + /* Program EPDC update to process buffer */ + epdc_set_update_addr(fb_data->phys_start); + epdc_set_update_coord(0, 0); + epdc_set_update_dimensions(xres, yres); + if (fb_data->rev > 20) + epdc_set_update_stride(0); + epdc_submit_update(0, fb_data->wv_modes.mode_init, UPDATE_MODE_FULL, + false, true, 0xFF); + + dev_dbg(fb_data->dev, "Mode0 update - Waiting for LUT to complete...\n"); + + /* Will timeout after ~4-5 seconds */ + + for (i = 0; i < 40; i++) { + if (!epdc_is_lut_active(0)) { + dev_dbg(fb_data->dev, "Mode0 init complete\n"); + return; + } + msleep(100); + } + + dev_err(fb_data->dev, "Mode0 init failed!\n"); + + return; +} + + +static void mxc_epdc_fb_fw_handler(const struct firmware *fw, + void *context) +{ + struct mxc_epdc_fb_data *fb_data = context; + int ret; + struct mxcfb_waveform_data_file *wv_file; + int wv_data_offs; + int i; + struct mxcfb_update_data update; + struct mxcfb_update_marker_data upd_marker_data; + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 xres, yres; + struct clk *epdc_parent; + unsigned long rounded_parent_rate, epdc_pix_rate, + rounded_pix_clk, target_pix_clk; + + if (fw == NULL) { + /* If default FW file load failed, we give up */ + if (fb_data->fw_default_load) + return; + + /* Try to load default waveform */ + dev_dbg(fb_data->dev, + "Can't find firmware. Trying fallback fw\n"); + fb_data->fw_default_load = true; + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + "imx/epdc/epdc.fw", fb_data->dev, GFP_KERNEL, fb_data, + mxc_epdc_fb_fw_handler); + if (ret) + dev_err(fb_data->dev, + "Failed request_firmware_nowait err %d\n", ret); + + return; + } + + wv_file = (struct mxcfb_waveform_data_file *)fw->data; + + /* Get size and allocate temperature range table */ + fb_data->trt_entries = wv_file->wdh.trc + 1; + fb_data->temp_range_bounds = kzalloc(fb_data->trt_entries, GFP_KERNEL); + + for (i = 0; i < fb_data->trt_entries; i++) + dev_dbg(fb_data->dev, "trt entry #%d = 0x%x\n", i, *((u8 *)&wv_file->data + i)); + + /* Copy TRT data */ + memcpy(fb_data->temp_range_bounds, &wv_file->data, fb_data->trt_entries); + + /* Set default temperature index using TRT and room temp */ + fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, DEFAULT_TEMP); + + /* Get offset and size for waveform data */ + wv_data_offs = sizeof(wv_file->wdh) + fb_data->trt_entries + 1; + fb_data->waveform_buffer_size = fw->size - wv_data_offs; + + /* Allocate memory for waveform data */ + fb_data->waveform_buffer_virt = dma_alloc_coherent(fb_data->dev, + fb_data->waveform_buffer_size, + &fb_data->waveform_buffer_phys, + GFP_DMA | GFP_KERNEL); + if (fb_data->waveform_buffer_virt == NULL) { + dev_err(fb_data->dev, "Can't allocate mem for waveform!\n"); + return; + } + + memcpy(fb_data->waveform_buffer_virt, (u8 *)(fw->data) + wv_data_offs, + fb_data->waveform_buffer_size); + + release_firmware(fw); + + /* Enable clocks to access EPDC regs */ + clk_prepare_enable(fb_data->epdc_clk_axi); + + target_pix_clk = fb_data->cur_mode->vmode->pixclock; + /* Enable pix clk for EPDC */ + rounded_pix_clk = clk_round_rate(fb_data->epdc_clk_pix, target_pix_clk); + + if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) || + (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) { + /* Can't get close enough without changing parent clk */ + epdc_parent = clk_get_parent(fb_data->epdc_clk_pix); + rounded_parent_rate = clk_round_rate(epdc_parent, target_pix_clk); + + epdc_pix_rate = target_pix_clk; + while (epdc_pix_rate < rounded_parent_rate) + epdc_pix_rate *= 2; + clk_set_rate(epdc_parent, epdc_pix_rate); + + rounded_pix_clk = clk_round_rate(fb_data->epdc_clk_pix, target_pix_clk); + if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) || + (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) + /* Still can't get a good clock, provide warning */ + dev_err(fb_data->dev, "Unable to get an accurate EPDC pix clk" + "desired = %lu, actual = %lu\n", target_pix_clk, + rounded_pix_clk); + } + + clk_set_rate(fb_data->epdc_clk_pix, rounded_pix_clk); + clk_prepare_enable(fb_data->epdc_clk_pix); + + epdc_init_sequence(fb_data); + + /* Disable clocks */ + clk_disable_unprepare(fb_data->epdc_clk_axi); + clk_disable_unprepare(fb_data->epdc_clk_pix); + + fb_data->hw_ready = true; + fb_data->hw_initializing = false; + + /* Use unrotated (native) width/height */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres = screeninfo->yres; + yres = screeninfo->xres; + } else { + xres = screeninfo->xres; + yres = screeninfo->yres; + } + + update.update_region.left = 0; + update.update_region.width = xres; + update.update_region.top = 0; + update.update_region.height = yres; + update.update_mode = UPDATE_MODE_FULL; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_marker = INIT_UPDATE_MARKER; + update.temp = TEMP_USE_AMBIENT; + update.flags = 0; + + upd_marker_data.update_marker = update.update_marker; + + mxc_epdc_fb_send_update(&update, &fb_data->info); + + /* Block on initial update */ + ret = mxc_epdc_fb_wait_update_complete(&upd_marker_data, + &fb_data->info); + if (ret < 0) + dev_err(fb_data->dev, + "Wait for initial update complete failed." + " Error = 0x%x", ret); +} + +static int mxc_epdc_fb_init_hw(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + int ret; + + /* + * Create fw search string based on ID string in selected videomode. + * Format is "imx/epdc_[panel string].fw" + */ + if (fb_data->cur_mode) { + strcpy(fb_data->fw_str, "imx/epdc/epdc_"); + strcat(fb_data->fw_str, fb_data->cur_mode->vmode->name); + strcat(fb_data->fw_str, ".fw"); + } + + fb_data->fw_default_load = false; + + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + fb_data->fw_str, fb_data->dev, GFP_KERNEL, + fb_data, mxc_epdc_fb_fw_handler); + if (ret) + dev_dbg(fb_data->dev, + "Failed request_firmware_nowait err %d\n", ret); + + return ret; +} + +static ssize_t store_update(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxcfb_update_data update; + struct fb_info *info = dev_get_drvdata(device); + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + + if (strncmp(buf, "direct", 6) == 0) + update.waveform_mode = fb_data->wv_modes.mode_du; + else if (strncmp(buf, "gc16", 4) == 0) + update.waveform_mode = fb_data->wv_modes.mode_gc16; + else if (strncmp(buf, "gc4", 3) == 0) + update.waveform_mode = fb_data->wv_modes.mode_gc4; + + /* Now, request full screen update */ + update.update_region.left = 0; + update.update_region.width = fb_data->epdc_fb_var.xres; + update.update_region.top = 0; + update.update_region.height = fb_data->epdc_fb_var.yres; + update.update_mode = UPDATE_MODE_FULL; + update.temp = TEMP_USE_AMBIENT; + update.update_marker = 0; + update.flags = 0; + + mxc_epdc_fb_send_update(&update, info); + + return count; +} + +static struct device_attribute fb_attrs[] = { + __ATTR(update, S_IRUGO|S_IWUSR, NULL, store_update), +}; + +static const struct of_device_id imx_epdc_dt_ids[] = { + { .compatible = "fsl,imx6dl-epdc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_epdc_dt_ids); + +int mxc_epdc_fb_probe(struct platform_device *pdev) +{ + int ret = 0; + struct pinctrl *pinctrl; + struct mxc_epdc_fb_data *fb_data; + struct resource *res; + struct fb_info *info; + char *options, *opt; + char *panel_str = NULL; + char name[] = "mxcepdcfb"; + struct fb_videomode *vmode; + int xres_virt, yres_virt, buf_size; + int xres_virt_rot, yres_virt_rot, pix_size_rot; + struct fb_var_screeninfo *var_info; + struct fb_fix_screeninfo *fix_info; + struct pxp_config_data *pxp_conf; + struct pxp_proc_data *proc_data; + struct scatterlist *sg; + struct update_data_list *upd_list; + struct update_data_list *plist, *temp_list; + int i; + unsigned long x_mem_size = 0; + u32 val; + int irq; + + fb_data = (struct mxc_epdc_fb_data *)framebuffer_alloc( + sizeof(struct mxc_epdc_fb_data), &pdev->dev); + if (fb_data == NULL) { + ret = -ENOMEM; + goto out; + } + + /* Get platform data and check validity */ + fb_data->pdata = &epdc_data; + if ((fb_data->pdata == NULL) || (fb_data->pdata->num_modes < 1) + || (fb_data->pdata->epdc_mode == NULL) + || (fb_data->pdata->epdc_mode->vmode == NULL)) { + ret = -EINVAL; + goto out_fbdata; + } + + if (fb_get_options(name, &options)) { + ret = -ENODEV; + goto out_fbdata; + } + + fb_data->tce_prevent = 0; + + if (options) + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "bpp=", 4)) + fb_data->default_bpp = + simple_strtoul(opt + 4, NULL, 0); + else if (!strncmp(opt, "x_mem=", 6)) + x_mem_size = memparse(opt + 6, NULL); + else if (!strncmp(opt, "tce_prevent", 11)) + fb_data->tce_prevent = 1; + else + panel_str = opt; + } + + fb_data->dev = &pdev->dev; + + if (!fb_data->default_bpp) + fb_data->default_bpp = 16; + + /* Set default (first defined mode) before searching for a match */ + fb_data->cur_mode = &fb_data->pdata->epdc_mode[0]; + + if (panel_str) + for (i = 0; i < fb_data->pdata->num_modes; i++) + if (!strcmp(fb_data->pdata->epdc_mode[i].vmode->name, + panel_str)) { + fb_data->cur_mode = + &fb_data->pdata->epdc_mode[i]; + break; + } + + vmode = fb_data->cur_mode->vmode; + + platform_set_drvdata(pdev, fb_data); + info = &fb_data->info; + + /* Allocate color map for the FB */ + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) + goto out_fbdata; + + dev_dbg(&pdev->dev, "resolution %dx%d, bpp %d\n", + vmode->xres, vmode->yres, fb_data->default_bpp); + + /* + * GPU alignment restrictions dictate framebuffer parameters: + * - 32-byte alignment for buffer width + * - 128-byte alignment for buffer height + * => 4K buffer alignment for buffer start + */ + xres_virt = ALIGN(vmode->xres, 32); + yres_virt = ALIGN(vmode->yres, 128); + fb_data->max_pix_size = PAGE_ALIGN(xres_virt * yres_virt); + + /* + * Have to check to see if aligned buffer size when rotated + * is bigger than when not rotated, and use the max + */ + xres_virt_rot = ALIGN(vmode->yres, 32); + yres_virt_rot = ALIGN(vmode->xres, 128); + pix_size_rot = PAGE_ALIGN(xres_virt_rot * yres_virt_rot); + fb_data->max_pix_size = (fb_data->max_pix_size > pix_size_rot) ? + fb_data->max_pix_size : pix_size_rot; + + buf_size = fb_data->max_pix_size * fb_data->default_bpp/8; + + /* Compute the number of screens needed based on X memory requested */ + if (x_mem_size > 0) { + fb_data->num_screens = DIV_ROUND_UP(x_mem_size, buf_size); + if (fb_data->num_screens < NUM_SCREENS_MIN) + fb_data->num_screens = NUM_SCREENS_MIN; + else if (buf_size * fb_data->num_screens > SZ_16M) + fb_data->num_screens = SZ_16M / buf_size; + } else + fb_data->num_screens = NUM_SCREENS_MIN; + + fb_data->map_size = buf_size * fb_data->num_screens; + dev_dbg(&pdev->dev, "memory to allocate: %d\n", fb_data->map_size); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENODEV; + goto out_cmap; + } + + epdc_base = devm_ioremap_resource(&pdev->dev, res); + if (epdc_base == NULL) { + ret = -ENOMEM; + goto out_cmap; + } + + /* Allocate FB memory */ + info->screen_base = dma_alloc_wc(&pdev->dev, + fb_data->map_size, + &fb_data->phys_start, + GFP_DMA | GFP_KERNEL); + + if (info->screen_base == NULL) { + ret = -ENOMEM; + goto out_cmap; + } + dev_dbg(&pdev->dev, "allocated at %p:0x%x\n", info->screen_base, + fb_data->phys_start); + + var_info = &info->var; + var_info->activate = FB_ACTIVATE_TEST; + var_info->bits_per_pixel = fb_data->default_bpp; + var_info->xres = vmode->xres; + var_info->yres = vmode->yres; + var_info->xres_virtual = xres_virt; + /* Additional screens allow for panning and buffer flipping */ + var_info->yres_virtual = yres_virt * fb_data->num_screens; + + var_info->pixclock = vmode->pixclock; + var_info->left_margin = vmode->left_margin; + var_info->right_margin = vmode->right_margin; + var_info->upper_margin = vmode->upper_margin; + var_info->lower_margin = vmode->lower_margin; + var_info->hsync_len = vmode->hsync_len; + var_info->vsync_len = vmode->vsync_len; + var_info->vmode = FB_VMODE_NONINTERLACED; + + switch (fb_data->default_bpp) { + case 32: + case 24: + var_info->red.offset = 16; + var_info->red.length = 8; + var_info->green.offset = 8; + var_info->green.length = 8; + var_info->blue.offset = 0; + var_info->blue.length = 8; + break; + + case 16: + var_info->red.offset = 11; + var_info->red.length = 5; + var_info->green.offset = 5; + var_info->green.length = 6; + var_info->blue.offset = 0; + var_info->blue.length = 5; + break; + + case 8: + /* + * For 8-bit grayscale, R, G, and B offset are equal. + * + */ + var_info->grayscale = GRAYSCALE_8BIT; + + var_info->red.length = 8; + var_info->red.offset = 0; + var_info->red.msb_right = 0; + var_info->green.length = 8; + var_info->green.offset = 0; + var_info->green.msb_right = 0; + var_info->blue.length = 8; + var_info->blue.offset = 0; + var_info->blue.msb_right = 0; + break; + + default: + dev_err(&pdev->dev, "unsupported bitwidth %d\n", + fb_data->default_bpp); + ret = -EINVAL; + goto out_dma_fb; + } + + fix_info = &info->fix; + + strcpy(fix_info->id, "mxc_epdc_fb"); + fix_info->type = FB_TYPE_PACKED_PIXELS; + fix_info->visual = FB_VISUAL_TRUECOLOR; + fix_info->xpanstep = 0; + fix_info->ypanstep = 0; + fix_info->ywrapstep = 0; + fix_info->accel = FB_ACCEL_NONE; + fix_info->smem_start = fb_data->phys_start; + fix_info->smem_len = fb_data->map_size; + fix_info->ypanstep = 0; + + fb_data->native_width = vmode->xres; + fb_data->native_height = vmode->yres; + + info->fbops = &mxc_epdc_fb_ops; + info->var.activate = FB_ACTIVATE_NOW; + info->pseudo_palette = fb_data->pseudo_palette; + info->screen_size = info->fix.smem_len; + info->flags = FBINFO_FLAG_DEFAULT; + + mxc_epdc_fb_set_fix(info); + + fb_data->auto_mode = AUTO_UPDATE_MODE_REGION_MODE; + fb_data->upd_scheme = UPDATE_SCHEME_QUEUE_AND_MERGE; + + /* Initialize our internal copy of the screeninfo */ + fb_data->epdc_fb_var = *var_info; + fb_data->fb_offset = 0; + fb_data->eof_sync_period = 0; + + fb_data->epdc_clk_axi = clk_get(fb_data->dev, "epdc_axi"); + if (IS_ERR(fb_data->epdc_clk_axi)) { + dev_err(&pdev->dev, "Unable to get EPDC AXI clk." + "err = %d\n", (int)fb_data->epdc_clk_axi); + ret = -ENODEV; + goto out_dma_fb; + } + fb_data->epdc_clk_pix = clk_get(fb_data->dev, "epdc_pix"); + if (IS_ERR(fb_data->epdc_clk_pix)) { + dev_err(&pdev->dev, "Unable to get EPDC pix clk." + "err = %d\n", (int)fb_data->epdc_clk_pix); + ret = -ENODEV; + goto out_dma_fb; + } + + clk_prepare_enable(fb_data->epdc_clk_axi); + val = __raw_readl(EPDC_VERSION); + clk_disable_unprepare(fb_data->epdc_clk_axi); + fb_data->rev = ((val & EPDC_VERSION_MAJOR_MASK) >> + EPDC_VERSION_MAJOR_OFFSET) * 10 + + ((val & EPDC_VERSION_MINOR_MASK) >> + EPDC_VERSION_MINOR_OFFSET); + dev_dbg(&pdev->dev, "EPDC version = %d\n", fb_data->rev); + + if (fb_data->rev < 20) { + fb_data->num_luts = EPDC_V1_NUM_LUTS; + fb_data->max_num_updates = EPDC_V1_MAX_NUM_UPDATES; + } else { + fb_data->num_luts = EPDC_V2_NUM_LUTS; + fb_data->max_num_updates = EPDC_V2_MAX_NUM_UPDATES; + if (vmode->xres > EPDC_V2_MAX_UPDATE_WIDTH) + fb_data->restrict_width = true; + } + fb_data->max_num_buffers = EPDC_MAX_NUM_BUFFERS; + + /* + * Initialize lists for pending updates, + * active update requests, update collisions, + * and freely available updates. + */ + INIT_LIST_HEAD(&fb_data->upd_pending_list); + INIT_LIST_HEAD(&fb_data->upd_buf_queue); + INIT_LIST_HEAD(&fb_data->upd_buf_free_list); + INIT_LIST_HEAD(&fb_data->upd_buf_collision_list); + + /* Allocate update buffers and add them to the list */ + for (i = 0; i < fb_data->max_num_updates; i++) { + upd_list = kzalloc(sizeof(*upd_list), GFP_KERNEL); + if (upd_list == NULL) { + ret = -ENOMEM; + goto out_upd_lists; + } + + /* Add newly allocated buffer to free list */ + list_add(&upd_list->list, &fb_data->upd_buf_free_list); + } + + fb_data->virt_addr_updbuf = + kzalloc(sizeof(void *) * fb_data->max_num_buffers, GFP_KERNEL); + fb_data->phys_addr_updbuf = + kzalloc(sizeof(dma_addr_t) * fb_data->max_num_buffers, + GFP_KERNEL); + for (i = 0; i < fb_data->max_num_buffers; i++) { + /* + * Allocate memory for PxP output buffer. + * Each update buffer is 1 byte per pixel, and can + * be as big as the full-screen frame buffer + */ + fb_data->virt_addr_updbuf[i] = + kmalloc(fb_data->max_pix_size, GFP_KERNEL); + fb_data->phys_addr_updbuf[i] = + virt_to_phys(fb_data->virt_addr_updbuf[i]); + if (fb_data->virt_addr_updbuf[i] == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + + dev_dbg(fb_data->info.device, "allocated %d bytes @ 0x%08X\n", + fb_data->max_pix_size, fb_data->phys_addr_updbuf[i]); + } + + /* Counter indicating which update buffer should be used next. */ + fb_data->upd_buffer_num = 0; + + /* + * Allocate memory for PxP SW workaround buffer + * These buffers are used to hold copy of the update region, + * before sending it to PxP for processing. + */ + fb_data->virt_addr_copybuf = + dma_alloc_coherent(fb_data->info.device, fb_data->max_pix_size*2, + &fb_data->phys_addr_copybuf, + GFP_DMA | GFP_KERNEL); + if (fb_data->virt_addr_copybuf == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + + fb_data->working_buffer_size = vmode->yres * vmode->xres * 2; + /* Allocate memory for EPDC working buffer */ + fb_data->working_buffer_virt = + dma_alloc_coherent(&pdev->dev, fb_data->working_buffer_size, + &fb_data->working_buffer_phys, + GFP_DMA | GFP_KERNEL); + if (fb_data->working_buffer_virt == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for working buf!\n"); + ret = -ENOMEM; + goto out_copybuffer; + } + + /* Initialize EPDC pins */ + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) { + dev_err(&pdev->dev, "can't get/select pinctrl\n"); + ret = PTR_ERR(pinctrl); + goto out_copybuffer; + } + + fb_data->in_init = false; + + fb_data->hw_ready = false; + fb_data->hw_initializing = false; + + /* + * Set default waveform mode values. + * Should be overwritten via ioctl. + */ + fb_data->wv_modes.mode_init = 0; + fb_data->wv_modes.mode_du = 1; + fb_data->wv_modes.mode_gc4 = 3; + fb_data->wv_modes.mode_gc8 = 2; + fb_data->wv_modes.mode_gc16 = 2; + fb_data->wv_modes.mode_gc32 = 2; + fb_data->wv_modes_update = true; + + /* Initialize marker list */ + INIT_LIST_HEAD(&fb_data->full_marker_list); + + /* Initialize all LUTs to inactive */ + fb_data->lut_update_order = + kzalloc(fb_data->num_luts * sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < fb_data->num_luts; i++) + fb_data->lut_update_order[i] = 0; + + INIT_DELAYED_WORK(&fb_data->epdc_done_work, epdc_done_work_func); + fb_data->epdc_submit_workqueue = alloc_workqueue("EPDC Submit", + WQ_MEM_RECLAIM | WQ_HIGHPRI | + WQ_CPU_INTENSIVE | WQ_UNBOUND, 1); + INIT_WORK(&fb_data->epdc_submit_work, epdc_submit_work_func); + fb_data->epdc_intr_workqueue = alloc_workqueue("EPDC Interrupt", + WQ_MEM_RECLAIM | WQ_HIGHPRI | + WQ_CPU_INTENSIVE | WQ_UNBOUND, 1); + INIT_WORK(&fb_data->epdc_intr_work, epdc_intr_work_func); + + /* Retrieve EPDC IRQ num */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "cannot get IRQ resource\n"); + ret = -ENODEV; + goto out_dma_work_buf; + } + fb_data->epdc_irq = irq; + + /* Register IRQ handler */ + ret = devm_request_irq(&pdev->dev, fb_data->epdc_irq, + mxc_epdc_irq_handler, 0, "epdc", fb_data); + if (ret) { + dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", + fb_data->epdc_irq, ret); + ret = -ENODEV; + goto out_dma_work_buf; + } + + info->fbdefio = &mxc_epdc_fb_defio; +#ifdef CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE + fb_deferred_io_init(info); +#endif + + /* get pmic regulators */ + fb_data->display_regulator = devm_regulator_get(&pdev->dev, "DISPLAY"); + if (IS_ERR(fb_data->display_regulator)) { + dev_err(&pdev->dev, "Unable to get display PMIC regulator." + "err = 0x%x\n", (int)fb_data->display_regulator); + ret = -ENODEV; + goto out_dma_work_buf; + } + fb_data->vcom_regulator = devm_regulator_get(&pdev->dev, "VCOM"); + if (IS_ERR(fb_data->vcom_regulator)) { + dev_err(&pdev->dev, "Unable to get VCOM regulator." + "err = 0x%x\n", (int)fb_data->vcom_regulator); + ret = -ENODEV; + goto out_dma_work_buf; + } + fb_data->v3p3_regulator = devm_regulator_get(&pdev->dev, "V3P3"); + if (IS_ERR(fb_data->v3p3_regulator)) { + dev_err(&pdev->dev, "Unable to get V3P3 regulator." + "err = 0x%x\n", (int)fb_data->vcom_regulator); + ret = -ENODEV; + goto out_dma_work_buf; + } + + if (device_create_file(info->dev, &fb_attrs[0])) + dev_err(&pdev->dev, "Unable to create file from fb_attrs\n"); + + fb_data->cur_update = NULL; + + mutex_init(&fb_data->queue_mutex); + mutex_init(&fb_data->pxp_mutex); + mutex_init(&fb_data->power_mutex); + + /* + * Fill out PxP config data structure based on FB info and + * processing tasks required + */ + pxp_conf = &fb_data->pxp_conf; + proc_data = &pxp_conf->proc_data; + + /* Initialize non-channel-specific PxP parameters */ + proc_data->drect.left = proc_data->srect.left = 0; + proc_data->drect.top = proc_data->srect.top = 0; + proc_data->drect.width = proc_data->srect.width = fb_data->info.var.xres; + proc_data->drect.height = proc_data->srect.height = fb_data->info.var.yres; + proc_data->scaling = 0; + proc_data->hflip = 0; + proc_data->vflip = 0; + proc_data->rotate = 0; + proc_data->bgcolor = 0; + proc_data->overlay_state = 0; + proc_data->lut_transform = PXP_LUT_NONE; + proc_data->lut_map = NULL; + + /* + * We initially configure PxP for RGB->YUV conversion, + * and only write out Y component of the result. + */ + + /* + * Initialize S0 channel parameters + * Parameters should match FB format/width/height + */ + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + pxp_conf->s0_param.width = fb_data->info.var.xres_virtual; + pxp_conf->s0_param.height = fb_data->info.var.yres; + pxp_conf->s0_param.color_key = -1; + pxp_conf->s0_param.color_key_enable = false; + + /* + * Initialize OL0 channel parameters + * No overlay will be used for PxP operation + */ + pxp_conf->ol_param[0].combine_enable = false; + pxp_conf->ol_param[0].width = 0; + pxp_conf->ol_param[0].height = 0; + pxp_conf->ol_param[0].pixel_fmt = PXP_PIX_FMT_RGB565; + pxp_conf->ol_param[0].color_key_enable = false; + pxp_conf->ol_param[0].color_key = -1; + pxp_conf->ol_param[0].global_alpha_enable = false; + pxp_conf->ol_param[0].global_alpha = 0; + pxp_conf->ol_param[0].local_alpha_enable = false; + + /* + * Initialize Output channel parameters + * Output is Y-only greyscale + * Output width/height will vary based on update region size + */ + pxp_conf->out_param.width = fb_data->info.var.xres; + pxp_conf->out_param.height = fb_data->info.var.yres; + pxp_conf->out_param.stride = pxp_conf->out_param.width; + pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GREY; + + /* Initialize color map for conversion of 8-bit gray pixels */ + fb_data->pxp_conf.proc_data.lut_map = kmalloc(256, GFP_KERNEL); + if (fb_data->pxp_conf.proc_data.lut_map == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for lut map!\n"); + ret = -ENOMEM; + goto out_dma_work_buf; + } + for (i = 0; i < 256; i++) + fb_data->pxp_conf.proc_data.lut_map[i] = i; + + fb_data->pxp_conf.proc_data.lut_map_updated = true; + + /* + * Ensure this is set to NULL here...we will initialize pxp_chan + * later in our thread. + */ + fb_data->pxp_chan = NULL; + + /* Initialize Scatter-gather list containing 2 buffer addresses. */ + sg = fb_data->sg; + sg_init_table(sg, 2); + + /* + * For use in PxP transfers: + * sg[0] holds the FB buffer pointer + * sg[1] holds the Output buffer pointer (configured before TX request) + */ + sg_dma_address(&sg[0]) = info->fix.smem_start; + sg_set_page(&sg[0], virt_to_page(info->screen_base), + info->fix.smem_len, offset_in_page(info->screen_base)); + + fb_data->order_cnt = 0; + fb_data->waiting_for_wb = false; + fb_data->waiting_for_lut = false; + fb_data->waiting_for_lut15 = false; + fb_data->waiting_for_idle = false; + fb_data->blank = FB_BLANK_UNBLANK; + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + fb_data->wait_for_powerdown = false; + fb_data->updates_active = false; + fb_data->pwrdown_delay = 0; + + /* Register FB */ + ret = register_framebuffer(info); + if (ret) { + dev_err(&pdev->dev, + "register_framebuffer failed with error %d\n", ret); + goto out_lutmap; + } + + g_fb_data = fb_data; + + pm_runtime_enable(fb_data->dev); + +#ifdef DEFAULT_PANEL_HW_INIT + ret = mxc_epdc_fb_init_hw((struct fb_info *)fb_data); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize HW!\n"); + } +#endif + + goto out; + +out_lutmap: + kfree(fb_data->pxp_conf.proc_data.lut_map); +out_dma_work_buf: + dma_free_wc(&pdev->dev, + fb_data->working_buffer_size, + fb_data->working_buffer_virt, + fb_data->working_buffer_phys); +out_copybuffer: + dma_free_wc(&pdev->dev, + fb_data->max_pix_size*2, + fb_data->virt_addr_copybuf, + fb_data->phys_addr_copybuf); +out_upd_buffers: + for (i = 0; i < fb_data->max_num_buffers; i++) + if (fb_data->virt_addr_updbuf[i] != NULL) + kfree(fb_data->virt_addr_updbuf[i]); + if (fb_data->virt_addr_updbuf != NULL) + kfree(fb_data->virt_addr_updbuf); + if (fb_data->phys_addr_updbuf != NULL) + kfree(fb_data->phys_addr_updbuf); +out_upd_lists: + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list, + list) { + list_del(&plist->list); + kfree(plist); + } +out_dma_fb: + dma_free_wc(&pdev->dev, fb_data->map_size, info->screen_base, + fb_data->phys_start); + +out_cmap: + fb_dealloc_cmap(&info->cmap); +out_fbdata: + kfree(fb_data); +out: + return ret; +} + +static int mxc_epdc_fb_remove(struct platform_device *pdev) +{ + struct update_data_list *plist, *temp_list; + struct mxc_epdc_fb_data *fb_data = platform_get_drvdata(pdev); + int i; + + mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &fb_data->info); + + flush_workqueue(fb_data->epdc_submit_workqueue); + destroy_workqueue(fb_data->epdc_submit_workqueue); + + unregister_framebuffer(&fb_data->info); + + for (i = 0; i < fb_data->max_num_buffers; i++) + if (fb_data->virt_addr_updbuf[i] != NULL) + kfree(fb_data->virt_addr_updbuf[i]); + if (fb_data->virt_addr_updbuf != NULL) + kfree(fb_data->virt_addr_updbuf); + if (fb_data->phys_addr_updbuf != NULL) + kfree(fb_data->phys_addr_updbuf); + + dma_free_wc(&pdev->dev, fb_data->working_buffer_size, + fb_data->working_buffer_virt, + fb_data->working_buffer_phys); + if (fb_data->waveform_buffer_virt != NULL) + dma_free_wc(&pdev->dev, fb_data->waveform_buffer_size, + fb_data->waveform_buffer_virt, + fb_data->waveform_buffer_phys); + if (fb_data->virt_addr_copybuf != NULL) + dma_free_wc(&pdev->dev, fb_data->max_pix_size*2, + fb_data->virt_addr_copybuf, + fb_data->phys_addr_copybuf); + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list, + list) { + list_del(&plist->list); + kfree(plist); + } +#ifdef CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE + fb_deferred_io_cleanup(&fb_data->info); +#endif + + dma_free_wc(&pdev->dev, fb_data->map_size, fb_data->info.screen_base, + fb_data->phys_start); + + /* Release PxP-related resources */ + if (fb_data->pxp_chan != NULL) + dma_release_channel(&fb_data->pxp_chan->dma_chan); + + fb_dealloc_cmap(&fb_data->info.cmap); + + framebuffer_release(&fb_data->info); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mxc_epdc_fb_suspend(struct device *dev) +{ + struct mxc_epdc_fb_data *data = dev_get_drvdata(dev); + int ret; + + data->pwrdown_delay = FB_POWERDOWN_DISABLE; + ret = mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &data->info); + if (ret) + goto out; + +out: + return ret; +} + +static int mxc_epdc_fb_resume(struct device *dev) +{ + struct mxc_epdc_fb_data *data = dev_get_drvdata(dev); + + mxc_epdc_fb_blank(FB_BLANK_UNBLANK, &data->info); + epdc_init_settings(data); + data->updates_active = false; + + return 0; +} +#else +#define mxc_epdc_fb_suspend NULL +#define mxc_epdc_fb_resume NULL +#endif + +#ifdef CONFIG_PM +static int mxc_epdc_fb_runtime_suspend(struct device *dev) +{ + release_bus_freq(BUS_FREQ_HIGH); + dev_dbg(dev, "epdc busfreq high release.\n"); + + return 0; +} + +static int mxc_epdc_fb_runtime_resume(struct device *dev) +{ + request_bus_freq(BUS_FREQ_HIGH); + dev_dbg(dev, "epdc busfreq high request.\n"); + + return 0; +} +#else +#define mxc_epdc_fb_runtime_suspend NULL +#define mxc_epdc_fb_runtime_resume NULL +#endif + +static const struct dev_pm_ops mxc_epdc_fb_pm_ops = { + SET_RUNTIME_PM_OPS(mxc_epdc_fb_runtime_suspend, + mxc_epdc_fb_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(mxc_epdc_fb_suspend, mxc_epdc_fb_resume) +}; + +static void mxc_epdc_fb_shutdown(struct platform_device *pdev) +{ + struct mxc_epdc_fb_data *fb_data = platform_get_drvdata(pdev); + + /* Disable power to the EPD panel */ + if (regulator_is_enabled(fb_data->vcom_regulator)) + regulator_disable(fb_data->vcom_regulator); + if (regulator_is_enabled(fb_data->display_regulator)) + regulator_disable(fb_data->display_regulator); + + /* Disable clocks to EPDC */ + clk_prepare_enable(fb_data->epdc_clk_axi); + clk_prepare_enable(fb_data->epdc_clk_pix); + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_SET); + clk_disable_unprepare(fb_data->epdc_clk_pix); + clk_disable_unprepare(fb_data->epdc_clk_axi); + + /* turn off the V3p3 */ + if (regulator_is_enabled(fb_data->v3p3_regulator)) + regulator_disable(fb_data->v3p3_regulator); +} + +static struct platform_driver mxc_epdc_fb_driver = { + .probe = mxc_epdc_fb_probe, + .remove = mxc_epdc_fb_remove, + .shutdown = mxc_epdc_fb_shutdown, + .driver = { + .name = "imx_epdc_fb", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(imx_epdc_dt_ids), + .pm = &mxc_epdc_fb_pm_ops, + }, +}; + +/* Callback function triggered after PxP receives an EOF interrupt */ +static void pxp_dma_done(void *arg) +{ + struct pxp_tx_desc *tx_desc = to_tx_desc(arg); + struct dma_chan *chan = tx_desc->txd.chan; + struct pxp_channel *pxp_chan = to_pxp_channel(chan); + struct mxc_epdc_fb_data *fb_data = pxp_chan->client; + + /* This call will signal wait_for_completion_timeout() in send_buffer_to_pxp */ + complete(&fb_data->pxp_tx_cmpl); +} + +static bool chan_filter(struct dma_chan *chan, void *arg) +{ + if (imx_dma_is_pxp(chan)) + return true; + else + return false; +} + +/* Function to request PXP DMA channel */ +static int pxp_chan_init(struct mxc_epdc_fb_data *fb_data) +{ + dma_cap_mask_t mask; + struct dma_chan *chan; + + /* + * Request a free channel + */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_PRIVATE, mask); + chan = dma_request_channel(mask, chan_filter, NULL); + if (!chan) { + dev_err(fb_data->dev, "Unsuccessfully received channel!!!!\n"); + return -EBUSY; + } + + fb_data->pxp_chan = to_pxp_channel(chan); + fb_data->pxp_chan->client = fb_data; + + init_completion(&fb_data->pxp_tx_cmpl); + + return 0; +} + +/* + * Function to call PxP DMA driver and send our latest FB update region + * through the PxP and out to an intermediate buffer. + * Note: This is a blocking call, so upon return the PxP tx should be complete. + */ +static int pxp_process_update(struct mxc_epdc_fb_data *fb_data, + u32 src_width, u32 src_height, + struct mxcfb_rect *update_region) +{ + dma_cookie_t cookie; + struct scatterlist *sg = fb_data->sg; + struct dma_chan *dma_chan; + struct pxp_tx_desc *desc; + struct dma_async_tx_descriptor *txd; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; + int i, ret; + int length; + + dev_dbg(fb_data->dev, "Starting PxP Send Buffer\n"); + + /* First, check to see that we have acquired a PxP Channel object */ + if (fb_data->pxp_chan == NULL) { + /* + * PxP Channel has not yet been created and initialized, + * so let's go ahead and try + */ + ret = pxp_chan_init(fb_data); + if (ret) { + /* + * PxP channel init failed, and we can't use the + * PxP until the PxP DMA driver has loaded, so we abort + */ + dev_err(fb_data->dev, "PxP chan init failed\n"); + return -ENODEV; + } + } + + /* + * Init completion, so that we + * can be properly informed of the completion + * of the PxP task when it is done. + */ + init_completion(&fb_data->pxp_tx_cmpl); + + dma_chan = &fb_data->pxp_chan->dma_chan; + + txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2, + DMA_TO_DEVICE, + DMA_PREP_INTERRUPT, + NULL); + if (!txd) { + dev_err(fb_data->info.device, + "Error preparing a DMA transaction descriptor.\n"); + return -EIO; + } + + txd->callback_param = txd; + txd->callback = pxp_dma_done; + + /* + * Configure PxP for processing of new update region + * The rest of our config params were set up in + * probe() and should not need to be changed. + */ + pxp_conf->s0_param.width = src_width; + pxp_conf->s0_param.height = src_height; + proc_data->srect.top = update_region->top; + proc_data->srect.left = update_region->left; + proc_data->srect.width = update_region->width; + proc_data->srect.height = update_region->height; + + /* + * Because only YUV/YCbCr image can be scaled, configure + * drect equivalent to srect, as such do not perform scaling. + */ + proc_data->drect.top = 0; + proc_data->drect.left = 0; + + /* PXP expects rotation in terms of degrees */ + proc_data->rotate = fb_data->epdc_fb_var.rotate * 90; + if (proc_data->rotate > 270) + proc_data->rotate = 0; + + /* Just as V4L2 PXP, we should pass the rotated values to PXP */ + if ((proc_data->rotate == 90) || (proc_data->rotate == 270)) { + proc_data->drect.width = proc_data->srect.height; + proc_data->drect.height = proc_data->srect.width; + pxp_conf->out_param.width = update_region->height; + pxp_conf->out_param.height = update_region->width; + pxp_conf->out_param.stride = update_region->height; + } else { + proc_data->drect.width = proc_data->srect.width; + proc_data->drect.height = proc_data->srect.height; + pxp_conf->out_param.width = update_region->width; + pxp_conf->out_param.height = update_region->height; + pxp_conf->out_param.stride = update_region->width; + } + + /* For EPDC v2.0, we need output to be 64-bit + * aligned since EPDC stride does not work. */ + if (fb_data->rev <= 20) + pxp_conf->out_param.stride = ALIGN(pxp_conf->out_param.stride, 8); + + + desc = to_tx_desc(txd); + length = desc->len; + for (i = 0; i < length; i++) { + if (i == 0) {/* S0 */ + memcpy(&desc->proc_data, proc_data, sizeof(struct pxp_proc_data)); + pxp_conf->s0_param.paddr = sg_dma_address(&sg[0]); + memcpy(&desc->layer_param.s0_param, &pxp_conf->s0_param, + sizeof(struct pxp_layer_param)); + } else if (i == 1) { + pxp_conf->out_param.paddr = sg_dma_address(&sg[1]); + memcpy(&desc->layer_param.out_param, &pxp_conf->out_param, + sizeof(struct pxp_layer_param)); + } + /* TODO: OverLay */ + + desc = desc->next; + } + + /* Submitting our TX starts the PxP processing task */ + cookie = txd->tx_submit(txd); + if (cookie < 0) { + dev_err(fb_data->info.device, "Error sending FB through PxP\n"); + return -EIO; + } + + fb_data->txd = txd; + + /* trigger ePxP */ + dma_async_issue_pending(dma_chan); + + return 0; +} + +static int pxp_complete_update(struct mxc_epdc_fb_data *fb_data, u32 *hist_stat) +{ + int ret; + /* + * Wait for completion event, which will be set + * through our TX callback function. + */ + ret = wait_for_completion_timeout(&fb_data->pxp_tx_cmpl, HZ / 10); + if (ret <= 0) { + dev_info(fb_data->info.device, + "PxP operation failed due to %s\n", + ret < 0 ? "user interrupt" : "timeout"); + dma_release_channel(&fb_data->pxp_chan->dma_chan); + fb_data->pxp_chan = NULL; + return ret ? : -ETIMEDOUT; + } + + if ((fb_data->pxp_conf.proc_data.lut_transform & EPDC_FLAG_USE_CMAP) && + fb_data->pxp_conf.proc_data.lut_map_updated) + fb_data->pxp_conf.proc_data.lut_map_updated = false; + + *hist_stat = to_tx_desc(fb_data->txd)->hist_status; + dma_release_channel(&fb_data->pxp_chan->dma_chan); + fb_data->pxp_chan = NULL; + + dev_dbg(fb_data->dev, "TX completed\n"); + + return 0; +} + +/* + * Different dithering algorithm can be used. We chose + * to implement Bill Atkinson's algorithm as an example + * Thanks Bill Atkinson for his dithering algorithm. + */ + +/* + * Dithering algorithm implementation - Y8->Y1 version 1.0 for i.MX + */ +static void do_dithering_processing_Y1_v1_0( + unsigned char *update_region_virt_ptr, + dma_addr_t update_region_phys_ptr, + struct mxcfb_rect *update_region, + unsigned long update_region_stride, + int *err_dist) +{ + + /* create a temp error distribution array */ + int bwPix; + int y; + int col; + int *err_dist_l0, *err_dist_l1, *err_dist_l2, distrib_error; + int width_3 = update_region->width + 3; + char *y8buf; + int x_offset = 0; + + /* prime a few elements the error distribution array */ + for (y = 0; y < update_region->height; y++) { + /* Dithering the Y8 in sbuf to BW suitable for A2 waveform */ + err_dist_l0 = err_dist + (width_3) * (y % 3); + err_dist_l1 = err_dist + (width_3) * ((y + 1) % 3); + err_dist_l2 = err_dist + (width_3) * ((y + 2) % 3); + + y8buf = update_region_virt_ptr + x_offset; + + /* scan the line and convert the Y8 to BW */ + for (col = 1; col <= update_region->width; col++) { + bwPix = *(err_dist_l0 + col) + *y8buf; + + if (bwPix >= 128) { + *y8buf++ = 0xff; + distrib_error = (bwPix - 255) >> 3; + } else { + *y8buf++ = 0; + distrib_error = bwPix >> 3; + } + + /* modify the error distribution buffer */ + *(err_dist_l0 + col + 2) += distrib_error; + *(err_dist_l1 + col + 1) += distrib_error; + *(err_dist_l0 + col + 1) += distrib_error; + *(err_dist_l1 + col - 1) += distrib_error; + *(err_dist_l1 + col) += distrib_error; + *(err_dist_l2 + col) = distrib_error; + } + x_offset += update_region_stride; + } + + flush_cache_all(); + outer_flush_range(update_region_phys_ptr, update_region_phys_ptr + + update_region->height * update_region->width); +} + +/* + * Dithering algorithm implementation - Y8->Y4 version 1.0 for i.MX + */ + +static void do_dithering_processing_Y4_v1_0( + unsigned char *update_region_virt_ptr, + dma_addr_t update_region_phys_ptr, + struct mxcfb_rect *update_region, + unsigned long update_region_stride, + int *err_dist) +{ + + /* create a temp error distribution array */ + int gcPix; + int y; + int col; + int *err_dist_l0, *err_dist_l1, *err_dist_l2, distrib_error; + int width_3 = update_region->width + 3; + char *y8buf; + int x_offset = 0; + + /* prime a few elements the error distribution array */ + for (y = 0; y < update_region->height; y++) { + /* Dithering the Y8 in sbuf to Y4 */ + err_dist_l0 = err_dist + (width_3) * (y % 3); + err_dist_l1 = err_dist + (width_3) * ((y + 1) % 3); + err_dist_l2 = err_dist + (width_3) * ((y + 2) % 3); + + y8buf = update_region_virt_ptr + x_offset; + + /* scan the line and convert the Y8 to Y4 */ + for (col = 1; col <= update_region->width; col++) { + gcPix = *(err_dist_l0 + col) + *y8buf; + + if (gcPix > 255) + gcPix = 255; + else if (gcPix < 0) + gcPix = 0; + + distrib_error = (*y8buf - (gcPix & 0xf0)) >> 3; + + *y8buf++ = gcPix & 0xf0; + + /* modify the error distribution buffer */ + *(err_dist_l0 + col + 2) += distrib_error; + *(err_dist_l1 + col + 1) += distrib_error; + *(err_dist_l0 + col + 1) += distrib_error; + *(err_dist_l1 + col - 1) += distrib_error; + *(err_dist_l1 + col) += distrib_error; + *(err_dist_l2 + col) = distrib_error; + } + x_offset += update_region_stride; + } + + flush_cache_all(); + outer_flush_range(update_region_phys_ptr, update_region_phys_ptr + + update_region->height * update_region->width); +} + +static int __init mxc_epdc_fb_init(void) +{ + return platform_driver_register(&mxc_epdc_fb_driver); +} +late_initcall(mxc_epdc_fb_init); + +static void __exit mxc_epdc_fb_exit(void) +{ + platform_driver_unregister(&mxc_epdc_fb_driver); +} +module_exit(mxc_epdc_fb_exit); + + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC EPDC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/fbdev/mxc/mxc_epdc_v2_fb.c b/drivers/video/fbdev/mxc/mxc_epdc_v2_fb.c new file mode 100644 index 000000000000..f69430d3d163 --- /dev/null +++ b/drivers/video/fbdev/mxc/mxc_epdc_v2_fb.c @@ -0,0 +1,6862 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2017-2019 NXP + */ +/* + * Based on STMP378X LCDIF + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +#include <linux/busfreq-imx.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/uaccess.h> +#include <linux/cpufreq.h> +#include <linux/firmware.h> +#include <linux/kthread.h> +#include <linux/dmaengine.h> +#include <linux/pxp_dma.h> +#include <linux/pm_runtime.h> +#include <linux/mxcfb.h> +#include <linux/mxcfb_epdc.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/max17135.h> +#include <linux/fsl_devices.h> +#include <linux/bitops.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_data/dma-imx.h> +#include <asm/cacheflush.h> + +#include "epdc_v2_regs.h" + +#define EPDC_STANDARD_MODE + +#define USE_PS_AS_OUTPUT + +/* + * Enable this define to have a default panel + * loaded during driver initialization + */ +/*#define DEFAULT_PANEL_HW_INIT*/ + +#define SG_NUM 14 /* 2+4+4+4 */ +#define NUM_SCREENS_MIN 2 + +#define EPDC_V1_NUM_LUTS 16 +#define EPDC_V1_MAX_NUM_UPDATES 20 +#define EPDC_V2_NUM_LUTS 64 +#define EPDC_V2_MAX_NUM_UPDATES 64 +#define EPDC_MAX_NUM_BUFFERS 2 +#define INVALID_LUT (-1) +#define DRY_RUN_NO_LUT 100 + +/* Maximum update buffer image width due to v2.0 and v2.1 errata ERR005313. */ +#define EPDC_V2_MAX_UPDATE_WIDTH 2047 +#define EPDC_V2_ROTATION_ALIGNMENT 8 + +#define DEFAULT_TEMP_INDEX 0 +#define DEFAULT_TEMP 20 /* room temp in deg Celsius */ + +#define INIT_UPDATE_MARKER 0x12345678 +#define PAN_UPDATE_MARKER 0x12345679 + +#define POWER_STATE_OFF 0 +#define POWER_STATE_ON 1 + +#define MERGE_OK 0 +#define MERGE_FAIL 1 +#define MERGE_BLOCK 2 + +static u64 used_luts = 0x1; /* do not use LUT0 */ +static unsigned long default_bpp = 16; + +struct update_marker_data { + struct list_head full_list; + struct list_head upd_list; + u32 update_marker; + struct completion update_completion; + int lut_num; + bool collision_test; + bool waiting; +}; + +struct update_desc_list { + struct list_head list; + struct mxcfb_update_data upd_data;/* Update parameters */ + u32 epdc_offs; /* Added to buffer ptr to resolve alignment */ + u32 epdc_stride; /* Depends on rotation & whether we skip PxP */ + struct list_head upd_marker_list; /* List of markers for this update */ + u32 update_order; /* Numeric ordering value for update */ +}; + +/* This structure represents a list node containing both + * a memory region allocated as an output buffer for the PxP + * update processing task, and the update description (mode, region, etc.) */ +struct update_data_list { + struct list_head list; + dma_addr_t phys_addr; /* Pointer to phys address of processed Y buf */ + void *virt_addr; + struct update_desc_list *update_desc; + int lut_num; /* Assigned before update is processed into working buffer */ + u64 collision_mask; /* Set when update creates collision */ + /* Mask of the LUTs the update collides with */ +}; + +struct mxc_epdc_fb_data { + struct fb_info info; + struct fb_var_screeninfo epdc_fb_var; /* Internal copy of screeninfo + so we can sync changes to it */ + u32 pseudo_palette[16]; + char fw_str[24]; + struct list_head list; + struct imx_epdc_fb_mode *cur_mode; + struct imx_epdc_fb_platform_data *pdata; + int blank; + u32 max_pix_size; + ssize_t map_size; + dma_addr_t phys_start; + void *virt_start; + u32 fb_offset; + int default_bpp; + int native_width; + int native_height; + int num_screens; + int epdc_irq; + struct device *dev; + int power_state; + int wait_for_powerdown; + struct completion powerdown_compl; + struct clk *epdc_clk_axi; + struct clk *epdc_clk_pix; + struct regulator *display_regulator; + struct regulator *vcom_regulator; + struct regulator *v3p3_regulator; + bool fw_default_load; + int rev; + + /* FB elements related to EPDC updates */ + int num_luts; + int max_num_updates; + bool in_init; + bool hw_ready; + bool hw_initializing; + bool waiting_for_idle; + u32 auto_mode; + u32 upd_scheme; + struct list_head upd_pending_list; + struct list_head upd_buf_queue; + struct list_head upd_buf_free_list; + struct list_head upd_buf_collision_list; + struct update_data_list *cur_update; + struct mutex queue_mutex; + int trt_entries; + int temp_index; + u8 *temp_range_bounds; + struct mxcfb_waveform_modes wv_modes; + bool wv_modes_update; + bool waveform_is_advanced; + u32 *waveform_buffer_virt; + u32 waveform_buffer_phys; + u32 waveform_buffer_size; + u32 *working_buffer_virt; + u32 working_buffer_phys; + u32 working_buffer_size; + u32 *tmp_working_buffer_virt; + u32 tmp_working_buffer_phys; + dma_addr_t *phys_addr_updbuf; + void **virt_addr_updbuf; + u32 upd_buffer_num; + u32 max_num_buffers; + dma_addr_t phys_addr_copybuf; /* Phys address of copied update data */ + void *virt_addr_copybuf; /* Used for PxP SW workaround */ + dma_addr_t phys_addr_y4; + void *virt_addr_y4; + dma_addr_t phys_addr_y4c; + void *virt_addr_y4c; + dma_addr_t phys_addr_black; + void *virt_addr_black; + u32 order_cnt; + struct list_head full_marker_list; + u32 *lut_update_order; /* Array size = number of luts */ + u64 epdc_colliding_luts; + u64 luts_complete_wb; + u64 luts_complete; + struct completion updates_done; + struct delayed_work epdc_done_work; + struct workqueue_struct *epdc_submit_workqueue; + struct work_struct epdc_submit_work; + struct workqueue_struct *epdc_intr_workqueue; + struct work_struct epdc_intr_work; + bool waiting_for_wb; + bool waiting_for_lut; + bool waiting_for_lut15; + struct completion update_res_free; + struct completion lut15_free; + struct completion eof_event; + int eof_sync_period; + struct mutex power_mutex; + bool powering_down; + bool updates_active; + int pwrdown_delay; + unsigned long tce_prevent; + bool restrict_width; /* work around rev >=2.0 width and + stride restriction */ + + /* FB elements related to PxP DMA */ + struct completion pxp_tx_cmpl; + struct pxp_channel *pxp_chan; + struct pxp_config_data pxp_conf; + struct dma_async_tx_descriptor *txd; + dma_cookie_t cookie; + struct scatterlist sg[SG_NUM]; + struct mutex pxp_mutex; /* protects access to PxP */ + + /* external mode or internal mode */ + int epdc_wb_mode; + struct pxp_collision_info col_info; + u32 hist_status; + + struct regmap *gpr; + u8 req_gpr; + u8 req_bit; + + /* qos */ + struct regmap *qos_regmap; +}; + +struct waveform_data_header { + unsigned int wi0; + unsigned int wi1; + unsigned int wi2; + unsigned int wi3; + unsigned int wi4; + unsigned int wi5; + unsigned int wi6; + unsigned int xwia:24; + unsigned int cs1:8; + unsigned int wmta:24; + unsigned int fvsn:8; + unsigned int luts:8; + unsigned int mc:8; + unsigned int trc:8; + unsigned int reserved0_0:8; + unsigned int eb:8; + unsigned int sb:8; + unsigned int reserved0_1:8; + unsigned int reserved0_2:8; + unsigned int reserved0_3:8; + unsigned int reserved0_4:8; + unsigned int reserved0_5:8; + unsigned int cs2:8; +}; + +struct mxcfb_waveform_data_file { + struct waveform_data_header wdh; + u32 *data; /* Temperature Range Table + Waveform Data */ +}; + +#define WAVEFORM_HDR_LUT_ADVANCED_ALGO_MASK 0xc + +static struct fb_videomode ed060xh2c1mode = { + .name = "ED060XH2C1", + .refresh = 85, + .xres = 1024, + .yres = 758, + .pixclock = 40000000, + .left_margin = 12, + .right_margin = 76, + .upper_margin = 4, + .lower_margin = 5, + .hsync_len = 12, + .vsync_len = 2, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, +}; + +static struct fb_videomode e60_v110_mode = { + .name = "E60_V110", + .refresh = 50, + .xres = 800, + .yres = 600, + .pixclock = 18604700, + .left_margin = 8, + .right_margin = 178, + .upper_margin = 4, + .lower_margin = 10, + .hsync_len = 20, + .vsync_len = 4, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, +}; + +static struct fb_videomode e60_v220_mode = { + .name = "E60_V220", + .refresh = 85, + .xres = 800, + .yres = 600, + .pixclock = 30000000, + .left_margin = 8, + .right_margin = 164, + .upper_margin = 4, + .lower_margin = 8, + .hsync_len = 4, + .vsync_len = 1, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, +}; + +static struct fb_videomode e060scm_mode = { + .name = "E060SCM", + .refresh = 85, + .xres = 800, + .yres = 600, + .pixclock = 26666667, + .left_margin = 8, + .right_margin = 100, + .upper_margin = 4, + .lower_margin = 8, + .hsync_len = 4, + .vsync_len = 1, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, +}; + +static struct fb_videomode e97_v110_mode = { + .name = "E97_V110", + .refresh = 50, + .xres = 1200, + .yres = 825, + .pixclock = 32000000, + .left_margin = 12, + .right_margin = 128, + .upper_margin = 4, + .lower_margin = 10, + .hsync_len = 20, + .vsync_len = 4, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, +}; + +static struct imx_epdc_fb_mode panel_modes[] = { + { + &ed060xh2c1mode, /* struct fb_videomode *mode */ + 4, /* vscan_holdoff */ + 10, /* sdoed_width */ + 20, /* sdoed_delay */ + 10, /* sdoez_width */ + 20, /* sdoez_delay */ + 524, /* GDCLK_HP */ + 327, /* GDSP_OFF */ + 0, /* GDOE_OFF */ + 19, /* gdclk_offs */ + 1, /* num_ce */ + }, + { + &e60_v110_mode, + 4, /* vscan_holdoff */ + 10, /* sdoed_width */ + 20, /* sdoed_delay */ + 10, /* sdoez_width */ + 20, /* sdoez_delay */ + 428, /* gdclk_hp_offs */ + 20, /* gdsp_offs */ + 0, /* gdoe_offs */ + 1, /* gdclk_offs */ + 1, /* num_ce */ + }, + { + &e60_v220_mode, + 4, /* vscan_holdoff */ + 10, /* sdoed_width */ + 20, /* sdoed_delay */ + 10, /* sdoez_width */ + 20, /* sdoez_delay */ + 465, /* gdclk_hp_offs */ + 20, /* gdsp_offs */ + 0, /* gdoe_offs */ + 9, /* gdclk_offs */ + 1, /* num_ce */ + }, + { + &e060scm_mode, + 4, /* vscan_holdoff */ + 10, /* sdoed_width */ + 20, /* sdoed_delay */ + 10, /* sdoez_width */ + 20, /* sdoez_delay */ + 419, /* gdclk_hp_offs */ + 263, /* gdsp_offs */ + 0, /* gdoe_offs */ + 5, /* gdclk_offs */ + 1, /* num_ce */ + }, + { + &e97_v110_mode, + 8, /* vscan_holdoff */ + 10, /* sdoed_width */ + 20, /* sdoed_delay */ + 10, /* sdoez_width */ + 20, /* sdoez_delay */ + 632, /* gdclk_hp_offs */ + 20, /* gdsp_offs */ + 0, /* gdoe_offs */ + 1, /* gdclk_offs */ + 3, /* num_ce */ + } +}; + +static struct imx_epdc_fb_platform_data epdc_data = { + .epdc_mode = panel_modes, + .num_modes = ARRAY_SIZE(panel_modes), +}; + +void __iomem *epdc_v2_base; + +static struct mxc_epdc_fb_data *g_fb_data; + +/* forward declaration */ +static int mxc_epdc_fb_get_temp_index(struct mxc_epdc_fb_data *fb_data, + int temp); +static void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data); +static int mxc_epdc_fb_blank(int blank, struct fb_info *info); +static int mxc_epdc_fb_init_hw(struct fb_info *info); +static int pxp_legacy_process(struct mxc_epdc_fb_data *fb_data, + u32 src_width, u32 src_height, + struct mxcfb_rect *update_region); +static int pxp_process_dithering(struct mxc_epdc_fb_data *fb_data, + struct mxcfb_rect *update_region); +static int pxp_wfe_a_process(struct mxc_epdc_fb_data *fb_data, + struct mxcfb_rect *update_region, + struct update_data_list *upd_data_list); +static int pxp_wfe_b_process_update(struct mxc_epdc_fb_data *fb_data, + struct mxcfb_rect *update_region); +static int pxp_wfe_a_process_clear_workingbuffer(struct mxc_epdc_fb_data *fb_data, + u32 src_width, u32 src_height); +static int pxp_complete_update(struct mxc_epdc_fb_data *fb_data, u32 *hist_stat); + +static void draw_mode0(struct mxc_epdc_fb_data *fb_data); +static bool is_free_list_full(struct mxc_epdc_fb_data *fb_data); + +static void do_dithering_processing_Y1_v1_0( + unsigned char *update_region_virt_ptr, + dma_addr_t update_region_phys_ptr, + struct mxcfb_rect *update_region, + unsigned long update_region_stride, + int *err_dist); +static void do_dithering_processing_Y4_v1_0( + unsigned char *update_region_virt_ptr, + dma_addr_t update_region_phys_ptr, + struct mxcfb_rect *update_region, + unsigned long update_region_stride, + int *err_dist); +static inline void epdc_set_used_lut(u64 used_bit); +static inline void epdc_reset_used_lut(void); +static int pxp_clear_wb_work_func(struct mxc_epdc_fb_data *fb_data); +static int epdc_working_buffer_update(struct mxc_epdc_fb_data *fb_data, + struct update_data_list *upd_data_list, + struct mxcfb_rect *update_region); +extern void pxp_get_collision_info(struct pxp_collision_info *info); + +#ifdef DEBUG +static void dump_pxp_config(struct mxc_epdc_fb_data *fb_data, + struct pxp_config_data *pxp_conf) +{ + dev_info(fb_data->dev, "S0 fmt 0x%x", + pxp_conf->s0_param.pixel_fmt); + dev_info(fb_data->dev, "S0 width 0x%x", + pxp_conf->s0_param.width); + dev_info(fb_data->dev, "S0 height 0x%x", + pxp_conf->s0_param.height); + dev_info(fb_data->dev, "S0 ckey 0x%x", + pxp_conf->s0_param.color_key); + dev_info(fb_data->dev, "S0 ckey en 0x%x", + pxp_conf->s0_param.color_key_enable); + + dev_info(fb_data->dev, "OL0 combine en 0x%x", + pxp_conf->ol_param[0].combine_enable); + dev_info(fb_data->dev, "OL0 fmt 0x%x", + pxp_conf->ol_param[0].pixel_fmt); + dev_info(fb_data->dev, "OL0 width 0x%x", + pxp_conf->ol_param[0].width); + dev_info(fb_data->dev, "OL0 height 0x%x", + pxp_conf->ol_param[0].height); + dev_info(fb_data->dev, "OL0 ckey 0x%x", + pxp_conf->ol_param[0].color_key); + dev_info(fb_data->dev, "OL0 ckey en 0x%x", + pxp_conf->ol_param[0].color_key_enable); + dev_info(fb_data->dev, "OL0 alpha 0x%x", + pxp_conf->ol_param[0].global_alpha); + dev_info(fb_data->dev, "OL0 alpha en 0x%x", + pxp_conf->ol_param[0].global_alpha_enable); + dev_info(fb_data->dev, "OL0 local alpha en 0x%x", + pxp_conf->ol_param[0].local_alpha_enable); + + dev_info(fb_data->dev, "Out fmt 0x%x", + pxp_conf->out_param.pixel_fmt); + dev_info(fb_data->dev, "Out width 0x%x", + pxp_conf->out_param.width); + dev_info(fb_data->dev, "Out height 0x%x", + pxp_conf->out_param.height); + + dev_info(fb_data->dev, + "drect left 0x%x right 0x%x width 0x%x height 0x%x", + pxp_conf->proc_data.drect.left, pxp_conf->proc_data.drect.top, + pxp_conf->proc_data.drect.width, + pxp_conf->proc_data.drect.height); + dev_info(fb_data->dev, + "srect left 0x%x right 0x%x width 0x%x height 0x%x", + pxp_conf->proc_data.srect.left, pxp_conf->proc_data.srect.top, + pxp_conf->proc_data.srect.width, + pxp_conf->proc_data.srect.height); + dev_info(fb_data->dev, "Scaling en 0x%x", pxp_conf->proc_data.scaling); + dev_info(fb_data->dev, "HFlip en 0x%x", pxp_conf->proc_data.hflip); + dev_info(fb_data->dev, "VFlip en 0x%x", pxp_conf->proc_data.vflip); + dev_info(fb_data->dev, "Rotation 0x%x", pxp_conf->proc_data.rotate); + dev_info(fb_data->dev, "BG Color 0x%x", pxp_conf->proc_data.bgcolor); +} + +static void dump_epdc_reg(void) +{ + printk(KERN_DEBUG "\n\n"); + printk(KERN_DEBUG "EPDC_CTRL 0x%x\n", __raw_readl(EPDC_CTRL)); + printk(KERN_DEBUG "EPDC_WVADDR 0x%x\n", __raw_readl(EPDC_WVADDR)); + printk(KERN_DEBUG "EPDC_WB_ADDR 0x%x\n", __raw_readl(EPDC_WB_ADDR)); + printk(KERN_DEBUG "EPDC_RES 0x%x\n", __raw_readl(EPDC_RES)); + printk(KERN_DEBUG "EPDC_FORMAT 0x%x\n", __raw_readl(EPDC_FORMAT)); + printk(KERN_DEBUG "EPDC_FIFOCTRL 0x%x\n", __raw_readl(EPDC_FIFOCTRL)); + printk(KERN_DEBUG "EPDC_UPD_ADDR 0x%x\n", __raw_readl(EPDC_UPD_ADDR)); + printk(KERN_DEBUG "EPDC_UPD_STRIDE 0x%x\n", __raw_readl(EPDC_UPD_STRIDE)); + printk(KERN_DEBUG "EPDC_UPD_FIXED 0x%x\n", __raw_readl(EPDC_UPD_FIXED)); + printk(KERN_DEBUG "EPDC_UPD_CORD 0x%x\n", __raw_readl(EPDC_UPD_CORD)); + printk(KERN_DEBUG "EPDC_UPD_SIZE 0x%x\n", __raw_readl(EPDC_UPD_SIZE)); + printk(KERN_DEBUG "EPDC_UPD_CTRL 0x%x\n", __raw_readl(EPDC_UPD_CTRL)); + printk(KERN_DEBUG "EPDC_TEMP 0x%x\n", __raw_readl(EPDC_TEMP)); + printk(KERN_DEBUG "EPDC_AUTOWV_LUT 0x%x\n", __raw_readl(EPDC_AUTOWV_LUT)); + printk(KERN_DEBUG "EPDC_TCE_CTRL 0x%x\n", __raw_readl(EPDC_TCE_CTRL)); + printk(KERN_DEBUG "EPDC_TCE_SDCFG 0x%x\n", __raw_readl(EPDC_TCE_SDCFG)); + printk(KERN_DEBUG "EPDC_TCE_GDCFG 0x%x\n", __raw_readl(EPDC_TCE_GDCFG)); + printk(KERN_DEBUG "EPDC_TCE_HSCAN1 0x%x\n", __raw_readl(EPDC_TCE_HSCAN1)); + printk(KERN_DEBUG "EPDC_TCE_HSCAN2 0x%x\n", __raw_readl(EPDC_TCE_HSCAN2)); + printk(KERN_DEBUG "EPDC_TCE_VSCAN 0x%x\n", __raw_readl(EPDC_TCE_VSCAN)); + printk(KERN_DEBUG "EPDC_TCE_OE 0x%x\n", __raw_readl(EPDC_TCE_OE)); + printk(KERN_DEBUG "EPDC_TCE_POLARITY 0x%x\n", __raw_readl(EPDC_TCE_POLARITY)); + printk(KERN_DEBUG "EPDC_TCE_TIMING1 0x%x\n", __raw_readl(EPDC_TCE_TIMING1)); + printk(KERN_DEBUG "EPDC_TCE_TIMING2 0x%x\n", __raw_readl(EPDC_TCE_TIMING2)); + printk(KERN_DEBUG "EPDC_TCE_TIMING3 0x%x\n", __raw_readl(EPDC_TCE_TIMING3)); + printk(KERN_DEBUG "EPDC_PIGEON_CTRL0 0x%x\n", __raw_readl(EPDC_PIGEON_CTRL0)); + printk(KERN_DEBUG "EPDC_PIGEON_CTRL1 0x%x\n", __raw_readl(EPDC_PIGEON_CTRL1)); + printk(KERN_DEBUG "EPDC_IRQ_MASK1 0x%x\n", __raw_readl(EPDC_IRQ_MASK1)); + printk(KERN_DEBUG "EPDC_IRQ_MASK2 0x%x\n", __raw_readl(EPDC_IRQ_MASK2)); + printk(KERN_DEBUG "EPDC_IRQ1 0x%x\n", __raw_readl(EPDC_IRQ1)); + printk(KERN_DEBUG "EPDC_IRQ2 0x%x\n", __raw_readl(EPDC_IRQ2)); + printk(KERN_DEBUG "EPDC_IRQ_MASK 0x%x\n", __raw_readl(EPDC_IRQ_MASK)); + printk(KERN_DEBUG "EPDC_IRQ 0x%x\n", __raw_readl(EPDC_IRQ)); + printk(KERN_DEBUG "EPDC_STATUS_LUTS 0x%x\n", __raw_readl(EPDC_STATUS_LUTS)); + printk(KERN_DEBUG "EPDC_STATUS_LUTS2 0x%x\n", __raw_readl(EPDC_STATUS_LUTS2)); + printk(KERN_DEBUG "EPDC_STATUS_NEXTLUT 0x%x\n", __raw_readl(EPDC_STATUS_NEXTLUT)); + printk(KERN_DEBUG "EPDC_STATUS_COL1 0x%x\n", __raw_readl(EPDC_STATUS_COL)); + printk(KERN_DEBUG "EPDC_STATUS_COL2 0x%x\n", __raw_readl(EPDC_STATUS_COL2)); + printk(KERN_DEBUG "EPDC_STATUS 0x%x\n", __raw_readl(EPDC_STATUS)); + printk(KERN_DEBUG "EPDC_UPD_COL_CORD 0x%x\n", __raw_readl(EPDC_UPD_COL_CORD)); + printk(KERN_DEBUG "EPDC_UPD_COL_SIZE 0x%x\n", __raw_readl(EPDC_UPD_COL_SIZE)); + printk(KERN_DEBUG "EPDC_DEBUG 0x%x\n", __raw_readl(EPDC_DEBUG)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT 0x%x\n", __raw_readl(EPDC_DEBUG_LUT)); + printk(KERN_DEBUG "EPDC_HIST1_PARAM 0x%x\n", __raw_readl(EPDC_HIST1_PARAM)); + printk(KERN_DEBUG "EPDC_HIST2_PARAM 0x%x\n", __raw_readl(EPDC_HIST2_PARAM)); + printk(KERN_DEBUG "EPDC_HIST4_PARAM 0x%x\n", __raw_readl(EPDC_HIST4_PARAM)); + printk(KERN_DEBUG "EPDC_HIST8_PARAM0 0x%x\n", __raw_readl(EPDC_HIST8_PARAM0)); + printk(KERN_DEBUG "EPDC_HIST8_PARAM1 0x%x\n", __raw_readl(EPDC_HIST8_PARAM1)); + printk(KERN_DEBUG "EPDC_HIST16_PARAM0 0x%x\n", __raw_readl(EPDC_HIST16_PARAM0)); + printk(KERN_DEBUG "EPDC_HIST16_PARAM1 0x%x\n", __raw_readl(EPDC_HIST16_PARAM1)); + printk(KERN_DEBUG "EPDC_HIST16_PARAM2 0x%x\n", __raw_readl(EPDC_HIST16_PARAM2)); + printk(KERN_DEBUG "EPDC_HIST16_PARAM3 0x%x\n", __raw_readl(EPDC_HIST16_PARAM3)); + printk(KERN_DEBUG "EPDC_GPIO 0x%x\n", __raw_readl(EPDC_GPIO)); + printk(KERN_DEBUG "EPDC_VERSION 0x%x\n", __raw_readl(EPDC_VERSION)); + printk(KERN_DEBUG "\n\n"); +} + +static void dump_update_data(struct device *dev, + struct update_data_list *upd_data_list) +{ + dev_info(dev, + "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, " + "LUT = %d, Coll Mask = 0x%llx, order = %d\n", + upd_data_list->update_desc->upd_data.update_region.left, + upd_data_list->update_desc->upd_data.update_region.top, + upd_data_list->update_desc->upd_data.update_region.width, + upd_data_list->update_desc->upd_data.update_region.height, + upd_data_list->update_desc->upd_data.waveform_mode, + upd_data_list->lut_num, + upd_data_list->collision_mask, + upd_data_list->update_desc->update_order); +} + +static void dump_collision_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_info(fb_data->dev, "Collision List:\n"); + if (list_empty(&fb_data->upd_buf_collision_list)) + dev_info(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_collision_list, list) { + dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); + dump_update_data(fb_data->dev, plist); + } +} + +static void dump_free_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_info(fb_data->dev, "Free List:\n"); + if (list_empty(&fb_data->upd_buf_free_list)) + dev_info(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) + dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); +} + +static void dump_queue(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_info(fb_data->dev, "Queue:\n"); + if (list_empty(&fb_data->upd_buf_queue)) + dev_info(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_queue, list) { + dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); + dump_update_data(fb_data->dev, plist); + } +} + +static void dump_desc_data(struct device *dev, + struct update_desc_list *upd_desc_list) +{ + dev_info(dev, + "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, " + "order = %d\n", + upd_desc_list->upd_data.update_region.left, + upd_desc_list->upd_data.update_region.top, + upd_desc_list->upd_data.update_region.width, + upd_desc_list->upd_data.update_region.height, + upd_desc_list->upd_data.waveform_mode, + upd_desc_list->update_order); +} + +static void dump_pending_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_desc_list *plist; + + dev_info(fb_data->dev, "Queue:\n"); + if (list_empty(&fb_data->upd_pending_list)) + dev_info(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_pending_list, list) + dump_desc_data(fb_data->dev, plist); +} + +static void dump_all_updates(struct mxc_epdc_fb_data *fb_data) +{ + dump_free_list(fb_data); + dump_queue(fb_data); + dump_collision_list(fb_data); + dev_info(fb_data->dev, "Current update being processed:\n"); + if (fb_data->cur_update == NULL) + dev_info(fb_data->dev, "No current update\n"); + else + dump_update_data(fb_data->dev, fb_data->cur_update); +} + +static void dump_fw_header(struct device *dev, + struct mxcfb_waveform_data_file *fw) +{ + dev_dbg(dev, "Firmware Header:\n"); + dev_dbg(dev, "wi0 0x%08x\n", fw->wdh.wi0); + dev_dbg(dev, "wi1 0x%08x\n", fw->wdh.wi1); + dev_dbg(dev, "wi2 0x%08x\n", fw->wdh.wi2); + dev_dbg(dev, "wi3 0x%08x\n", fw->wdh.wi3); + dev_dbg(dev, "wi4 0x%08x\n", fw->wdh.wi4); + dev_dbg(dev, "wi5 0x%08x\n", fw->wdh.wi5); + dev_dbg(dev, "wi6 0x%08x\n", fw->wdh.wi6); + dev_dbg(dev, "xwia:24 0x%06x\n", fw->wdh.xwia); + dev_dbg(dev, "cs1:8 0x%02x\n", fw->wdh.cs1); + dev_dbg(dev, "wmta:24 0x%06x\n", fw->wdh.wmta); + dev_dbg(dev, "fvsn:8 0x%02x\n", fw->wdh.fvsn); + dev_dbg(dev, "luts:8 0x%02x\n", fw->wdh.luts); + dev_dbg(dev, "mc:8 0x%02x\n", fw->wdh.mc); + dev_dbg(dev, "trc:8 0x%02x\n", fw->wdh.trc); + dev_dbg(dev, "reserved0_0 0x%02x\n", fw->wdh.reserved0_0); + dev_dbg(dev, "eb:8 0x%02x\n", fw->wdh.eb); + dev_dbg(dev, "sb:8 0x%02x\n", fw->wdh.sb); + dev_dbg(dev, "reserved0_1 0x%02x\n", fw->wdh.reserved0_1); + dev_dbg(dev, "reserved0_2 0x%02x\n", fw->wdh.reserved0_2); + dev_dbg(dev, "reserved0_3 0x%02x\n", fw->wdh.reserved0_3); + dev_dbg(dev, "reserved0_4 0x%02x\n", fw->wdh.reserved0_4); + dev_dbg(dev, "reserved0_5 0x%02x\n", fw->wdh.reserved0_5); + dev_dbg(dev, "cs2:8 0x%02x\n", fw->wdh.cs2); +} + +#else +static inline void dump_pxp_config(struct mxc_epdc_fb_data *fb_data, + struct pxp_config_data *pxp_conf) {} +static inline void dump_epdc_reg(void) {} +static inline void dump_update_data(struct device *dev, + struct update_data_list *upd_data_list) {} +static inline void dump_collision_list(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_free_list(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_queue(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_all_updates(struct mxc_epdc_fb_data *fb_data) {} +static void dump_fw_header(struct device *dev, + struct mxcfb_waveform_data_file *fw) {} + +#endif + + +/******************************************************** + * Start Low-Level EPDC Functions + ********************************************************/ + +static inline void epdc_lut_complete_intr(int rev, u32 lut_num, bool enable) +{ + if (rev < 20) { + if (enable) + __raw_writel(1 << lut_num, EPDC_IRQ_MASK_SET); + else + __raw_writel(1 << lut_num, EPDC_IRQ_MASK_CLEAR); + } else { + if (enable) { + if (lut_num < 32) + __raw_writel(1 << lut_num, EPDC_IRQ_MASK1_SET); + else + __raw_writel(1 << (lut_num - 32), + EPDC_IRQ_MASK2_SET); + } else { + if (lut_num < 32) + __raw_writel(1 << lut_num, + EPDC_IRQ_MASK1_CLEAR); + else + __raw_writel(1 << (lut_num - 32), + EPDC_IRQ_MASK2_CLEAR); + } + } +} + +static inline void epdc_working_buf_intr(bool enable) +{ + if (enable) + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ, EPDC_IRQ_MASK_SET); + else + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ, EPDC_IRQ_MASK_CLEAR); +} + +static inline void epdc_clear_working_buf_irq(void) +{ + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ | EPDC_IRQ_LUT_COL_IRQ, + EPDC_IRQ_CLEAR); +} + +static inline void epdc_eof_intr(bool enable) +{ + if (enable) + __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_MASK_SET); + else + __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_MASK_CLEAR); +} + +static inline void epdc_clear_eof_irq(void) +{ + __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_CLEAR); +} + +static inline bool epdc_signal_eof(void) +{ + return (__raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ) + & EPDC_IRQ_FRAME_END_IRQ) ? true : false; +} + +static inline void epdc_set_temp(u32 temp) +{ + int ret = 0; + /* used to store external panel temperature value */ + unsigned int ext_temp, ext_temp_index = temp; + + if (temp == DEFAULT_TEMP_INDEX) { + ret = max17135_reg_read(REG_MAX17135_EXT_TEMP, &ext_temp); + if (ret == 0) { + ext_temp = ext_temp >> 8; + dev_dbg(g_fb_data->dev, "the current external temperature is %d\n", + ext_temp); + ext_temp_index = mxc_epdc_fb_get_temp_index(g_fb_data, ext_temp); + } + } + + __raw_writel(ext_temp_index, EPDC_TEMP); +} + +static inline void epdc_set_screen_res(u32 width, u32 height) +{ + u32 val = (height << EPDC_RES_VERTICAL_OFFSET) | width; + __raw_writel(val, EPDC_RES); +} + +static inline void epdc_set_update_addr(u32 addr) +{ +#ifdef EPDC_STANDARD_MODE + __raw_writel(0, EPDC_UPD_ADDR); +#else + __raw_writel(addr, EPDC_UPD_ADDR); +#endif +} + +static inline void epdc_set_update_coord(u32 x, u32 y) +{ + u32 val = (y << EPDC_UPD_CORD_YCORD_OFFSET) | x; + __raw_writel(val, EPDC_UPD_CORD); +} + +static inline void epdc_set_update_dimensions(u32 width, u32 height) +{ + u32 val = (height << EPDC_UPD_SIZE_HEIGHT_OFFSET) | width; + __raw_writel(val, EPDC_UPD_SIZE); +} + +static void epdc_set_update_waveform(struct mxcfb_waveform_modes *wv_modes) +{ + u32 val; + +#ifdef EPDC_STANDARD_MODE + return; +#endif + + /* Configure the auto-waveform look-up table based on waveform modes */ + + /* Entry 1 = DU, 2 = GC4, 3 = GC8, etc. */ + val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (0 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (1 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_gc4 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (2 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_gc8 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (3 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_gc16 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (4 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_gc32 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (5 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); +} + +static void epdc_set_update_stride(u32 stride) +{ +#ifdef EPDC_STANDARD_MODE + __raw_writel(0, EPDC_UPD_STRIDE); +#else + __raw_writel(stride, EPDC_UPD_STRIDE); +#endif +} + +static void epdc_submit_update(u32 lut_num, u32 waveform_mode, u32 update_mode, + bool use_dry_run, bool use_test_mode, u32 np_val) +{ + u32 reg_val = 0; + + if (use_test_mode) { + reg_val |= + ((np_val << EPDC_UPD_FIXED_FIXNP_OFFSET) & + EPDC_UPD_FIXED_FIXNP_MASK) | EPDC_UPD_FIXED_FIXNP_EN; + reg_val |= + ((np_val << EPDC_UPD_FIXED_FIXCP_OFFSET) & + EPDC_UPD_FIXED_FIXCP_MASK) | EPDC_UPD_FIXED_FIXCP_EN; + + __raw_writel(reg_val, EPDC_UPD_FIXED); + + reg_val = EPDC_UPD_CTRL_USE_FIXED; + } else { + __raw_writel(reg_val, EPDC_UPD_FIXED); + } + + if (waveform_mode == WAVEFORM_MODE_AUTO) + reg_val |= EPDC_UPD_CTRL_AUTOWV; + else + reg_val |= ((waveform_mode << + EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET) & + EPDC_UPD_CTRL_WAVEFORM_MODE_MASK); + + reg_val |= (use_dry_run ? EPDC_UPD_CTRL_DRY_RUN : 0) | + ((lut_num << EPDC_UPD_CTRL_LUT_SEL_OFFSET) & + EPDC_UPD_CTRL_LUT_SEL_MASK) | + update_mode; + +#ifdef EPDC_STANDARD_MODE + reg_val |= 0x80000000; + + epdc_set_used_lut(lut_num); +#endif + dump_epdc_reg(); + __raw_writel(reg_val, EPDC_UPD_CTRL); +} + +static inline bool epdc_is_lut_complete(int rev, u32 lut_num) +{ + u32 val; + bool is_compl; + if (rev < 20) { + val = __raw_readl(EPDC_IRQ); + is_compl = val & (1 << lut_num) ? true : false; + } else if (lut_num < 32) { + val = __raw_readl(EPDC_IRQ1); + is_compl = val & (1 << lut_num) ? true : false; + } else { + val = __raw_readl(EPDC_IRQ2); + is_compl = val & (1 << (lut_num - 32)) ? true : false; + } + + return is_compl; +} + +static inline void epdc_clear_lut_complete_irq(int rev, u32 lut_num) +{ + if (rev < 20) + __raw_writel(1 << lut_num, EPDC_IRQ_CLEAR); + else if (lut_num < 32) + __raw_writel(1 << lut_num, EPDC_IRQ1_CLEAR); + else + __raw_writel(1 << (lut_num - 32), EPDC_IRQ2_CLEAR); +} + +static inline bool epdc_is_lut_active(u32 lut_num) +{ + u32 val; + bool is_active; + + if (lut_num < 32) { + val = __raw_readl(EPDC_STATUS_LUTS); + is_active = val & (1 << lut_num) ? true : false; + } else { + val = __raw_readl(EPDC_STATUS_LUTS2); + is_active = val & (1 << (lut_num - 32)) ? true : false; + } + + return is_active; +} + +static inline bool epdc_any_luts_active(int rev) +{ + bool any_active; + + if (rev < 20) + any_active = __raw_readl(EPDC_STATUS_LUTS) ? true : false; + else + any_active = (__raw_readl(EPDC_STATUS_LUTS) | + __raw_readl(EPDC_STATUS_LUTS2)) ? true : false; + + return any_active; +} + +static inline bool epdc_any_luts_real_available(void) +{ + if ((__raw_readl(EPDC_STATUS_LUTS) != 0xfffffffe) || + (__raw_readl(EPDC_STATUS_LUTS2) != ~0UL)) + return true; + else + return false; +} + +static inline bool epdc_any_luts_available(void) +{ +#ifdef EPDC_STANDARD_MODE + if (((u32)used_luts != ~0UL) || ((u32)(used_luts >> 32) != ~0UL)) + return 1; + else + return 0; +#else + bool luts_available = + (__raw_readl(EPDC_STATUS_NEXTLUT) & + EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID) ? true : false; + return luts_available; +#endif +} + +static inline int epdc_get_next_lut(void) +{ + u32 val = + __raw_readl(EPDC_STATUS_NEXTLUT) & + EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK; + return val; +} + +static inline void epdc_set_used_lut(u64 used_bit) +{ + used_luts |= (u64)1 << used_bit; +} + +static inline void epdc_reset_used_lut(void) +{ + used_luts = 0x1; +} + +#ifdef EPDC_STANDARD_MODE +/* + * in previous flow, when all LUTs are used, the LUT cleanup operation + * need to wait for all the LUT to finish, it will not happen util last LUT + * is done. while in new flow, the cleanup operation does not need to wait + * for all LUTs to finish, instead it can start when there's LUT's done. + * The saved time is multiple LUT operation time. + */ +static int epdc_choose_next_lut(struct mxc_epdc_fb_data *fb_data, int *next_lut) +{ + while (!epdc_any_luts_available()) { + u64 luts_complete = fb_data->luts_complete; + pxp_clear_wb_work_func(fb_data); + used_luts &= ~luts_complete; + fb_data->luts_complete &= ~luts_complete; + mutex_unlock(&fb_data->queue_mutex); + msleep(10); + mutex_lock(&fb_data->queue_mutex); + } + + used_luts |= 0x1; + + if ((u32)used_luts != ~0UL) + *next_lut = ffz((u32)used_luts); + else if ((u32)(used_luts >> 32) != ~0UL) + *next_lut = ffz((u32)(used_luts >> 32)) + 32; + + return 0; +} +#else +static int epdc_choose_next_lut(struct mxc_epdc_fb_data *fb_data, int *next_lut) +{ + u64 luts_status, unprocessed_luts, used_luts; + /* Available LUTs are reduced to 16 in 5-bit waveform mode */ + bool format_p5n = ((__raw_readl(EPDC_FORMAT) & + EPDC_FORMAT_BUF_PIXEL_FORMAT_MASK) == + EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N); + + luts_status = __raw_readl(EPDC_STATUS_LUTS); + if ((fb_data->rev < 20) || format_p5n) + luts_status &= 0xFFFF; + else + luts_status |= ((u64)__raw_readl(EPDC_STATUS_LUTS2) << 32); + + if (fb_data->rev < 20) { + unprocessed_luts = __raw_readl(EPDC_IRQ) & 0xFFFF; + } else { + unprocessed_luts = __raw_readl(EPDC_IRQ1) | + ((u64)__raw_readl(EPDC_IRQ2) << 32); + if (format_p5n) + unprocessed_luts &= 0xFFFF; + } + + /* + * Note on unprocessed_luts: There is a race condition + * where a LUT completes, but has not been processed by + * IRQ handler workqueue, and then a new update request + * attempts to use that LUT. We prevent that here by + * ensuring that the LUT we choose doesn't have its IRQ + * bit set (indicating it has completed but not yet been + * processed). + */ + used_luts = luts_status | unprocessed_luts; + + /* + * Selecting a LUT to minimize incidence of TCE Underrun Error + * -------------------------------------------------------- + * We want to find the lowest order LUT that is of greater + * order than all other active LUTs. If highest order LUT + * is active, then we want to choose the lowest order + * available LUT. + * + * NOTE: For EPDC version 2.0 and later, TCE Underrun error + * bug is fixed, so it doesn't matter which LUT is used. + */ + + if ((fb_data->rev < 20) || format_p5n) { + *next_lut = fls64(used_luts); + if (*next_lut > 15) + *next_lut = ffz(used_luts); + } else { + if ((u32)used_luts != ~0UL) + *next_lut = ffz((u32)used_luts); + else if ((u32)(used_luts >> 32) != ~0UL) + *next_lut = ffz((u32)(used_luts >> 32)) + 32; + else + *next_lut = INVALID_LUT; + } + + if (used_luts & 0x8000) + return 1; + else + return 0; +} +#endif + +static inline bool epdc_is_working_buffer_busy(void) +{ + u32 val = __raw_readl(EPDC_STATUS); + bool is_busy = (val & EPDC_STATUS_WB_BUSY) ? true : false; + + return is_busy; +} + +static inline bool epdc_is_working_buffer_complete(void) +{ + u32 val = __raw_readl(EPDC_IRQ); + bool is_compl = (val & EPDC_IRQ_WB_CMPLT_IRQ) ? true : false; + + return is_compl; +} + +static inline bool epdc_is_lut_cancelled(void) +{ + u32 val = __raw_readl(EPDC_STATUS); + bool is_void = (val & EPDC_STATUS_UPD_VOID) ? true : false; + + return is_void; +} + +static inline bool epdc_is_collision(void) +{ + u32 val = __raw_readl(EPDC_IRQ); + return (val & EPDC_IRQ_LUT_COL_IRQ) ? true : false; +} + +static inline u64 epdc_get_colliding_luts(int rev) +{ + u32 val = __raw_readl(EPDC_STATUS_COL); + if (rev >= 20) + val |= (u64)__raw_readl(EPDC_STATUS_COL2) << 32; + return val; +} + +static void epdc_set_horizontal_timing(u32 horiz_start, u32 horiz_end, + u32 hsync_width, u32 hsync_line_length) +{ + u32 reg_val = + ((hsync_width << EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET) & + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK) + | ((hsync_line_length << EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET) & + EPDC_TCE_HSCAN1_LINE_SYNC_MASK); + __raw_writel(reg_val, EPDC_TCE_HSCAN1); + + reg_val = + ((horiz_start << EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET) & + EPDC_TCE_HSCAN2_LINE_BEGIN_MASK) + | ((horiz_end << EPDC_TCE_HSCAN2_LINE_END_OFFSET) & + EPDC_TCE_HSCAN2_LINE_END_MASK); + __raw_writel(reg_val, EPDC_TCE_HSCAN2); +} + +static void epdc_set_vertical_timing(u32 vert_start, u32 vert_end, + u32 vsync_width) +{ + u32 reg_val = + ((vert_start << EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET) & + EPDC_TCE_VSCAN_FRAME_BEGIN_MASK) + | ((vert_end << EPDC_TCE_VSCAN_FRAME_END_OFFSET) & + EPDC_TCE_VSCAN_FRAME_END_MASK) + | ((vsync_width << EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET) & + EPDC_TCE_VSCAN_FRAME_SYNC_MASK); + __raw_writel(reg_val, EPDC_TCE_VSCAN); +} + +static void epdc_init_settings(struct mxc_epdc_fb_data *fb_data) +{ + struct imx_epdc_fb_mode *epdc_mode = fb_data->cur_mode; + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 reg_val; + int num_ce; +#ifndef EPDC_STANDARD_MODE + int i; +#endif + int j; + unsigned char *bb_p; + + /* Enable clocks to access EPDC regs */ + clk_prepare_enable(fb_data->epdc_clk_axi); + clk_prepare_enable(fb_data->epdc_clk_pix); + + /* Reset */ + __raw_writel(EPDC_CTRL_SFTRST, EPDC_CTRL_SET); + while (!(__raw_readl(EPDC_CTRL) & EPDC_CTRL_CLKGATE)) + ; + __raw_writel(EPDC_CTRL_SFTRST, EPDC_CTRL_CLEAR); + + /* Enable clock gating (clear to enable) */ + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR); + while (__raw_readl(EPDC_CTRL) & (EPDC_CTRL_SFTRST | EPDC_CTRL_CLKGATE)) + ; + + /* EPDC_CTRL */ + reg_val = __raw_readl(EPDC_CTRL); + reg_val &= ~EPDC_CTRL_UPD_DATA_SWIZZLE_MASK; +#ifdef EPDC_STANDARD_MODE + reg_val |= EPDC_CTRL_UPD_DATA_SWIZZLE_ALL_BYTES_SWAP; +#else + reg_val |= EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP; +#endif + reg_val &= ~EPDC_CTRL_LUT_DATA_SWIZZLE_MASK; + reg_val |= EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP; + __raw_writel(reg_val, EPDC_CTRL_SET); + + /* EPDC_FORMAT - 2bit TFT and 4bit Buf pixel format */ + reg_val = EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT +#ifdef EPDC_STANDARD_MODE + | EPDC_FORMAT_WB_TYPE_WB_EXTERNAL16 +#endif + | EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N + | ((0x0 << EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET) & + EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK); + __raw_writel(reg_val, EPDC_FORMAT); + +#ifdef EPDC_STANDARD_MODE + reg_val = 0; + if (fb_data->waveform_is_advanced) { + reg_val = + ((EPDC_WB_FIELD_USAGE_PTS << EPDC_WB_FIELD_USAGE_OFFSET) & + EPDC_WB_FIELD_USAGE_MASK) + | ((0x8 << EPDC_WB_FIELD_FROM_OFFSET) & + EPDC_WB_FIELD_FROM_MASK) + | ((0x8 << EPDC_WB_FIELD_TO_OFFSET) & + EPDC_WB_FIELD_TO_MASK) + | ((0x1 << EPDC_WB_FIELD_LEN_OFFSET) & + EPDC_WB_FIELD_LEN_MASK); + } + __raw_writel(reg_val, EPDC_WB_FIELD3); +#endif + + /* EPDC_FIFOCTRL (disabled) */ + reg_val = + ((100 << EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK) + | ((200 << EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK) + | ((100 << EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK); + __raw_writel(reg_val, EPDC_FIFOCTRL); + + /* EPDC_TEMP - Use default temp to get index */ + epdc_set_temp(mxc_epdc_fb_get_temp_index(fb_data, DEFAULT_TEMP)); + + /* EPDC_RES */ + epdc_set_screen_res(epdc_mode->vmode->xres, epdc_mode->vmode->yres); + +#ifndef EPDC_STANDARD_MODE + /* EPDC_AUTOWV_LUT */ + /* Initialize all auto-wavefrom look-up values to 2 - GC16 */ + for (i = 0; i < 8; i++) + __raw_writel((2 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (i << EPDC_AUTOWV_LUT_ADDR_OFFSET), EPDC_AUTOWV_LUT); +#endif + + /* + * EPDC_TCE_CTRL + * VSCAN_HOLDOFF = 4 + * VCOM_MODE = MANUAL + * VCOM_VAL = 0 + * DDR_MODE = DISABLED + * LVDS_MODE_CE = DISABLED + * LVDS_MODE = DISABLED + * DUAL_SCAN = DISABLED + * SDDO_WIDTH = 8bit + * PIXELS_PER_SDCLK = 4 + */ + reg_val = + ((epdc_mode->vscan_holdoff << EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET) & + EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK) + | EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4; + __raw_writel(reg_val, EPDC_TCE_CTRL); + + /* EPDC_TCE_HSCAN */ + epdc_set_horizontal_timing(screeninfo->left_margin, + screeninfo->right_margin, + screeninfo->hsync_len, + screeninfo->hsync_len); + + /* EPDC_TCE_VSCAN */ + epdc_set_vertical_timing(screeninfo->upper_margin, + screeninfo->lower_margin, + screeninfo->vsync_len); + + /* EPDC_TCE_OE */ + reg_val = + ((epdc_mode->sdoed_width << EPDC_TCE_OE_SDOED_WIDTH_OFFSET) & + EPDC_TCE_OE_SDOED_WIDTH_MASK) + | ((epdc_mode->sdoed_delay << EPDC_TCE_OE_SDOED_DLY_OFFSET) & + EPDC_TCE_OE_SDOED_DLY_MASK) + | ((epdc_mode->sdoez_width << EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET) & + EPDC_TCE_OE_SDOEZ_WIDTH_MASK) + | ((epdc_mode->sdoez_delay << EPDC_TCE_OE_SDOEZ_DLY_OFFSET) & + EPDC_TCE_OE_SDOEZ_DLY_MASK); + __raw_writel(reg_val, EPDC_TCE_OE); + + /* EPDC_TCE_TIMING1 */ + __raw_writel(0x0, EPDC_TCE_TIMING1); + + /* EPDC_TCE_TIMING2 */ + reg_val = + ((epdc_mode->gdclk_hp_offs << EPDC_TCE_TIMING2_GDCLK_HP_OFFSET) & + EPDC_TCE_TIMING2_GDCLK_HP_MASK) + | ((epdc_mode->gdsp_offs << EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET) & + EPDC_TCE_TIMING2_GDSP_OFFSET_MASK); + __raw_writel(reg_val, EPDC_TCE_TIMING2); + + /* EPDC_TCE_TIMING3 */ + reg_val = + ((epdc_mode->gdoe_offs << EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET) & + EPDC_TCE_TIMING3_GDOE_OFFSET_MASK) + | ((epdc_mode->gdclk_offs << EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET) & + EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK); + __raw_writel(reg_val, EPDC_TCE_TIMING3); + + /* + * EPDC_TCE_SDCFG + * SDCLK_HOLD = 1 + * SDSHR = 1 + * NUM_CE = 1 + * SDDO_REFORMAT = FLIP_PIXELS + * SDDO_INVERT = DISABLED + * PIXELS_PER_CE = display horizontal resolution + */ + num_ce = epdc_mode->num_ce; + if (num_ce == 0) + num_ce = 1; + reg_val = EPDC_TCE_SDCFG_SDCLK_HOLD | EPDC_TCE_SDCFG_SDSHR + | ((num_ce << EPDC_TCE_SDCFG_NUM_CE_OFFSET) & + EPDC_TCE_SDCFG_NUM_CE_MASK) + | EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS + | ((epdc_mode->vmode->xres/num_ce << EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET) & + EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK); + __raw_writel(reg_val, EPDC_TCE_SDCFG); + + /* + * EPDC_TCE_GDCFG + * GDRL = 1 + * GDOE_MODE = 0; + * GDSP_MODE = 0; + */ + reg_val = EPDC_TCE_SDCFG_GDRL; + __raw_writel(reg_val, EPDC_TCE_GDCFG); + + /* + * EPDC_TCE_POLARITY + * SDCE_POL = ACTIVE LOW + * SDLE_POL = ACTIVE HIGH + * SDOE_POL = ACTIVE HIGH + * GDOE_POL = ACTIVE HIGH + * GDSP_POL = ACTIVE LOW + */ + reg_val = EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH + | EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH + | EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH; + __raw_writel(reg_val, EPDC_TCE_POLARITY); + + /* EPDC_IRQ_MASK */ + __raw_writel(EPDC_IRQ_TCE_UNDERRUN_IRQ, EPDC_IRQ_MASK); + + /* + * EPDC_GPIO + * PWRCOM = ? + * PWRCTRL = ? + * BDR = ? + */ + reg_val = ((0 << EPDC_GPIO_PWRCTRL_OFFSET) & EPDC_GPIO_PWRCTRL_MASK) + | ((0 << EPDC_GPIO_BDR_OFFSET) & EPDC_GPIO_BDR_MASK); + __raw_writel(reg_val, EPDC_GPIO); + + __raw_writel(fb_data->waveform_buffer_phys, EPDC_WVADDR); + __raw_writel(fb_data->working_buffer_phys, EPDC_WB_ADDR); + __raw_writel(fb_data->working_buffer_phys, EPDC_WB_ADDR_TCE); + + bb_p = (unsigned char *)fb_data->virt_addr_black; + for (j = 0; j < fb_data->cur_mode->vmode->xres * fb_data->cur_mode->vmode->yres; j++) { + *bb_p = 0x0; + bb_p++; + } + + /* Disable clock */ + clk_disable_unprepare(fb_data->epdc_clk_axi); + clk_disable_unprepare(fb_data->epdc_clk_pix); +} + +static void epdc_powerup(struct mxc_epdc_fb_data *fb_data) +{ + int ret = 0; + mutex_lock(&fb_data->power_mutex); + + /* + * If power down request is pending, clear + * powering_down to cancel the request. + */ + if (fb_data->powering_down) + fb_data->powering_down = false; + + if (fb_data->power_state == POWER_STATE_ON) { + mutex_unlock(&fb_data->power_mutex); + return; + } + + dev_dbg(fb_data->dev, "EPDC Powerup\n"); + + fb_data->updates_active = true; + + /* Enable the v3p3 regulator */ + ret = regulator_enable(fb_data->v3p3_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable V3P3 regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + return; + } + + msleep(1); + + pm_runtime_get_sync(fb_data->dev); + + /* Enable clocks to EPDC */ + clk_prepare_enable(fb_data->epdc_clk_axi); + clk_prepare_enable(fb_data->epdc_clk_pix); + + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR); + + /* Enable power to the EPD panel */ + ret = regulator_enable(fb_data->display_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable DISPLAY regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + return; + } + ret = regulator_enable(fb_data->vcom_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable VCOM regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + return; + } + + fb_data->power_state = POWER_STATE_ON; + + mutex_unlock(&fb_data->power_mutex); +} + +static void epdc_powerdown(struct mxc_epdc_fb_data *fb_data) +{ + mutex_lock(&fb_data->power_mutex); + + /* If powering_down has been cleared, a powerup + * request is pre-empting this powerdown request. + */ + if (!fb_data->powering_down + || (fb_data->power_state == POWER_STATE_OFF)) { + mutex_unlock(&fb_data->power_mutex); + return; + } + + dev_dbg(fb_data->dev, "EPDC Powerdown\n"); + + /* Disable power to the EPD panel */ + regulator_disable(fb_data->vcom_regulator); + regulator_disable(fb_data->display_regulator); + + /* Disable clocks to EPDC */ + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_SET); + clk_disable_unprepare(fb_data->epdc_clk_pix); + clk_disable_unprepare(fb_data->epdc_clk_axi); + + pm_runtime_put_sync_suspend(fb_data->dev); + + /* turn off the V3p3 */ + regulator_disable(fb_data->v3p3_regulator); + + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + + if (fb_data->wait_for_powerdown) { + fb_data->wait_for_powerdown = false; + complete(&fb_data->powerdown_compl); + } + + mutex_unlock(&fb_data->power_mutex); +} + +static void epdc_init_sequence(struct mxc_epdc_fb_data *fb_data) +{ + /* Initialize EPDC, passing pointer to EPDC registers */ + epdc_init_settings(fb_data); + + fb_data->in_init = true; + epdc_powerup(fb_data); + draw_mode0(fb_data); + /* Force power down event */ + fb_data->powering_down = true; + epdc_powerdown(fb_data); + fb_data->updates_active = false; +} + +static int mxc_epdc_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + + if (offset < info->fix.smem_len) { + /* mapping framebuffer memory */ + len = info->fix.smem_len - offset; + vma->vm_pgoff = (info->fix.smem_start + offset) >> PAGE_SHIFT; + } else + return -EINVAL; + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) + return -EINVAL; + + /* make buffers bufferable */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(info->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + } + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxc_epdc_fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, struct fb_info *info) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + val = _chan_to_field(red, &info->var.red); + val |= _chan_to_field(green, &info->var.green); + val |= _chan_to_field(blue, &info->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +static int mxc_epdc_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info) +{ + int count, index, r; + u16 *red, *green, *blue, *transp; + u16 trans = 0xffff; + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + int i; + + dev_dbg(fb_data->dev, "setcmap\n"); + + if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) { + /* Only support an 8-bit, 256 entry lookup */ + if (cmap->len != 256) + return 1; + + mxc_epdc_fb_flush_updates(fb_data); + + mutex_lock(&fb_data->pxp_mutex); + /* + * Store colormap in pxp_conf structure for later transmit + * to PxP during update process to convert gray pixels. + * + * Since red=blue=green for pseudocolor visuals, we can + * just use red values. + */ + for (i = 0; i < 256; i++) + fb_data->pxp_conf.proc_data.lut_map[i] = cmap->red[i] & 0xFF; + + fb_data->pxp_conf.proc_data.lut_map_updated = true; + + mutex_unlock(&fb_data->pxp_mutex); + } else { + red = cmap->red; + green = cmap->green; + blue = cmap->blue; + transp = cmap->transp; + index = cmap->start; + + for (count = 0; count < cmap->len; count++) { + if (transp) + trans = *transp++; + r = mxc_epdc_fb_setcolreg(index++, *red++, *green++, *blue++, + trans, info); + if (r != 0) + return r; + } + } + + return 0; +} + +static void adjust_coordinates(u32 xres, u32 yres, u32 rotation, + struct mxcfb_rect *update_region, struct mxcfb_rect *adj_update_region) +{ + u32 temp; + + /* If adj_update_region == NULL, pass result back in update_region */ + /* If adj_update_region == valid, use it to pass back result */ + if (adj_update_region) + switch (rotation) { + case FB_ROTATE_UR: + adj_update_region->top = update_region->top; + adj_update_region->left = update_region->left; + adj_update_region->width = update_region->width; + adj_update_region->height = update_region->height; + break; + case FB_ROTATE_CW: + adj_update_region->top = update_region->left; + adj_update_region->left = yres - + (update_region->top + update_region->height); + adj_update_region->width = update_region->height; + adj_update_region->height = update_region->width; + break; + case FB_ROTATE_UD: + adj_update_region->width = update_region->width; + adj_update_region->height = update_region->height; + adj_update_region->top = yres - + (update_region->top + update_region->height); + adj_update_region->left = xres - + (update_region->left + update_region->width); + break; + case FB_ROTATE_CCW: + adj_update_region->left = update_region->top; + adj_update_region->top = xres - + (update_region->left + update_region->width); + adj_update_region->width = update_region->height; + adj_update_region->height = update_region->width; + break; + } + else + switch (rotation) { + case FB_ROTATE_UR: + /* No adjustment needed */ + break; + case FB_ROTATE_CW: + temp = update_region->top; + update_region->top = update_region->left; + update_region->left = yres - + (temp + update_region->height); + temp = update_region->width; + update_region->width = update_region->height; + update_region->height = temp; + break; + case FB_ROTATE_UD: + update_region->top = yres - + (update_region->top + update_region->height); + update_region->left = xres - + (update_region->left + update_region->width); + break; + case FB_ROTATE_CCW: + temp = update_region->left; + update_region->left = update_region->top; + update_region->top = xres - + (temp + update_region->width); + temp = update_region->width; + update_region->width = update_region->height; + update_region->height = temp; + break; + } +} + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxc_epdc_fb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + if (var->grayscale) + fix->visual = FB_VISUAL_STATIC_PSEUDOCOLOR; + else + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +/* + * This routine actually sets the video mode. It's in here where we + * the hardware state info->par and fix which can be affected by the + * change in par. For this driver it doesn't do much. + * + */ +static int mxc_epdc_fb_set_par(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &pxp_conf->proc_data; + struct fb_var_screeninfo *screeninfo = &fb_data->info.var; + struct imx_epdc_fb_mode *epdc_modes = fb_data->pdata->epdc_mode; + int i; + int ret; + __u32 xoffset_old, yoffset_old; + + /* + * Can't change the FB parameters until current updates have completed. + * This function returns when all active updates are done. + */ + mxc_epdc_fb_flush_updates(fb_data); + + mutex_lock(&fb_data->queue_mutex); + /* + * Set all screeninfo except for xoffset/yoffset + * Subsequent call to pan_display will handle those. + */ + xoffset_old = fb_data->epdc_fb_var.xoffset; + yoffset_old = fb_data->epdc_fb_var.yoffset; + fb_data->epdc_fb_var = *screeninfo; + fb_data->epdc_fb_var.xoffset = xoffset_old; + fb_data->epdc_fb_var.yoffset = yoffset_old; + mutex_unlock(&fb_data->queue_mutex); + + mutex_lock(&fb_data->pxp_mutex); + + /* + * Update PxP config data (used to process FB regions for updates) + * based on FB info and processing tasks required + */ + + /* Initialize non-channel-specific PxP parameters */ + proc_data->drect.left = proc_data->srect.left = 0; + proc_data->drect.top = proc_data->srect.top = 0; + proc_data->drect.width = proc_data->srect.width = screeninfo->xres; + proc_data->drect.height = proc_data->srect.height = screeninfo->yres; + proc_data->scaling = 0; + proc_data->hflip = 0; + proc_data->vflip = 0; + proc_data->rotate = screeninfo->rotate; + proc_data->bgcolor = 0; + proc_data->overlay_state = 0; + proc_data->lut_transform = PXP_LUT_NONE; + + /* + * configure S0 channel parameters + * Parameters should match FB format/width/height + */ + if (screeninfo->grayscale) + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_GREY; + else { + switch (screeninfo->bits_per_pixel) { + case 16: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + break; + case 24: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB24; + break; + case 32: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_XRGB32; + break; + default: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + break; + } + } + pxp_conf->s0_param.width = screeninfo->xres_virtual; + pxp_conf->s0_param.stride = (screeninfo->bits_per_pixel * pxp_conf->s0_param.width) >> 3; + pxp_conf->s0_param.height = screeninfo->yres; + pxp_conf->s0_param.color_key = -1; + pxp_conf->s0_param.color_key_enable = false; + + /* + * Initialize Output channel parameters + * Output is Y-only greyscale + * Output width/height will vary based on update region size + */ + pxp_conf->out_param.width = screeninfo->xres; + pxp_conf->out_param.height = screeninfo->yres; + pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GREY; + + mutex_unlock(&fb_data->pxp_mutex); + + /* + * If HW not yet initialized, check to see if we are being sent + * an initialization request. + */ + if (!fb_data->hw_ready) { + struct fb_videomode mode; + u32 xres_temp; + + fb_var_to_videomode(&mode, screeninfo); + + /* When comparing requested fb mode, + we need to use unrotated dimensions */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres_temp = mode.xres; + mode.xres = mode.yres; + mode.yres = xres_temp; + } + + /* + * If requested video mode does not match current video + * mode, search for a matching panel. + */ + if (fb_data->cur_mode && + !fb_mode_is_equal(fb_data->cur_mode->vmode, + &mode)) { + bool found_match = false; + + /* Match videomode against epdc modes */ + for (i = 0; i < fb_data->pdata->num_modes; i++) { + if (!fb_mode_is_equal(epdc_modes[i].vmode, + &mode)) + continue; + fb_data->cur_mode = &epdc_modes[i]; + found_match = true; + break; + } + + if (!found_match) { + dev_err(fb_data->dev, + "Failed to match requested " + "video mode\n"); + return EINVAL; + } + } + + /* Found a match - Grab timing params */ + screeninfo->left_margin = mode.left_margin; + screeninfo->right_margin = mode.right_margin; + screeninfo->upper_margin = mode.upper_margin; + screeninfo->lower_margin = mode.lower_margin; + screeninfo->hsync_len = mode.hsync_len; + screeninfo->vsync_len = mode.vsync_len; + + fb_data->hw_initializing = true; + + /* Initialize EPDC settings and init panel */ + ret = + mxc_epdc_fb_init_hw((struct fb_info *)fb_data); + if (ret) { + dev_err(fb_data->dev, + "Failed to load panel waveform data\n"); + return ret; + } + } + + /* + * EOF sync delay (in us) should be equal to the vscan holdoff time + * VSCAN_HOLDOFF time = (VSCAN_HOLDOFF value + 1) * Vertical lines + * Add 25us for additional margin + */ + fb_data->eof_sync_period = (fb_data->cur_mode->vscan_holdoff + 1) * + 1000000/(fb_data->cur_mode->vmode->refresh * + (fb_data->cur_mode->vmode->upper_margin + + fb_data->cur_mode->vmode->yres + + fb_data->cur_mode->vmode->lower_margin + + fb_data->cur_mode->vmode->vsync_len)) + 25; + + mxc_epdc_fb_set_fix(info); + + return 0; +} + +static int mxc_epdc_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + + if (!var->xres) + var->xres = 1; + if (!var->yres) + var->yres = 1; + + if (var->xres_virtual < var->xoffset + var->xres) + var->xres_virtual = var->xoffset + var->xres; + if (var->yres_virtual < var->yoffset + var->yres) + var->yres_virtual = var->yoffset + var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16) && (var->bits_per_pixel != 8)) + var->bits_per_pixel = default_bpp; + + switch (var->bits_per_pixel) { + case 8: + if (var->grayscale != 0) { + /* + * For 8-bit grayscale, R, G, and B offset are equal. + * + */ + var->red.length = 8; + var->red.offset = 0; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 0; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + } else { + var->red.length = 3; + var->red.offset = 5; + var->red.msb_right = 0; + + var->green.length = 3; + var->green.offset = 2; + var->green.msb_right = 0; + + var->blue.length = 2; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + } + break; + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + switch (var->rotate) { + case FB_ROTATE_UR: + case FB_ROTATE_UD: + var->xres = fb_data->native_width; + var->yres = fb_data->native_height; + break; + case FB_ROTATE_CW: + case FB_ROTATE_CCW: + var->xres = fb_data->native_height; + var->yres = fb_data->native_width; + break; + default: + /* Invalid rotation value */ + var->rotate = 0; + dev_dbg(fb_data->dev, "Invalid rotation request\n"); + return -EINVAL; + } + + var->xres_virtual = ALIGN(var->xres, 32); + var->yres_virtual = ALIGN(var->yres, 128) * fb_data->num_screens; + + var->height = -1; + var->width = -1; + + return 0; +} + +static void mxc_epdc_fb_set_waveform_modes(struct mxcfb_waveform_modes *modes, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + mutex_lock(&fb_data->queue_mutex); + + memcpy(&fb_data->wv_modes, modes, sizeof(struct mxcfb_waveform_modes)); + + /* Set flag to ensure that new waveform modes + * are programmed into EPDC before next update */ + fb_data->wv_modes_update = true; + + mutex_unlock(&fb_data->queue_mutex); +} + +static int mxc_epdc_fb_get_temp_index(struct mxc_epdc_fb_data *fb_data, int temp) +{ + int i; + int index = -1; + + if (fb_data->trt_entries == 0) { + dev_err(fb_data->dev, + "No TRT exists...using default temp index\n"); + return DEFAULT_TEMP_INDEX; + } + + /* Search temperature ranges for a match */ + for (i = 0; i < fb_data->trt_entries - 1; i++) { + if ((temp >= fb_data->temp_range_bounds[i]) + && (temp < fb_data->temp_range_bounds[i+1])) { + index = i; + break; + } + } + + if (index < 0) { + dev_err(fb_data->dev, + "No TRT index match...using default temp index\n"); + return DEFAULT_TEMP_INDEX; + } + + dev_dbg(fb_data->dev, "Using temperature index %d\n", index); + + return index; +} + +static int mxc_epdc_fb_set_temperature(int temperature, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + /* Store temp index. Used later when configuring updates. */ + mutex_lock(&fb_data->queue_mutex); + fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, temperature); + mutex_unlock(&fb_data->queue_mutex); + + return 0; +} + +static int mxc_epdc_fb_set_auto_update(u32 auto_mode, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + dev_dbg(fb_data->dev, "Setting auto update mode to %d\n", auto_mode); + + if ((auto_mode == AUTO_UPDATE_MODE_AUTOMATIC_MODE) + || (auto_mode == AUTO_UPDATE_MODE_REGION_MODE)) + fb_data->auto_mode = auto_mode; + else { + dev_err(fb_data->dev, "Invalid auto update mode parameter.\n"); + return -EINVAL; + } + + return 0; +} + +static int mxc_epdc_fb_set_upd_scheme(u32 upd_scheme, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + dev_dbg(fb_data->dev, "Setting optimization level to %d\n", upd_scheme); + + /* + * Can't change the scheme until current updates have completed. + * This function returns when all active updates are done. + */ + mxc_epdc_fb_flush_updates(fb_data); + + if ((upd_scheme == UPDATE_SCHEME_SNAPSHOT) + || (upd_scheme == UPDATE_SCHEME_QUEUE) + || (upd_scheme == UPDATE_SCHEME_QUEUE_AND_MERGE)) + fb_data->upd_scheme = upd_scheme; + else { + dev_err(fb_data->dev, "Invalid update scheme specified.\n"); + return -EINVAL; + } + + return 0; +} + +static void copy_before_process(struct mxc_epdc_fb_data *fb_data, + struct update_data_list *upd_data_list) +{ + struct mxcfb_update_data *upd_data = + &upd_data_list->update_desc->upd_data; + int i; + unsigned char *temp_buf_ptr = fb_data->virt_addr_copybuf; + unsigned char *src_ptr; + struct mxcfb_rect *src_upd_region; + int temp_buf_stride; + int src_stride; + int bpp = fb_data->epdc_fb_var.bits_per_pixel; + int left_offs, right_offs; + int x_trailing_bytes, y_trailing_bytes; + int alt_buf_offset; + + /* Set source buf pointer based on input source, panning, etc. */ + if (upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) { + src_upd_region = &upd_data->alt_buffer_data.alt_update_region; + src_stride = + upd_data->alt_buffer_data.width * bpp/8; + alt_buf_offset = upd_data->alt_buffer_data.phys_addr - + fb_data->info.fix.smem_start; + src_ptr = fb_data->info.screen_base + alt_buf_offset + + src_upd_region->top * src_stride; + } else { + src_upd_region = &upd_data->update_region; + src_stride = fb_data->epdc_fb_var.xres_virtual * bpp/8; + src_ptr = fb_data->info.screen_base + fb_data->fb_offset + + src_upd_region->top * src_stride; + } + + temp_buf_stride = ALIGN(src_upd_region->width, 8) * bpp/8; + left_offs = src_upd_region->left * bpp/8; + right_offs = src_upd_region->width * bpp/8; + x_trailing_bytes = (ALIGN(src_upd_region->width, 8) + - src_upd_region->width) * bpp/8; + + for (i = 0; i < src_upd_region->height; i++) { + /* Copy the full line */ + memcpy(temp_buf_ptr, src_ptr + left_offs, + src_upd_region->width * bpp/8); + + /* Clear any unwanted pixels at the end of each line */ + if (src_upd_region->width & 0x7) { + memset(temp_buf_ptr + right_offs, 0x0, + x_trailing_bytes); + } + + temp_buf_ptr += temp_buf_stride; + src_ptr += src_stride; + } + + /* Clear any unwanted pixels at the bottom of the end of each line */ + if (src_upd_region->height & 0x7) { + y_trailing_bytes = (ALIGN(src_upd_region->height, 8) + - src_upd_region->height) * + ALIGN(src_upd_region->width, 8) * bpp/8; + memset(temp_buf_ptr, 0x0, y_trailing_bytes); + } +} + +/* Before every update to panel, we should call this + * function to update the working buffer first. + */ +static int epdc_working_buffer_update(struct mxc_epdc_fb_data *fb_data, + struct update_data_list *upd_data_list, + struct mxcfb_rect *update_region) +{ + struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; + u32 wv_mode = upd_data_list->update_desc->upd_data.waveform_mode; + int ret = 0; + u32 hist_stat; + struct update_desc_list *upd_desc_list; + + ret = pxp_wfe_a_process(fb_data, update_region, upd_data_list); + if (ret) { + dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) { + epdc_powerup(fb_data); + } + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_complete_update(fb_data, &fb_data->hist_status); + if (ret) { + dev_err(fb_data->dev, "Unable to complete PxP update task: main process\n"); + return ret; + } + + upd_desc_list = upd_data_list->update_desc; + if (fb_data->epdc_wb_mode && + (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) { + hist_stat = fb_data->hist_status; + + if (hist_stat & 0x1) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_du; + else if (hist_stat & 0x2) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc4; + else if (hist_stat & 0x4) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc8; + else if (hist_stat & 0x8) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc16; + else + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc32; + + dev_dbg(fb_data->dev, "hist_stat = 0x%x, new waveform = 0x%x\n", + hist_stat, upd_desc_list->upd_data.waveform_mode); + } + + if (proc_data->detection_only == 1) { + dev_dbg(fb_data->dev, "collision detection only, no real update\n"); + return 0; + } + + if (fb_data->col_info.pixel_cnt) { + dev_dbg(fb_data->dev, "collision detected, can not do REAGl/-D\n"); + return 0; + } + + /* for REAGL/-D Processing */ + if (wv_mode == WAVEFORM_MODE_GLR16 + || wv_mode == WAVEFORM_MODE_GLD16) { + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_wfe_b_process_update(fb_data, update_region); + if (ret) { + dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) { + epdc_powerup(fb_data); + } + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_complete_update(fb_data, &hist_stat); + if (ret) { + dev_err(fb_data->dev, "Unable to complete PxP update task: reagl/-d process\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + } + + return 0; +} + +static int epdc_process_update(struct update_data_list *upd_data_list, + struct mxc_epdc_fb_data *fb_data) +{ + struct mxcfb_rect *src_upd_region; /* Region of src buffer for update */ + struct mxcfb_rect pxp_upd_region; + u32 src_width, src_height; + u32 offset_from_4, bytes_per_pixel; + u32 post_rotation_xcoord, post_rotation_ycoord, width_pxp_blocks; + u32 pxp_input_offs, pxp_output_offs, pxp_output_shift; + u32 hist_stat = 0; + int width_unaligned, height_unaligned; + bool input_unaligned = false; + bool line_overflow = false; + int pix_per_line_added; + bool use_temp_buf = false; + struct mxcfb_rect temp_buf_upd_region; + struct update_desc_list *upd_desc_list = upd_data_list->update_desc; + + int ret; + + /* + * Gotta do a whole bunch of buffer ptr manipulation to + * work around HW restrictions for PxP & EPDC + * Note: Applies to pre-2.0 versions of EPDC/PxP + */ + + /* + * Are we using FB or an alternate (overlay) + * buffer for source of update? + */ + if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) { + src_width = upd_desc_list->upd_data.alt_buffer_data.width; + src_height = upd_desc_list->upd_data.alt_buffer_data.height; + src_upd_region = &upd_desc_list->upd_data.alt_buffer_data.alt_update_region; + } else { + src_width = fb_data->epdc_fb_var.xres_virtual; + src_height = fb_data->epdc_fb_var.yres; + src_upd_region = &upd_desc_list->upd_data.update_region; + } + + bytes_per_pixel = fb_data->epdc_fb_var.bits_per_pixel/8; + + /* + * SW workaround for PxP limitation (for pre-v2.0 HW) + * + * There are 3 cases where we cannot process the update data + * directly from the input buffer: + * + * 1) PxP must process 8x8 pixel blocks, and all pixels in each block + * are considered for auto-waveform mode selection. If the + * update region is not 8x8 aligned, additional unwanted pixels + * will be considered in auto-waveform mode selection. + * + * 2) PxP input must be 32-bit aligned, so any update + * address not 32-bit aligned must be shifted to meet the + * 32-bit alignment. The PxP will thus end up processing pixels + * outside of the update region to satisfy this alignment restriction, + * which can affect auto-waveform mode selection. + * + * 3) If input fails 32-bit alignment, and the resulting expansion + * of the processed region would add at least 8 pixels more per + * line than the original update line width, the EPDC would + * cause screen artifacts by incorrectly handling the 8+ pixels + * at the end of each line. + * + * Workaround is to copy from source buffer into a temporary + * buffer, which we pad with zeros to match the 8x8 alignment + * requirement. This temp buffer becomes the input to the PxP. + */ + width_unaligned = src_upd_region->width & 0x7; + height_unaligned = src_upd_region->height & 0x7; + + offset_from_4 = src_upd_region->left & 0x3; + input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ? + true : false; + + pix_per_line_added = (offset_from_4 * bytes_per_pixel % 4) + / bytes_per_pixel; + if ((((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) || + fb_data->epdc_fb_var.rotate == FB_ROTATE_UD)) && + (ALIGN(src_upd_region->width, 8) < + ALIGN(src_upd_region->width + pix_per_line_added, 8))) + line_overflow = true; + + /* Grab pxp_mutex here so that we protect access + * to copybuf in addition to the PxP structures */ + mutex_lock(&fb_data->pxp_mutex); + + if (((((width_unaligned || height_unaligned || input_unaligned) && + (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) + || line_overflow) && (fb_data->rev < 20)) || + fb_data->restrict_width) { + dev_dbg(fb_data->dev, "Copying update before processing.\n"); + + /* Update to reflect what the new source buffer will be */ + src_width = ALIGN(src_upd_region->width, 8); + src_height = ALIGN(src_upd_region->height, 8); + + copy_before_process(fb_data, upd_data_list); + + /* + * src_upd_region should now describe + * the new update buffer attributes. + */ + temp_buf_upd_region.left = 0; + temp_buf_upd_region.top = 0; + temp_buf_upd_region.width = src_upd_region->width; + temp_buf_upd_region.height = src_upd_region->height; + src_upd_region = &temp_buf_upd_region; + + use_temp_buf = true; + } + + /* + * For pre-2.0 HW, input address must be 32-bit aligned + * Compute buffer offset to account for this PxP limitation + */ + offset_from_4 = src_upd_region->left & 0x3; + input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ? + true : false; + if ((fb_data->rev < 20) && input_unaligned) { + /* Leave a gap between PxP input addr and update region pixels */ + pxp_input_offs = + (src_upd_region->top * src_width + src_upd_region->left) + * bytes_per_pixel & 0xFFFFFFFC; + /* Update region left changes to reflect relative position to input ptr */ + pxp_upd_region.left = (offset_from_4 * bytes_per_pixel % 4) + / bytes_per_pixel; + } else { + pxp_input_offs = + (src_upd_region->top * src_width + src_upd_region->left) + * bytes_per_pixel; + pxp_upd_region.left = 0; + } + + pxp_upd_region.top = 0; + + /* + * For version 2.0 and later of EPDC & PxP, if no rotation, we don't + * need to align width & height (rotation always requires 8-pixel + * width & height alignment, per PxP limitations) + */ + if ((fb_data->epdc_fb_var.rotate == 0) && (fb_data->rev >= 20)) { + pxp_upd_region.width = src_upd_region->width; + pxp_upd_region.height = src_upd_region->height; + } else { + /* Update region dimensions to meet 8x8 pixel requirement */ + pxp_upd_region.width = ALIGN(src_upd_region->width + pxp_upd_region.left, 8); + pxp_upd_region.height = ALIGN(src_upd_region->height, 8); + } + + switch (fb_data->epdc_fb_var.rotate) { + case FB_ROTATE_UR: + default: + post_rotation_xcoord = pxp_upd_region.left; + post_rotation_ycoord = pxp_upd_region.top; + width_pxp_blocks = pxp_upd_region.width; + break; + case FB_ROTATE_CW: + width_pxp_blocks = pxp_upd_region.height; + post_rotation_xcoord = width_pxp_blocks - src_upd_region->height; + post_rotation_ycoord = pxp_upd_region.left; + break; + case FB_ROTATE_UD: + width_pxp_blocks = pxp_upd_region.width; + post_rotation_xcoord = width_pxp_blocks - src_upd_region->width - pxp_upd_region.left; + post_rotation_ycoord = pxp_upd_region.height - src_upd_region->height - pxp_upd_region.top; + break; + case FB_ROTATE_CCW: + width_pxp_blocks = pxp_upd_region.height; + post_rotation_xcoord = pxp_upd_region.top; + post_rotation_ycoord = pxp_upd_region.width - src_upd_region->width - pxp_upd_region.left; + break; + } + + /* Update region start coord to force PxP to process full 8x8 regions */ + pxp_upd_region.top &= ~0x7; + pxp_upd_region.left &= ~0x7; + + if (fb_data->rev < 20) { + pxp_output_shift = ALIGN(post_rotation_xcoord, 8) + - post_rotation_xcoord; + + pxp_output_offs = post_rotation_ycoord * width_pxp_blocks + + pxp_output_shift; + + upd_desc_list->epdc_offs = ALIGN(pxp_output_offs, 8); + } else { + pxp_output_shift = 0; + pxp_output_offs = post_rotation_ycoord * width_pxp_blocks + + post_rotation_xcoord; + + upd_desc_list->epdc_offs = pxp_output_offs; + } + + upd_desc_list->epdc_stride = width_pxp_blocks; + + /* Source address either comes from alternate buffer + provided in update data, or from the framebuffer. */ + if (use_temp_buf) + sg_dma_address(&fb_data->sg[0]) = + fb_data->phys_addr_copybuf; + else if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) + sg_dma_address(&fb_data->sg[0]) = + upd_desc_list->upd_data.alt_buffer_data.phys_addr + + pxp_input_offs; + else { + sg_dma_address(&fb_data->sg[0]) = + fb_data->info.fix.smem_start + fb_data->fb_offset + + pxp_input_offs; + sg_set_page(&fb_data->sg[0], + virt_to_page(fb_data->info.screen_base), + fb_data->info.fix.smem_len, + offset_in_page(fb_data->info.screen_base)); + } + + /* Update sg[1] to point to output of PxP proc task */ + sg_dma_address(&fb_data->sg[1]) = upd_data_list->phys_addr + + pxp_output_shift; + sg_set_page(&fb_data->sg[1], virt_to_page(upd_data_list->virt_addr), + fb_data->max_pix_size, + offset_in_page(upd_data_list->virt_addr)); + + /* + * Set PxP LUT transform type based on update flags. + */ + fb_data->pxp_conf.proc_data.lut_transform = 0; + if (upd_desc_list->upd_data.flags & EPDC_FLAG_ENABLE_INVERSION) + fb_data->pxp_conf.proc_data.lut_transform |= PXP_LUT_INVERT; + if (upd_desc_list->upd_data.flags & EPDC_FLAG_FORCE_MONOCHROME) + fb_data->pxp_conf.proc_data.lut_transform |= + PXP_LUT_BLACK_WHITE; + if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_CMAP) + fb_data->pxp_conf.proc_data.lut_transform |= + PXP_LUT_USE_CMAP; + + /* + * Toggle inversion processing if 8-bit + * inverted is the current pixel format. + */ + if (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT_INVERTED) + fb_data->pxp_conf.proc_data.lut_transform ^= PXP_LUT_INVERT; + +#ifdef USE_PS_AS_OUTPUT + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_legacy_process(fb_data, src_width, src_height, + &pxp_upd_region); + if (ret) { + dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) { + epdc_powerup(fb_data); + } + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_complete_update(fb_data, &hist_stat); + if (ret) { + dev_err(fb_data->dev, "Unable to complete PxP update task: pre_prcoess.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } +#endif + pr_debug(" upd_data.dither_mode %d \n", upd_desc_list->upd_data.dither_mode); + fb_data->pxp_conf.proc_data.dither_mode = 0; + + /* Dithering */ + if ((EPDC_FLAG_USE_DITHERING_PASSTHROUGH < upd_desc_list->upd_data.dither_mode) && + (upd_desc_list->upd_data.dither_mode < EPDC_FLAG_USE_DITHERING_MAX)) { + + fb_data->pxp_conf.proc_data.dither_mode = upd_desc_list->upd_data.dither_mode; + fb_data->pxp_conf.proc_data.quant_bit = upd_desc_list->upd_data.quant_bit; + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_process_dithering(fb_data, &pxp_upd_region); + if (ret) { + dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) { + epdc_powerup(fb_data); + } + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_complete_update(fb_data, &hist_stat); + if (ret) { + dev_err(fb_data->dev, "Unable to complete PxP update task: dithering process\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + } + + /* Regal D Processing */ + fb_data->pxp_conf.proc_data.reagl_d_en = + (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_GLD16); + + mutex_unlock(&fb_data->pxp_mutex); + + /* Update waveform mode from PxP histogram results */ + if ((fb_data->rev <= 20) && + (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) { + if (hist_stat & 0x1) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_du; + else if (hist_stat & 0x2) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc4; + else if (hist_stat & 0x4) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc8; + else if (hist_stat & 0x8) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc16; + else + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc32; + + dev_dbg(fb_data->dev, "hist_stat = 0x%x, new waveform = 0x%x\n", + hist_stat, upd_desc_list->upd_data.waveform_mode); + } + + return 0; +} + +static int epdc_submit_merge(struct update_desc_list *upd_desc_list, + struct update_desc_list *update_to_merge, + struct mxc_epdc_fb_data *fb_data) +{ + struct mxcfb_update_data *a, *b; + struct mxcfb_rect *arect, *brect; + struct mxcfb_rect combine; + bool use_flags = false; + + a = &upd_desc_list->upd_data; + b = &update_to_merge->upd_data; + arect = &upd_desc_list->upd_data.update_region; + brect = &update_to_merge->upd_data.update_region; + + /* Do not merge a dry-run collision test update */ + if ((a->flags & EPDC_FLAG_TEST_COLLISION) || + (b->flags & EPDC_FLAG_TEST_COLLISION)) + return MERGE_BLOCK; + + /* + * Updates with different flags must be executed sequentially. + * Halt the merge process to ensure this. + */ + if (a->flags != b->flags) { + /* + * Special exception: if update regions are identical, + * we may be able to merge them. + */ + if ((arect->left != brect->left) || + (arect->top != brect->top) || + (arect->width != brect->width) || + (arect->height != brect->height)) + return MERGE_BLOCK; + + use_flags = true; + } + + if (a->update_mode != b->update_mode) + a->update_mode = UPDATE_MODE_FULL; + + if (a->waveform_mode != b->waveform_mode) + a->waveform_mode = WAVEFORM_MODE_AUTO; + + if (arect->left > (brect->left + brect->width) || + brect->left > (arect->left + arect->width) || + arect->top > (brect->top + brect->height) || + brect->top > (arect->top + arect->height)) + return MERGE_FAIL; + + combine.left = arect->left < brect->left ? arect->left : brect->left; + combine.top = arect->top < brect->top ? arect->top : brect->top; + combine.width = (arect->left + arect->width) > + (brect->left + brect->width) ? + (arect->left + arect->width - combine.left) : + (brect->left + brect->width - combine.left); + combine.height = (arect->top + arect->height) > + (brect->top + brect->height) ? + (arect->top + arect->height - combine.top) : + (brect->top + brect->height - combine.top); + + /* Don't merge if combined width exceeds max width */ + if (fb_data->restrict_width) { + u32 max_width = EPDC_V2_MAX_UPDATE_WIDTH; + u32 combined_width = combine.width; + if (fb_data->epdc_fb_var.rotate != FB_ROTATE_UR) + max_width -= EPDC_V2_ROTATION_ALIGNMENT; + if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_CW) || + (fb_data->epdc_fb_var.rotate == FB_ROTATE_CCW)) + combined_width = combine.height; + if (combined_width > max_width) + return MERGE_FAIL; + } + + *arect = combine; + + /* Use flags of the later update */ + if (use_flags) + a->flags = b->flags; + + /* Merge markers */ + list_splice_tail(&update_to_merge->upd_marker_list, + &upd_desc_list->upd_marker_list); + + /* Merged update should take on the earliest order */ + upd_desc_list->update_order = + (upd_desc_list->update_order > update_to_merge->update_order) ? + upd_desc_list->update_order : update_to_merge->update_order; + + return MERGE_OK; +} + +static void epdc_submit_work_func(struct work_struct *work) +{ + int temp_index; + struct update_data_list *next_update, *temp_update; + struct update_desc_list *next_desc, *temp_desc; + struct update_marker_data *next_marker, *temp_marker; + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, epdc_submit_work); + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &pxp_conf->proc_data; + struct update_data_list *upd_data_list = NULL; + struct mxcfb_rect adj_update_region, *upd_region; + bool end_merge = false; + bool is_transform; + u32 update_addr; + int *err_dist; + int ret; + + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + /* + * Are any of our collision updates able to go now? + * Go through all updates in the collision list and check to see + * if the collision mask has been fully cleared + */ + list_for_each_entry_safe(next_update, temp_update, + &fb_data->upd_buf_collision_list, list) { + + if (next_update->collision_mask != 0) + continue; + + dev_dbg(fb_data->dev, "A collision update is ready to go!\n"); + + /* Force waveform mode to auto for resubmitted collisions */ + next_update->update_desc->upd_data.waveform_mode = + WAVEFORM_MODE_AUTO; + + /* + * We have a collision cleared, so select it for resubmission. + * If an update is already selected, attempt to merge. + */ + if (!upd_data_list) { + upd_data_list = next_update; + list_del_init(&next_update->list); + if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) + /* If not merging, we have our update */ + break; + } else { + switch (epdc_submit_merge(upd_data_list->update_desc, + next_update->update_desc, + fb_data)) { + case MERGE_OK: + dev_dbg(fb_data->dev, + "Update merged [collision]\n"); + list_del_init(&next_update->update_desc->list); + kfree(next_update->update_desc); + next_update->update_desc = NULL; + list_del_init(&next_update->list); + /* Add to free buffer list */ + list_add_tail(&next_update->list, + &fb_data->upd_buf_free_list); + break; + case MERGE_FAIL: + dev_dbg(fb_data->dev, + "Update not merged [collision]\n"); + break; + case MERGE_BLOCK: + dev_dbg(fb_data->dev, + "Merge blocked [collision]\n"); + end_merge = true; + break; + } + + if (end_merge) { + end_merge = false; + break; + } + } + } + + /* + * Skip pending update list only if we found a collision + * update and we are not merging + */ + if (!((fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) && + upd_data_list)) { + /* + * If we didn't find a collision update ready to go, we + * need to get a free buffer and match it to a pending update. + */ + + /* + * Can't proceed if there are no free buffers (and we don't + * already have a collision update selected) + */ + if (!upd_data_list && + list_empty(&fb_data->upd_buf_free_list)) { + mutex_unlock(&fb_data->queue_mutex); + return; + } + + list_for_each_entry_safe(next_desc, temp_desc, + &fb_data->upd_pending_list, list) { + + dev_dbg(fb_data->dev, "Found a pending update!\n"); + + if (!upd_data_list) { + if (list_empty(&fb_data->upd_buf_free_list)) + break; + upd_data_list = + list_entry(fb_data->upd_buf_free_list.next, + struct update_data_list, list); + list_del_init(&upd_data_list->list); + upd_data_list->update_desc = next_desc; + list_del_init(&next_desc->list); + if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) + /* If not merging, we have an update */ + break; + } else { + switch (epdc_submit_merge(upd_data_list->update_desc, + next_desc, fb_data)) { + case MERGE_OK: + dev_dbg(fb_data->dev, + "Update merged [queue]\n"); + list_del_init(&next_desc->list); + kfree(next_desc); + break; + case MERGE_FAIL: + dev_dbg(fb_data->dev, + "Update not merged [queue]\n"); + break; + case MERGE_BLOCK: + dev_dbg(fb_data->dev, + "Merge blocked [collision]\n"); + end_merge = true; + break; + } + + if (end_merge) + break; + } + } + } + + /* Is update list empty? */ + if (!upd_data_list) { + mutex_unlock(&fb_data->queue_mutex); + return; + } + + /* + * If no processing required, skip update processing + * No processing means: + * - FB unrotated + * - FB pixel format = 8-bit grayscale + * - No look-up transformations (inversion, posterization, etc.) + * - No scaling/flip + */ + is_transform = ((upd_data_list->update_desc->upd_data.flags & + (EPDC_FLAG_ENABLE_INVERSION | EPDC_FLAG_USE_DITHERING_Y1 | + EPDC_FLAG_USE_DITHERING_Y4 | EPDC_FLAG_FORCE_MONOCHROME | + EPDC_FLAG_USE_CMAP)) && (proc_data->scaling == 0) && + (proc_data->hflip == 0) && (proc_data->vflip == 0)) ? + true : false; + + /*XXX if we use external mode, we should first use pxp + * to update upd buffer data to working buffer first. + */ + if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) && + (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT) && + !is_transform && (proc_data->dither_mode == 0) && + !fb_data->restrict_width) { + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) + epdc_powerup(fb_data); + + /* + * Set update buffer pointer to the start of + * the update region in the frame buffer. + */ + upd_region = &upd_data_list->update_desc->upd_data.update_region; + update_addr = fb_data->info.fix.smem_start + + ((upd_region->top * fb_data->info.var.xres_virtual) + + upd_region->left) * fb_data->info.var.bits_per_pixel/8; + upd_data_list->update_desc->epdc_stride = + fb_data->info.var.xres_virtual * + fb_data->info.var.bits_per_pixel/8; + } else { + /* Select from PxP output buffers */ + upd_data_list->phys_addr = + fb_data->phys_addr_updbuf[fb_data->upd_buffer_num]; + upd_data_list->virt_addr = + fb_data->virt_addr_updbuf[fb_data->upd_buffer_num]; + fb_data->upd_buffer_num++; + if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1) + fb_data->upd_buffer_num = 0; + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + + /* Perform PXP processing - EPDC power will also be enabled */ + if (epdc_process_update(upd_data_list, fb_data)) { + dev_dbg(fb_data->dev, "PXP processing error.\n"); + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + list_del_init(&upd_data_list->update_desc->list); + kfree(upd_data_list->update_desc); + upd_data_list->update_desc = NULL; + /* Add to free buffer list */ + list_add_tail(&upd_data_list->list, + &fb_data->upd_buf_free_list); + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + return; + } + + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + update_addr = upd_data_list->phys_addr + + upd_data_list->update_desc->epdc_offs; + } + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data->epdc_fb_var.xres, + fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate, + &upd_data_list->update_desc->upd_data.update_region, + &adj_update_region); + + /* + * Is the working buffer idle? + * If the working buffer is busy, we must wait for the resource + * to become free. The IST will signal this event. + */ + if (fb_data->cur_update != NULL) { + dev_dbg(fb_data->dev, "working buf busy!\n"); + + /* Initialize event signalling an update resource is free */ + init_completion(&fb_data->update_res_free); + + fb_data->waiting_for_wb = true; + + /* Leave spinlock while waiting for WB to complete */ + mutex_unlock(&fb_data->queue_mutex); + wait_for_completion(&fb_data->update_res_free); + mutex_lock(&fb_data->queue_mutex); + } + + /* + * Dithering Processing (Version 1.0 - for i.MX508 and i.MX6SL) + */ + if (upd_data_list->update_desc->upd_data.flags & + EPDC_FLAG_USE_DITHERING_Y1) { + + err_dist = kzalloc((fb_data->info.var.xres_virtual + 3) * 3 + * sizeof(int), GFP_KERNEL); + + /* Dithering Y8 -> Y1 */ + do_dithering_processing_Y1_v1_0( + (uint8_t *)(upd_data_list->virt_addr + + upd_data_list->update_desc->epdc_offs), + upd_data_list->phys_addr + + upd_data_list->update_desc->epdc_offs, + &adj_update_region, + (fb_data->rev < 20) ? + ALIGN(adj_update_region.width, 8) : + adj_update_region.width, + err_dist); + + kfree(err_dist); + } else if (upd_data_list->update_desc->upd_data.flags & + EPDC_FLAG_USE_DITHERING_Y4) { + + err_dist = kzalloc((fb_data->info.var.xres_virtual + 3) * 3 + * sizeof(int), GFP_KERNEL); + + /* Dithering Y8 -> Y1 */ + do_dithering_processing_Y4_v1_0( + (uint8_t *)(upd_data_list->virt_addr + + upd_data_list->update_desc->epdc_offs), + upd_data_list->phys_addr + + upd_data_list->update_desc->epdc_offs, + &adj_update_region, + (fb_data->rev < 20) ? + ALIGN(adj_update_region.width, 8) : + adj_update_region.width, + err_dist); + + kfree(err_dist); + } + + /* + * If there are no LUTs available, + * then we must wait for the resource to become free. + * The IST will signal this event. + */ + { + bool luts_available; + + luts_available = fb_data->epdc_wb_mode ? epdc_any_luts_real_available() : + epdc_any_luts_available(); + if (!luts_available) { + dev_dbg(fb_data->dev, "no luts available!\n"); + + /* Initialize event signalling an update resource is free */ + init_completion(&fb_data->update_res_free); + + fb_data->waiting_for_lut = true; + + /* Leave spinlock while waiting for LUT to free up */ + mutex_unlock(&fb_data->queue_mutex); + wait_for_completion(&fb_data->update_res_free); + mutex_lock(&fb_data->queue_mutex); + } + } + + ret = epdc_choose_next_lut(fb_data, &upd_data_list->lut_num); + /* + * If LUT15 is in use (for pre-EPDC v2.0 hardware): + * - Wait for LUT15 to complete is if TCE underrun prevent is enabled + * - If we go ahead with update, sync update submission with EOF + */ + if (ret && fb_data->tce_prevent && (fb_data->rev < 20)) { + dev_dbg(fb_data->dev, "Waiting for LUT15\n"); + + /* Initialize event signalling that lut15 is free */ + init_completion(&fb_data->lut15_free); + + fb_data->waiting_for_lut15 = true; + + /* Leave spinlock while waiting for LUT to free up */ + mutex_unlock(&fb_data->queue_mutex); + wait_for_completion(&fb_data->lut15_free); + mutex_lock(&fb_data->queue_mutex); + + epdc_choose_next_lut(fb_data, &upd_data_list->lut_num); + } else if (ret && (fb_data->rev < 20)) { + /* Synchronize update submission time to reduce + chances of TCE underrun */ + init_completion(&fb_data->eof_event); + + epdc_eof_intr(true); + + /* Leave spinlock while waiting for EOF event */ + mutex_unlock(&fb_data->queue_mutex); + ret = wait_for_completion_timeout(&fb_data->eof_event, + msecs_to_jiffies(1000)); + if (!ret) { + dev_err(fb_data->dev, "Missed EOF event!\n"); + epdc_eof_intr(false); + } + udelay(fb_data->eof_sync_period); + mutex_lock(&fb_data->queue_mutex); + + } + + /* LUTs are available, so we get one here */ + fb_data->cur_update = upd_data_list; + + /* Reset mask for LUTS that have completed during WB processing */ + fb_data->luts_complete_wb = 0; + + /* If we are just testing for collision, we don't assign a LUT, + * so we don't need to update LUT-related resources. */ + if (!(upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION)) { + /* Associate LUT with update marker */ + list_for_each_entry_safe(next_marker, temp_marker, + &upd_data_list->update_desc->upd_marker_list, upd_list) + next_marker->lut_num = fb_data->cur_update->lut_num; + + /* Mark LUT with order */ + fb_data->lut_update_order[upd_data_list->lut_num] = + upd_data_list->update_desc->update_order; + + epdc_lut_complete_intr(fb_data->rev, upd_data_list->lut_num, + true); + } + + /* Enable Collision and WB complete IRQs */ + epdc_working_buf_intr(true); + + /* add working buffer update here for external mode */ + if (fb_data->epdc_wb_mode) + ret = epdc_working_buffer_update(fb_data, upd_data_list, + &adj_update_region); + + /* Program EPDC update to process buffer */ + if (upd_data_list->update_desc->upd_data.temp != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, + upd_data_list->update_desc->upd_data.temp); + epdc_set_temp(temp_index); + } else + epdc_set_temp(fb_data->temp_index); + + epdc_set_update_addr(update_addr); + epdc_set_update_coord(adj_update_region.left, adj_update_region.top); + epdc_set_update_dimensions(adj_update_region.width, + adj_update_region.height); + if (fb_data->rev > 20) + epdc_set_update_stride(upd_data_list->update_desc->epdc_stride); + if (fb_data->wv_modes_update && + (upd_data_list->update_desc->upd_data.waveform_mode + == WAVEFORM_MODE_AUTO)) { + epdc_set_update_waveform(&fb_data->wv_modes); + fb_data->wv_modes_update = false; + } + + epdc_submit_update(upd_data_list->lut_num, + upd_data_list->update_desc->upd_data.waveform_mode, + upd_data_list->update_desc->upd_data.update_mode, + (upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION) ? true : false, + false, 0); + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); +} + +static int mxc_epdc_fb_send_single_update(struct mxcfb_update_data *upd_data, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + struct update_data_list *upd_data_list = NULL; + struct mxcfb_rect *screen_upd_region; /* Region on screen to update */ + int temp_index; + int ret; + struct update_desc_list *upd_desc; + struct update_marker_data *marker_data, *next_marker, *temp_marker; + + /* Has EPDC HW been initialized? */ + if (!fb_data->hw_ready) { + /* Throw message if we are not mid-initialization */ + if (!fb_data->hw_initializing) + dev_err(fb_data->dev, "Display HW not properly" + "initialized. Aborting update.\n"); + return -EPERM; + } + + /* Check validity of update params */ + if ((upd_data->update_mode != UPDATE_MODE_PARTIAL) && + (upd_data->update_mode != UPDATE_MODE_FULL)) { + dev_err(fb_data->dev, + "Update mode 0x%x is invalid. Aborting update.\n", + upd_data->update_mode); + return -EINVAL; + } + if ((upd_data->waveform_mode > 255) && + (upd_data->waveform_mode != WAVEFORM_MODE_AUTO)) { + dev_err(fb_data->dev, + "Update waveform mode 0x%x is invalid." + " Aborting update.\n", + upd_data->waveform_mode); + return -EINVAL; + } + if ((upd_data->update_region.left >= fb_data->epdc_fb_var.xres) || + (upd_data->update_region.top >= fb_data->epdc_fb_var.yres) || + (upd_data->update_region.width > fb_data->epdc_fb_var.xres) || + (upd_data->update_region.height > fb_data->epdc_fb_var.yres) || + (upd_data->update_region.left + upd_data->update_region.width > fb_data->epdc_fb_var.xres) || + (upd_data->update_region.top + upd_data->update_region.height > fb_data->epdc_fb_var.yres)) { + dev_err(fb_data->dev, + "Update region is outside bounds of framebuffer." + "Aborting update.\n"); + return -EINVAL; + } + if (upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) { + if ((upd_data->update_region.width != + upd_data->alt_buffer_data.alt_update_region.width) || + (upd_data->update_region.height != + upd_data->alt_buffer_data.alt_update_region.height)) { + dev_err(fb_data->dev, + "Alternate update region dimensions must " + "match screen update region dimensions.\n"); + return -EINVAL; + } + /* Validate physical address parameter */ + if ((upd_data->alt_buffer_data.phys_addr < + fb_data->info.fix.smem_start) || + (upd_data->alt_buffer_data.phys_addr > + fb_data->info.fix.smem_start + fb_data->map_size)) { + dev_err(fb_data->dev, + "Invalid physical address for alternate " + "buffer. Aborting update...\n"); + return -EINVAL; + } + } + + mutex_lock(&fb_data->queue_mutex); + + /* + * If we are waiting to go into suspend, or the FB is blanked, + * we do not accept new updates + */ + if ((fb_data->waiting_for_idle) || + (fb_data->blank != FB_BLANK_UNBLANK)) { + dev_dbg(fb_data->dev, "EPDC not active." + "Update request abort.\n"); + mutex_unlock(&fb_data->queue_mutex); + return -EPERM; + } + + if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) { + int count = 0; + struct update_data_list *plist; + + /* + * If next update is a FULL mode update, then we must + * ensure that all pending & active updates are complete + * before submitting the update. Otherwise, the FULL + * mode update may cause an endless collision loop with + * other updates. Block here until updates are flushed. + */ + if (upd_data->update_mode == UPDATE_MODE_FULL) { + mutex_unlock(&fb_data->queue_mutex); + mxc_epdc_fb_flush_updates(fb_data); + mutex_lock(&fb_data->queue_mutex); + } + + /* Count buffers in free buffer list */ + list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) + count++; + + /* Use count to determine if we have enough + * free buffers to handle this update request */ + if (count + fb_data->max_num_buffers + <= fb_data->max_num_updates) { + dev_err(fb_data->dev, + "No free intermediate buffers available.\n"); + mutex_unlock(&fb_data->queue_mutex); + return -ENOMEM; + } + + /* Grab first available buffer and delete from the free list */ + upd_data_list = + list_entry(fb_data->upd_buf_free_list.next, + struct update_data_list, list); + + list_del_init(&upd_data_list->list); + } + + /* + * Create new update data structure, fill it with new update + * data and add it to the list of pending updates + */ + upd_desc = kzalloc(sizeof(struct update_desc_list), GFP_KERNEL); + if (!upd_desc) { + dev_err(fb_data->dev, + "Insufficient system memory for update! Aborting.\n"); + if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) { + list_add(&upd_data_list->list, + &fb_data->upd_buf_free_list); + } + mutex_unlock(&fb_data->queue_mutex); + return -EPERM; + } + /* Initialize per-update marker list */ + INIT_LIST_HEAD(&upd_desc->upd_marker_list); + upd_desc->upd_data = *upd_data; + upd_desc->update_order = fb_data->order_cnt++; + list_add_tail(&upd_desc->list, &fb_data->upd_pending_list); + + /* If marker specified, associate it with a completion */ + if (upd_data->update_marker != 0) { + /* Allocate new update marker and set it up */ + marker_data = kzalloc(sizeof(struct update_marker_data), + GFP_KERNEL); + if (!marker_data) { + dev_err(fb_data->dev, "No memory for marker!\n"); + mutex_unlock(&fb_data->queue_mutex); + return -ENOMEM; + } + list_add_tail(&marker_data->upd_list, + &upd_desc->upd_marker_list); + marker_data->update_marker = upd_data->update_marker; + if (upd_desc->upd_data.flags & EPDC_FLAG_TEST_COLLISION) + marker_data->lut_num = DRY_RUN_NO_LUT; + else + marker_data->lut_num = INVALID_LUT; + init_completion(&marker_data->update_completion); + /* Add marker to master marker list */ + list_add_tail(&marker_data->full_list, + &fb_data->full_marker_list); + } + + if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { + /* Queued update scheme processing */ + + mutex_unlock(&fb_data->queue_mutex); + + /* Signal workqueue to handle new update */ + queue_work(fb_data->epdc_submit_workqueue, + &fb_data->epdc_submit_work); + + return 0; + } + + /* Snapshot update scheme processing */ + + /* Set descriptor for current update, delete from pending list */ + upd_data_list->update_desc = upd_desc; + list_del_init(&upd_desc->list); + + mutex_unlock(&fb_data->queue_mutex); + + /* + * Hold on to original screen update region, which we + * will ultimately use when telling EPDC where to update on panel + */ + screen_upd_region = &upd_desc->upd_data.update_region; + + /* Select from PxP output buffers */ + upd_data_list->phys_addr = + fb_data->phys_addr_updbuf[fb_data->upd_buffer_num]; + upd_data_list->virt_addr = + fb_data->virt_addr_updbuf[fb_data->upd_buffer_num]; + fb_data->upd_buffer_num++; + if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1) + fb_data->upd_buffer_num = 0; + + ret = epdc_process_update(upd_data_list, fb_data); + if (ret) { + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + /* Pass selected waveform mode back to user */ + upd_data->waveform_mode = upd_desc->upd_data.waveform_mode; + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data->epdc_fb_var.xres, + fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate, + &upd_desc->upd_data.update_region, NULL); + + /* Grab lock for queue manipulation and update submission */ + mutex_lock(&fb_data->queue_mutex); + + /* + * Is the working buffer idle? + * If either the working buffer is busy, or there are no LUTs available, + * then we return and let the ISR handle the update later + */ + { + bool luts_available; + + luts_available = fb_data->epdc_wb_mode ? epdc_any_luts_real_available() : + epdc_any_luts_available(); + if ((fb_data->cur_update != NULL) || !luts_available) { + /* Add processed Y buffer to update list */ + list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue); + + /* Return and allow the update to be submitted by the ISR. */ + mutex_unlock(&fb_data->queue_mutex); + return 0; + } + } + + /* LUTs are available, so we get one here */ + ret = epdc_choose_next_lut(fb_data, &upd_data_list->lut_num); + if (ret && fb_data->tce_prevent && (fb_data->rev < 20)) { + dev_dbg(fb_data->dev, "Must wait for LUT15\n"); + /* Add processed Y buffer to update list */ + list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue); + + /* Return and allow the update to be submitted by the ISR. */ + mutex_unlock(&fb_data->queue_mutex); + return 0; + } + + if (!(upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION)) { + + /* Save current update */ + fb_data->cur_update = upd_data_list; + + /* Reset mask for LUTS that have completed during WB processing */ + fb_data->luts_complete_wb = 0; + + /* Associate LUT with update marker */ + list_for_each_entry_safe(next_marker, temp_marker, + &upd_data_list->update_desc->upd_marker_list, upd_list) + next_marker->lut_num = upd_data_list->lut_num; + + /* Mark LUT as containing new update */ + fb_data->lut_update_order[upd_data_list->lut_num] = + upd_desc->update_order; + + epdc_lut_complete_intr(fb_data->rev, upd_data_list->lut_num, + true); + } + + /* Clear status and Enable LUT complete and WB complete IRQs */ + epdc_working_buf_intr(true); + + /* add working buffer update before display for external mode */ + if (fb_data->epdc_wb_mode) + ret = epdc_working_buffer_update(fb_data, upd_data_list, + screen_upd_region); + + /* Program EPDC update to process buffer */ + epdc_set_update_addr(upd_data_list->phys_addr + upd_desc->epdc_offs); + epdc_set_update_coord(screen_upd_region->left, screen_upd_region->top); + epdc_set_update_dimensions(screen_upd_region->width, + screen_upd_region->height); + if (fb_data->rev > 20) + epdc_set_update_stride(upd_desc->epdc_stride); + if (upd_desc->upd_data.temp != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, + upd_desc->upd_data.temp); + epdc_set_temp(temp_index); + } else + epdc_set_temp(fb_data->temp_index); + if (fb_data->wv_modes_update && + (upd_desc->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) { + epdc_set_update_waveform(&fb_data->wv_modes); + fb_data->wv_modes_update = false; + } + + epdc_submit_update(upd_data_list->lut_num, + upd_desc->upd_data.waveform_mode, + upd_desc->upd_data.update_mode, + (upd_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION) ? true : false, + false, 0); + + mutex_unlock(&fb_data->queue_mutex); + return 0; +} + +static int mxc_epdc_fb_send_update(struct mxcfb_update_data *upd_data, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + if (!fb_data->restrict_width) { + /* No width restriction, send entire update region */ + return mxc_epdc_fb_send_single_update(upd_data, info); + } else { + int ret; + __u32 width, left; + __u32 marker; + __u32 *region_width, *region_left; + u32 max_upd_width = EPDC_V2_MAX_UPDATE_WIDTH; + + /* Further restrict max width due to pxp rotation + * alignment requirement + */ + if (fb_data->epdc_fb_var.rotate != FB_ROTATE_UR) + max_upd_width -= EPDC_V2_ROTATION_ALIGNMENT; + + /* Select split of width or height based on rotation */ + if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) || + (fb_data->epdc_fb_var.rotate == FB_ROTATE_UD)) { + region_width = &upd_data->update_region.width; + region_left = &upd_data->update_region.left; + } else { + region_width = &upd_data->update_region.height; + region_left = &upd_data->update_region.top; + } + + if (*region_width <= max_upd_width) + return mxc_epdc_fb_send_single_update(upd_data, info); + + width = *region_width; + left = *region_left; + marker = upd_data->update_marker; + upd_data->update_marker = 0; + + do { + *region_width = max_upd_width; + *region_left = left; + ret = mxc_epdc_fb_send_single_update(upd_data, info); + if (ret) + return ret; + width -= max_upd_width; + left += max_upd_width; + } while (width > max_upd_width); + + *region_width = width; + *region_left = left; + upd_data->update_marker = marker; + return mxc_epdc_fb_send_single_update(upd_data, info); + } +} + +static int mxc_epdc_fb_wait_update_complete(struct mxcfb_update_marker_data *marker_data, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + struct update_marker_data *next_marker; + struct update_marker_data *temp; + bool marker_found = false; + int ret = 0; + + /* 0 is an invalid update_marker value */ + if (marker_data->update_marker == 0) + return -EINVAL; + + /* + * Find completion associated with update_marker requested. + * Note: If update completed already, marker will have been + * cleared, it won't be found, and function will just return. + */ + + /* Grab queue lock to protect access to marker list */ + mutex_lock(&fb_data->queue_mutex); + + list_for_each_entry_safe(next_marker, temp, + &fb_data->full_marker_list, full_list) { + if (next_marker->update_marker == marker_data->update_marker) { + dev_dbg(fb_data->dev, "Waiting for marker %d\n", + marker_data->update_marker); + next_marker->waiting = true; + marker_found = true; + break; + } + } + + mutex_unlock(&fb_data->queue_mutex); + + /* + * If marker not found, it has either been signalled already + * or the update request failed. In either case, just return. + */ + if (!marker_found) + return ret; + + ret = wait_for_completion_timeout(&next_marker->update_completion, + msecs_to_jiffies(5000)); + if (!ret) { + dev_err(fb_data->dev, + "Timed out waiting for update completion\n"); + return -ETIMEDOUT; + } + + marker_data->collision_test = next_marker->collision_test; + + /* Free update marker object */ + kfree(next_marker); + + return ret; +} + +static int mxc_epdc_fb_set_pwrdown_delay(u32 pwrdown_delay, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + fb_data->pwrdown_delay = pwrdown_delay; + + return 0; +} + +static int mxc_epdc_get_pwrdown_delay(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + return fb_data->pwrdown_delay; +} + +static int mxc_epdc_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int ret = -EINVAL; + + switch (cmd) { + case MXCFB_SET_WAVEFORM_MODES: + { + struct mxcfb_waveform_modes modes; + if (!copy_from_user(&modes, argp, sizeof(modes))) { + mxc_epdc_fb_set_waveform_modes(&modes, info); + ret = 0; + } + break; + } + case MXCFB_SET_TEMPERATURE: + { + int temperature; + if (!get_user(temperature, (int32_t __user *) arg)) + ret = mxc_epdc_fb_set_temperature(temperature, + info); + break; + } + case MXCFB_SET_AUTO_UPDATE_MODE: + { + u32 auto_mode = 0; + if (!get_user(auto_mode, (__u32 __user *) arg)) + ret = mxc_epdc_fb_set_auto_update(auto_mode, + info); + break; + } + case MXCFB_SET_UPDATE_SCHEME: + { + u32 upd_scheme = 0; + if (!get_user(upd_scheme, (__u32 __user *) arg)) + ret = mxc_epdc_fb_set_upd_scheme(upd_scheme, + info); + break; + } + case MXCFB_SEND_UPDATE: + { + struct mxcfb_update_data upd_data; + if (!copy_from_user(&upd_data, argp, + sizeof(upd_data))) { + ret = mxc_epdc_fb_send_update(&upd_data, info); + if (ret == 0 && copy_to_user(argp, &upd_data, + sizeof(upd_data))) + ret = -EFAULT; + } else { + ret = -EFAULT; + } + + break; + } + case MXCFB_WAIT_FOR_UPDATE_COMPLETE: + { + struct mxcfb_update_marker_data upd_marker_data; + if (!copy_from_user(&upd_marker_data, argp, + sizeof(upd_marker_data))) { + ret = mxc_epdc_fb_wait_update_complete( + &upd_marker_data, info); + if (copy_to_user(argp, &upd_marker_data, + sizeof(upd_marker_data))) + ret = -EFAULT; + } else { + ret = -EFAULT; + } + + break; + } + + case MXCFB_SET_PWRDOWN_DELAY: + { + int delay = 0; + if (!get_user(delay, (__u32 __user *) arg)) + ret = + mxc_epdc_fb_set_pwrdown_delay(delay, info); + break; + } + + case MXCFB_GET_PWRDOWN_DELAY: + { + int pwrdown_delay = mxc_epdc_get_pwrdown_delay(info); + if (put_user(pwrdown_delay, + (int __user *)argp)) + ret = -EFAULT; + ret = 0; + break; + } + + case MXCFB_GET_WORK_BUFFER: + { + /* copy the epdc working buffer to the user space */ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + flush_cache_all(); + outer_flush_range(fb_data->working_buffer_phys, + fb_data->working_buffer_phys + + fb_data->working_buffer_size); + if (copy_to_user((void __user *)arg, + (const void *) fb_data->working_buffer_virt, + fb_data->working_buffer_size)) + ret = -EFAULT; + else + ret = 0; + flush_cache_all(); + outer_flush_range(fb_data->working_buffer_phys, + fb_data->working_buffer_phys + + fb_data->working_buffer_size); + break; + } + + default: + break; + } + return ret; +} + +static void mxc_epdc_fb_update_pages(struct mxc_epdc_fb_data *fb_data, + u16 y1, u16 y2) +{ + struct mxcfb_update_data update; + + /* Do partial screen update, Update full horizontal lines */ + update.update_region.left = 0; + update.update_region.width = fb_data->epdc_fb_var.xres; + update.update_region.top = y1; + update.update_region.height = y2 - y1; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_mode = UPDATE_MODE_FULL; + update.update_marker = 0; + update.temp = TEMP_USE_AMBIENT; + update.flags = 0; + + mxc_epdc_fb_send_update(&update, &fb_data->info); +} + +/* this is called back from the deferred io workqueue */ +static void mxc_epdc_fb_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + struct page *page; + unsigned long beg, end; + int y1, y2, miny, maxy; + + if (fb_data->auto_mode != AUTO_UPDATE_MODE_AUTOMATIC_MODE) + return; + + miny = INT_MAX; + maxy = 0; + list_for_each_entry(page, pagelist, lru) { + beg = page->index << PAGE_SHIFT; + end = beg + PAGE_SIZE - 1; + y1 = beg / info->fix.line_length; + y2 = end / info->fix.line_length; + if (y2 >= fb_data->epdc_fb_var.yres) + y2 = fb_data->epdc_fb_var.yres - 1; + if (miny > y1) + miny = y1; + if (maxy < y2) + maxy = y2; + } + + mxc_epdc_fb_update_pages(fb_data, miny, maxy); +} + +void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data) +{ + int ret; + + if (fb_data->in_init) + return; + + /* Grab queue lock to prevent any new updates from being submitted */ + mutex_lock(&fb_data->queue_mutex); + + /* + * 3 places to check for updates that are active or pending: + * 1) Updates in the pending list + * 2) Update buffers in use (e.g., PxP processing) + * 3) Active updates to panel - We can key off of EPDC + * power state to know if we have active updates. + */ + if (!list_empty(&fb_data->upd_pending_list) || + !is_free_list_full(fb_data) || + (fb_data->updates_active == true)) { + /* Initialize event signalling updates are done */ + init_completion(&fb_data->updates_done); + fb_data->waiting_for_idle = true; + + mutex_unlock(&fb_data->queue_mutex); + /* Wait for any currently active updates to complete */ + ret = wait_for_completion_timeout(&fb_data->updates_done, + msecs_to_jiffies(8000)); + if (!ret) + dev_err(fb_data->dev, + "Flush updates timeout! ret = 0x%x\n", ret); + + mutex_lock(&fb_data->queue_mutex); + fb_data->waiting_for_idle = false; + } + + mutex_unlock(&fb_data->queue_mutex); +} + +static int mxc_epdc_fb_blank(int blank, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + int ret; + + dev_dbg(fb_data->dev, "blank = %d\n", blank); + + if (fb_data->blank == blank) + return 0; + + fb_data->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + mxc_epdc_fb_flush_updates(fb_data); + /* Wait for powerdown */ + mutex_lock(&fb_data->power_mutex); + if ((fb_data->power_state == POWER_STATE_ON) && + (fb_data->pwrdown_delay == FB_POWERDOWN_DISABLE)) { + + /* Powerdown disabled, so we disable EPDC manually */ + int count = 0; + int sleep_ms = 10; + + mutex_unlock(&fb_data->power_mutex); + + /* If any active updates, wait for them to complete */ + while (fb_data->updates_active) { + /* Timeout after 1 sec */ + if ((count * sleep_ms) > 1000) + break; + msleep(sleep_ms); + count++; + } + + fb_data->powering_down = true; + epdc_powerdown(fb_data); + } else if (fb_data->power_state != POWER_STATE_OFF) { + fb_data->wait_for_powerdown = true; + init_completion(&fb_data->powerdown_compl); + mutex_unlock(&fb_data->power_mutex); + ret = wait_for_completion_timeout(&fb_data->powerdown_compl, + msecs_to_jiffies(5000)); + if (!ret) { + dev_err(fb_data->dev, + "No powerdown received!\n"); + return -ETIMEDOUT; + } + } else + mutex_unlock(&fb_data->power_mutex); + break; + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + mxc_epdc_fb_flush_updates(fb_data); + break; + } + return 0; +} + +static int mxc_epdc_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + u_int y_bottom; + + dev_dbg(info->device, "%s: var->yoffset %d, info->var.yoffset %d\n", + __func__, var->yoffset, info->var.yoffset); + /* check if var is valid; also, xpan is not supported */ + if (!var || (var->xoffset != info->var.xoffset) || + (var->yoffset + var->yres > var->yres_virtual)) { + dev_dbg(info->device, "x panning not supported\n"); + return -EINVAL; + } + + if ((fb_data->epdc_fb_var.xoffset == var->xoffset) && + (fb_data->epdc_fb_var.yoffset == var->yoffset)) + return 0; /* No change, do nothing */ + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += var->yres; + + if (y_bottom > info->var.yres_virtual) + return -EINVAL; + + mutex_lock(&fb_data->queue_mutex); + + fb_data->fb_offset = (var->yoffset * var->xres_virtual + var->xoffset) + * (var->bits_per_pixel) / 8; + + fb_data->epdc_fb_var.xoffset = var->xoffset; + fb_data->epdc_fb_var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + + mutex_unlock(&fb_data->queue_mutex); + + return 0; +} + +static struct fb_ops mxc_epdc_fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mxc_epdc_fb_check_var, + .fb_set_par = mxc_epdc_fb_set_par, + .fb_setcmap = mxc_epdc_fb_setcmap, + .fb_setcolreg = mxc_epdc_fb_setcolreg, + .fb_pan_display = mxc_epdc_fb_pan_display, + .fb_ioctl = mxc_epdc_fb_ioctl, + .fb_mmap = mxc_epdc_fb_mmap, + .fb_blank = mxc_epdc_fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static struct fb_deferred_io mxc_epdc_fb_defio = { + .delay = HZ, + .deferred_io = mxc_epdc_fb_deferred_io, +}; + +static void epdc_done_work_func(struct work_struct *work) +{ + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, + epdc_done_work.work); + epdc_powerdown(fb_data); +} + +static bool is_free_list_full(struct mxc_epdc_fb_data *fb_data) +{ + int count = 0; + struct update_data_list *plist; + + /* Count buffers in free buffer list */ + list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) + count++; + + /* Check to see if all buffers are in this list */ + if (count == fb_data->max_num_updates) + return true; + else + return false; +} + +static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id) +{ + struct mxc_epdc_fb_data *fb_data = dev_id; + u32 ints_fired, luts1_ints_fired, luts2_ints_fired; + + /* + * If we just completed one-time panel init, bypass + * queue handling, clear interrupt and return + */ + if (fb_data->in_init) { + if (epdc_is_working_buffer_complete()) { + epdc_working_buf_intr(false); + epdc_clear_working_buf_irq(); + dev_dbg(fb_data->dev, "Cleared WB for init update\n"); + } + + if (epdc_is_lut_complete(fb_data->rev, 0)) { + epdc_lut_complete_intr(fb_data->rev, 0, false); + epdc_clear_lut_complete_irq(fb_data->rev, 0); + fb_data->in_init = false; + dev_dbg(fb_data->dev, "Cleared LUT complete for init update\n"); + } + + return IRQ_HANDLED; + } + + ints_fired = __raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ); + if (fb_data->rev < 20) { + luts1_ints_fired = 0; + luts2_ints_fired = 0; + } else { + luts1_ints_fired = __raw_readl(EPDC_IRQ_MASK1) & __raw_readl(EPDC_IRQ1); + luts2_ints_fired = __raw_readl(EPDC_IRQ_MASK2) & __raw_readl(EPDC_IRQ2); + } + + if (!(ints_fired || luts1_ints_fired || luts2_ints_fired)) + return IRQ_HANDLED; + + if (__raw_readl(EPDC_IRQ) & EPDC_IRQ_TCE_UNDERRUN_IRQ) { + dev_err(fb_data->dev, + "TCE underrun! Will continue to update panel\n"); + /* Clear TCE underrun IRQ */ + __raw_writel(EPDC_IRQ_TCE_UNDERRUN_IRQ, EPDC_IRQ_CLEAR); + } + + /* Check if we are waiting on EOF to sync a new update submission */ + if (epdc_signal_eof()) { + epdc_eof_intr(false); + epdc_clear_eof_irq(); + complete(&fb_data->eof_event); + } + + /* + * Workaround for EPDC v2.0/v2.1 errata: Must read collision status + * before clearing IRQ, or else collision status for bits 16:63 + * will be automatically cleared. So we read it here, and there is + * no conflict with using it in epdc_intr_work_func since the + * working buffer processing flow is strictly sequential (i.e., + * only one WB processing done at a time, so the data grabbed + * here should be up-to-date and accurate when the WB processing + * completes. Also, note that there is no impact to other versions + * of EPDC by reading LUT status here. + */ + if (fb_data->cur_update != NULL) + fb_data->epdc_colliding_luts = epdc_get_colliding_luts(fb_data->rev); + + /* Clear the interrupt mask for any interrupts signalled */ + __raw_writel(ints_fired, EPDC_IRQ_MASK_CLEAR); + __raw_writel(luts1_ints_fired, EPDC_IRQ_MASK1_CLEAR); + __raw_writel(luts2_ints_fired, EPDC_IRQ_MASK2_CLEAR); + + dev_dbg(fb_data->dev, "EPDC interrupts fired = 0x%x, " + "LUTS1 fired = 0x%x, LUTS2 fired = 0x%x\n", + ints_fired, luts1_ints_fired, luts2_ints_fired); + + queue_work(fb_data->epdc_intr_workqueue, + &fb_data->epdc_intr_work); + + return IRQ_HANDLED; +} + +static void epdc_intr_work_func(struct work_struct *work) +{ + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, epdc_intr_work); + struct update_data_list *collision_update; + struct mxcfb_rect *next_upd_region; + struct update_marker_data *next_marker; + struct update_marker_data *temp; + int temp_index; + u64 temp_mask; + u32 lut; + bool ignore_collision = false; + int i; + bool wb_lut_done = false; + bool free_update = true; + int next_lut, epdc_next_lut_15; + u32 epdc_luts_active, epdc_wb_busy, epdc_luts_avail, epdc_lut_cancelled; + u32 epdc_collision; + u64 epdc_irq_stat; + bool epdc_waiting_on_wb; + u32 coll_coord, coll_size; + struct mxcfb_rect coll_region; + + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + /* Capture EPDC status one time to limit exposure to race conditions */ + epdc_luts_active = epdc_any_luts_active(fb_data->rev); + epdc_wb_busy = epdc_is_working_buffer_busy(); + + /*XXX unsupport update cancelled in external mode temporarily */ + if (fb_data->epdc_wb_mode) + epdc_lut_cancelled = 0; + else + epdc_lut_cancelled = epdc_is_lut_cancelled(); + + if (fb_data->epdc_wb_mode) + epdc_luts_avail = epdc_any_luts_real_available(); + else + epdc_luts_avail = epdc_any_luts_available(); + + if (fb_data->epdc_wb_mode) + epdc_collision = fb_data->col_info.pixel_cnt ? 1 : 0; + else + epdc_collision = epdc_is_collision(); + + if (fb_data->rev < 20) + epdc_irq_stat = __raw_readl(EPDC_IRQ); + else + epdc_irq_stat = (u64)__raw_readl(EPDC_IRQ1) | + ((u64)__raw_readl(EPDC_IRQ2) << 32); + epdc_waiting_on_wb = (fb_data->cur_update != NULL) ? true : false; + + /* Free any LUTs that have completed */ + for (i = 0; i < fb_data->num_luts; i++) { + if ((epdc_irq_stat & (1ULL << i)) == 0) + continue; + + dev_dbg(fb_data->dev, "LUT %d completed\n", i); + + /* Disable IRQ for completed LUT */ + epdc_lut_complete_intr(fb_data->rev, i, false); + + /* + * Go through all updates in the collision list and + * unmask any updates that were colliding with + * the completed LUT. + */ + list_for_each_entry(collision_update, + &fb_data->upd_buf_collision_list, list) { + collision_update->collision_mask = + collision_update->collision_mask & ~(1ULL << i); + } + + epdc_clear_lut_complete_irq(fb_data->rev, i); + + fb_data->luts_complete_wb |= 1ULL << i; + if (i != 0) + fb_data->luts_complete |= 1ULL << i; + + fb_data->lut_update_order[i] = 0; + + /* Signal completion if submit workqueue needs a LUT */ + if (fb_data->waiting_for_lut) { + complete(&fb_data->update_res_free); + fb_data->waiting_for_lut = false; + } + + /* Signal completion if LUT15 free and is needed */ + if (fb_data->waiting_for_lut15 && (i == 15)) { + complete(&fb_data->lut15_free); + fb_data->waiting_for_lut15 = false; + } + + /* Detect race condition where WB and its LUT complete + (i.e. full update completes) in one swoop */ + if (epdc_waiting_on_wb && + (i == fb_data->cur_update->lut_num)) + wb_lut_done = true; + + /* Signal completion if anyone waiting on this LUT */ + if (!wb_lut_done) + list_for_each_entry_safe(next_marker, temp, + &fb_data->full_marker_list, + full_list) { + if (next_marker->lut_num != i) + continue; + + /* Found marker to signal - remove from list */ + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, "Signaling marker %d\n", + next_marker->update_marker); + if (next_marker->waiting) + complete(&next_marker->update_completion); + else + kfree(next_marker); + } + } + + /* Check to see if all updates have completed */ + if (list_empty(&fb_data->upd_pending_list) && + is_free_list_full(fb_data) && + !epdc_waiting_on_wb && + !epdc_luts_active) { + + fb_data->updates_active = false; + + if (fb_data->pwrdown_delay != FB_POWERDOWN_DISABLE) { + /* + * Set variable to prevent overlapping + * enable/disable requests + */ + fb_data->powering_down = true; + + /* Schedule task to disable EPDC HW until next update */ + schedule_delayed_work(&fb_data->epdc_done_work, + msecs_to_jiffies(fb_data->pwrdown_delay)); + + /* Reset counter to reduce chance of overflow */ + fb_data->order_cnt = 0; + } + + if (fb_data->waiting_for_idle) + complete(&fb_data->updates_done); + } + + /* Is Working Buffer busy? */ + if (epdc_wb_busy) { + /* Can't submit another update until WB is done */ + mutex_unlock(&fb_data->queue_mutex); + return; + } + + /* + * Were we waiting on working buffer? + * If so, update queues and check for collisions + */ + if (epdc_waiting_on_wb) { + dev_dbg(fb_data->dev, "\nWorking buffer completed\n"); + + /* Signal completion if submit workqueue was waiting on WB */ + if (fb_data->waiting_for_wb) { + complete(&fb_data->update_res_free); + fb_data->waiting_for_wb = false; + } + + if (fb_data->cur_update->update_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION) { + /* This was a dry run to test for collision */ + + /* Signal marker */ + list_for_each_entry_safe(next_marker, temp, + &fb_data->full_marker_list, + full_list) { + if (next_marker->lut_num != DRY_RUN_NO_LUT) + continue; + + if (epdc_collision) + next_marker->collision_test = true; + else + next_marker->collision_test = false; + + dev_dbg(fb_data->dev, + "In IRQ, collision_test = %d\n", + next_marker->collision_test); + + /* Found marker to signal - remove from list */ + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, "Signaling marker " + "for dry-run - %d\n", + next_marker->update_marker); + complete(&next_marker->update_completion); + } + memset(&fb_data->col_info, 0x0, sizeof(struct pxp_collision_info)); + } else if (epdc_lut_cancelled && !epdc_collision) { + /* + * Note: The update may be cancelled (void) if all + * pixels collided. In that case we handle it as a + * collision, not a cancel. + */ + + /* Clear LUT status (might be set if no AUTOWV used) */ + + /* + * Disable and clear IRQ for the LUT used. + * Even though LUT is cancelled in HW, the LUT + * complete bit may be set if AUTOWV not used. + */ + epdc_lut_complete_intr(fb_data->rev, + fb_data->cur_update->lut_num, false); + epdc_clear_lut_complete_irq(fb_data->rev, + fb_data->cur_update->lut_num); + + fb_data->lut_update_order[fb_data->cur_update->lut_num] = 0; + + /* Signal completion if submit workqueue needs a LUT */ + if (fb_data->waiting_for_lut) { + complete(&fb_data->update_res_free); + fb_data->waiting_for_lut = false; + } + + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, + upd_list) { + + /* Del from per-update & full list */ + list_del_init(&next_marker->upd_list); + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, + "Signaling marker (cancelled) %d\n", + next_marker->update_marker); + if (next_marker->waiting) + complete(&next_marker->update_completion); + else + kfree(next_marker); + } + } else if (epdc_collision) { + /* Real update (no dry-run), collision occurred */ + + /* Check list of colliding LUTs, and add to our collision mask */ + if (fb_data->epdc_wb_mode) + fb_data->epdc_colliding_luts = (u64)fb_data->col_info.victim_luts[0] | + (((u64)fb_data->col_info.victim_luts[1]) << 32); + + fb_data->cur_update->collision_mask = + fb_data->epdc_colliding_luts; + + /* Clear collisions that completed since WB began */ + fb_data->cur_update->collision_mask &= + ~fb_data->luts_complete_wb; + + dev_dbg(fb_data->dev, "Collision mask = 0x%llx\n", + fb_data->epdc_colliding_luts); + + /* For EPDC 2.0 and later, minimum collision bounds + are provided by HW. Recompute new bounds here. */ + if ((fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) + && (fb_data->rev >= 20)) { + u32 xres, yres, rotate; + struct mxcfb_rect adj_update_region; + struct mxcfb_rect *cur_upd_rect = + &fb_data->cur_update->update_desc->upd_data.update_region; + + if (fb_data->epdc_wb_mode) { + adjust_coordinates(fb_data->epdc_fb_var.xres, + fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate, + cur_upd_rect, &adj_update_region); + + coll_region.left = fb_data->col_info.rect_min_x + adj_update_region.left; + coll_region.top = fb_data->col_info.rect_min_y + adj_update_region.top; + coll_region.width = fb_data->col_info.rect_max_x - fb_data->col_info.rect_min_x + 1; + coll_region.height = fb_data->col_info.rect_max_y - fb_data->col_info.rect_min_y + 1; + memset(&fb_data->col_info, 0x0, sizeof(struct pxp_collision_info)); + } else { + /* Get collision region coords from EPDC */ + coll_coord = __raw_readl(EPDC_UPD_COL_CORD); + coll_size = __raw_readl(EPDC_UPD_COL_SIZE); + coll_region.left = + (coll_coord & EPDC_UPD_COL_CORD_XCORD_MASK) + >> EPDC_UPD_COL_CORD_XCORD_OFFSET; + coll_region.top = + (coll_coord & EPDC_UPD_COL_CORD_YCORD_MASK) + >> EPDC_UPD_COL_CORD_YCORD_OFFSET; + coll_region.width = + (coll_size & EPDC_UPD_COL_SIZE_WIDTH_MASK) + >> EPDC_UPD_COL_SIZE_WIDTH_OFFSET; + coll_region.height = + (coll_size & EPDC_UPD_COL_SIZE_HEIGHT_MASK) + >> EPDC_UPD_COL_SIZE_HEIGHT_OFFSET; + } + dev_dbg(fb_data->dev, "Coll region: l = %d, " + "t = %d, w = %d, h = %d\n", + coll_region.left, coll_region.top, + coll_region.width, coll_region.height); + + /* Convert coords back to orig orientation */ + switch (fb_data->epdc_fb_var.rotate) { + case FB_ROTATE_CW: + xres = fb_data->epdc_fb_var.yres; + yres = fb_data->epdc_fb_var.xres; + rotate = FB_ROTATE_CCW; + break; + case FB_ROTATE_UD: + xres = fb_data->epdc_fb_var.xres; + yres = fb_data->epdc_fb_var.yres; + rotate = FB_ROTATE_UD; + break; + case FB_ROTATE_CCW: + xres = fb_data->epdc_fb_var.yres; + yres = fb_data->epdc_fb_var.xres; + rotate = FB_ROTATE_CW; + break; + default: + xres = fb_data->epdc_fb_var.xres; + yres = fb_data->epdc_fb_var.yres; + rotate = FB_ROTATE_UR; + break; + } + adjust_coordinates(xres, yres, rotate, + &coll_region, cur_upd_rect); + + dev_dbg(fb_data->dev, "Adj coll region: l = %d, " + "t = %d, w = %d, h = %d\n", + cur_upd_rect->left, cur_upd_rect->top, + cur_upd_rect->width, + cur_upd_rect->height); + } + + /* + * If we collide with newer updates, then + * we don't need to re-submit the update. The + * idea is that the newer updates should take + * precedence anyways, so we don't want to + * overwrite them. + */ + for (temp_mask = fb_data->cur_update->collision_mask, lut = 0; + temp_mask != 0; + lut++, temp_mask = temp_mask >> 1) { + if (!(temp_mask & 0x1)) + continue; + + if (fb_data->lut_update_order[lut] >= + fb_data->cur_update->update_desc->update_order) { + dev_dbg(fb_data->dev, + "Ignoring collision with" + "newer update.\n"); + ignore_collision = true; + break; + } + } + + if (!ignore_collision) { + free_update = false; + /* + * If update has markers, clear the LUTs to + * avoid signalling that they have completed. + */ + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, + upd_list) + next_marker->lut_num = INVALID_LUT; + + /* Move to collision list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_collision_list); + } + } + + /* Do we need to free the current update descriptor? */ + if (free_update) { + /* Handle condition where WB & LUT are both complete */ + if (wb_lut_done) + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, + upd_list) { + + /* Del from per-update & full list */ + list_del_init(&next_marker->upd_list); + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, + "Signaling marker (wb) %d\n", + next_marker->update_marker); + if (next_marker->waiting) + complete(&next_marker->update_completion); + else + kfree(next_marker); + } + + /* Free marker list and update descriptor */ + kfree(fb_data->cur_update->update_desc); + + /* Add to free buffer list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_free_list); + + /* Check to see if all updates have completed */ + if (list_empty(&fb_data->upd_pending_list) && + is_free_list_full(fb_data) && + !epdc_luts_active) { + + fb_data->updates_active = false; + + if (fb_data->pwrdown_delay != + FB_POWERDOWN_DISABLE) { + /* + * Set variable to prevent overlapping + * enable/disable requests + */ + fb_data->powering_down = true; + + /* Schedule EPDC disable */ + schedule_delayed_work(&fb_data->epdc_done_work, + msecs_to_jiffies(fb_data->pwrdown_delay)); + + /* Reset counter to reduce chance of overflow */ + fb_data->order_cnt = 0; + } + + if (fb_data->waiting_for_idle) + complete(&fb_data->updates_done); + } + } + + /* Clear current update */ + fb_data->cur_update = NULL; + + /* Clear IRQ for working buffer */ + epdc_working_buf_intr(false); + epdc_clear_working_buf_irq(); + } + + if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { + /* Queued update scheme processing */ + + /* Schedule task to submit collision and pending update */ + if (!fb_data->powering_down) + queue_work(fb_data->epdc_submit_workqueue, + &fb_data->epdc_submit_work); + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + + return; + } + + /* Snapshot update scheme processing */ + + /* Check to see if any LUTs are free */ + if (!epdc_luts_avail) { + dev_dbg(fb_data->dev, "No luts available.\n"); + mutex_unlock(&fb_data->queue_mutex); + return; + } + + epdc_next_lut_15 = epdc_choose_next_lut(fb_data, &next_lut); + /* Check to see if there is a valid LUT to use */ + if (epdc_next_lut_15 && fb_data->tce_prevent && (fb_data->rev < 20)) { + dev_dbg(fb_data->dev, "Must wait for LUT15\n"); + mutex_unlock(&fb_data->queue_mutex); + return; + } + + /* + * Are any of our collision updates able to go now? + * Go through all updates in the collision list and check to see + * if the collision mask has been fully cleared + */ + list_for_each_entry(collision_update, + &fb_data->upd_buf_collision_list, list) { + + if (collision_update->collision_mask != 0) + continue; + + dev_dbg(fb_data->dev, "A collision update is ready to go!\n"); + /* + * We have a collision cleared, so select it + * and we will retry the update + */ + fb_data->cur_update = collision_update; + list_del_init(&fb_data->cur_update->list); + break; + } + + /* + * If we didn't find a collision update ready to go, + * we try to grab one from the update queue + */ + if (fb_data->cur_update == NULL) { + /* Is update list empty? */ + if (list_empty(&fb_data->upd_buf_queue)) { + dev_dbg(fb_data->dev, "No pending updates.\n"); + + /* No updates pending, so we are done */ + mutex_unlock(&fb_data->queue_mutex); + return; + } else { + dev_dbg(fb_data->dev, "Found a pending update!\n"); + + /* Process next item in update list */ + fb_data->cur_update = + list_entry(fb_data->upd_buf_queue.next, + struct update_data_list, list); + list_del_init(&fb_data->cur_update->list); + } + } + + /* Use LUT selected above */ + fb_data->cur_update->lut_num = next_lut; + + /* Associate LUT with update markers */ + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, upd_list) + next_marker->lut_num = fb_data->cur_update->lut_num; + + /* Mark LUT as containing new update */ + fb_data->lut_update_order[fb_data->cur_update->lut_num] = + fb_data->cur_update->update_desc->update_order; + + /* Enable Collision and WB complete IRQs */ + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->rev, fb_data->cur_update->lut_num, true); + + /* Program EPDC update to process buffer */ + next_upd_region = + &fb_data->cur_update->update_desc->upd_data.update_region; + + /* add working buffer update here for external mode */ + if (fb_data->epdc_wb_mode) + epdc_working_buffer_update(fb_data, fb_data->cur_update, + next_upd_region); + + if (fb_data->cur_update->update_desc->upd_data.temp + != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, + fb_data->cur_update->update_desc->upd_data.temp); + epdc_set_temp(temp_index); + } else + epdc_set_temp(fb_data->temp_index); + epdc_set_update_addr(fb_data->cur_update->phys_addr + + fb_data->cur_update->update_desc->epdc_offs); + epdc_set_update_coord(next_upd_region->left, next_upd_region->top); + epdc_set_update_dimensions(next_upd_region->width, + next_upd_region->height); + if (fb_data->rev > 20) + epdc_set_update_stride(fb_data->cur_update->update_desc->epdc_stride); + if (fb_data->wv_modes_update && + (fb_data->cur_update->update_desc->upd_data.waveform_mode + == WAVEFORM_MODE_AUTO)) { + epdc_set_update_waveform(&fb_data->wv_modes); + fb_data->wv_modes_update = false; + } + + epdc_submit_update(fb_data->cur_update->lut_num, + fb_data->cur_update->update_desc->upd_data.waveform_mode, + fb_data->cur_update->update_desc->upd_data.update_mode, + false, false, 0); + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + + return; +} + +static void draw_mode0(struct mxc_epdc_fb_data *fb_data) +{ + u32 *upd_buf_ptr; + int i; + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 xres, yres; + + upd_buf_ptr = (u32 *)fb_data->info.screen_base; + + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->rev, 0, true); + + /* Use unrotated (native) width/height */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres = screeninfo->yres; + yres = screeninfo->xres; + } else { + xres = screeninfo->xres; + yres = screeninfo->yres; + } + + /* Program EPDC update to process buffer */ + epdc_set_update_addr(fb_data->phys_start); + epdc_set_update_coord(0, 0); + epdc_set_update_dimensions(xres, yres); + if (fb_data->rev > 20) + epdc_set_update_stride(0); + epdc_submit_update(0, fb_data->wv_modes.mode_init, UPDATE_MODE_FULL, + false, true, 0xFF); + + dev_dbg(fb_data->dev, "Mode0 update - Waiting for LUT to complete...\n"); + + /* Will timeout after ~4-5 seconds */ + + for (i = 0; i < 40; i++) { + if (!epdc_is_lut_active(0)) { + dev_dbg(fb_data->dev, "Mode0 init complete\n"); + return; + } + msleep(100); + } + + dev_err(fb_data->dev, "Mode0 init failed!\n"); + + return; +} + + +static void mxc_epdc_fb_fw_handler(const struct firmware *fw, + void *context) +{ + struct mxc_epdc_fb_data *fb_data = context; + int ret; + struct mxcfb_waveform_data_file *wv_file; + int wv_data_offs; + int i; + struct mxcfb_update_data update; + struct mxcfb_update_marker_data upd_marker_data; + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 xres, yres; + struct clk *epdc_parent; + unsigned long rounded_parent_rate, epdc_pix_rate, + rounded_pix_clk, target_pix_clk; + + if (fw == NULL) { + /* If default FW file load failed, we give up */ + if (fb_data->fw_default_load) + return; + + /* Try to load default waveform */ + dev_dbg(fb_data->dev, + "Can't find firmware. Trying fallback fw\n"); + fb_data->fw_default_load = true; + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + "imx/epdc/epdc.fw", fb_data->dev, GFP_KERNEL, fb_data, + mxc_epdc_fb_fw_handler); + if (ret) + dev_err(fb_data->dev, + "Failed request_firmware_nowait err %d\n", ret); + + return; + } + + wv_file = (struct mxcfb_waveform_data_file *)fw->data; + + dump_fw_header(fb_data->dev, wv_file); + + /* Get size and allocate temperature range table */ + fb_data->trt_entries = wv_file->wdh.trc + 1; + fb_data->temp_range_bounds = kzalloc(fb_data->trt_entries, GFP_KERNEL); + + for (i = 0; i < fb_data->trt_entries; i++) + dev_dbg(fb_data->dev, "trt entry #%d = 0x%x\n", i, *((u8 *)&wv_file->data + i)); + + /* Copy TRT data */ + memcpy(fb_data->temp_range_bounds, &wv_file->data, fb_data->trt_entries); + + /* Set default temperature index using TRT and room temp */ + fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, DEFAULT_TEMP); + + /* Get offset and size for waveform data */ + wv_data_offs = sizeof(wv_file->wdh) + fb_data->trt_entries + 1; + fb_data->waveform_buffer_size = fw->size - wv_data_offs; + + /* Allocate memory for waveform data */ + fb_data->waveform_buffer_virt = dma_alloc_coherent(fb_data->dev, + fb_data->waveform_buffer_size, + &fb_data->waveform_buffer_phys, + GFP_DMA | GFP_KERNEL); + if (fb_data->waveform_buffer_virt == NULL) { + dev_err(fb_data->dev, "Can't allocate mem for waveform!\n"); + return; + } + + memcpy(fb_data->waveform_buffer_virt, (u8 *)(fw->data) + wv_data_offs, + fb_data->waveform_buffer_size); + + /* Check for advanced algorithms */ + if ((wv_file->wdh.luts & WAVEFORM_HDR_LUT_ADVANCED_ALGO_MASK) != 0) { + dev_dbg(fb_data->dev, + "Waveform file supports advanced algorithms\n"); + fb_data->waveform_is_advanced = true; + } else { + dev_dbg(fb_data->dev, + "Waveform file does not support advanced algorithms\n"); + fb_data->waveform_is_advanced = false; + } + + release_firmware(fw); + + /* Enable clocks to access EPDC regs */ + clk_prepare_enable(fb_data->epdc_clk_axi); + + target_pix_clk = fb_data->cur_mode->vmode->pixclock; + + rounded_pix_clk = clk_round_rate(fb_data->epdc_clk_pix, target_pix_clk); + + if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) || + (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) { + /* Can't get close enough without changing parent clk */ + epdc_parent = clk_get_parent(fb_data->epdc_clk_pix); + rounded_parent_rate = clk_round_rate(epdc_parent, target_pix_clk); + + epdc_pix_rate = target_pix_clk; + while (epdc_pix_rate < rounded_parent_rate) + epdc_pix_rate *= 2; + clk_set_rate(epdc_parent, epdc_pix_rate); + + rounded_pix_clk = clk_round_rate(fb_data->epdc_clk_pix, target_pix_clk); + if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) || + (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) + /* Still can't get a good clock, provide warning */ + dev_err(fb_data->dev, "Unable to get an accurate EPDC pix clk" + "desired = %lu, actual = %lu\n", target_pix_clk, + rounded_pix_clk); + } + + clk_set_rate(fb_data->epdc_clk_pix, rounded_pix_clk); + + /* Enable pix clk for EPDC */ + clk_prepare_enable(fb_data->epdc_clk_pix); + + epdc_init_sequence(fb_data); + + /* Disable clocks */ + clk_disable_unprepare(fb_data->epdc_clk_axi); + clk_disable_unprepare(fb_data->epdc_clk_pix); + + fb_data->hw_ready = true; + fb_data->hw_initializing = false; + + /* Use unrotated (native) width/height */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres = screeninfo->yres; + yres = screeninfo->xres; + } else { + xres = screeninfo->xres; + yres = screeninfo->yres; + } + + update.update_region.left = 0; + update.update_region.width = xres; + update.update_region.top = 0; + update.update_region.height = yres; + update.update_mode = UPDATE_MODE_FULL; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_marker = INIT_UPDATE_MARKER; + update.temp = TEMP_USE_AMBIENT; + update.flags = 0; + update.dither_mode = 0; + + upd_marker_data.update_marker = update.update_marker; + + mxc_epdc_fb_send_update(&update, &fb_data->info); + + /* Block on initial update */ + ret = mxc_epdc_fb_wait_update_complete(&upd_marker_data, + &fb_data->info); + if (ret < 0) + dev_err(fb_data->dev, + "Wait for initial update complete failed." + " Error = 0x%x", ret); +} + +static int mxc_epdc_fb_init_hw(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + int ret; + + /* + * Create fw search string based on ID string in selected videomode. + * Format is "imx/epdc/epdc_[panel string].fw" + */ + if (fb_data->cur_mode) { + strcpy(fb_data->fw_str, "imx/epdc/epdc_"); + strcat(fb_data->fw_str, fb_data->cur_mode->vmode->name); + strcat(fb_data->fw_str, ".fw"); + } + + fb_data->fw_default_load = false; + + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + fb_data->fw_str, fb_data->dev, GFP_KERNEL, + fb_data, mxc_epdc_fb_fw_handler); + if (ret) + dev_dbg(fb_data->dev, + "Failed request_firmware_nowait err %d\n", ret); + + return ret; +} + +static ssize_t store_update(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxcfb_update_data update; + struct fb_info *info = dev_get_drvdata(device); + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + + if (strncmp(buf, "direct", 6) == 0) + update.waveform_mode = fb_data->wv_modes.mode_du; + else if (strncmp(buf, "gc16", 4) == 0) + update.waveform_mode = fb_data->wv_modes.mode_gc16; + else if (strncmp(buf, "gc4", 3) == 0) + update.waveform_mode = fb_data->wv_modes.mode_gc4; + + /* Now, request full screen update */ + update.update_region.left = 0; + update.update_region.width = fb_data->epdc_fb_var.xres; + update.update_region.top = 0; + update.update_region.height = fb_data->epdc_fb_var.yres; + update.update_mode = UPDATE_MODE_FULL; + update.temp = TEMP_USE_AMBIENT; + update.update_marker = 0; + update.flags = 0; + + mxc_epdc_fb_send_update(&update, info); + + return count; +} + +static struct device_attribute fb_attrs[] = { + __ATTR(update, S_IRUGO|S_IWUSR, NULL, store_update), +}; + +static const struct of_device_id imx_epdc_dt_ids[] = { + { .compatible = "fsl,imx7d-epdc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_epdc_dt_ids); + +static int mxc_epdc_fb_probe(struct platform_device *pdev) +{ + int ret = 0; + struct pinctrl *pinctrl; + struct mxc_epdc_fb_data *fb_data; + struct resource *res; + struct fb_info *info; + char *options, *opt; + char *panel_str = NULL; + char name[] = "mxcepdcfb"; + struct fb_videomode *vmode; + int xres_virt, yres_virt, buf_size; + int xres_virt_rot, yres_virt_rot, pix_size_rot; + struct fb_var_screeninfo *var_info; + struct fb_fix_screeninfo *fix_info; + struct pxp_config_data *pxp_conf; + struct pxp_proc_data *proc_data; + struct scatterlist *sg; + struct update_data_list *upd_list; + struct update_data_list *plist, *temp_list; + int i; + unsigned long x_mem_size = 0; + u32 val; + int irq; + struct device_node *np = pdev->dev.of_node; + struct device_node *node; + phandle phandle; + u32 out_val[3]; + int enable_gpio; + enum of_gpio_flags flag; + unsigned short *wk_p; + + if (!np) + return -EINVAL; + + fb_data = (struct mxc_epdc_fb_data *)framebuffer_alloc( + sizeof(struct mxc_epdc_fb_data), &pdev->dev); + if (fb_data == NULL) { + ret = -ENOMEM; + goto out; + } + + ret = of_property_read_u32_array(np, "epdc-ram", out_val, 3); + if (ret) { + dev_dbg(&pdev->dev, "no epdc-ram property found\n"); + } else { + phandle = *out_val; + + node = of_find_node_by_phandle(phandle); + if (!node) { + dev_dbg(&pdev->dev, "not find gpr node by phandle\n"); + ret = PTR_ERR(node); + goto out_fbdata; + } + fb_data->gpr = syscon_node_to_regmap(node); + if (IS_ERR(fb_data->gpr)) { + dev_err(&pdev->dev, "failed to get gpr regmap\n"); + ret = PTR_ERR(fb_data->gpr); + goto out_fbdata; + } + of_node_put(node); + fb_data->req_gpr = out_val[1]; + fb_data->req_bit = out_val[2]; + + regmap_update_bits(fb_data->gpr, fb_data->req_gpr, + 1 << fb_data->req_bit, 0); + } + + if (of_find_property(np, "en-gpios", NULL)) { + enable_gpio = of_get_named_gpio_flags(np, "en-gpios", 0, &flag); + if (enable_gpio == -EPROBE_DEFER) { + dev_info(&pdev->dev, "GPIO requested is not" + "here yet, deferring the probe\n"); + return -EPROBE_DEFER; + } + if (!gpio_is_valid(enable_gpio)) { + dev_warn(&pdev->dev, "No dt property: en-gpios\n"); + } else { + + ret = devm_gpio_request_one(&pdev->dev, + enable_gpio, + (flag & OF_GPIO_ACTIVE_LOW) + ? GPIOF_OUT_INIT_LOW : + GPIOF_OUT_INIT_HIGH, + "en_pins"); + if (ret) { + dev_err(&pdev->dev, "failed to request gpio" + " %d: %d\n", enable_gpio, ret); + return -EINVAL; + } + } + } + + fb_data->qos_regmap = syscon_regmap_lookup_by_phandle(np, "qos"); + if (IS_ERR(fb_data->qos_regmap)) { + dev_warn(&pdev->dev, "No qos phandle specified. Ignored.\n"); + } + + /* Get platform data and check validity */ + fb_data->pdata = &epdc_data; + if ((fb_data->pdata == NULL) || (fb_data->pdata->num_modes < 1) + || (fb_data->pdata->epdc_mode == NULL) + || (fb_data->pdata->epdc_mode->vmode == NULL)) { + ret = -EINVAL; + goto out_fbdata; + } + + if (fb_get_options(name, &options)) { + ret = -ENODEV; + goto out_fbdata; + } + + fb_data->epdc_wb_mode = 1; + fb_data->tce_prevent = 0; + + if (options) + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "bpp=", 4)) + fb_data->default_bpp = + simple_strtoul(opt + 4, NULL, 0); + else if (!strncmp(opt, "x_mem=", 6)) + x_mem_size = memparse(opt + 6, NULL); + else if (!strncmp(opt, "tce_prevent", 11)) + fb_data->tce_prevent = 1; + else + panel_str = opt; + } + + fb_data->dev = &pdev->dev; + + if (!fb_data->default_bpp) + fb_data->default_bpp = 16; + + /* Set default (first defined mode) before searching for a match */ + fb_data->cur_mode = &fb_data->pdata->epdc_mode[0]; + + if (panel_str) + for (i = 0; i < fb_data->pdata->num_modes; i++) + if (!strcmp(fb_data->pdata->epdc_mode[i].vmode->name, + panel_str)) { + fb_data->cur_mode = + &fb_data->pdata->epdc_mode[i]; + break; + } + + vmode = fb_data->cur_mode->vmode; + + platform_set_drvdata(pdev, fb_data); + info = &fb_data->info; + + /* Allocate color map for the FB */ + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) + goto out_fbdata; + + dev_dbg(&pdev->dev, "resolution %dx%d, bpp %d\n", + vmode->xres, vmode->yres, fb_data->default_bpp); + + /* + * GPU alignment restrictions dictate framebuffer parameters: + * - 32-byte alignment for buffer width + * - 128-byte alignment for buffer height + * => 4K buffer alignment for buffer start + */ + xres_virt = ALIGN(vmode->xres, 32); + yres_virt = ALIGN(vmode->yres, 128); + fb_data->max_pix_size = PAGE_ALIGN(xres_virt * yres_virt); + + /* + * Have to check to see if aligned buffer size when rotated + * is bigger than when not rotated, and use the max + */ + xres_virt_rot = ALIGN(vmode->yres, 32); + yres_virt_rot = ALIGN(vmode->xres, 128); + pix_size_rot = PAGE_ALIGN(xres_virt_rot * yres_virt_rot); + fb_data->max_pix_size = (fb_data->max_pix_size > pix_size_rot) ? + fb_data->max_pix_size : pix_size_rot; + + buf_size = fb_data->max_pix_size * fb_data->default_bpp/8; + + /* Compute the number of screens needed based on X memory requested */ + if (x_mem_size > 0) { + fb_data->num_screens = DIV_ROUND_UP(x_mem_size, buf_size); + if (fb_data->num_screens < NUM_SCREENS_MIN) + fb_data->num_screens = NUM_SCREENS_MIN; + else if (buf_size * fb_data->num_screens > SZ_16M) + fb_data->num_screens = SZ_16M / buf_size; + } else + fb_data->num_screens = NUM_SCREENS_MIN; + + fb_data->map_size = buf_size * fb_data->num_screens; + dev_dbg(&pdev->dev, "memory to allocate: %d\n", fb_data->map_size); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENODEV; + goto out_cmap; + } + + epdc_v2_base = devm_ioremap_resource(&pdev->dev, res); + if (epdc_v2_base == NULL) { + ret = -ENOMEM; + goto out_cmap; + } + + /* Allocate FB memory */ + info->screen_base = dma_alloc_wc(&pdev->dev, + fb_data->map_size, + &fb_data->phys_start, + GFP_DMA | GFP_KERNEL); + + if (info->screen_base == NULL) { + ret = -ENOMEM; + goto out_cmap; + } + dev_dbg(&pdev->dev, "allocated at %p:0x%x\n", info->screen_base, + fb_data->phys_start); + + var_info = &info->var; + var_info->activate = FB_ACTIVATE_TEST; + var_info->bits_per_pixel = fb_data->default_bpp; + var_info->xres = vmode->xres; + var_info->yres = vmode->yres; + var_info->xres_virtual = xres_virt; + /* Additional screens allow for panning and buffer flipping */ + var_info->yres_virtual = yres_virt * fb_data->num_screens; + + var_info->pixclock = vmode->pixclock; + var_info->left_margin = vmode->left_margin; + var_info->right_margin = vmode->right_margin; + var_info->upper_margin = vmode->upper_margin; + var_info->lower_margin = vmode->lower_margin; + var_info->hsync_len = vmode->hsync_len; + var_info->vsync_len = vmode->vsync_len; + var_info->vmode = FB_VMODE_NONINTERLACED; + + switch (fb_data->default_bpp) { + case 32: + case 24: + var_info->red.offset = 16; + var_info->red.length = 8; + var_info->green.offset = 8; + var_info->green.length = 8; + var_info->blue.offset = 0; + var_info->blue.length = 8; + break; + + case 16: + var_info->red.offset = 11; + var_info->red.length = 5; + var_info->green.offset = 5; + var_info->green.length = 6; + var_info->blue.offset = 0; + var_info->blue.length = 5; + break; + + case 8: + /* + * For 8-bit grayscale, R, G, and B offset are equal. + * + */ + var_info->grayscale = GRAYSCALE_8BIT; + + var_info->red.length = 8; + var_info->red.offset = 0; + var_info->red.msb_right = 0; + var_info->green.length = 8; + var_info->green.offset = 0; + var_info->green.msb_right = 0; + var_info->blue.length = 8; + var_info->blue.offset = 0; + var_info->blue.msb_right = 0; + break; + + default: + dev_err(&pdev->dev, "unsupported bitwidth %d\n", + fb_data->default_bpp); + ret = -EINVAL; + goto out_dma_fb; + } + + fix_info = &info->fix; + + strcpy(fix_info->id, "mxc_epdc_fb"); + fix_info->type = FB_TYPE_PACKED_PIXELS; + fix_info->visual = FB_VISUAL_TRUECOLOR; + fix_info->xpanstep = 0; + fix_info->ypanstep = 0; + fix_info->ywrapstep = 0; + fix_info->accel = FB_ACCEL_NONE; + fix_info->smem_start = fb_data->phys_start; + fix_info->smem_len = fb_data->map_size; + fix_info->ypanstep = 0; + + fb_data->native_width = vmode->xres; + fb_data->native_height = vmode->yres; + + info->fbops = &mxc_epdc_fb_ops; + info->var.activate = FB_ACTIVATE_NOW; + info->pseudo_palette = fb_data->pseudo_palette; + info->screen_size = info->fix.smem_len; + info->flags = FBINFO_FLAG_DEFAULT; + + mxc_epdc_fb_set_fix(info); + + fb_data->auto_mode = AUTO_UPDATE_MODE_REGION_MODE; + fb_data->upd_scheme = UPDATE_SCHEME_QUEUE_AND_MERGE; + + /* Initialize our internal copy of the screeninfo */ + fb_data->epdc_fb_var = *var_info; + fb_data->fb_offset = 0; + fb_data->eof_sync_period = 0; + + fb_data->epdc_clk_axi = clk_get(fb_data->dev, "epdc_axi"); + if (IS_ERR(fb_data->epdc_clk_axi)) { + dev_err(&pdev->dev, "Unable to get EPDC AXI clk." + "err = %d\n", (int)fb_data->epdc_clk_axi); + ret = -ENODEV; + goto out_dma_fb; + } + fb_data->epdc_clk_pix = clk_get(fb_data->dev, "epdc_pix"); + if (IS_ERR(fb_data->epdc_clk_pix)) { + dev_err(&pdev->dev, "Unable to get EPDC pix clk." + "err = %d\n", (int)fb_data->epdc_clk_pix); + ret = -ENODEV; + goto out_dma_fb; + } + + clk_prepare_enable(fb_data->epdc_clk_axi); + clk_prepare_enable(fb_data->epdc_clk_pix); + val = __raw_readl(EPDC_VERSION); + clk_disable_unprepare(fb_data->epdc_clk_pix); + clk_disable_unprepare(fb_data->epdc_clk_axi); + fb_data->rev = ((val & EPDC_VERSION_MAJOR_MASK) >> + EPDC_VERSION_MAJOR_OFFSET) * 10 + + ((val & EPDC_VERSION_MINOR_MASK) >> + EPDC_VERSION_MINOR_OFFSET); + dev_dbg(&pdev->dev, "EPDC version = %d\n", fb_data->rev); + + if (fb_data->rev < 20) { + fb_data->num_luts = EPDC_V1_NUM_LUTS; + fb_data->max_num_updates = EPDC_V1_MAX_NUM_UPDATES; + } else { + fb_data->num_luts = EPDC_V2_NUM_LUTS; + fb_data->max_num_updates = EPDC_V2_MAX_NUM_UPDATES; + if (vmode->xres > EPDC_V2_MAX_UPDATE_WIDTH) + fb_data->restrict_width = true; + } + fb_data->max_num_buffers = EPDC_MAX_NUM_BUFFERS; + + /* + * Initialize lists for pending updates, + * active update requests, update collisions, + * and freely available updates. + */ + INIT_LIST_HEAD(&fb_data->upd_pending_list); + INIT_LIST_HEAD(&fb_data->upd_buf_queue); + INIT_LIST_HEAD(&fb_data->upd_buf_free_list); + INIT_LIST_HEAD(&fb_data->upd_buf_collision_list); + + /* Allocate update buffers and add them to the list */ + for (i = 0; i < fb_data->max_num_updates; i++) { + upd_list = kzalloc(sizeof(*upd_list), GFP_KERNEL); + if (upd_list == NULL) { + ret = -ENOMEM; + goto out_upd_lists; + } + + /* Add newly allocated buffer to free list */ + list_add(&upd_list->list, &fb_data->upd_buf_free_list); + } + + fb_data->virt_addr_updbuf = + kzalloc(sizeof(void *) * fb_data->max_num_buffers, GFP_KERNEL); + fb_data->phys_addr_updbuf = + kzalloc(sizeof(dma_addr_t) * fb_data->max_num_buffers, + GFP_KERNEL); + for (i = 0; i < fb_data->max_num_buffers; i++) { + /* + * Allocate memory for PxP output buffer. + * Each update buffer is 1 byte per pixel, and can + * be as big as the full-screen frame buffer + */ + fb_data->virt_addr_updbuf[i] = + kmalloc(fb_data->max_pix_size, GFP_KERNEL); + fb_data->phys_addr_updbuf[i] = + virt_to_phys(fb_data->virt_addr_updbuf[i]); + if (fb_data->virt_addr_updbuf[i] == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + + dev_dbg(fb_data->info.device, "allocated %d bytes @ 0x%08X\n", + fb_data->max_pix_size, fb_data->phys_addr_updbuf[i]); + } + + /* Counter indicating which update buffer should be used next. */ + fb_data->upd_buffer_num = 0; + + /* + * Allocate memory for PxP SW workaround buffer + * These buffers are used to hold copy of the update region, + * before sending it to PxP for processing. + */ + fb_data->virt_addr_copybuf = + dma_alloc_coherent(fb_data->info.device, fb_data->max_pix_size*2, + &fb_data->phys_addr_copybuf, + GFP_DMA | GFP_KERNEL); + if (fb_data->virt_addr_copybuf == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + + fb_data->virt_addr_y4 = + dma_alloc_coherent(fb_data->info.device, fb_data->max_pix_size*2, + &fb_data->phys_addr_y4, + GFP_DMA | GFP_KERNEL); + if (fb_data->virt_addr_y4 == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + + fb_data->virt_addr_y4c = + dma_alloc_coherent(fb_data->info.device, fb_data->max_pix_size*2, + &fb_data->phys_addr_y4c, + GFP_DMA | GFP_KERNEL); + if (fb_data->virt_addr_y4c == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + + fb_data->virt_addr_black = + dma_alloc_coherent(fb_data->info.device, fb_data->max_pix_size*2, + &fb_data->phys_addr_black, + GFP_DMA | GFP_KERNEL); + if (fb_data->virt_addr_black == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + + fb_data->working_buffer_size = vmode->yres * vmode->xres * 2; + + /* Allocate memory for EPDC working buffer */ + fb_data->working_buffer_virt = + dma_alloc_coherent(&pdev->dev, fb_data->working_buffer_size, + &fb_data->working_buffer_phys, + GFP_DMA | GFP_KERNEL); + if (fb_data->working_buffer_virt == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for working buf!\n"); + ret = -ENOMEM; + goto out_copybuffer; + } + + /* initialize the working buffer */ + wk_p = (unsigned short *)fb_data->working_buffer_virt; + for (i = 0; i < fb_data->cur_mode->vmode->xres * + fb_data->cur_mode->vmode->yres; i++) { + *wk_p = 0x00F0; + wk_p++; + } + + fb_data->tmp_working_buffer_virt = + dma_alloc_coherent(&pdev->dev, fb_data->working_buffer_size, + &fb_data->tmp_working_buffer_phys, + GFP_DMA | GFP_KERNEL); + if (fb_data->tmp_working_buffer_virt == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for tmp working buf!\n"); + ret = -ENOMEM; + goto out_copybuffer; + } + + /* Initialize EPDC pins */ + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) { + dev_err(&pdev->dev, "can't get/select pinctrl\n"); + ret = PTR_ERR(pinctrl); + goto out_copybuffer; + } + + fb_data->in_init = false; + + fb_data->hw_ready = false; + fb_data->hw_initializing = false; + + /* + * Set default waveform mode values. + * Should be overwritten via ioctl. + */ + fb_data->wv_modes.mode_init = 0; + fb_data->wv_modes.mode_du = 1; + fb_data->wv_modes.mode_gc4 = 3; + fb_data->wv_modes.mode_gc8 = 2; + fb_data->wv_modes.mode_gc16 = 2; + fb_data->wv_modes.mode_gc32 = 2; + fb_data->wv_modes_update = true; + + /* Initialize marker list */ + INIT_LIST_HEAD(&fb_data->full_marker_list); + + /* Initialize all LUTs to inactive */ + fb_data->lut_update_order = + kzalloc(fb_data->num_luts * sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < fb_data->num_luts; i++) + fb_data->lut_update_order[i] = 0; + + INIT_DELAYED_WORK(&fb_data->epdc_done_work, epdc_done_work_func); + fb_data->epdc_submit_workqueue = alloc_workqueue("EPDC Submit", + WQ_MEM_RECLAIM | WQ_HIGHPRI | + WQ_CPU_INTENSIVE | WQ_UNBOUND, 1); + INIT_WORK(&fb_data->epdc_submit_work, epdc_submit_work_func); + fb_data->epdc_intr_workqueue = alloc_workqueue("EPDC Interrupt", + WQ_MEM_RECLAIM | WQ_HIGHPRI | + WQ_CPU_INTENSIVE | WQ_UNBOUND, 1); + INIT_WORK(&fb_data->epdc_intr_work, epdc_intr_work_func); + + /* Retrieve EPDC IRQ num */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "cannot get IRQ resource\n"); + ret = -ENODEV; + goto out_dma_work_buf; + } + fb_data->epdc_irq = irq; + + /* Register IRQ handler */ + ret = devm_request_irq(&pdev->dev, fb_data->epdc_irq, + mxc_epdc_irq_handler, 0, "epdc", fb_data); + if (ret) { + dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", + fb_data->epdc_irq, ret); + ret = -ENODEV; + goto out_dma_work_buf; + } + + info->fbdefio = &mxc_epdc_fb_defio; +#ifdef CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE + fb_deferred_io_init(info); +#endif + + /* get pmic regulators */ + fb_data->display_regulator = devm_regulator_get(&pdev->dev, "DISPLAY"); + if (IS_ERR(fb_data->display_regulator)) { + dev_err(&pdev->dev, "Unable to get display PMIC regulator." + "err = 0x%x\n", (int)fb_data->display_regulator); + ret = -ENODEV; + goto out_dma_work_buf; + } + fb_data->vcom_regulator = devm_regulator_get(&pdev->dev, "VCOM"); + if (IS_ERR(fb_data->vcom_regulator)) { + dev_err(&pdev->dev, "Unable to get VCOM regulator." + "err = 0x%x\n", (int)fb_data->vcom_regulator); + ret = -ENODEV; + goto out_dma_work_buf; + } + fb_data->v3p3_regulator = devm_regulator_get(&pdev->dev, "V3P3"); + if (IS_ERR(fb_data->v3p3_regulator)) { + dev_err(&pdev->dev, "Unable to get V3P3 regulator." + "err = 0x%x\n", (int)fb_data->vcom_regulator); + ret = -ENODEV; + goto out_dma_work_buf; + } + + if (device_create_file(info->dev, &fb_attrs[0])) + dev_err(&pdev->dev, "Unable to create file from fb_attrs\n"); + + fb_data->cur_update = NULL; + + mutex_init(&fb_data->queue_mutex); + mutex_init(&fb_data->pxp_mutex); + mutex_init(&fb_data->power_mutex); + + /* + * Fill out PxP config data structure based on FB info and + * processing tasks required + */ + pxp_conf = &fb_data->pxp_conf; + proc_data = &pxp_conf->proc_data; + + /* Initialize non-channel-specific PxP parameters */ + proc_data->drect.left = proc_data->srect.left = 0; + proc_data->drect.top = proc_data->srect.top = 0; + proc_data->drect.width = proc_data->srect.width = fb_data->info.var.xres; + proc_data->drect.height = proc_data->srect.height = fb_data->info.var.yres; + proc_data->scaling = 0; + proc_data->hflip = 0; + proc_data->vflip = 0; + proc_data->rotate = 0; + proc_data->bgcolor = 0; + proc_data->overlay_state = 0; + proc_data->lut_transform = PXP_LUT_NONE; + proc_data->lut_map = NULL; + + /* + * We initially configure PxP for RGB->YUV conversion, + * and only write out Y component of the result. + */ + + /* + * Initialize S0 channel parameters + * Parameters should match FB format/width/height + */ + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + pxp_conf->s0_param.width = fb_data->info.var.xres_virtual; + pxp_conf->s0_param.stride = (var_info->bits_per_pixel * pxp_conf->s0_param.width) >> 3; + pxp_conf->s0_param.height = fb_data->info.var.yres; + pxp_conf->s0_param.color_key = -1; + pxp_conf->s0_param.color_key_enable = false; + + /* + * Initialize OL0 channel parameters + * No overlay will be used for PxP operation + */ + pxp_conf->ol_param[0].combine_enable = false; + pxp_conf->ol_param[0].width = 0; + pxp_conf->ol_param[0].height = 0; + pxp_conf->ol_param[0].pixel_fmt = PXP_PIX_FMT_RGB565; + pxp_conf->ol_param[0].color_key_enable = false; + pxp_conf->ol_param[0].color_key = -1; + pxp_conf->ol_param[0].global_alpha_enable = false; + pxp_conf->ol_param[0].global_alpha = 0; + pxp_conf->ol_param[0].local_alpha_enable = false; + + /* + * Initialize Output channel parameters + * Output is Y-only greyscale + * Output width/height will vary based on update region size + */ + pxp_conf->out_param.width = fb_data->info.var.xres; + pxp_conf->out_param.height = fb_data->info.var.yres; + pxp_conf->out_param.stride = pxp_conf->out_param.width; + pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GREY; + + /* Initialize color map for conversion of 8-bit gray pixels */ + fb_data->pxp_conf.proc_data.lut_map = kmalloc(256, GFP_KERNEL); + if (fb_data->pxp_conf.proc_data.lut_map == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for lut map!\n"); + ret = -ENOMEM; + goto out_dma_work_buf; + } + for (i = 0; i < 256; i++) + fb_data->pxp_conf.proc_data.lut_map[i] = i; + + fb_data->pxp_conf.proc_data.lut_map_updated = true; + + /* + * Ensure this is set to NULL here...we will initialize pxp_chan + * later in our thread. + */ + fb_data->pxp_chan = NULL; + + /* Initialize Scatter-gather list containing 2 buffer addresses. */ + sg = fb_data->sg; + sg_init_table(sg, SG_NUM); + + /* + * For use in PxP transfers: + * sg[0] holds the FB buffer pointer + * sg[1] holds the Output buffer pointer (configured before TX request) + */ + sg_dma_address(&sg[0]) = info->fix.smem_start; + sg_set_page(&sg[0], virt_to_page(info->screen_base), + info->fix.smem_len, offset_in_page(info->screen_base)); + + fb_data->order_cnt = 0; + fb_data->waiting_for_wb = false; + fb_data->waiting_for_lut = false; + fb_data->waiting_for_lut15 = false; + fb_data->waiting_for_idle = false; + fb_data->blank = FB_BLANK_UNBLANK; + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + fb_data->wait_for_powerdown = false; + fb_data->updates_active = false; + fb_data->pwrdown_delay = 0; + + /* Register FB */ + ret = register_framebuffer(info); + if (ret) { + dev_err(&pdev->dev, + "register_framebuffer failed with error %d\n", ret); + goto out_lutmap; + } + + g_fb_data = fb_data; + + pm_runtime_enable(fb_data->dev); + +#ifdef DEFAULT_PANEL_HW_INIT + ret = mxc_epdc_fb_init_hw((struct fb_info *)fb_data); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize HW!\n"); + } +#endif + + goto out; + +out_lutmap: + kfree(fb_data->pxp_conf.proc_data.lut_map); +out_dma_work_buf: + dma_free_wc(&pdev->dev, fb_data->working_buffer_size, + fb_data->working_buffer_virt, fb_data->working_buffer_phys); +out_copybuffer: + dma_free_wc(&pdev->dev, fb_data->max_pix_size*2, + fb_data->virt_addr_copybuf, + fb_data->phys_addr_copybuf); +out_upd_buffers: + for (i = 0; i < fb_data->max_num_buffers; i++) + if (fb_data->virt_addr_updbuf[i] != NULL) + kfree(fb_data->virt_addr_updbuf[i]); + if (fb_data->virt_addr_updbuf != NULL) + kfree(fb_data->virt_addr_updbuf); + if (fb_data->phys_addr_updbuf != NULL) + kfree(fb_data->phys_addr_updbuf); +out_upd_lists: + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list, + list) { + list_del(&plist->list); + kfree(plist); + } +out_dma_fb: + dma_free_wc(&pdev->dev, fb_data->map_size, info->screen_base, + fb_data->phys_start); + +out_cmap: + fb_dealloc_cmap(&info->cmap); +out_fbdata: + kfree(fb_data); +out: + return ret; +} + +static int mxc_epdc_fb_remove(struct platform_device *pdev) +{ + struct update_data_list *plist, *temp_list; + struct mxc_epdc_fb_data *fb_data = platform_get_drvdata(pdev); + int i; + + mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &fb_data->info); + + flush_workqueue(fb_data->epdc_submit_workqueue); + destroy_workqueue(fb_data->epdc_submit_workqueue); + + unregister_framebuffer(&fb_data->info); + + for (i = 0; i < fb_data->max_num_buffers; i++) + if (fb_data->virt_addr_updbuf[i] != NULL) + kfree(fb_data->virt_addr_updbuf[i]); + if (fb_data->virt_addr_updbuf != NULL) + kfree(fb_data->virt_addr_updbuf); + if (fb_data->phys_addr_updbuf != NULL) + kfree(fb_data->phys_addr_updbuf); + + dma_free_wc(&pdev->dev, fb_data->working_buffer_size, + fb_data->working_buffer_virt, + fb_data->working_buffer_phys); + if (fb_data->waveform_buffer_virt != NULL) + dma_free_wc(&pdev->dev, fb_data->waveform_buffer_size, + fb_data->waveform_buffer_virt, + fb_data->waveform_buffer_phys); + if (fb_data->virt_addr_copybuf != NULL) + dma_free_wc(&pdev->dev, fb_data->max_pix_size*2, + fb_data->virt_addr_copybuf, + fb_data->phys_addr_copybuf); + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list, + list) { + list_del(&plist->list); + kfree(plist); + } +#ifdef CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE + fb_deferred_io_cleanup(&fb_data->info); +#endif + + dma_free_wc(&pdev->dev, fb_data->map_size, fb_data->info.screen_base, + fb_data->phys_start); + + /* Release PxP-related resources */ + if (fb_data->pxp_chan != NULL) + dma_release_channel(&fb_data->pxp_chan->dma_chan); + + fb_dealloc_cmap(&fb_data->info.cmap); + + framebuffer_release(&fb_data->info); + if (!IS_ERR_OR_NULL(fb_data->gpr)) + regmap_update_bits(fb_data->gpr, fb_data->req_gpr, + 1 << fb_data->req_bit, 1 << fb_data->req_bit); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mxc_epdc_fb_suspend(struct device *dev) +{ + struct mxc_epdc_fb_data *data = dev_get_drvdata(dev); + int ret; + + data->pwrdown_delay = FB_POWERDOWN_DISABLE; + ret = mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &data->info); + + if (ret) + goto out; + +out: + pinctrl_pm_select_sleep_state(dev); + + return ret; +} + +static void mxc_epdc_restore_qos(struct mxc_epdc_fb_data *data) +{ + if (IS_ERR_OR_NULL(data->qos_regmap)) { + dev_dbg(data->dev, "no QoS setting found.\n"); + return; + } + +#define QOS_EPDC_OFFSET 0x3400 +#define QOS_PXP0_OFFSET 0x2C00 +#define QOS_PXP1_OFFSET 0x3C00 + regmap_write(data->qos_regmap, 0, 0); + regmap_write(data->qos_regmap, 0x60, 0); + regmap_write(data->qos_regmap, QOS_EPDC_OFFSET, 0); + regmap_write(data->qos_regmap, QOS_PXP0_OFFSET, 0); + regmap_write(data->qos_regmap, QOS_PXP1_OFFSET, 0); + + regmap_write(data->qos_regmap, QOS_EPDC_OFFSET + 0xd0, 0x0f020722); + regmap_write(data->qos_regmap, QOS_EPDC_OFFSET + 0xe0, 0x0f020722); + + regmap_write(data->qos_regmap, QOS_PXP0_OFFSET, 1); + regmap_write(data->qos_regmap, QOS_PXP1_OFFSET, 1); + + regmap_write(data->qos_regmap, QOS_PXP0_OFFSET + 0x50, 0x0f020222); + regmap_write(data->qos_regmap, QOS_PXP1_OFFSET + 0x50, 0x0f020222); + regmap_write(data->qos_regmap, QOS_PXP0_OFFSET + 0x60, 0x0f020222); + regmap_write(data->qos_regmap, QOS_PXP1_OFFSET + 0x60, 0x0f020222); + regmap_write(data->qos_regmap, QOS_PXP0_OFFSET + 0x70, 0x0f020422); + regmap_write(data->qos_regmap, QOS_PXP1_OFFSET + 0x70, 0x0f020422); + + if (!IS_ERR_OR_NULL(data->gpr)) + regmap_update_bits(data->gpr, 0x34, 0xe080, 0xe080); +} + +static int mxc_epdc_fb_resume(struct device *dev) +{ + struct mxc_epdc_fb_data *data = dev_get_drvdata(dev); + + pinctrl_pm_select_default_state(dev); + + mxc_epdc_restore_qos(data); + + mxc_epdc_fb_blank(FB_BLANK_UNBLANK, &data->info); + epdc_init_settings(data); + data->updates_active = false; + + return 0; +} +#else +#define mxc_epdc_fb_suspend NULL +#define mxc_epdc_fb_resume NULL +#endif + +#ifdef CONFIG_PM +static int mxc_epdc_fb_runtime_suspend(struct device *dev) +{ + release_bus_freq(BUS_FREQ_HIGH); + dev_dbg(dev, "epdc busfreq high release.\n"); + + return 0; +} + +static int mxc_epdc_fb_runtime_resume(struct device *dev) +{ + request_bus_freq(BUS_FREQ_HIGH); + dev_dbg(dev, "epdc busfreq high request.\n"); + + return 0; +} +#else +#define mxc_epdc_fb_runtime_suspend NULL +#define mxc_epdc_fb_runtime_resume NULL +#endif + +static const struct dev_pm_ops mxc_epdc_fb_pm_ops = { + SET_RUNTIME_PM_OPS(mxc_epdc_fb_runtime_suspend, + mxc_epdc_fb_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(mxc_epdc_fb_suspend, mxc_epdc_fb_resume) +}; + +static void mxc_epdc_fb_shutdown(struct platform_device *pdev) +{ + struct mxc_epdc_fb_data *fb_data = platform_get_drvdata(pdev); + + /* Disable power to the EPD panel */ + if (regulator_is_enabled(fb_data->vcom_regulator)) + regulator_disable(fb_data->vcom_regulator); + if (regulator_is_enabled(fb_data->display_regulator)) + regulator_disable(fb_data->display_regulator); + + /* Disable clocks to EPDC */ + clk_prepare_enable(fb_data->epdc_clk_axi); + clk_prepare_enable(fb_data->epdc_clk_pix); + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_SET); + clk_disable_unprepare(fb_data->epdc_clk_pix); + clk_disable_unprepare(fb_data->epdc_clk_axi); + + /* turn off the V3p3 */ + if (regulator_is_enabled(fb_data->v3p3_regulator)) + regulator_disable(fb_data->v3p3_regulator); +} + +static struct platform_driver mxc_epdc_fb_driver = { + .probe = mxc_epdc_fb_probe, + .remove = mxc_epdc_fb_remove, + .shutdown = mxc_epdc_fb_shutdown, + .driver = { + .name = "imx_epdc_v2_fb", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(imx_epdc_dt_ids), + .pm = &mxc_epdc_fb_pm_ops, + }, +}; + +/* Callback function triggered after PxP receives an EOF interrupt */ +static void pxp_dma_done(void *arg) +{ + struct pxp_tx_desc *tx_desc = to_tx_desc(arg); + struct dma_chan *chan = tx_desc->txd.chan; + struct pxp_channel *pxp_chan = to_pxp_channel(chan); + struct mxc_epdc_fb_data *fb_data = pxp_chan->client; + + /* + * if epd works in external mode, we should queue epdc_intr_workqueue + * after a wfe_a process finishes. + */ + if (fb_data->epdc_wb_mode && (tx_desc->proc_data.engine_enable & PXP_ENABLE_WFE_A)) { + pxp_get_collision_info(&fb_data->col_info); + queue_work(fb_data->epdc_intr_workqueue, + &fb_data->epdc_intr_work); + } + + /* This call will signal wait_for_completion_timeout() in send_buffer_to_pxp */ + complete(&fb_data->pxp_tx_cmpl); +} + +static bool chan_filter(struct dma_chan *chan, void *arg) +{ + if (imx_dma_is_pxp(chan)) + return true; + else + return false; +} + +/* Function to request PXP DMA channel */ +static int pxp_chan_init(struct mxc_epdc_fb_data *fb_data) +{ + dma_cap_mask_t mask; + struct dma_chan *chan; + + /* + * Request a free channel + */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_PRIVATE, mask); + chan = dma_request_channel(mask, chan_filter, NULL); + if (!chan) { + dev_err(fb_data->dev, "Unsuccessfully received channel!!!!\n"); + return -EBUSY; + } + + fb_data->pxp_chan = to_pxp_channel(chan); + fb_data->pxp_chan->client = fb_data; + + init_completion(&fb_data->pxp_tx_cmpl); + + return 0; +} + +static int pxp_wfe_a_process_clear_workingbuffer(struct mxc_epdc_fb_data *fb_data, + u32 panel_width, u32 panel_height) +{ + dma_cookie_t cookie; + struct scatterlist *sg = fb_data->sg; + struct dma_chan *dma_chan; + struct pxp_tx_desc *desc; + struct dma_async_tx_descriptor *txd; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; + int i, j = 0, ret; + int length; + + dev_dbg(fb_data->dev, "Starting PxP WFE_A process for clearing WB.\n"); + + /* First, check to see that we have acquired a PxP Channel object */ + if (fb_data->pxp_chan == NULL) { + /* + * PxP Channel has not yet been created and initialized, + * so let's go ahead and try + */ + ret = pxp_chan_init(fb_data); + if (ret) { + /* + * PxP channel init failed, and we can't use the + * PxP until the PxP DMA driver has loaded, so we abort + */ + dev_err(fb_data->dev, "PxP chan init failed\n"); + return -ENODEV; + } + } + + /* + * Init completion, so that we + * can be properly informed of the completion + * of the PxP task when it is done. + */ + init_completion(&fb_data->pxp_tx_cmpl); + + dma_chan = &fb_data->pxp_chan->dma_chan; + + txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2 + 4, + DMA_TO_DEVICE, + DMA_PREP_INTERRUPT, + NULL); + if (!txd) { + dev_err(fb_data->info.device, + "Error preparing a DMA transaction descriptor.\n"); + return -EIO; + } + + txd->callback_param = txd; + txd->callback = pxp_dma_done; + + proc_data->working_mode = PXP_MODE_STANDARD; + proc_data->engine_enable = PXP_ENABLE_WFE_A; + proc_data->lut = 0; + proc_data->detection_only = 0; + proc_data->reagl_en = 0; + proc_data->partial_update = 0; + proc_data->alpha_en = 1; + proc_data->lut_sels = fb_data->luts_complete; + proc_data->lut_cleanup = 1; + + pxp_conf->wfe_a_fetch_param[0].stride = panel_width; + pxp_conf->wfe_a_fetch_param[0].width = panel_width; + pxp_conf->wfe_a_fetch_param[0].height = panel_height; + pxp_conf->wfe_a_fetch_param[0].paddr = fb_data->phys_addr_black; + pxp_conf->wfe_a_fetch_param[1].stride = panel_width; + pxp_conf->wfe_a_fetch_param[1].width = panel_width; + pxp_conf->wfe_a_fetch_param[1].height = panel_height; + pxp_conf->wfe_a_fetch_param[1].paddr = fb_data->working_buffer_phys; + pxp_conf->wfe_a_fetch_param[0].left = 0; + pxp_conf->wfe_a_fetch_param[0].top = 0; + pxp_conf->wfe_a_fetch_param[1].left = 0; + pxp_conf->wfe_a_fetch_param[1].top = 0; + + pxp_conf->wfe_a_store_param[0].stride = panel_width; + pxp_conf->wfe_a_store_param[0].width = panel_width; + pxp_conf->wfe_a_store_param[0].height = panel_height; + pxp_conf->wfe_a_store_param[0].paddr = fb_data->phys_addr_y4c; + pxp_conf->wfe_a_store_param[1].stride = panel_width; + pxp_conf->wfe_a_store_param[1].width = panel_width; + pxp_conf->wfe_a_store_param[1].height = panel_height; + pxp_conf->wfe_a_store_param[1].paddr = fb_data->working_buffer_phys; + pxp_conf->wfe_a_store_param[0].left = 0; + pxp_conf->wfe_a_store_param[0].top = 0; + pxp_conf->wfe_a_store_param[1].left = 0; + pxp_conf->wfe_a_store_param[1].top = 0; + + desc = to_tx_desc(txd); + length = desc->len; + + memcpy(&desc->proc_data, proc_data, sizeof(struct pxp_proc_data)); + for (i = 0; i < length; i++) { + if (i == 0 || i == 1) {/* wfe_a won't use s0 or output at all */ + desc = desc->next; + + } else if ((pxp_conf->proc_data.engine_enable & PXP_ENABLE_WFE_A) && (j < 4)) { + for (j = 0; j < 4; j++) { + if (j == 0) { + memcpy(&desc->layer_param.processing_param, + &pxp_conf->wfe_a_fetch_param[0], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_FETCH0; + } else if (j == 1) { + memcpy(&desc->layer_param.processing_param, + &pxp_conf->wfe_a_fetch_param[1], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_FETCH1; + } else if (j == 2) { + memcpy(&desc->layer_param.processing_param, + &pxp_conf->wfe_a_store_param[0], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_STORE0; + } else if (j == 3) { + memcpy(&desc->layer_param.processing_param, + &pxp_conf->wfe_a_store_param[1], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_STORE1; + } + + desc = desc->next; + } + + i += 4; + } + } + + /* Submitting our TX starts the PxP processing task */ + cookie = txd->tx_submit(txd); + if (cookie < 0) { + dev_err(fb_data->info.device, "Error sending FB through PxP\n"); + return -EIO; + } + + fb_data->txd = txd; + + /* trigger ePxP */ + dma_async_issue_pending(dma_chan); + + return 0; +} + +static int pxp_clear_wb_work_func(struct mxc_epdc_fb_data *fb_data) +{ + unsigned int hist_stat; + int ret; + + dev_dbg(fb_data->dev, "PxP WFE to clear working buffer.\n"); + + mutex_lock(&fb_data->pxp_mutex); + ret = pxp_wfe_a_process_clear_workingbuffer(fb_data, fb_data->cur_mode->vmode->xres, fb_data->cur_mode->vmode->yres); + if (ret) { + dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + mutex_unlock(&fb_data->pxp_mutex); + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) { + epdc_powerup(fb_data); + } + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_complete_update(fb_data, &hist_stat); + if (ret) { + dev_err(fb_data->dev, "Unable to complete PxP update task: clear wb process\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + return 0; +} + + +/* PS_AS_OUT */ +static int pxp_legacy_process(struct mxc_epdc_fb_data *fb_data, + u32 src_width, u32 src_height, + struct mxcfb_rect *update_region) +{ + dma_cookie_t cookie; + struct scatterlist *sg = fb_data->sg; + struct dma_chan *dma_chan; + struct pxp_tx_desc *desc; + struct dma_async_tx_descriptor *txd; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; + struct fb_var_screeninfo *screeninfo = &fb_data->info.var; + int i, ret; + int length; + + dev_dbg(fb_data->dev, "Starting PxP legacy process.\n"); + + /* First, check to see that we have acquired a PxP Channel object */ + if (fb_data->pxp_chan == NULL) { + /* + * PxP Channel has not yet been created and initialized, + * so let's go ahead and try + */ + ret = pxp_chan_init(fb_data); + if (ret) { + /* + * PxP channel init failed, and we can't use the + * PxP until the PxP DMA driver has loaded, so we abort + */ + dev_err(fb_data->dev, "PxP chan init failed\n"); + return -ENODEV; + } + } + + /* + * Init completion, so that we + * can be properly informed of the completion + * of the PxP task when it is done. + */ + init_completion(&fb_data->pxp_tx_cmpl); + + dma_chan = &fb_data->pxp_chan->dma_chan; + + txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2, + DMA_TO_DEVICE, + DMA_PREP_INTERRUPT, + NULL); + if (!txd) { + dev_err(fb_data->info.device, + "Error preparing a DMA transaction descriptor.\n"); + return -EIO; + } + + txd->callback_param = txd; + txd->callback = pxp_dma_done; + + proc_data->working_mode = PXP_MODE_LEGACY; + proc_data->engine_enable = PXP_ENABLE_PS_AS_OUT; + + /* + * Configure PxP for processing of new update region + * The rest of our config params were set up in + * probe() and should not need to be changed. + */ + pxp_conf->s0_param.width = src_width; + pxp_conf->s0_param.stride = (screeninfo->bits_per_pixel * src_width) >> 3; + pxp_conf->s0_param.height = src_height; + proc_data->srect.top = update_region->top; + proc_data->srect.left = update_region->left; + proc_data->srect.width = update_region->width; + proc_data->srect.height = update_region->height; + proc_data->lut_cleanup = 0; + + /* + * Because only YUV/YCbCr image can be scaled, configure + * drect equivalent to srect, as such do not perform scaling. + */ + proc_data->drect.top = 0; + proc_data->drect.left = 0; + + /* PXP expects rotation in terms of degrees */ + proc_data->rotate = fb_data->epdc_fb_var.rotate * 90; + if (proc_data->rotate > 270) + proc_data->rotate = 0; + + /* we should pass the rotated values to PXP */ + if ((proc_data->rotate == 90) || (proc_data->rotate == 270)) { + proc_data->drect.width = proc_data->srect.height; + proc_data->drect.height = proc_data->srect.width; + pxp_conf->out_param.width = update_region->height; + pxp_conf->out_param.height = update_region->width; + pxp_conf->out_param.stride = update_region->height; + } else { + proc_data->drect.width = proc_data->srect.width; + proc_data->drect.height = proc_data->srect.height; + pxp_conf->out_param.width = update_region->width; + pxp_conf->out_param.height = update_region->height; + pxp_conf->out_param.stride = update_region->width; + } + + /* For EPDC v2.0, we need output to be 64-bit + * aligned since EPDC stride does not work. */ + if (fb_data->rev <= 20) + pxp_conf->out_param.stride = ALIGN(pxp_conf->out_param.stride, 8); + + desc = to_tx_desc(txd); + length = desc->len; + + for (i = 0; i < length; i++) { + if (i == 0) {/* S0 */ + memcpy(&desc->proc_data, proc_data, sizeof(struct pxp_proc_data)); + pxp_conf->s0_param.paddr = sg_dma_address(&sg[0]); + memcpy(&desc->layer_param.s0_param, &pxp_conf->s0_param, + sizeof(struct pxp_layer_param)); + desc = desc->next; + + } else if (i == 1) { + pxp_conf->out_param.paddr = sg_dma_address(&sg[1]); + memcpy(&desc->layer_param.out_param, &pxp_conf->out_param, + sizeof(struct pxp_layer_param)); + desc = desc->next; + } + } + + /* Submitting our TX starts the PxP processing task */ + cookie = txd->tx_submit(txd); + if (cookie < 0) { + dev_err(fb_data->info.device, "Error sending FB through PxP\n"); + return -EIO; + } + + fb_data->txd = txd; + + /* trigger ePxP */ + dma_async_issue_pending(dma_chan); + + return 0; +} + +static int pxp_process_dithering(struct mxc_epdc_fb_data *fb_data, + struct mxcfb_rect *update_region) +{ + dma_cookie_t cookie; + struct scatterlist *sg = fb_data->sg; + struct dma_chan *dma_chan; + struct pxp_tx_desc *desc; + struct dma_async_tx_descriptor *txd; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; + int i, j = 0, ret; + int length; + + dev_dbg(fb_data->dev, "Starting PxP Dithering process.\n"); + + /* First, check to see that we have acquired a PxP Channel object */ + if (fb_data->pxp_chan == NULL) { + /* + * PxP Channel has not yet been created and initialized, + * so let's go ahead and try + */ + ret = pxp_chan_init(fb_data); + if (ret) { + /* + * PxP channel init failed, and we can't use the + * PxP until the PxP DMA driver has loaded, so we abort + */ + dev_err(fb_data->dev, "PxP chan init failed\n"); + return -ENODEV; + } + } + + /* + * Init completion, so that we + * can be properly informed of the completion + * of the PxP task when it is done. + */ + init_completion(&fb_data->pxp_tx_cmpl); + + dma_chan = &fb_data->pxp_chan->dma_chan; + + txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2 + 4, + DMA_TO_DEVICE, + DMA_PREP_INTERRUPT, + NULL); + if (!txd) { + dev_err(fb_data->info.device, + "Error preparing a DMA transaction descriptor.\n"); + return -EIO; + } + + txd->callback_param = txd; + txd->callback = pxp_dma_done; + + proc_data->working_mode = PXP_MODE_STANDARD; + proc_data->engine_enable = PXP_ENABLE_DITHER; + + pxp_conf->dither_fetch_param[0].stride = update_region->width; + pxp_conf->dither_fetch_param[0].width = update_region->width; + pxp_conf->dither_fetch_param[0].height = update_region->height; +#ifdef USE_PS_AS_OUTPUT + pxp_conf->dither_fetch_param[0].paddr = sg_dma_address(&sg[1]); +#else + pxp_conf->dither_fetch_param[0].paddr = sg_dma_address(&sg[0]); +#endif + pxp_conf->dither_fetch_param[1].stride = update_region->width; + pxp_conf->dither_fetch_param[1].width = update_region->width; + pxp_conf->dither_fetch_param[1].height = update_region->height; + pxp_conf->dither_fetch_param[1].paddr = pxp_conf->dither_fetch_param[0].paddr; + + pxp_conf->dither_store_param[0].stride = update_region->width; + pxp_conf->dither_store_param[0].width = update_region->width; + pxp_conf->dither_store_param[0].height = update_region->height; + pxp_conf->dither_store_param[0].paddr = fb_data->phys_addr_y4; + pxp_conf->dither_store_param[1].stride = update_region->width; + pxp_conf->dither_store_param[1].width = update_region->width; + pxp_conf->dither_store_param[1].height = update_region->height; + pxp_conf->dither_store_param[1].paddr = pxp_conf->dither_store_param[0].paddr; + + desc = to_tx_desc(txd); + length = desc->len; + + for (i = 0; i < length; i++) { + if (i == 0) {/* S0 */ + memcpy(&desc->proc_data, proc_data, sizeof(struct pxp_proc_data)); + pxp_conf->s0_param.paddr = sg_dma_address(&sg[0]); + memcpy(&desc->layer_param.s0_param, &pxp_conf->s0_param, + sizeof(struct pxp_layer_param)); + desc = desc->next; + } else if (i == 1) { + pxp_conf->out_param.paddr = sg_dma_address(&sg[1]); + memcpy(&desc->layer_param.out_param, &pxp_conf->out_param, + sizeof(struct pxp_layer_param)); + desc = desc->next; + } else if ((pxp_conf->proc_data.engine_enable & PXP_ENABLE_DITHER) && (j < 4)) { + for (j = 0; j < 4; j++) { + if (j == 0) { + memcpy(&desc->layer_param.processing_param, + &pxp_conf->dither_fetch_param[0], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param.flag = PXP_BUF_FLAG_DITHER_FETCH0; + } else if (j == 1) { + memcpy(&desc->layer_param.processing_param, + &pxp_conf->dither_fetch_param[1], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param.flag = PXP_BUF_FLAG_DITHER_FETCH1; + } else if (j == 2) { + memcpy(&desc->layer_param.processing_param, + &pxp_conf->dither_store_param[0], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param.flag = PXP_BUF_FLAG_DITHER_STORE0; + } else if (j == 3) { + memcpy(&desc->layer_param.processing_param, + &pxp_conf->dither_store_param[1], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param.flag = PXP_BUF_FLAG_DITHER_STORE1; + } + + desc = desc->next; + } + + i += 4; + } + } + + /* Submitting our TX starts the PxP processing task */ + cookie = txd->tx_submit(txd); + if (cookie < 0) { + dev_err(fb_data->info.device, "Error sending FB through PxP\n"); + return -EIO; + } + + fb_data->txd = txd; + + /* trigger ePxP */ + dma_async_issue_pending(dma_chan); + + return 0; +} + +/* + * Function to call PxP DMA driver and send our latest FB update region + * through the PxP and out to an intermediate buffer. + * Note: This is a blocking call, so upon return the PxP tx should be complete. + */ +static int pxp_wfe_a_process(struct mxc_epdc_fb_data *fb_data, + struct mxcfb_rect *update_region, + struct update_data_list *upd_data_list) +{ + dma_cookie_t cookie; + struct scatterlist *sg = fb_data->sg; + struct dma_chan *dma_chan; + struct pxp_tx_desc *desc; + struct dma_async_tx_descriptor *txd; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; + u32 wv_mode = upd_data_list->update_desc->upd_data.waveform_mode; + int i, j = 0, ret; + int length; + bool is_transform; + + dev_dbg(fb_data->dev, "Starting PxP WFE_A process.\n"); + + /* First, check to see that we have acquired a PxP Channel object */ + if (fb_data->pxp_chan == NULL) { + /* + * PxP Channel has not yet been created and initialized, + * so let's go ahead and try + */ + ret = pxp_chan_init(fb_data); + if (ret) { + /* + * PxP channel init failed, and we can't use the + * PxP until the PxP DMA driver has loaded, so we abort + */ + dev_err(fb_data->dev, "PxP chan init failed\n"); + return -ENODEV; + } + } + + /* + * Init completion, so that we + * can be properly informed of the completion + * of the PxP task when it is done. + */ + init_completion(&fb_data->pxp_tx_cmpl); + + dma_chan = &fb_data->pxp_chan->dma_chan; + + txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2 + 4, + DMA_TO_DEVICE, + DMA_PREP_INTERRUPT, + NULL); + if (!txd) { + dev_err(fb_data->info.device, + "Error preparing a DMA transaction descriptor.\n"); + return -EIO; + } + + txd->callback_param = txd; + txd->callback = pxp_dma_done; + + proc_data->working_mode = PXP_MODE_STANDARD; + proc_data->engine_enable = PXP_ENABLE_WFE_A; + proc_data->lut = upd_data_list->lut_num; + proc_data->alpha_en = 0; + proc_data->lut_sels = fb_data->luts_complete; + proc_data->lut_status_1 = __raw_readl(EPDC_STATUS_LUTS); + proc_data->lut_status_2 = __raw_readl(EPDC_STATUS_LUTS2); + proc_data->lut_cleanup = 0; + + if (upd_data_list->update_desc->upd_data.flags & EPDC_FLAG_TEST_COLLISION) { + proc_data->detection_only = 1; + dev_dbg(fb_data->info.device, + "collision test_only send to pxp\n"); + } else + proc_data->detection_only = 0; + + if (wv_mode == WAVEFORM_MODE_GLR16 + || wv_mode == WAVEFORM_MODE_GLD16) + proc_data->reagl_en = 1; + + if (upd_data_list->update_desc->upd_data.update_mode == UPDATE_MODE_PARTIAL) + proc_data->partial_update = 1; + else + proc_data->partial_update = 0; + + /* fetch0 is upd buffer */ + pxp_conf->wfe_a_fetch_param[0].stride = upd_data_list->update_desc->epdc_stride; + pxp_conf->wfe_a_fetch_param[0].width = update_region->width; + pxp_conf->wfe_a_fetch_param[0].height = update_region->height; + /* upd buffer left and top should be always 0 */ + pxp_conf->wfe_a_fetch_param[0].left = 0; + pxp_conf->wfe_a_fetch_param[0].top = 0; + if (proc_data->dither_mode) { + pxp_conf->wfe_a_fetch_param[0].paddr = fb_data->phys_addr_y4; + } else { + is_transform = ((upd_data_list->update_desc->upd_data.flags & + (EPDC_FLAG_ENABLE_INVERSION | EPDC_FLAG_USE_DITHERING_Y1 | + EPDC_FLAG_USE_DITHERING_Y4 | EPDC_FLAG_FORCE_MONOCHROME | + EPDC_FLAG_USE_CMAP)) && (proc_data->scaling == 0) && + (proc_data->hflip == 0) && (proc_data->vflip == 0)) ? + true : false; + + if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) && + (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT) && + !is_transform && (proc_data->dither_mode == 0) && + !(upd_data_list->update_desc->upd_data.flags & + EPDC_FLAG_USE_ALT_BUFFER) && + !fb_data->restrict_width) { + sg_dma_address(&sg[0]) = fb_data->info.fix.smem_start; + sg_set_page(&sg[0], + virt_to_page(fb_data->info.screen_base), + fb_data->info.fix.smem_len, + offset_in_page(fb_data->info.screen_base)); + pxp_conf->wfe_a_fetch_param[0].paddr = + sg_dma_address(&sg[0]); + + pxp_conf->wfe_a_fetch_param[0].left = update_region->left; + pxp_conf->wfe_a_fetch_param[0].top = update_region->top; + } else + pxp_conf->wfe_a_fetch_param[0].paddr = + upd_data_list->phys_addr + upd_data_list->update_desc->epdc_offs; + } + + /* fetch1 is working buffer */ + pxp_conf->wfe_a_fetch_param[1].stride = fb_data->cur_mode->vmode->xres; + pxp_conf->wfe_a_fetch_param[1].width = update_region->width; + pxp_conf->wfe_a_fetch_param[1].height = update_region->height; + pxp_conf->wfe_a_fetch_param[1].paddr = fb_data->working_buffer_phys; + pxp_conf->wfe_a_fetch_param[1].left = update_region->left; + pxp_conf->wfe_a_fetch_param[1].top = update_region->top; + + /* store0 is y4c buffer */ + pxp_conf->wfe_a_store_param[0].stride = fb_data->cur_mode->vmode->xres; + pxp_conf->wfe_a_store_param[0].width = update_region->width; + pxp_conf->wfe_a_store_param[0].height = update_region->height; + pxp_conf->wfe_a_store_param[0].paddr = fb_data->phys_addr_y4c; + + /* store1 is (temp) working buffer */ + pxp_conf->wfe_a_store_param[1].stride = fb_data->cur_mode->vmode->xres; + pxp_conf->wfe_a_store_param[1].width = update_region->width; + pxp_conf->wfe_a_store_param[1].height = update_region->height; + if (wv_mode == WAVEFORM_MODE_GLR16 + || wv_mode == WAVEFORM_MODE_GLD16) + pxp_conf->wfe_a_store_param[1].paddr = fb_data->tmp_working_buffer_phys; + else + pxp_conf->wfe_a_store_param[1].paddr = fb_data->working_buffer_phys; + pxp_conf->wfe_a_store_param[1].left = update_region->left; + pxp_conf->wfe_a_store_param[1].top = update_region->top; + + desc = to_tx_desc(txd); + length = desc->len; + + memcpy(&desc->proc_data, proc_data, sizeof(struct pxp_proc_data)); + for (i = 0; i < length; i++) { + if (i == 0 || i == 1) {/* wfe_a won't use s0 or output at all */ + desc = desc->next; + } else if ((pxp_conf->proc_data.engine_enable & PXP_ENABLE_WFE_A) && (j < 4)) { + for (j = 0; j < 4; j++) { + if (j == 0) { + memcpy(&desc->layer_param.processing_param, + &pxp_conf->wfe_a_fetch_param[0], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_FETCH0; + } else if (j == 1) { + memcpy(&desc->layer_param.processing_param, + &pxp_conf->wfe_a_fetch_param[1], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_FETCH1; + } else if (j == 2) { + memcpy(&desc->layer_param.processing_param, + &pxp_conf->wfe_a_store_param[0], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_STORE0; + } else if (j == 3) { + memcpy(&desc->layer_param.processing_param, + &pxp_conf->wfe_a_store_param[1], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_STORE1; + } + + desc = desc->next; + } + + i += 4; + } + } + + /* Submitting our TX starts the PxP processing task */ + cookie = txd->tx_submit(txd); + if (cookie < 0) { + dev_err(fb_data->info.device, "Error sending FB through PxP\n"); + return -EIO; + } + + fb_data->txd = txd; + + /* trigger ePxP */ + dma_async_issue_pending(dma_chan); + + return 0; +} + +/* For REAGL/-D processing */ +static int pxp_wfe_b_process_update(struct mxc_epdc_fb_data *fb_data, + struct mxcfb_rect *update_region) +{ + dma_cookie_t cookie; + struct scatterlist *sg = fb_data->sg; + struct dma_chan *dma_chan; + struct pxp_tx_desc *desc; + struct dma_async_tx_descriptor *txd; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; + int i, j = 0, ret; + int length; + + dev_dbg(fb_data->dev, "Starting PxP WFE_B process.\n"); + + /* First, check to see that we have acquired a PxP Channel object */ + if (fb_data->pxp_chan == NULL) { + /* + * PxP Channel has not yet been created and initialized, + * so let's go ahead and try + */ + ret = pxp_chan_init(fb_data); + if (ret) { + /* + * PxP channel init failed, and we can't use the + * PxP until the PxP DMA driver has loaded, so we abort + */ + dev_err(fb_data->dev, "PxP chan init failed\n"); + return -ENODEV; + } + } + + /* + * Init completion, so that we + * can be properly informed of the completion + * of the PxP task when it is done. + */ + init_completion(&fb_data->pxp_tx_cmpl); + + dma_chan = &fb_data->pxp_chan->dma_chan; + + txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2 + 4, + DMA_TO_DEVICE, + DMA_PREP_INTERRUPT, + NULL); + if (!txd) { + dev_err(fb_data->info.device, + "Error preparing a DMA transaction descriptor.\n"); + return -EIO; + } + + txd->callback_param = txd; + txd->callback = pxp_dma_done; + + proc_data->working_mode = PXP_MODE_STANDARD; + proc_data->engine_enable = PXP_ENABLE_WFE_B; + proc_data->lut_update = false; + proc_data->lut_cleanup = 0; + + pxp_conf->wfe_b_fetch_param[0].stride = fb_data->cur_mode->vmode->xres; + pxp_conf->wfe_b_fetch_param[0].width = fb_data->cur_mode->vmode->xres; + pxp_conf->wfe_b_fetch_param[0].height = fb_data->cur_mode->vmode->yres; + pxp_conf->wfe_b_fetch_param[0].paddr = fb_data->phys_addr_black; + pxp_conf->wfe_b_fetch_param[1].stride = fb_data->cur_mode->vmode->xres; + pxp_conf->wfe_b_fetch_param[1].width = update_region->width; + pxp_conf->wfe_b_fetch_param[1].height = update_region->height; + pxp_conf->wfe_b_fetch_param[1].top = update_region->top; + pxp_conf->wfe_b_fetch_param[1].left = update_region->left; + pxp_conf->wfe_b_fetch_param[1].paddr = fb_data->tmp_working_buffer_phys; + + pxp_conf->wfe_b_store_param[0].stride = fb_data->cur_mode->vmode->xres; + pxp_conf->wfe_b_store_param[0].width = update_region->width; + pxp_conf->wfe_b_store_param[0].height = update_region->height; + pxp_conf->wfe_b_store_param[0].top = update_region->top; + pxp_conf->wfe_b_store_param[0].left = update_region->left; + pxp_conf->wfe_b_store_param[0].paddr = fb_data->working_buffer_phys; + pxp_conf->wfe_b_store_param[1].stride = fb_data->cur_mode->vmode->xres; + pxp_conf->wfe_b_store_param[1].width = update_region->width; + pxp_conf->wfe_b_store_param[1].height = update_region->height; + pxp_conf->wfe_b_store_param[1].paddr = 0; + + desc = to_tx_desc(txd); + length = desc->len; + + for (i = 0; i < length; i++) { + if (i == 0) { /* S0 */ + memcpy(&desc->proc_data, proc_data, + sizeof(struct pxp_proc_data)); + pxp_conf->s0_param.paddr = sg_dma_address(&sg[0]); + memcpy(&desc->layer_param.s0_param, &pxp_conf->s0_param, + sizeof(struct pxp_layer_param)); + desc = desc->next; + } else if (i == 1) { + pxp_conf->out_param.paddr = sg_dma_address(&sg[1]); + memcpy(&desc->layer_param.out_param, + &pxp_conf->out_param, + sizeof(struct pxp_layer_param)); + desc = desc->next; + } else + if ((pxp_conf->proc_data.engine_enable & PXP_ENABLE_WFE_B) + && (j < 4)) { + for (j = 0; j < 4; j++) { + if (j == 0) { + memcpy(&desc->layer_param. + processing_param, + &pxp_conf->wfe_b_fetch_param[0], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param. + flag = PXP_BUF_FLAG_WFE_B_FETCH0; + } else if (j == 1) { + memcpy(&desc->layer_param. + processing_param, + &pxp_conf->wfe_b_fetch_param[1], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param. + flag = PXP_BUF_FLAG_WFE_B_FETCH1; + } else if (j == 2) { + memcpy(&desc->layer_param. + processing_param, + &pxp_conf->wfe_b_store_param[0], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param. + flag = PXP_BUF_FLAG_WFE_B_STORE0; + } else if (j == 3) { + memcpy(&desc->layer_param. + processing_param, + &pxp_conf->wfe_b_store_param[1], + sizeof(struct pxp_layer_param)); + desc->layer_param.processing_param. + flag = PXP_BUF_FLAG_WFE_B_STORE1; + } + + desc = desc->next; + } + + i += 4; + } + } + + /* Submitting our TX starts the PxP processing task */ + cookie = txd->tx_submit(txd); + if (cookie < 0) { + dev_err(fb_data->info.device, "Error sending FB through PxP\n"); + return -EIO; + } + + fb_data->txd = txd; + + /* trigger ePxP */ + dma_async_issue_pending(dma_chan); + + return 0; +} + +static int pxp_complete_update(struct mxc_epdc_fb_data *fb_data, u32 *hist_stat) +{ + int ret; + /* + * Wait for completion event, which will be set + * through our TX callback function. + */ + ret = wait_for_completion_timeout(&fb_data->pxp_tx_cmpl, HZ * 2); + if (ret <= 0) { + dev_info(fb_data->info.device, + "PxP operation failed due to %s\n", + ret < 0 ? "user interrupt" : "timeout"); + dma_release_channel(&fb_data->pxp_chan->dma_chan); + fb_data->pxp_chan = NULL; + return ret ? : -ETIMEDOUT; + } + + if ((fb_data->pxp_conf.proc_data.lut_transform & EPDC_FLAG_USE_CMAP) && + fb_data->pxp_conf.proc_data.lut_map_updated) + fb_data->pxp_conf.proc_data.lut_map_updated = false; + + *hist_stat = to_tx_desc(fb_data->txd)->hist_status; + dma_release_channel(&fb_data->pxp_chan->dma_chan); + fb_data->pxp_chan = NULL; + + dev_dbg(fb_data->dev, "TX completed\n"); + + return 0; +} + +/* + * Different dithering algorithm can be used. We chose + * to implement Bill Atkinson's algorithm as an example + * Thanks Bill Atkinson for his dithering algorithm. + */ + +/* + * Dithering algorithm implementation - Y8->Y1 version 1.0 for i.MX + */ +static void do_dithering_processing_Y1_v1_0( + unsigned char *update_region_virt_ptr, + dma_addr_t update_region_phys_ptr, + struct mxcfb_rect *update_region, + unsigned long update_region_stride, + int *err_dist) +{ + + /* create a temp error distribution array */ + int bwPix; + int y; + int col; + int *err_dist_l0, *err_dist_l1, *err_dist_l2, distrib_error; + int width_3 = update_region->width + 3; + char *y8buf; + int x_offset = 0; + + /* prime a few elements the error distribution array */ + for (y = 0; y < update_region->height; y++) { + /* Dithering the Y8 in sbuf to BW suitable for A2 waveform */ + err_dist_l0 = err_dist + (width_3) * (y % 3); + err_dist_l1 = err_dist + (width_3) * ((y + 1) % 3); + err_dist_l2 = err_dist + (width_3) * ((y + 2) % 3); + + y8buf = update_region_virt_ptr + x_offset; + + /* scan the line and convert the Y8 to BW */ + for (col = 1; col <= update_region->width; col++) { + bwPix = *(err_dist_l0 + col) + *y8buf; + + if (bwPix >= 128) { + *y8buf++ = 0xff; + distrib_error = (bwPix - 255) >> 3; + } else { + *y8buf++ = 0; + distrib_error = bwPix >> 3; + } + + /* modify the error distribution buffer */ + *(err_dist_l0 + col + 2) += distrib_error; + *(err_dist_l1 + col + 1) += distrib_error; + *(err_dist_l0 + col + 1) += distrib_error; + *(err_dist_l1 + col - 1) += distrib_error; + *(err_dist_l1 + col) += distrib_error; + *(err_dist_l2 + col) = distrib_error; + } + x_offset += update_region_stride; + } + + flush_cache_all(); + outer_flush_range(update_region_phys_ptr, update_region_phys_ptr + + update_region->height * update_region->width); +} + +/* + * Dithering algorithm implementation - Y8->Y4 version 1.0 for i.MX + */ + +static void do_dithering_processing_Y4_v1_0( + unsigned char *update_region_virt_ptr, + dma_addr_t update_region_phys_ptr, + struct mxcfb_rect *update_region, + unsigned long update_region_stride, + int *err_dist) +{ + + /* create a temp error distribution array */ + int gcPix; + int y; + int col; + int *err_dist_l0, *err_dist_l1, *err_dist_l2, distrib_error; + int width_3 = update_region->width + 3; + char *y8buf; + int x_offset = 0; + + /* prime a few elements the error distribution array */ + for (y = 0; y < update_region->height; y++) { + /* Dithering the Y8 in sbuf to Y4 */ + err_dist_l0 = err_dist + (width_3) * (y % 3); + err_dist_l1 = err_dist + (width_3) * ((y + 1) % 3); + err_dist_l2 = err_dist + (width_3) * ((y + 2) % 3); + + y8buf = update_region_virt_ptr + x_offset; + + /* scan the line and convert the Y8 to Y4 */ + for (col = 1; col <= update_region->width; col++) { + gcPix = *(err_dist_l0 + col) + *y8buf; + + if (gcPix > 255) + gcPix = 255; + else if (gcPix < 0) + gcPix = 0; + + distrib_error = (*y8buf - (gcPix & 0xf0)) >> 3; + + *y8buf++ = gcPix & 0xf0; + + /* modify the error distribution buffer */ + *(err_dist_l0 + col + 2) += distrib_error; + *(err_dist_l1 + col + 1) += distrib_error; + *(err_dist_l0 + col + 1) += distrib_error; + *(err_dist_l1 + col - 1) += distrib_error; + *(err_dist_l1 + col) += distrib_error; + *(err_dist_l2 + col) = distrib_error; + } + x_offset += update_region_stride; + } + + flush_cache_all(); + outer_flush_range(update_region_phys_ptr, update_region_phys_ptr + + update_region->height * update_region->width); +} + +static int __init mxc_epdc_fb_init(void) +{ + return platform_driver_register(&mxc_epdc_fb_driver); +} +late_initcall(mxc_epdc_fb_init); + +static void __exit mxc_epdc_fb_exit(void) +{ + platform_driver_unregister(&mxc_epdc_fb_driver); +} +module_exit(mxc_epdc_fb_exit); + + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC EPDC V2 framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/fbdev/mxc/mxc_hdmi.c b/drivers/video/fbdev/mxc/mxc_hdmi.c new file mode 100644 index 000000000000..708a37cd56f5 --- /dev/null +++ b/drivers/video/fbdev/mxc/mxc_hdmi.c @@ -0,0 +1,2986 @@ +/* + * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * SH-Mobile High-Definition Multimedia Interface (HDMI) driver + * for SLISHDMI13T and SLIPHDMIT IP cores + * + * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/fb.h> +#include <linux/fbcon.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/uaccess.h> +#include <linux/cpufreq.h> +#include <linux/firmware.h> +#include <linux/kthread.h> +#include <linux/regulator/driver.h> +#include <linux/fsl_devices.h> +#include <linux/ipu.h> +#include <linux/regmap.h> +#include <linux/pinctrl/consumer.h> +#include <linux/of_device.h> + +#include <linux/console.h> +#include <linux/types.h> + +#include "../edid.h" +#include <video/mxc_edid.h> +#include <video/mxc_hdmi.h> +#include "mxc_dispdrv.h" + +#include <linux/mfd/mxc-hdmi-core.h> + +#define DISPDRV_HDMI "hdmi" +#define HDMI_EDID_LEN 512 + +/* status codes for reading edid */ +#define HDMI_EDID_SUCCESS 0 +#define HDMI_EDID_FAIL -1 +#define HDMI_EDID_SAME -2 +#define HDMI_EDID_NO_MODES -3 + +#define NUM_CEA_VIDEO_MODES 64 +#define DEFAULT_VIDEO_MODE 16 /* 1080P */ + +#define RGB 0 +#define YCBCR444 1 +#define YCBCR422_16BITS 2 +#define YCBCR422_8BITS 3 +#define XVYCC444 4 + +/* + * We follow a flowchart which is in the "Synopsys DesignWare Courses + * HDMI Transmitter Controller User Guide, 1.30a", section 3.1 + * (dwc_hdmi_tx_user.pdf) + * + * Below are notes that say "HDMI Initialization Step X" + * These correspond to the flowchart. + */ + +/* + * We are required to configure VGA mode before reading edid + * in HDMI Initialization Step B + */ +static const struct fb_videomode vga_mode = { + /* 640x480 @ 60 Hz, 31.5 kHz hsync */ + NULL, 60, 640, 480, 39721, 48, 16, 33, 10, 96, 2, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, FB_MODE_IS_VESA, +}; + +enum hdmi_datamap { + RGB444_8B = 0x01, + RGB444_10B = 0x03, + RGB444_12B = 0x05, + RGB444_16B = 0x07, + YCbCr444_8B = 0x09, + YCbCr444_10B = 0x0B, + YCbCr444_12B = 0x0D, + YCbCr444_16B = 0x0F, + YCbCr422_8B = 0x16, + YCbCr422_10B = 0x14, + YCbCr422_12B = 0x12, +}; + +enum hdmi_colorimetry { + eITU601, + eITU709, +}; + +struct hdmi_vmode { + bool mDVI; + bool mHSyncPolarity; + bool mVSyncPolarity; + bool mInterlaced; + bool mDataEnablePolarity; + + unsigned int mPixelClock; + unsigned int mPixelRepetitionInput; + unsigned int mPixelRepetitionOutput; +}; + +struct hdmi_data_info { + unsigned int enc_in_format; + unsigned int enc_out_format; + unsigned int enc_color_depth; + unsigned int colorimetry; + unsigned int pix_repet_factor; + unsigned int hdcp_enable; + unsigned int rgb_out_enable; + struct hdmi_vmode video_mode; +}; + +struct hdmi_phy_reg_config { + /* HDMI PHY register config for pass HCT */ + u16 reg_vlev; + u16 reg_cksymtx; +}; + +struct mxc_hdmi { + struct platform_device *pdev; + struct platform_device *core_pdev; + struct mxc_dispdrv_handle *disp_mxc_hdmi; + struct fb_info *fbi; + struct clk *hdmi_isfr_clk; + struct clk *hdmi_iahb_clk; + struct clk *mipi_core_clk; + struct delayed_work hotplug_work; + struct delayed_work hdcp_hdp_work; + + struct notifier_block nb; + + struct hdmi_data_info hdmi_data; + int vic; + struct mxc_edid_cfg edid_cfg; + u8 edid[HDMI_EDID_LEN]; + bool fb_reg; + bool cable_plugin; + u8 blank; + bool dft_mode_set; + char *dft_mode_str; + int default_bpp; + u8 latest_intr_stat; + bool irq_enabled; + spinlock_t irq_lock; + bool phy_enabled; + struct fb_videomode default_mode; + struct fb_videomode previous_non_vga_mode; + bool requesting_vga_for_initialization; + + int *gpr_base; + int *gpr_hdmi_base; + int *gpr_sdma_base; + int cpu_type; + int cpu_version; + struct hdmi_phy_reg_config phy_config; + + struct pinctrl *pinctrl; +}; + +static int hdmi_major; +static struct class *hdmi_class; + +struct i2c_client *hdmi_i2c; +struct mxc_hdmi *g_hdmi; + +static bool hdmi_inited; +static bool hdcp_init; +static struct regulator *hdmi_regulator; + +extern const struct fb_videomode mxc_cea_mode[64]; +extern void mxc_hdmi_cec_handle(u16 cec_stat); + +static void mxc_hdmi_setup(struct mxc_hdmi *hdmi, unsigned long event); +static void mxc_hdmi_phy_disable(struct mxc_hdmi *hdmi); +static void hdmi_enable_overflow_interrupts(void); +static void hdmi_disable_overflow_interrupts(void); + +static struct platform_device_id imx_hdmi_devtype[] = { + { + .name = "hdmi-imx6DL", + .driver_data = IMX6DL_HDMI, + }, { + .name = "hdmi-imx6Q", + .driver_data = IMX6Q_HDMI, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, imx_hdmi_devtype); + +static const struct of_device_id imx_hdmi_dt_ids[] = { + { .compatible = "fsl,imx6dl-hdmi-video", .data = &imx_hdmi_devtype[IMX6DL_HDMI], }, + { .compatible = "fsl,imx6q-hdmi-video", .data = &imx_hdmi_devtype[IMX6Q_HDMI], }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_hdmi_dt_ids); + +static inline int cpu_is_imx6dl(struct mxc_hdmi *hdmi) +{ + return hdmi->cpu_type == IMX6DL_HDMI; +} +#ifdef DEBUG +static void dump_fb_videomode(const struct fb_videomode *m) +{ + pr_debug("fb_videomode = %d %d %d %d %d %d %d %d %d %d %d %d %d\n", + m->refresh, m->xres, m->yres, m->pixclock, m->left_margin, + m->right_margin, m->upper_margin, m->lower_margin, + m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag); +} +#else +static void dump_fb_videomode(const struct fb_videomode *m) +{} +#endif + +static ssize_t mxc_hdmi_show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxc_hdmi *hdmi = dev_get_drvdata(dev); + + strcpy(buf, hdmi->fbi->fix.id); + sprintf(buf+strlen(buf), "\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(fb_name, S_IRUGO, mxc_hdmi_show_name, NULL); + +static ssize_t mxc_hdmi_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxc_hdmi *hdmi = dev_get_drvdata(dev); + + if (hdmi->cable_plugin == false) + strcpy(buf, "plugout\n"); + else + strcpy(buf, "plugin\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(cable_state, S_IRUGO, mxc_hdmi_show_state, NULL); + +static ssize_t mxc_hdmi_show_edid(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxc_hdmi *hdmi = dev_get_drvdata(dev); + int i, j, len = 0; + + for (j = 0; j < HDMI_EDID_LEN/16; j++) { + for (i = 0; i < 16; i++) + len += sprintf(buf+len, "0x%02X ", + hdmi->edid[j*16 + i]); + len += sprintf(buf+len, "\n"); + } + + return len; +} + +static DEVICE_ATTR(edid, S_IRUGO, mxc_hdmi_show_edid, NULL); + +static ssize_t mxc_hdmi_show_rgb_out_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxc_hdmi *hdmi = dev_get_drvdata(dev); + + if (hdmi->hdmi_data.rgb_out_enable == true) + strcpy(buf, "RGB out\n"); + else + strcpy(buf, "YCbCr out\n"); + + return strlen(buf); +} + +static ssize_t mxc_hdmi_store_rgb_out_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct mxc_hdmi *hdmi = dev_get_drvdata(dev); + unsigned long value; + int ret; + + ret = kstrtoul(buf, 10, &value); + if (ret) + return ret; + + hdmi->hdmi_data.rgb_out_enable = value; + + /* Reconfig HDMI for output color space change */ + mxc_hdmi_setup(hdmi, 0); + + return count; +} + +static DEVICE_ATTR(rgb_out_enable, S_IRUGO | S_IWUSR, + mxc_hdmi_show_rgb_out_enable, + mxc_hdmi_store_rgb_out_enable); + +static ssize_t mxc_hdmi_show_hdcp_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxc_hdmi *hdmi = dev_get_drvdata(dev); + + if (hdmi->hdmi_data.hdcp_enable == false) + strcpy(buf, "hdcp disable\n"); + else + strcpy(buf, "hdcp enable\n"); + + return strlen(buf); + +} + +static ssize_t mxc_hdmi_store_hdcp_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct mxc_hdmi *hdmi = dev_get_drvdata(dev); + char event_string[32]; + char *envp[] = { event_string, NULL }; + unsigned long value; + u8 clkdis; + int ret; + + ret = kstrtoul(buf, 10, &value); + if (ret) + return ret; + + hdmi->hdmi_data.hdcp_enable = value; + + /* Disable All HDMI clock and HDMI PHY */ + clkdis = hdmi_readb(HDMI_MC_CLKDIS); + clkdis |= 0x5f; + hdmi_writeb(clkdis, HDMI_MC_CLKDIS); + mxc_hdmi_phy_disable(hdmi); + hdmi_disable_overflow_interrupts(); + + /* Reconfig HDMI for HDCP */ + mxc_hdmi_setup(hdmi, 0); + + if (hdmi->hdmi_data.hdcp_enable == false) { + sprintf(event_string, "EVENT=hdcpdisable"); + kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp); + } else { + sprintf(event_string, "EVENT=hdcpenable"); + kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp); + } + + return count; + +} + +static DEVICE_ATTR(hdcp_enable, S_IRUGO | S_IWUSR, + mxc_hdmi_show_hdcp_enable, mxc_hdmi_store_hdcp_enable); + +/*! + * this submodule is responsible for the video data synchronization. + * for example, for RGB 4:4:4 input, the data map is defined as + * pin{47~40} <==> R[7:0] + * pin{31~24} <==> G[7:0] + * pin{15~8} <==> B[7:0] + */ +static void hdmi_video_sample(struct mxc_hdmi *hdmi) +{ + int color_format = 0; + u8 val; + + if (hdmi->hdmi_data.enc_in_format == RGB) { + if (hdmi->hdmi_data.enc_color_depth == 8) + color_format = 0x01; + else if (hdmi->hdmi_data.enc_color_depth == 10) + color_format = 0x03; + else if (hdmi->hdmi_data.enc_color_depth == 12) + color_format = 0x05; + else if (hdmi->hdmi_data.enc_color_depth == 16) + color_format = 0x07; + else + return; + } else if (hdmi->hdmi_data.enc_in_format == YCBCR444) { + if (hdmi->hdmi_data.enc_color_depth == 8) + color_format = 0x09; + else if (hdmi->hdmi_data.enc_color_depth == 10) + color_format = 0x0B; + else if (hdmi->hdmi_data.enc_color_depth == 12) + color_format = 0x0D; + else if (hdmi->hdmi_data.enc_color_depth == 16) + color_format = 0x0F; + else + return; + } else if (hdmi->hdmi_data.enc_in_format == YCBCR422_8BITS) { + if (hdmi->hdmi_data.enc_color_depth == 8) + color_format = 0x16; + else if (hdmi->hdmi_data.enc_color_depth == 10) + color_format = 0x14; + else if (hdmi->hdmi_data.enc_color_depth == 12) + color_format = 0x12; + else + return; + } + + val = HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE | + ((color_format << HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET) & + HDMI_TX_INVID0_VIDEO_MAPPING_MASK); + hdmi_writeb(val, HDMI_TX_INVID0); + + /* Enable TX stuffing: When DE is inactive, fix the output data to 0 */ + val = HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE | + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE | + HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE; + hdmi_writeb(val, HDMI_TX_INSTUFFING); + hdmi_writeb(0x0, HDMI_TX_GYDATA0); + hdmi_writeb(0x0, HDMI_TX_GYDATA1); + hdmi_writeb(0x0, HDMI_TX_RCRDATA0); + hdmi_writeb(0x0, HDMI_TX_RCRDATA1); + hdmi_writeb(0x0, HDMI_TX_BCBDATA0); + hdmi_writeb(0x0, HDMI_TX_BCBDATA1); +} + +static int isColorSpaceConversion(struct mxc_hdmi *hdmi) +{ + return (hdmi->hdmi_data.enc_in_format != + hdmi->hdmi_data.enc_out_format); +} + +static int isColorSpaceDecimation(struct mxc_hdmi *hdmi) +{ + return ((hdmi->hdmi_data.enc_out_format == YCBCR422_8BITS) && + (hdmi->hdmi_data.enc_in_format == RGB || + hdmi->hdmi_data.enc_in_format == YCBCR444)); +} + +static int isColorSpaceInterpolation(struct mxc_hdmi *hdmi) +{ + return ((hdmi->hdmi_data.enc_in_format == YCBCR422_8BITS) && + (hdmi->hdmi_data.enc_out_format == RGB + || hdmi->hdmi_data.enc_out_format == YCBCR444)); +} + +/*! + * update the color space conversion coefficients. + */ +static void update_csc_coeffs(struct mxc_hdmi *hdmi) +{ + unsigned short csc_coeff[3][4]; + unsigned int csc_scale = 1; + u8 val; + bool coeff_selected = false; + + if (isColorSpaceConversion(hdmi)) { /* csc needed */ + if (hdmi->hdmi_data.enc_out_format == RGB) { + if (hdmi->hdmi_data.colorimetry == eITU601) { + csc_coeff[0][0] = 0x2000; + csc_coeff[0][1] = 0x6926; + csc_coeff[0][2] = 0x74fd; + csc_coeff[0][3] = 0x010e; + + csc_coeff[1][0] = 0x2000; + csc_coeff[1][1] = 0x2cdd; + csc_coeff[1][2] = 0x0000; + csc_coeff[1][3] = 0x7e9a; + + csc_coeff[2][0] = 0x2000; + csc_coeff[2][1] = 0x0000; + csc_coeff[2][2] = 0x38b4; + csc_coeff[2][3] = 0x7e3b; + + csc_scale = 1; + coeff_selected = true; + } else if (hdmi->hdmi_data.colorimetry == eITU709) { + csc_coeff[0][0] = 0x2000; + csc_coeff[0][1] = 0x7106; + csc_coeff[0][2] = 0x7a02; + csc_coeff[0][3] = 0x00a7; + + csc_coeff[1][0] = 0x2000; + csc_coeff[1][1] = 0x3264; + csc_coeff[1][2] = 0x0000; + csc_coeff[1][3] = 0x7e6d; + + csc_coeff[2][0] = 0x2000; + csc_coeff[2][1] = 0x0000; + csc_coeff[2][2] = 0x3b61; + csc_coeff[2][3] = 0x7e25; + + csc_scale = 1; + coeff_selected = true; + } + } else if (hdmi->hdmi_data.enc_in_format == RGB) { + if (hdmi->hdmi_data.colorimetry == eITU601) { + csc_coeff[0][0] = 0x2591; + csc_coeff[0][1] = 0x1322; + csc_coeff[0][2] = 0x074b; + csc_coeff[0][3] = 0x0000; + + csc_coeff[1][0] = 0x6535; + csc_coeff[1][1] = 0x2000; + csc_coeff[1][2] = 0x7acc; + csc_coeff[1][3] = 0x0200; + + csc_coeff[2][0] = 0x6acd; + csc_coeff[2][1] = 0x7534; + csc_coeff[2][2] = 0x2000; + csc_coeff[2][3] = 0x0200; + + csc_scale = 0; + coeff_selected = true; + } else if (hdmi->hdmi_data.colorimetry == eITU709) { + csc_coeff[0][0] = 0x2dc5; + csc_coeff[0][1] = 0x0d9b; + csc_coeff[0][2] = 0x049e; + csc_coeff[0][3] = 0x0000; + + csc_coeff[1][0] = 0x62f0; + csc_coeff[1][1] = 0x2000; + csc_coeff[1][2] = 0x7d11; + csc_coeff[1][3] = 0x0200; + + csc_coeff[2][0] = 0x6756; + csc_coeff[2][1] = 0x78ab; + csc_coeff[2][2] = 0x2000; + csc_coeff[2][3] = 0x0200; + + csc_scale = 0; + coeff_selected = true; + } + } + } + + if (!coeff_selected) { + csc_coeff[0][0] = 0x2000; + csc_coeff[0][1] = 0x0000; + csc_coeff[0][2] = 0x0000; + csc_coeff[0][3] = 0x0000; + + csc_coeff[1][0] = 0x0000; + csc_coeff[1][1] = 0x2000; + csc_coeff[1][2] = 0x0000; + csc_coeff[1][3] = 0x0000; + + csc_coeff[2][0] = 0x0000; + csc_coeff[2][1] = 0x0000; + csc_coeff[2][2] = 0x2000; + csc_coeff[2][3] = 0x0000; + + csc_scale = 1; + } + + /* Update CSC parameters in HDMI CSC registers */ + hdmi_writeb((unsigned char)(csc_coeff[0][0] & 0xFF), + HDMI_CSC_COEF_A1_LSB); + hdmi_writeb((unsigned char)(csc_coeff[0][0] >> 8), + HDMI_CSC_COEF_A1_MSB); + hdmi_writeb((unsigned char)(csc_coeff[0][1] & 0xFF), + HDMI_CSC_COEF_A2_LSB); + hdmi_writeb((unsigned char)(csc_coeff[0][1] >> 8), + HDMI_CSC_COEF_A2_MSB); + hdmi_writeb((unsigned char)(csc_coeff[0][2] & 0xFF), + HDMI_CSC_COEF_A3_LSB); + hdmi_writeb((unsigned char)(csc_coeff[0][2] >> 8), + HDMI_CSC_COEF_A3_MSB); + hdmi_writeb((unsigned char)(csc_coeff[0][3] & 0xFF), + HDMI_CSC_COEF_A4_LSB); + hdmi_writeb((unsigned char)(csc_coeff[0][3] >> 8), + HDMI_CSC_COEF_A4_MSB); + + hdmi_writeb((unsigned char)(csc_coeff[1][0] & 0xFF), + HDMI_CSC_COEF_B1_LSB); + hdmi_writeb((unsigned char)(csc_coeff[1][0] >> 8), + HDMI_CSC_COEF_B1_MSB); + hdmi_writeb((unsigned char)(csc_coeff[1][1] & 0xFF), + HDMI_CSC_COEF_B2_LSB); + hdmi_writeb((unsigned char)(csc_coeff[1][1] >> 8), + HDMI_CSC_COEF_B2_MSB); + hdmi_writeb((unsigned char)(csc_coeff[1][2] & 0xFF), + HDMI_CSC_COEF_B3_LSB); + hdmi_writeb((unsigned char)(csc_coeff[1][2] >> 8), + HDMI_CSC_COEF_B3_MSB); + hdmi_writeb((unsigned char)(csc_coeff[1][3] & 0xFF), + HDMI_CSC_COEF_B4_LSB); + hdmi_writeb((unsigned char)(csc_coeff[1][3] >> 8), + HDMI_CSC_COEF_B4_MSB); + + hdmi_writeb((unsigned char)(csc_coeff[2][0] & 0xFF), + HDMI_CSC_COEF_C1_LSB); + hdmi_writeb((unsigned char)(csc_coeff[2][0] >> 8), + HDMI_CSC_COEF_C1_MSB); + hdmi_writeb((unsigned char)(csc_coeff[2][1] & 0xFF), + HDMI_CSC_COEF_C2_LSB); + hdmi_writeb((unsigned char)(csc_coeff[2][1] >> 8), + HDMI_CSC_COEF_C2_MSB); + hdmi_writeb((unsigned char)(csc_coeff[2][2] & 0xFF), + HDMI_CSC_COEF_C3_LSB); + hdmi_writeb((unsigned char)(csc_coeff[2][2] >> 8), + HDMI_CSC_COEF_C3_MSB); + hdmi_writeb((unsigned char)(csc_coeff[2][3] & 0xFF), + HDMI_CSC_COEF_C4_LSB); + hdmi_writeb((unsigned char)(csc_coeff[2][3] >> 8), + HDMI_CSC_COEF_C4_MSB); + + val = hdmi_readb(HDMI_CSC_SCALE); + val &= ~HDMI_CSC_SCALE_CSCSCALE_MASK; + val |= csc_scale & HDMI_CSC_SCALE_CSCSCALE_MASK; + hdmi_writeb(val, HDMI_CSC_SCALE); +} + +static void hdmi_video_csc(struct mxc_hdmi *hdmi) +{ + int color_depth = 0; + int interpolation = HDMI_CSC_CFG_INTMODE_DISABLE; + int decimation = 0; + u8 val; + + /* YCC422 interpolation to 444 mode */ + if (isColorSpaceInterpolation(hdmi)) + interpolation = HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1; + else if (isColorSpaceDecimation(hdmi)) + decimation = HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA3; + + if (hdmi->hdmi_data.enc_color_depth == 8) + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP; + else if (hdmi->hdmi_data.enc_color_depth == 10) + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_30BPP; + else if (hdmi->hdmi_data.enc_color_depth == 12) + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_36BPP; + else if (hdmi->hdmi_data.enc_color_depth == 16) + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_48BPP; + else + return; + + /*configure the CSC registers */ + hdmi_writeb(interpolation | decimation, HDMI_CSC_CFG); + val = hdmi_readb(HDMI_CSC_SCALE); + val &= ~HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK; + val |= color_depth; + hdmi_writeb(val, HDMI_CSC_SCALE); + + update_csc_coeffs(hdmi); +} + +/*! + * HDMI video packetizer is used to packetize the data. + * for example, if input is YCC422 mode or repeater is used, + * data should be repacked this module can be bypassed. + */ +static void hdmi_video_packetize(struct mxc_hdmi *hdmi) +{ + unsigned int color_depth = 0; + unsigned int remap_size = HDMI_VP_REMAP_YCC422_16bit; + unsigned int output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_PP; + struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data; + u8 val; + + if (hdmi_data->enc_out_format == RGB + || hdmi_data->enc_out_format == YCBCR444) { + if (hdmi_data->enc_color_depth == 0) + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; + else if (hdmi_data->enc_color_depth == 8) { + color_depth = 4; + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; + } else if (hdmi_data->enc_color_depth == 10) + color_depth = 5; + else if (hdmi_data->enc_color_depth == 12) + color_depth = 6; + else if (hdmi_data->enc_color_depth == 16) + color_depth = 7; + else + return; + } else if (hdmi_data->enc_out_format == YCBCR422_8BITS) { + if (hdmi_data->enc_color_depth == 0 || + hdmi_data->enc_color_depth == 8) + remap_size = HDMI_VP_REMAP_YCC422_16bit; + else if (hdmi_data->enc_color_depth == 10) + remap_size = HDMI_VP_REMAP_YCC422_20bit; + else if (hdmi_data->enc_color_depth == 12) + remap_size = HDMI_VP_REMAP_YCC422_24bit; + else + return; + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422; + } else + return; + + /* HDMI not support deep color, + * because IPU MAX support color depth is 24bit */ + color_depth = 0; + + /* set the packetizer registers */ + val = ((color_depth << HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET) & + HDMI_VP_PR_CD_COLOR_DEPTH_MASK) | + ((hdmi_data->pix_repet_factor << + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_OFFSET) & + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK); + hdmi_writeb(val, HDMI_VP_PR_CD); + + val = hdmi_readb(HDMI_VP_STUFF); + val &= ~HDMI_VP_STUFF_PR_STUFFING_MASK; + val |= HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE; + hdmi_writeb(val, HDMI_VP_STUFF); + + /* Data from pixel repeater block */ + if (hdmi_data->pix_repet_factor > 1) { + val = hdmi_readb(HDMI_VP_CONF); + val &= ~(HDMI_VP_CONF_PR_EN_MASK | + HDMI_VP_CONF_BYPASS_SELECT_MASK); + val |= HDMI_VP_CONF_PR_EN_ENABLE | + HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER; + hdmi_writeb(val, HDMI_VP_CONF); + } else { /* data from packetizer block */ + val = hdmi_readb(HDMI_VP_CONF); + val &= ~(HDMI_VP_CONF_PR_EN_MASK | + HDMI_VP_CONF_BYPASS_SELECT_MASK); + val |= HDMI_VP_CONF_PR_EN_DISABLE | + HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER; + hdmi_writeb(val, HDMI_VP_CONF); + } + + val = hdmi_readb(HDMI_VP_STUFF); + val &= ~HDMI_VP_STUFF_IDEFAULT_PHASE_MASK; + val |= 1 << HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET; + hdmi_writeb(val, HDMI_VP_STUFF); + + hdmi_writeb(remap_size, HDMI_VP_REMAP); + + if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_PP) { + val = hdmi_readb(HDMI_VP_CONF); + val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK | + HDMI_VP_CONF_PP_EN_ENMASK | + HDMI_VP_CONF_YCC422_EN_MASK); + val |= HDMI_VP_CONF_BYPASS_EN_DISABLE | + HDMI_VP_CONF_PP_EN_ENABLE | + HDMI_VP_CONF_YCC422_EN_DISABLE; + hdmi_writeb(val, HDMI_VP_CONF); + } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422) { + val = hdmi_readb(HDMI_VP_CONF); + val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK | + HDMI_VP_CONF_PP_EN_ENMASK | + HDMI_VP_CONF_YCC422_EN_MASK); + val |= HDMI_VP_CONF_BYPASS_EN_DISABLE | + HDMI_VP_CONF_PP_EN_DISABLE | + HDMI_VP_CONF_YCC422_EN_ENABLE; + hdmi_writeb(val, HDMI_VP_CONF); + } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS) { + val = hdmi_readb(HDMI_VP_CONF); + val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK | + HDMI_VP_CONF_PP_EN_ENMASK | + HDMI_VP_CONF_YCC422_EN_MASK); + val |= HDMI_VP_CONF_BYPASS_EN_ENABLE | + HDMI_VP_CONF_PP_EN_DISABLE | + HDMI_VP_CONF_YCC422_EN_DISABLE; + hdmi_writeb(val, HDMI_VP_CONF); + } else { + return; + } + + val = hdmi_readb(HDMI_VP_STUFF); + val &= ~(HDMI_VP_STUFF_PP_STUFFING_MASK | + HDMI_VP_STUFF_YCC422_STUFFING_MASK); + val |= HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE | + HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE; + hdmi_writeb(val, HDMI_VP_STUFF); + + val = hdmi_readb(HDMI_VP_CONF); + val &= ~HDMI_VP_CONF_OUTPUT_SELECTOR_MASK; + val |= output_select; + hdmi_writeb(val, HDMI_VP_CONF); +} + +#if 0 +/* Force a fixed color screen */ +static void hdmi_video_force_output(struct mxc_hdmi *hdmi, unsigned char force) +{ + u8 val; + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + if (force) { + hdmi_writeb(0x00, HDMI_FC_DBGTMDS2); /* R */ + hdmi_writeb(0x00, HDMI_FC_DBGTMDS1); /* G */ + hdmi_writeb(0xFF, HDMI_FC_DBGTMDS0); /* B */ + val = hdmi_readb(HDMI_FC_DBGFORCE); + val |= HDMI_FC_DBGFORCE_FORCEVIDEO; + hdmi_writeb(val, HDMI_FC_DBGFORCE); + } else { + val = hdmi_readb(HDMI_FC_DBGFORCE); + val &= ~HDMI_FC_DBGFORCE_FORCEVIDEO; + hdmi_writeb(val, HDMI_FC_DBGFORCE); + hdmi_writeb(0x00, HDMI_FC_DBGTMDS2); /* R */ + hdmi_writeb(0x00, HDMI_FC_DBGTMDS1); /* G */ + hdmi_writeb(0x00, HDMI_FC_DBGTMDS0); /* B */ + } +} +#endif + +static inline void hdmi_phy_test_clear(struct mxc_hdmi *hdmi, + unsigned char bit) +{ + u8 val = hdmi_readb(HDMI_PHY_TST0); + val &= ~HDMI_PHY_TST0_TSTCLR_MASK; + val |= (bit << HDMI_PHY_TST0_TSTCLR_OFFSET) & + HDMI_PHY_TST0_TSTCLR_MASK; + hdmi_writeb(val, HDMI_PHY_TST0); +} + +static inline void hdmi_phy_test_enable(struct mxc_hdmi *hdmi, + unsigned char bit) +{ + u8 val = hdmi_readb(HDMI_PHY_TST0); + val &= ~HDMI_PHY_TST0_TSTEN_MASK; + val |= (bit << HDMI_PHY_TST0_TSTEN_OFFSET) & + HDMI_PHY_TST0_TSTEN_MASK; + hdmi_writeb(val, HDMI_PHY_TST0); +} + +static inline void hdmi_phy_test_clock(struct mxc_hdmi *hdmi, + unsigned char bit) +{ + u8 val = hdmi_readb(HDMI_PHY_TST0); + val &= ~HDMI_PHY_TST0_TSTCLK_MASK; + val |= (bit << HDMI_PHY_TST0_TSTCLK_OFFSET) & + HDMI_PHY_TST0_TSTCLK_MASK; + hdmi_writeb(val, HDMI_PHY_TST0); +} + +static inline void hdmi_phy_test_din(struct mxc_hdmi *hdmi, + unsigned char bit) +{ + hdmi_writeb(bit, HDMI_PHY_TST1); +} + +static inline void hdmi_phy_test_dout(struct mxc_hdmi *hdmi, + unsigned char bit) +{ + hdmi_writeb(bit, HDMI_PHY_TST2); +} + +static bool hdmi_phy_wait_i2c_done(struct mxc_hdmi *hdmi, int msec) +{ + unsigned char val = 0; + val = hdmi_readb(HDMI_IH_I2CMPHY_STAT0) & 0x3; + while (val == 0) { + udelay(1000); + if (msec-- == 0) + return false; + val = hdmi_readb(HDMI_IH_I2CMPHY_STAT0) & 0x3; + } + return true; +} + +static void hdmi_phy_i2c_write(struct mxc_hdmi *hdmi, unsigned short data, + unsigned char addr) +{ + hdmi_writeb(0xFF, HDMI_IH_I2CMPHY_STAT0); + hdmi_writeb(addr, HDMI_PHY_I2CM_ADDRESS_ADDR); + hdmi_writeb((unsigned char)(data >> 8), + HDMI_PHY_I2CM_DATAO_1_ADDR); + hdmi_writeb((unsigned char)(data >> 0), + HDMI_PHY_I2CM_DATAO_0_ADDR); + hdmi_writeb(HDMI_PHY_I2CM_OPERATION_ADDR_WRITE, + HDMI_PHY_I2CM_OPERATION_ADDR); + hdmi_phy_wait_i2c_done(hdmi, 1000); +} + +#if 0 +static unsigned short hdmi_phy_i2c_read(struct mxc_hdmi *hdmi, + unsigned char addr) +{ + unsigned short data; + unsigned char msb = 0, lsb = 0; + hdmi_writeb(0xFF, HDMI_IH_I2CMPHY_STAT0); + hdmi_writeb(addr, HDMI_PHY_I2CM_ADDRESS_ADDR); + hdmi_writeb(HDMI_PHY_I2CM_OPERATION_ADDR_READ, + HDMI_PHY_I2CM_OPERATION_ADDR); + hdmi_phy_wait_i2c_done(hdmi, 1000); + msb = hdmi_readb(HDMI_PHY_I2CM_DATAI_1_ADDR); + lsb = hdmi_readb(HDMI_PHY_I2CM_DATAI_0_ADDR); + data = (msb << 8) | lsb; + return data; +} + +static int hdmi_phy_i2c_write_verify(struct mxc_hdmi *hdmi, unsigned short data, + unsigned char addr) +{ + unsigned short val = 0; + hdmi_phy_i2c_write(hdmi, data, addr); + val = hdmi_phy_i2c_read(hdmi, addr); + return (val == data); +} +#endif + +static bool hdmi_edid_wait_i2c_done(struct mxc_hdmi *hdmi, int msec) +{ + unsigned char val = 0; + val = hdmi_readb(HDMI_IH_I2CM_STAT0) & 0x2; + while (val == 0) { + + udelay(1000); + if (msec-- == 0) { + dev_dbg(&hdmi->pdev->dev, + "HDMI EDID i2c operation time out!!\n"); + return false; + } + val = hdmi_readb(HDMI_IH_I2CM_STAT0) & 0x2; + } + return true; +} + +static u8 hdmi_edid_i2c_read(struct mxc_hdmi *hdmi, + u8 addr, u8 blockno) +{ + u8 spointer = blockno / 2; + u8 edidaddress = ((blockno % 2) * 0x80) + addr; + u8 data; + + hdmi_writeb(0xFF, HDMI_IH_I2CM_STAT0); + hdmi_writeb(edidaddress, HDMI_I2CM_ADDRESS); + hdmi_writeb(spointer, HDMI_I2CM_SEGADDR); + if (spointer == 0) + hdmi_writeb(HDMI_I2CM_OPERATION_READ, + HDMI_I2CM_OPERATION); + else + hdmi_writeb(HDMI_I2CM_OPERATION_READ_EXT, + HDMI_I2CM_OPERATION); + + hdmi_edid_wait_i2c_done(hdmi, 30); + data = hdmi_readb(HDMI_I2CM_DATAI); + hdmi_writeb(0xFF, HDMI_IH_I2CM_STAT0); + return data; +} + + +/* "Power-down enable (active low)" + * That mean that power up == 1! */ +static void mxc_hdmi_phy_enable_power(u8 enable) +{ + hdmi_mask_writeb(enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_PDZ_OFFSET, + HDMI_PHY_CONF0_PDZ_MASK); +} + +static void mxc_hdmi_phy_enable_tmds(u8 enable) +{ + hdmi_mask_writeb(enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_ENTMDS_OFFSET, + HDMI_PHY_CONF0_ENTMDS_MASK); +} + +static void mxc_hdmi_phy_gen2_pddq(u8 enable) +{ + hdmi_mask_writeb(enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET, + HDMI_PHY_CONF0_GEN2_PDDQ_MASK); +} + +static void mxc_hdmi_phy_gen2_txpwron(u8 enable) +{ + hdmi_mask_writeb(enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET, + HDMI_PHY_CONF0_GEN2_TXPWRON_MASK); +} + +#if 0 +static void mxc_hdmi_phy_gen2_enhpdrxsense(u8 enable) +{ + hdmi_mask_writeb(enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_OFFSET, + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_MASK); +} +#endif + +static void mxc_hdmi_phy_sel_data_en_pol(u8 enable) +{ + hdmi_mask_writeb(enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_SELDATAENPOL_OFFSET, + HDMI_PHY_CONF0_SELDATAENPOL_MASK); +} + +static void mxc_hdmi_phy_sel_interface_control(u8 enable) +{ + hdmi_mask_writeb(enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_SELDIPIF_OFFSET, + HDMI_PHY_CONF0_SELDIPIF_MASK); +} + +static int hdmi_phy_configure(struct mxc_hdmi *hdmi, unsigned char pRep, + unsigned char cRes, int cscOn) +{ + u8 val; + u8 msec; + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + /* color resolution 0 is 8 bit colour depth */ + if (cRes == 0) + cRes = 8; + + if (pRep != 0) + return false; + else if (cRes != 8 && cRes != 12) + return false; + + /* Enable csc path */ + if (cscOn) + val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH; + else + val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS; + + hdmi_writeb(val, HDMI_MC_FLOWCTRL); + + /* gen2 tx power off */ + mxc_hdmi_phy_gen2_txpwron(0); + + /* gen2 pddq */ + mxc_hdmi_phy_gen2_pddq(1); + + /* PHY reset */ + hdmi_writeb(HDMI_MC_PHYRSTZ_DEASSERT, HDMI_MC_PHYRSTZ); + hdmi_writeb(HDMI_MC_PHYRSTZ_ASSERT, HDMI_MC_PHYRSTZ); + + hdmi_writeb(HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST); + + hdmi_phy_test_clear(hdmi, 1); + hdmi_writeb(HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2, + HDMI_PHY_I2CM_SLAVE_ADDR); + hdmi_phy_test_clear(hdmi, 0); + + if (hdmi->hdmi_data.video_mode.mPixelClock <= 45250000) { + switch (cRes) { + case 8: + /* PLL/MPLL Cfg */ + hdmi_phy_i2c_write(hdmi, 0x01e0, 0x06); + hdmi_phy_i2c_write(hdmi, 0x0000, 0x15); /* GMPCTRL */ + break; + case 10: + hdmi_phy_i2c_write(hdmi, 0x21e1, 0x06); + hdmi_phy_i2c_write(hdmi, 0x0000, 0x15); + break; + case 12: + hdmi_phy_i2c_write(hdmi, 0x41e2, 0x06); + hdmi_phy_i2c_write(hdmi, 0x0000, 0x15); + break; + default: + return false; + } + } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 92500000) { + switch (cRes) { + case 8: + hdmi_phy_i2c_write(hdmi, 0x0140, 0x06); + hdmi_phy_i2c_write(hdmi, 0x0005, 0x15); + break; + case 10: + hdmi_phy_i2c_write(hdmi, 0x2141, 0x06); + hdmi_phy_i2c_write(hdmi, 0x0005, 0x15); + break; + case 12: + hdmi_phy_i2c_write(hdmi, 0x4142, 0x06); + hdmi_phy_i2c_write(hdmi, 0x0005, 0x15); + break; + default: + return false; + } + } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 148500000) { + switch (cRes) { + case 8: + hdmi_phy_i2c_write(hdmi, 0x00a0, 0x06); + hdmi_phy_i2c_write(hdmi, 0x000a, 0x15); + break; + case 10: + hdmi_phy_i2c_write(hdmi, 0x20a1, 0x06); + hdmi_phy_i2c_write(hdmi, 0x000a, 0x15); + break; + case 12: + hdmi_phy_i2c_write(hdmi, 0x40a2, 0x06); + hdmi_phy_i2c_write(hdmi, 0x000a, 0x15); + break; + default: + return false; + } + } else { + switch (cRes) { + case 8: + hdmi_phy_i2c_write(hdmi, 0x00a0, 0x06); + hdmi_phy_i2c_write(hdmi, 0x000a, 0x15); + break; + case 10: + hdmi_phy_i2c_write(hdmi, 0x2001, 0x06); + hdmi_phy_i2c_write(hdmi, 0x000f, 0x15); + break; + case 12: + hdmi_phy_i2c_write(hdmi, 0x4002, 0x06); + hdmi_phy_i2c_write(hdmi, 0x000f, 0x15); + break; + default: + return false; + } + } + + if (hdmi->hdmi_data.video_mode.mPixelClock <= 54000000) { + switch (cRes) { + case 8: + hdmi_phy_i2c_write(hdmi, 0x091c, 0x10); /* CURRCTRL */ + break; + case 10: + hdmi_phy_i2c_write(hdmi, 0x091c, 0x10); + break; + case 12: + hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10); + break; + default: + return false; + } + } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 58400000) { + switch (cRes) { + case 8: + hdmi_phy_i2c_write(hdmi, 0x091c, 0x10); + break; + case 10: + hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10); + break; + case 12: + hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10); + break; + default: + return false; + } + } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 72000000) { + switch (cRes) { + case 8: + hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10); + break; + case 10: + hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10); + break; + case 12: + hdmi_phy_i2c_write(hdmi, 0x091c, 0x10); + break; + default: + return false; + } + } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 74250000) { + switch (cRes) { + case 8: + hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10); + break; + case 10: + hdmi_phy_i2c_write(hdmi, 0x0b5c, 0x10); + break; + case 12: + hdmi_phy_i2c_write(hdmi, 0x091c, 0x10); + break; + default: + return false; + } + } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 118800000) { + switch (cRes) { + case 8: + hdmi_phy_i2c_write(hdmi, 0x091c, 0x10); + break; + case 10: + hdmi_phy_i2c_write(hdmi, 0x091c, 0x10); + break; + case 12: + hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10); + break; + default: + return false; + } + } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 216000000) { + switch (cRes) { + case 8: + hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10); + break; + case 10: + hdmi_phy_i2c_write(hdmi, 0x0b5c, 0x10); + break; + case 12: + hdmi_phy_i2c_write(hdmi, 0x091c, 0x10); + break; + default: + return false; + } + } else { + dev_err(&hdmi->pdev->dev, + "Pixel clock %d - unsupported by HDMI\n", + hdmi->hdmi_data.video_mode.mPixelClock); + return false; + } + + hdmi_phy_i2c_write(hdmi, 0x0000, 0x13); /* PLLPHBYCTRL */ + hdmi_phy_i2c_write(hdmi, 0x0006, 0x17); + /* RESISTANCE TERM 133Ohm Cfg */ + hdmi_phy_i2c_write(hdmi, 0x0005, 0x19); /* TXTERM */ + /* PREEMP Cgf 0.00 */ + hdmi_phy_i2c_write(hdmi, 0x800d, 0x09); /* CKSYMTXCTRL */ + /* TX/CK LVL 10 */ + hdmi_phy_i2c_write(hdmi, 0x01ad, 0x0E); /* VLEVCTRL */ + + /* Board specific setting for PHY register 0x09, 0x0e to pass HCT */ + if (hdmi->phy_config.reg_cksymtx != 0) + hdmi_phy_i2c_write(hdmi, hdmi->phy_config.reg_cksymtx, 0x09); + + if (hdmi->phy_config.reg_vlev != 0) + hdmi_phy_i2c_write(hdmi, hdmi->phy_config.reg_vlev, 0x0E); + + /* REMOVE CLK TERM */ + hdmi_phy_i2c_write(hdmi, 0x8000, 0x05); /* CKCALCTRL */ + + if (hdmi->hdmi_data.video_mode.mPixelClock > 148500000) { + hdmi_phy_i2c_write(hdmi, 0x800b, 0x09); + hdmi_phy_i2c_write(hdmi, 0x0129, 0x0E); + } + + mxc_hdmi_phy_enable_power(1); + + /* toggle TMDS enable */ + mxc_hdmi_phy_enable_tmds(0); + mxc_hdmi_phy_enable_tmds(1); + + /* gen2 tx power on */ + mxc_hdmi_phy_gen2_txpwron(1); + mxc_hdmi_phy_gen2_pddq(0); + + /*Wait for PHY PLL lock */ + msec = 4; + val = hdmi_readb(HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK; + while (val == 0) { + udelay(1000); + if (msec-- == 0) { + dev_dbg(&hdmi->pdev->dev, "PHY PLL not locked\n"); + return false; + } + val = hdmi_readb(HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK; + } + + return true; +} + +static void mxc_hdmi_phy_init(struct mxc_hdmi *hdmi) +{ + int i; + bool cscon = false; + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + /* Never do phy init if pixel clock is gated. + * Otherwise HDMI PHY will get messed up and generate an overflow + * interrupt that can't be cleared or detected by accessing the + * status register. */ + if (!hdmi->fb_reg || !hdmi->cable_plugin + || (hdmi->blank != FB_BLANK_UNBLANK)) + return; + + /*check csc whether needed activated in HDMI mode */ + cscon = (isColorSpaceConversion(hdmi) && + !hdmi->hdmi_data.video_mode.mDVI); + + /* HDMI Phy spec says to do the phy initialization sequence twice */ + for (i = 0 ; i < 2 ; i++) { + mxc_hdmi_phy_sel_data_en_pol(1); + mxc_hdmi_phy_sel_interface_control(0); + mxc_hdmi_phy_enable_tmds(0); + mxc_hdmi_phy_enable_power(0); + + /* Enable CSC */ + hdmi_phy_configure(hdmi, 0, 8, cscon); + } + + hdmi->phy_enabled = true; +} + +static void hdmi_config_AVI(struct mxc_hdmi *hdmi) +{ + u8 val; + u8 pix_fmt; + u8 under_scan; + u8 act_ratio, coded_ratio, colorimetry, ext_colorimetry; + struct fb_videomode mode; + const struct fb_videomode *edid_mode; + bool aspect_16_9; + + dev_dbg(&hdmi->pdev->dev, "set up AVI frame\n"); + + fb_var_to_videomode(&mode, &hdmi->fbi->var); + /* Use mode from list extracted from EDID to get aspect ratio */ + if (!list_empty(&hdmi->fbi->modelist)) { + edid_mode = fb_find_nearest_mode(&mode, &hdmi->fbi->modelist); + if (edid_mode->vmode & FB_VMODE_ASPECT_16_9) + aspect_16_9 = true; + else + aspect_16_9 = false; + } else + aspect_16_9 = false; + + /******************************************** + * AVI Data Byte 1 + ********************************************/ + if (hdmi->hdmi_data.enc_out_format == YCBCR444) + pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_YCBCR444; + else if (hdmi->hdmi_data.enc_out_format == YCBCR422_8BITS) + pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_YCBCR422; + else + pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_RGB; + + if (hdmi->edid_cfg.cea_underscan) + under_scan = HDMI_FC_AVICONF0_SCAN_INFO_UNDERSCAN; + else + under_scan = HDMI_FC_AVICONF0_SCAN_INFO_NODATA; + + /* + * Active format identification data is present in the AVI InfoFrame. + * Under scan info, no bar data + */ + val = pix_fmt | under_scan | + HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT | + HDMI_FC_AVICONF0_BAR_DATA_NO_DATA; + + hdmi_writeb(val, HDMI_FC_AVICONF0); + + /******************************************** + * AVI Data Byte 2 + ********************************************/ + + /* Set the Aspect Ratio */ + if (aspect_16_9) { + act_ratio = HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_16_9; + coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_16_9; + } else { + act_ratio = HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_4_3; + coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_4_3; + } + + /* Set up colorimetry */ + if (hdmi->hdmi_data.enc_out_format == XVYCC444) { + colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_EXTENDED_INFO; + if (hdmi->hdmi_data.colorimetry == eITU601) + ext_colorimetry = + HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601; + else /* hdmi->hdmi_data.colorimetry == eITU709 */ + ext_colorimetry = + HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC709; + } else if (hdmi->hdmi_data.enc_out_format != RGB) { + if (hdmi->hdmi_data.colorimetry == eITU601) + colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_SMPTE; + else /* hdmi->hdmi_data.colorimetry == eITU709 */ + colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_ITUR; + ext_colorimetry = HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601; + } else { /* Carries no data */ + colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_NO_DATA; + ext_colorimetry = HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601; + } + + val = colorimetry | coded_ratio | act_ratio; + hdmi_writeb(val, HDMI_FC_AVICONF1); + + /******************************************** + * AVI Data Byte 3 + ********************************************/ + + val = HDMI_FC_AVICONF2_IT_CONTENT_NO_DATA | ext_colorimetry | + HDMI_FC_AVICONF2_RGB_QUANT_DEFAULT | + HDMI_FC_AVICONF2_SCALING_NONE; + hdmi_writeb(val, HDMI_FC_AVICONF2); + + /******************************************** + * AVI Data Byte 4 + ********************************************/ + hdmi_writeb(hdmi->vic, HDMI_FC_AVIVID); + + /******************************************** + * AVI Data Byte 5 + ********************************************/ + + /* Set up input and output pixel repetition */ + val = (((hdmi->hdmi_data.video_mode.mPixelRepetitionInput + 1) << + HDMI_FC_PRCONF_INCOMING_PR_FACTOR_OFFSET) & + HDMI_FC_PRCONF_INCOMING_PR_FACTOR_MASK) | + ((hdmi->hdmi_data.video_mode.mPixelRepetitionOutput << + HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_OFFSET) & + HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_MASK); + hdmi_writeb(val, HDMI_FC_PRCONF); + + /* IT Content and quantization range = don't care */ + val = HDMI_FC_AVICONF3_IT_CONTENT_TYPE_GRAPHICS | + HDMI_FC_AVICONF3_QUANT_RANGE_LIMITED; + hdmi_writeb(val, HDMI_FC_AVICONF3); + + /******************************************** + * AVI Data Bytes 6-13 + ********************************************/ + hdmi_writeb(0, HDMI_FC_AVIETB0); + hdmi_writeb(0, HDMI_FC_AVIETB1); + hdmi_writeb(0, HDMI_FC_AVISBB0); + hdmi_writeb(0, HDMI_FC_AVISBB1); + hdmi_writeb(0, HDMI_FC_AVIELB0); + hdmi_writeb(0, HDMI_FC_AVIELB1); + hdmi_writeb(0, HDMI_FC_AVISRB0); + hdmi_writeb(0, HDMI_FC_AVISRB1); +} + +/*! + * this submodule is responsible for the video/audio data composition. + */ +static void hdmi_av_composer(struct mxc_hdmi *hdmi) +{ + u8 inv_val; + struct fb_info *fbi = hdmi->fbi; + struct fb_videomode fb_mode; + struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; + int hblank, vblank; + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + fb_var_to_videomode(&fb_mode, &fbi->var); + + vmode->mHSyncPolarity = ((fb_mode.sync & FB_SYNC_HOR_HIGH_ACT) != 0); + vmode->mVSyncPolarity = ((fb_mode.sync & FB_SYNC_VERT_HIGH_ACT) != 0); + vmode->mInterlaced = ((fb_mode.vmode & FB_VMODE_INTERLACED) != 0); + vmode->mPixelClock = (fb_mode.xres + fb_mode.left_margin + + fb_mode.right_margin + fb_mode.hsync_len) * (fb_mode.yres + + fb_mode.upper_margin + fb_mode.lower_margin + + fb_mode.vsync_len) * fb_mode.refresh; + + dev_dbg(&hdmi->pdev->dev, "final pixclk = %d\n", vmode->mPixelClock); + + /* Set up HDMI_FC_INVIDCONF */ + inv_val = (hdmi->hdmi_data.hdcp_enable ? + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE : + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE); + + inv_val |= (vmode->mVSyncPolarity ? + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW); + + inv_val |= (vmode->mHSyncPolarity ? + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW); + + inv_val |= (vmode->mDataEnablePolarity ? + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_LOW); + + if (hdmi->vic == 39) + inv_val |= HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH; + else + inv_val |= (vmode->mInterlaced ? + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW); + + inv_val |= (vmode->mInterlaced ? + HDMI_FC_INVIDCONF_IN_I_P_INTERLACED : + HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE); + + inv_val |= (vmode->mDVI ? + HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE : + HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE); + + hdmi_writeb(inv_val, HDMI_FC_INVIDCONF); + + /* Set up horizontal active pixel region width */ + hdmi_writeb(fb_mode.xres >> 8, HDMI_FC_INHACTV1); + hdmi_writeb(fb_mode.xres, HDMI_FC_INHACTV0); + + /* Set up vertical blanking pixel region width */ + hdmi_writeb(fb_mode.yres >> 8, HDMI_FC_INVACTV1); + hdmi_writeb(fb_mode.yres, HDMI_FC_INVACTV0); + + /* Set up horizontal blanking pixel region width */ + hblank = fb_mode.left_margin + fb_mode.right_margin + + fb_mode.hsync_len; + hdmi_writeb(hblank >> 8, HDMI_FC_INHBLANK1); + hdmi_writeb(hblank, HDMI_FC_INHBLANK0); + + /* Set up vertical blanking pixel region width */ + vblank = fb_mode.upper_margin + fb_mode.lower_margin + + fb_mode.vsync_len; + hdmi_writeb(vblank, HDMI_FC_INVBLANK); + + /* Set up HSYNC active edge delay width (in pixel clks) */ + hdmi_writeb(fb_mode.right_margin >> 8, HDMI_FC_HSYNCINDELAY1); + hdmi_writeb(fb_mode.right_margin, HDMI_FC_HSYNCINDELAY0); + + /* Set up VSYNC active edge delay (in pixel clks) */ + hdmi_writeb(fb_mode.lower_margin, HDMI_FC_VSYNCINDELAY); + + /* Set up HSYNC active pulse width (in pixel clks) */ + hdmi_writeb(fb_mode.hsync_len >> 8, HDMI_FC_HSYNCINWIDTH1); + hdmi_writeb(fb_mode.hsync_len, HDMI_FC_HSYNCINWIDTH0); + + /* Set up VSYNC active edge delay (in pixel clks) */ + hdmi_writeb(fb_mode.vsync_len, HDMI_FC_VSYNCINWIDTH); + + dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__); +} + +static int mxc_edid_read_internal(struct mxc_hdmi *hdmi, unsigned char *edid, + struct mxc_edid_cfg *cfg, struct fb_info *fbi) +{ + int extblknum; + int i, j, ret; + unsigned char *ediddata = edid; + unsigned char tmpedid[EDID_LENGTH]; + + dev_info(&hdmi->pdev->dev, "%s\n", __func__); + + if (!edid || !cfg || !fbi) + return -EINVAL; + + /* init HDMI I2CM for read edid*/ + hdmi_writeb(0x0, HDMI_I2CM_DIV); + hdmi_writeb(0x00, HDMI_I2CM_SS_SCL_HCNT_1_ADDR); + hdmi_writeb(0x79, HDMI_I2CM_SS_SCL_HCNT_0_ADDR); + hdmi_writeb(0x00, HDMI_I2CM_SS_SCL_LCNT_1_ADDR); + hdmi_writeb(0x91, HDMI_I2CM_SS_SCL_LCNT_0_ADDR); + + hdmi_writeb(0x00, HDMI_I2CM_FS_SCL_HCNT_1_ADDR); + hdmi_writeb(0x0F, HDMI_I2CM_FS_SCL_HCNT_0_ADDR); + hdmi_writeb(0x00, HDMI_I2CM_FS_SCL_LCNT_1_ADDR); + hdmi_writeb(0x21, HDMI_I2CM_FS_SCL_LCNT_0_ADDR); + + hdmi_writeb(0x50, HDMI_I2CM_SLAVE); + hdmi_writeb(0x30, HDMI_I2CM_SEGADDR); + + /* Umask edid interrupt */ + hdmi_writeb(HDMI_I2CM_INT_DONE_POL, + HDMI_I2CM_INT); + + hdmi_writeb(HDMI_I2CM_CTLINT_NAC_POL | + HDMI_I2CM_CTLINT_ARBITRATION_POL, + HDMI_I2CM_CTLINT); + + /* reset edid data zero */ + memset(edid, 0, EDID_LENGTH*4); + memset(cfg, 0, sizeof(struct mxc_edid_cfg)); + + /* Check first three byte of EDID head */ + if (!(hdmi_edid_i2c_read(hdmi, 0, 0) == 0x00) || + !(hdmi_edid_i2c_read(hdmi, 1, 0) == 0xFF) || + !(hdmi_edid_i2c_read(hdmi, 2, 0) == 0xFF)) { + dev_info(&hdmi->pdev->dev, "EDID head check failed!"); + return -ENOENT; + } + + for (i = 0; i < 128; i++) { + *ediddata = hdmi_edid_i2c_read(hdmi, i, 0); + ediddata++; + } + + extblknum = edid[0x7E]; + if (extblknum == 255) + extblknum = 0; + + if (extblknum) { + ediddata = edid + EDID_LENGTH; + for (i = 0; i < 128; i++) { + *ediddata = hdmi_edid_i2c_read(hdmi, i, 1); + ediddata++; + } + } + + /* edid first block parsing */ + memset(&fbi->monspecs, 0, sizeof(fbi->monspecs)); + fb_edid_to_monspecs(edid, &fbi->monspecs); + + if (extblknum) { + ret = mxc_edid_parse_ext_blk(edid + EDID_LENGTH, + cfg, &fbi->monspecs); + if (ret < 0) + return -ENOENT; + } + + /* need read segment block? */ + if (extblknum > 1) { + for (j = 2; j <= extblknum; j++) { + for (i = 0; i < 128; i++) + tmpedid[i] = hdmi_edid_i2c_read(hdmi, i, j); + + /* edid ext block parsing */ + ret = mxc_edid_parse_ext_blk(tmpedid, + cfg, &fbi->monspecs); + if (ret < 0) + return -ENOENT; + } + } + + return 0; +} + +static int mxc_hdmi_read_edid(struct mxc_hdmi *hdmi) +{ + int ret; + u8 edid_old[HDMI_EDID_LEN]; + u8 clkdis; + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + /* save old edid */ + memcpy(edid_old, hdmi->edid, HDMI_EDID_LEN); + + /* Read EDID via HDMI DDC when HDCP Enable */ + if (!hdcp_init) + ret = mxc_edid_read(hdmi_i2c->adapter, hdmi_i2c->addr, + hdmi->edid, &hdmi->edid_cfg, hdmi->fbi); + else { + + /* Disable HDCP clk */ + if (hdmi->hdmi_data.hdcp_enable) { + clkdis = hdmi_readb(HDMI_MC_CLKDIS); + clkdis |= HDMI_MC_CLKDIS_HDCPCLK_DISABLE; + hdmi_writeb(clkdis, HDMI_MC_CLKDIS); + } + + ret = mxc_edid_read_internal(hdmi, hdmi->edid, + &hdmi->edid_cfg, hdmi->fbi); + + /* Enable HDCP clk */ + if (hdmi->hdmi_data.hdcp_enable) { + clkdis = hdmi_readb(HDMI_MC_CLKDIS); + clkdis &= ~HDMI_MC_CLKDIS_HDCPCLK_DISABLE; + hdmi_writeb(clkdis, HDMI_MC_CLKDIS); + } + + } + if (ret < 0) { + dev_dbg(&hdmi->pdev->dev, "read failed\n"); + return HDMI_EDID_FAIL; + } + + /* Save edid cfg for audio driver */ + hdmi_set_edid_cfg(&hdmi->edid_cfg); + + if (!memcmp(edid_old, hdmi->edid, HDMI_EDID_LEN)) { + dev_info(&hdmi->pdev->dev, "same edid\n"); + return HDMI_EDID_SAME; + } + + if (hdmi->fbi->monspecs.modedb_len == 0) { + dev_info(&hdmi->pdev->dev, "No modes read from edid\n"); + return HDMI_EDID_NO_MODES; + } + + return HDMI_EDID_SUCCESS; +} + +static void mxc_hdmi_phy_disable(struct mxc_hdmi *hdmi) +{ + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + if (!hdmi->phy_enabled) + return; + + hdmi_disable_overflow_interrupts(); + + /* Setting PHY to reset status */ + hdmi_writeb(HDMI_MC_PHYRSTZ_DEASSERT, HDMI_MC_PHYRSTZ); + + /* Power down PHY */ + mxc_hdmi_phy_enable_tmds(0); + mxc_hdmi_phy_enable_power(0); + mxc_hdmi_phy_gen2_txpwron(0); + mxc_hdmi_phy_gen2_pddq(1); + + hdmi->phy_enabled = false; + dev_dbg(&hdmi->pdev->dev, "%s - exit\n", __func__); +} + +/* HDMI Initialization Step B.4 */ +static void mxc_hdmi_enable_video_path(struct mxc_hdmi *hdmi) +{ + u8 clkdis; + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + /* control period minimum duration */ + hdmi_writeb(12, HDMI_FC_CTRLDUR); + hdmi_writeb(32, HDMI_FC_EXCTRLDUR); + hdmi_writeb(1, HDMI_FC_EXCTRLSPAC); + + /* Set to fill TMDS data channels */ + hdmi_writeb(0x0B, HDMI_FC_CH0PREAM); + hdmi_writeb(0x16, HDMI_FC_CH1PREAM); + hdmi_writeb(0x21, HDMI_FC_CH2PREAM); + + /* Enable pixel clock and tmds data path */ + clkdis = 0x7F; + clkdis &= ~HDMI_MC_CLKDIS_PIXELCLK_DISABLE; + hdmi_writeb(clkdis, HDMI_MC_CLKDIS); + + clkdis &= ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE; + hdmi_writeb(clkdis, HDMI_MC_CLKDIS); + + /* Enable csc path */ + if (isColorSpaceConversion(hdmi)) { + clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE; + hdmi_writeb(clkdis, HDMI_MC_CLKDIS); + } +} + +static void hdmi_enable_audio_clk(struct mxc_hdmi *hdmi) +{ + u8 clkdis; + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + clkdis = hdmi_readb(HDMI_MC_CLKDIS); + clkdis &= ~HDMI_MC_CLKDIS_AUDCLK_DISABLE; + hdmi_writeb(clkdis, HDMI_MC_CLKDIS); +} + +/* Workaround to clear the overflow condition */ +static void mxc_hdmi_clear_overflow(struct mxc_hdmi *hdmi) +{ + int count; + u8 val; + + /* TMDS software reset */ + hdmi_writeb((u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, HDMI_MC_SWRSTZ); + + val = hdmi_readb(HDMI_FC_INVIDCONF); + + if (cpu_is_imx6dl(hdmi)) { + hdmi_writeb(val, HDMI_FC_INVIDCONF); + return; + } + + for (count = 0 ; count < 5 ; count++) + hdmi_writeb(val, HDMI_FC_INVIDCONF); +} + +static void hdmi_enable_overflow_interrupts(void) +{ + pr_debug("%s\n", __func__); + hdmi_writeb(0, HDMI_FC_MASK2); + hdmi_writeb(0, HDMI_IH_MUTE_FC_STAT2); +} + +static void hdmi_disable_overflow_interrupts(void) +{ + pr_debug("%s\n", __func__); + hdmi_writeb(HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK, + HDMI_IH_MUTE_FC_STAT2); + hdmi_writeb(0xff, HDMI_FC_MASK2); +} + +static void mxc_hdmi_notify_fb(struct mxc_hdmi *hdmi) +{ + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + /* Don't notify if we aren't registered yet */ + WARN_ON(!hdmi->fb_reg); + + /* disable the phy before ipu changes mode */ + mxc_hdmi_phy_disable(hdmi); + + /* + * Note that fb_set_var will block. During this time, + * FB_EVENT_MODE_CHANGE callback will happen. + * So by the end of this function, mxc_hdmi_setup() + * will be done. + */ + hdmi->fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + if (!fb_set_var(hdmi->fbi, &hdmi->fbi->var)) + fbcon_update_vcs(hdmi->fbi, hdmi->fbi->var.activate & FB_ACTIVATE_ALL); + console_unlock(); + + dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__); +} + +static void mxc_hdmi_edid_rebuild_modelist(struct mxc_hdmi *hdmi) +{ + int i; + struct fb_videomode *mode; + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + console_lock(); + + fb_destroy_modelist(&hdmi->fbi->modelist); + fb_add_videomode(&vga_mode, &hdmi->fbi->modelist); + + for (i = 0; i < hdmi->fbi->monspecs.modedb_len; i++) { + /* + * We might check here if mode is supported by HDMI. + * We do not currently support interlaced modes. + * And add CEA modes in the modelist. + */ + mode = &hdmi->fbi->monspecs.modedb[i]; + + if (!(mode->vmode & FB_VMODE_INTERLACED) && + (mxc_edid_mode_to_vic(mode) != 0)) { + + dev_dbg(&hdmi->pdev->dev, "Added mode %d:", i); + dev_dbg(&hdmi->pdev->dev, + "xres = %d, yres = %d, freq = %d, vmode = %d, flag = %d\n", + hdmi->fbi->monspecs.modedb[i].xres, + hdmi->fbi->monspecs.modedb[i].yres, + hdmi->fbi->monspecs.modedb[i].refresh, + hdmi->fbi->monspecs.modedb[i].vmode, + hdmi->fbi->monspecs.modedb[i].flag); + + fb_add_videomode(mode, &hdmi->fbi->modelist); + } + } + + fb_new_modelist(hdmi->fbi); + + console_unlock(); +} + +static void mxc_hdmi_default_edid_cfg(struct mxc_hdmi *hdmi) +{ + /* Default setting HDMI working in HDMI mode */ + hdmi->edid_cfg.hdmi_cap = true; +} + +static void mxc_hdmi_default_modelist(struct mxc_hdmi *hdmi) +{ + struct fb_modelist *modelist; + struct fb_videomode *m; + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + /* If no EDID data read, set up default modelist; since we don't know + * the supported modes of the current sink, we will use only one mode in + * this modelist: + * the default_mode set up at init (usually got from cmdline) + */ + dev_info(&hdmi->pdev->dev, "create default modelist\n"); + + /* If the current modelist is already default, don't re-create it*/ + if (list_is_singular(&hdmi->fbi->modelist)) { + modelist = list_entry((&hdmi->fbi->modelist)->next, + struct fb_modelist, list); + m = &modelist->mode; + if (fb_mode_is_equal(m, &hdmi->default_mode)) { + dev_info(&hdmi->pdev->dev, + "Modelist is already default, no need to re-create!\n"); + return; + } + + } + + console_lock(); + fb_destroy_modelist(&hdmi->fbi->modelist); + fb_add_videomode(&hdmi->default_mode, &hdmi->fbi->modelist); + fb_new_modelist(hdmi->fbi); + console_unlock(); +} + +static void mxc_hdmi_set_mode_to_vga_dvi(struct mxc_hdmi *hdmi) +{ + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + hdmi_disable_overflow_interrupts(); + + fb_videomode_to_var(&hdmi->fbi->var, &vga_mode); + + hdmi->requesting_vga_for_initialization = true; + mxc_hdmi_notify_fb(hdmi); + hdmi->requesting_vga_for_initialization = false; +} + +static void mxc_hdmi_set_mode(struct mxc_hdmi *hdmi) +{ + const struct fb_videomode *mode; + struct fb_videomode m; + struct fb_var_screeninfo var; + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + /* Set the default mode only once. */ + if (!hdmi->dft_mode_set) { + fb_videomode_to_var(&var, &hdmi->default_mode); + hdmi->dft_mode_set = true; + } else + fb_videomode_to_var(&var, &hdmi->previous_non_vga_mode); + + fb_var_to_videomode(&m, &var); + dump_fb_videomode(&m); + + mode = fb_find_nearest_mode(&m, &hdmi->fbi->modelist); + if (!mode) { + pr_err("%s: could not find mode in modelist\n", __func__); + return; + } + + /* If both video mode and work mode same as previous, + * init HDMI again */ + if (fb_mode_is_equal(&hdmi->previous_non_vga_mode, mode) && + (hdmi->edid_cfg.hdmi_cap != hdmi->hdmi_data.video_mode.mDVI)) { + dev_dbg(&hdmi->pdev->dev, + "%s: Video mode same as previous\n", __func__); + /* update fbi mode in case modelist is updated */ + hdmi->fbi->mode = (struct fb_videomode *)mode; + fb_videomode_to_var(&hdmi->fbi->var, mode); + /* update hdmi setting in case EDID data updated */ + mxc_hdmi_setup(hdmi, 0); + } else { + dev_dbg(&hdmi->pdev->dev, "%s: New video mode\n", __func__); + mxc_hdmi_set_mode_to_vga_dvi(hdmi); + fb_videomode_to_var(&hdmi->fbi->var, mode); + dump_fb_videomode((struct fb_videomode *)mode); + mxc_hdmi_notify_fb(hdmi); + } + +} + +static void mxc_hdmi_cable_connected(struct mxc_hdmi *hdmi) +{ + int edid_status; + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + hdmi->cable_plugin = true; + + /* HDMI Initialization Step C */ + edid_status = mxc_hdmi_read_edid(hdmi); + + /* Read EDID again if first EDID read failed */ + if (edid_status == HDMI_EDID_NO_MODES || + edid_status == HDMI_EDID_FAIL) { + int retry_status; + dev_info(&hdmi->pdev->dev, "Read EDID again\n"); + msleep(200); + retry_status = mxc_hdmi_read_edid(hdmi); + /* If we get NO_MODES on the 1st and SAME on the 2nd attempt we + * want NO_MODES as final result. */ + if (retry_status != HDMI_EDID_SAME) + edid_status = retry_status; + } + + /* HDMI Initialization Steps D, E, F */ + switch (edid_status) { + case HDMI_EDID_SUCCESS: + mxc_hdmi_edid_rebuild_modelist(hdmi); + break; + + /* Nothing to do if EDID same */ + case HDMI_EDID_SAME: + break; + + case HDMI_EDID_FAIL: + mxc_hdmi_default_edid_cfg(hdmi); + /* fall through */ + case HDMI_EDID_NO_MODES: + /* fall through */ + default: + mxc_hdmi_default_modelist(hdmi); + break; + } + + /* Setting video mode */ + mxc_hdmi_set_mode(hdmi); + + dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__); +} + +static int mxc_hdmi_power_on(struct mxc_dispdrv_handle *disp, + struct fb_info *fbi) +{ + struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp); + mxc_hdmi_phy_init(hdmi); + return 0; +} + +static void mxc_hdmi_power_off(struct mxc_dispdrv_handle *disp, + struct fb_info *fbi) +{ + struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp); + mxc_hdmi_phy_disable(hdmi); +} + +static void mxc_hdmi_cable_disconnected(struct mxc_hdmi *hdmi) +{ + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + /* Disable All HDMI clock */ + hdmi_writeb(0xff, HDMI_MC_CLKDIS); + + mxc_hdmi_phy_disable(hdmi); + + hdmi_disable_overflow_interrupts(); + + hdmi->cable_plugin = false; +} + +static void hotplug_worker(struct work_struct *work) +{ + struct delayed_work *delay_work = to_delayed_work(work); + struct mxc_hdmi *hdmi = + container_of(delay_work, struct mxc_hdmi, hotplug_work); + u32 phy_int_stat, phy_int_pol, phy_int_mask; + u8 val; + unsigned long flags; + char event_string[32]; + char *envp[] = { event_string, NULL }; + + phy_int_stat = hdmi->latest_intr_stat; + phy_int_pol = hdmi_readb(HDMI_PHY_POL0); + + dev_dbg(&hdmi->pdev->dev, "phy_int_stat=0x%x, phy_int_pol=0x%x\n", + phy_int_stat, phy_int_pol); + + /* check cable status */ + if (phy_int_stat & HDMI_IH_PHY_STAT0_HPD) { + /* cable connection changes */ + if (phy_int_pol & HDMI_PHY_HPD) { + /* Plugin event */ + dev_dbg(&hdmi->pdev->dev, "EVENT=plugin\n"); + mxc_hdmi_cable_connected(hdmi); + + /* Make HPD intr active low to capture unplug event */ + val = hdmi_readb(HDMI_PHY_POL0); + val &= ~HDMI_PHY_HPD; + hdmi_writeb(val, HDMI_PHY_POL0); + + hdmi_set_cable_state(1); + + sprintf(event_string, "EVENT=plugin"); + kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp); +#ifdef CONFIG_MXC_HDMI_CEC + mxc_hdmi_cec_handle(0x80); +#endif + } else if (!(phy_int_pol & HDMI_PHY_HPD)) { + /* Plugout event */ + dev_dbg(&hdmi->pdev->dev, "EVENT=plugout\n"); + hdmi_set_cable_state(0); + mxc_hdmi_abort_stream(); + mxc_hdmi_cable_disconnected(hdmi); + + /* Make HPD intr active high to capture plugin event */ + val = hdmi_readb(HDMI_PHY_POL0); + val |= HDMI_PHY_HPD; + hdmi_writeb(val, HDMI_PHY_POL0); + + sprintf(event_string, "EVENT=plugout"); + kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp); +#ifdef CONFIG_MXC_HDMI_CEC + mxc_hdmi_cec_handle(0x100); +#endif + + } else + dev_dbg(&hdmi->pdev->dev, "EVENT=none?\n"); + } + + /* Lock here to ensure full powerdown sequence + * completed before next interrupt processed */ + spin_lock_irqsave(&hdmi->irq_lock, flags); + + /* Re-enable HPD interrupts */ + phy_int_mask = hdmi_readb(HDMI_PHY_MASK0); + phy_int_mask &= ~HDMI_PHY_HPD; + hdmi_writeb(phy_int_mask, HDMI_PHY_MASK0); + + /* Unmute interrupts */ + hdmi_writeb(~HDMI_IH_MUTE_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); + + if (hdmi_readb(HDMI_IH_FC_STAT2) & HDMI_IH_FC_STAT2_OVERFLOW_MASK) + mxc_hdmi_clear_overflow(hdmi); + + spin_unlock_irqrestore(&hdmi->irq_lock, flags); +} + +static void hdcp_hdp_worker(struct work_struct *work) +{ + struct delayed_work *delay_work = to_delayed_work(work); + struct mxc_hdmi *hdmi = + container_of(delay_work, struct mxc_hdmi, hdcp_hdp_work); + char event_string[32]; + char *envp[] = { event_string, NULL }; + + /* HDCP interrupt */ + sprintf(event_string, "EVENT=hdcpint"); + kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp); + + /* Unmute interrupts in HDCP application*/ +} + +static irqreturn_t mxc_hdmi_hotplug(int irq, void *data) +{ + struct mxc_hdmi *hdmi = data; + u8 val, intr_stat; + unsigned long flags; + + spin_lock_irqsave(&hdmi->irq_lock, flags); + + /* Check and clean packet overflow interrupt.*/ + if (hdmi_readb(HDMI_IH_FC_STAT2) & + HDMI_IH_FC_STAT2_OVERFLOW_MASK) { + mxc_hdmi_clear_overflow(hdmi); + + dev_dbg(&hdmi->pdev->dev, "Overflow interrupt received\n"); + /* clear irq status */ + hdmi_writeb(HDMI_IH_FC_STAT2_OVERFLOW_MASK, + HDMI_IH_FC_STAT2); + } + + /* + * We could not disable the irq. Probably the audio driver + * has enabled it. Masking off the HDMI interrupts using + * HDMI registers. + */ + /* Capture status - used in hotplug_worker ISR */ + intr_stat = hdmi_readb(HDMI_IH_PHY_STAT0); + + if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { + + dev_dbg(&hdmi->pdev->dev, "Hotplug interrupt received\n"); + hdmi->latest_intr_stat = intr_stat; + + /* Mute interrupts until handled */ + + val = hdmi_readb(HDMI_IH_MUTE_PHY_STAT0); + val |= HDMI_IH_MUTE_PHY_STAT0_HPD; + hdmi_writeb(val, HDMI_IH_MUTE_PHY_STAT0); + + val = hdmi_readb(HDMI_PHY_MASK0); + val |= HDMI_PHY_HPD; + hdmi_writeb(val, HDMI_PHY_MASK0); + + /* Clear Hotplug interrupts */ + hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0); + + schedule_delayed_work(&(hdmi->hotplug_work), msecs_to_jiffies(20)); + } + + /* Check HDCP interrupt state */ + if (hdmi->hdmi_data.hdcp_enable) { + val = hdmi_readb(HDMI_A_APIINTSTAT); + if (val != 0) { + /* Mute interrupts until interrupt handled */ + val = 0xFF; + hdmi_writeb(val, HDMI_A_APIINTMSK); + schedule_delayed_work(&(hdmi->hdcp_hdp_work), msecs_to_jiffies(50)); + } + } + + spin_unlock_irqrestore(&hdmi->irq_lock, flags); + return IRQ_HANDLED; +} + +static void mxc_hdmi_setup(struct mxc_hdmi *hdmi, unsigned long event) +{ + struct fb_videomode m; + const struct fb_videomode *edid_mode; + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + fb_var_to_videomode(&m, &hdmi->fbi->var); + dump_fb_videomode(&m); + + dev_dbg(&hdmi->pdev->dev, "%s - video mode changed\n", __func__); + + hdmi->vic = 0; + if (!hdmi->requesting_vga_for_initialization) { + /* Save mode if this isn't the result of requesting + * vga default. */ + memcpy(&hdmi->previous_non_vga_mode, &m, + sizeof(struct fb_videomode)); + if (!list_empty(&hdmi->fbi->modelist)) { + edid_mode = fb_find_nearest_mode(&m, &hdmi->fbi->modelist); + pr_debug("edid mode "); + dump_fb_videomode((struct fb_videomode *)edid_mode); + /* update fbi mode */ + hdmi->fbi->mode = (struct fb_videomode *)edid_mode; + hdmi->vic = mxc_edid_mode_to_vic(edid_mode); + } + } + + hdmi_disable_overflow_interrupts(); + + dev_dbg(&hdmi->pdev->dev, "CEA mode used vic=%d\n", hdmi->vic); + if (hdmi->edid_cfg.hdmi_cap) + hdmi->hdmi_data.video_mode.mDVI = false; + else { + dev_dbg(&hdmi->pdev->dev, "CEA mode vic=%d work in DVI\n", hdmi->vic); + hdmi->hdmi_data.video_mode.mDVI = true; + } + + if ((hdmi->vic == 6) || (hdmi->vic == 7) || + (hdmi->vic == 21) || (hdmi->vic == 22) || + (hdmi->vic == 2) || (hdmi->vic == 3) || + (hdmi->vic == 17) || (hdmi->vic == 18)) + hdmi->hdmi_data.colorimetry = eITU601; + else + hdmi->hdmi_data.colorimetry = eITU709; + + if ((hdmi->vic == 10) || (hdmi->vic == 11) || + (hdmi->vic == 12) || (hdmi->vic == 13) || + (hdmi->vic == 14) || (hdmi->vic == 15) || + (hdmi->vic == 25) || (hdmi->vic == 26) || + (hdmi->vic == 27) || (hdmi->vic == 28) || + (hdmi->vic == 29) || (hdmi->vic == 30) || + (hdmi->vic == 35) || (hdmi->vic == 36) || + (hdmi->vic == 37) || (hdmi->vic == 38)) + hdmi->hdmi_data.video_mode.mPixelRepetitionOutput = 1; + else + hdmi->hdmi_data.video_mode.mPixelRepetitionOutput = 0; + + hdmi->hdmi_data.video_mode.mPixelRepetitionInput = 0; + + /* TODO: Get input format from IPU (via FB driver iface) */ + hdmi->hdmi_data.enc_in_format = RGB; + + hdmi->hdmi_data.enc_out_format = RGB; + + /* YCbCr only enabled in HDMI mode */ + if (!hdmi->hdmi_data.video_mode.mDVI && + !hdmi->hdmi_data.rgb_out_enable) { + if (hdmi->edid_cfg.cea_ycbcr444) + hdmi->hdmi_data.enc_out_format = YCBCR444; + else if (hdmi->edid_cfg.cea_ycbcr422) + hdmi->hdmi_data.enc_out_format = YCBCR422_8BITS; + } + + /* IPU not support depth color output */ + hdmi->hdmi_data.enc_color_depth = 8; + hdmi->hdmi_data.pix_repet_factor = 0; + hdmi->hdmi_data.video_mode.mDataEnablePolarity = true; + + /* HDMI Initialization Step B.1 */ + hdmi_av_composer(hdmi); + + /* HDMI Initializateion Step B.2 */ + mxc_hdmi_phy_init(hdmi); + + /* HDMI Initialization Step B.3 */ + mxc_hdmi_enable_video_path(hdmi); + + /* not for DVI mode */ + if (hdmi->hdmi_data.video_mode.mDVI) + dev_dbg(&hdmi->pdev->dev, "%s DVI mode\n", __func__); + else { + dev_dbg(&hdmi->pdev->dev, "%s CEA mode\n", __func__); + + /* HDMI Initialization Step E - Configure audio */ + hdmi_clk_regenerator_update_pixel_clock(hdmi->fbi->var.pixclock); + hdmi_enable_audio_clk(hdmi); + + /* HDMI Initialization Step F - Configure AVI InfoFrame */ + hdmi_config_AVI(hdmi); + } + + hdmi_video_packetize(hdmi); + hdmi_video_csc(hdmi); + hdmi_video_sample(hdmi); + + /* delay 20ms before tmds start work */ + msleep(20); + mxc_hdmi_clear_overflow(hdmi); + + if (!hdmi->hdmi_data.video_mode.mDVI) + hdmi_enable_overflow_interrupts(); + + dev_dbg(&hdmi->pdev->dev, "%s exit\n\n", __func__); +} + +/* Wait until we are registered to enable interrupts */ +static void mxc_hdmi_fb_registered(struct mxc_hdmi *hdmi) +{ + unsigned long flags; + + if (hdmi->fb_reg) + return; + + spin_lock_irqsave(&hdmi->irq_lock, flags); + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + hdmi_writeb(HDMI_PHY_I2CM_INT_ADDR_DONE_POL, + HDMI_PHY_I2CM_INT_ADDR); + + hdmi_writeb(HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL | + HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL, + HDMI_PHY_I2CM_CTLINT_ADDR); + + /* enable cable hot plug irq */ + hdmi_writeb((u8)~HDMI_PHY_HPD, HDMI_PHY_MASK0); + + /* Clear Hotplug interrupts */ + hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0); + + /* Unmute interrupts */ + hdmi_writeb(~HDMI_IH_MUTE_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); + + hdmi->fb_reg = true; + + spin_unlock_irqrestore(&hdmi->irq_lock, flags); + +} + +static int mxc_hdmi_fb_event(struct notifier_block *nb, + unsigned long val, void *v) +{ + struct fb_event *event = v; + struct mxc_hdmi *hdmi = container_of(nb, struct mxc_hdmi, nb); + + if (strcmp(event->info->fix.id, hdmi->fbi->fix.id)) + return 0; + + switch (val) { + + case FB_EVENT_FB_REGISTERED: + dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_FB_REGISTERED\n"); + mxc_hdmi_fb_registered(hdmi); + hdmi_set_registered(1); + break; + + case FB_EVENT_FB_UNREGISTERED: + dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_FB_UNREGISTERED\n"); + hdmi->fb_reg = false; + hdmi_set_registered(0); + break; + + case FB_EVENT_MODE_CHANGE: + dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_MODE_CHANGE\n"); + if (hdmi->fb_reg) + mxc_hdmi_setup(hdmi, val); + break; + + case FB_EVENT_BLANK: + if ((*((int *)event->data) == FB_BLANK_UNBLANK) && + (*((int *)event->data) != hdmi->blank)) { + dev_dbg(&hdmi->pdev->dev, + "event=FB_EVENT_BLANK - UNBLANK\n"); + + hdmi->blank = *((int *)event->data); + + if (hdmi->fb_reg && hdmi->cable_plugin) + mxc_hdmi_setup(hdmi, val); + hdmi_set_blank_state(1); + + } else if (*((int *)event->data) != hdmi->blank) { + dev_dbg(&hdmi->pdev->dev, + "event=FB_EVENT_BLANK - BLANK\n"); + hdmi_set_blank_state(0); + mxc_hdmi_abort_stream(); + + mxc_hdmi_phy_disable(hdmi); + + hdmi->blank = *((int *)event->data); + } else + dev_dbg(&hdmi->pdev->dev, + "FB BLANK state no changed!\n"); + + break; + + case FB_EVENT_SUSPEND: + dev_dbg(&hdmi->pdev->dev, + "event=FB_EVENT_SUSPEND\n"); + + if (hdmi->blank == FB_BLANK_UNBLANK) { + mxc_hdmi_phy_disable(hdmi); + clk_disable(hdmi->hdmi_iahb_clk); + clk_disable(hdmi->hdmi_isfr_clk); + clk_disable(hdmi->mipi_core_clk); + } + break; + + case FB_EVENT_RESUME: + dev_dbg(&hdmi->pdev->dev, + "event=FB_EVENT_RESUME\n"); + + if (hdmi->blank == FB_BLANK_UNBLANK) { + clk_enable(hdmi->mipi_core_clk); + clk_enable(hdmi->hdmi_iahb_clk); + clk_enable(hdmi->hdmi_isfr_clk); + mxc_hdmi_phy_init(hdmi); + } + break; + + } + return 0; +} + +static void hdmi_init_route(struct mxc_hdmi *hdmi) +{ + uint32_t hdmi_mux_setting, reg; + int ipu_id, disp_id; + + ipu_id = mxc_hdmi_ipu_id; + disp_id = mxc_hdmi_disp_id; + + if ((ipu_id > 1) || (ipu_id < 0)) { + pr_err("Invalid IPU select for HDMI: %d. Set to 0\n", ipu_id); + ipu_id = 0; + } + + if ((disp_id > 1) || (disp_id < 0)) { + pr_err("Invalid DI select for HDMI: %d. Set to 0\n", disp_id); + disp_id = 0; + } + + reg = readl(hdmi->gpr_hdmi_base); + + /* Configure the connection between IPU1/2 and HDMI */ + hdmi_mux_setting = 2*ipu_id + disp_id; + + /* GPR3, bits 2-3 = HDMI_MUX_CTL */ + reg &= ~0xd; + reg |= hdmi_mux_setting << 2; + + writel(reg, hdmi->gpr_hdmi_base); + + /* Set HDMI event as SDMA event2 for HDMI audio */ + reg = readl(hdmi->gpr_sdma_base); + reg |= 0x1; + writel(reg, hdmi->gpr_sdma_base); +} + +static void hdmi_hdcp_get_property(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + + /* Check hdcp enable by dts.*/ + hdcp_init = of_property_read_bool(np, "fsl,hdcp"); + if (hdcp_init) + dev_dbg(&pdev->dev, "hdcp enable\n"); + else + dev_dbg(&pdev->dev, "hdcp disable\n"); +} + +static void hdmi_get_of_property(struct mxc_hdmi *hdmi) +{ + struct platform_device *pdev = hdmi->pdev; + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id = + of_match_device(imx_hdmi_dt_ids, &pdev->dev); + int ret; + u32 phy_reg_vlev = 0, phy_reg_cksymtx = 0; + + if (of_id) { + pdev->id_entry = of_id->data; + hdmi->cpu_type = pdev->id_entry->driver_data; + } + + /* HDMI PHY register vlev and cksymtx preperty is optional. + * It is for specific board to pass HCT electrical part. + * Default value will been setting in HDMI PHY config function + * if it is not define in device tree. + */ + ret = of_property_read_u32(np, "fsl,phy_reg_vlev", &phy_reg_vlev); + if (ret) + dev_dbg(&pdev->dev, "No board specific HDMI PHY vlev\n"); + + ret = of_property_read_u32(np, "fsl,phy_reg_cksymtx", &phy_reg_cksymtx); + if (ret) + dev_dbg(&pdev->dev, "No board specific HDMI PHY cksymtx\n"); + + /* Specific phy config */ + hdmi->phy_config.reg_cksymtx = phy_reg_cksymtx; + hdmi->phy_config.reg_vlev = phy_reg_vlev; + +} + +/* HDMI Initialization Step A */ +static int mxc_hdmi_disp_init(struct mxc_dispdrv_handle *disp, + struct mxc_dispdrv_setting *setting) +{ + int ret = 0; + u32 i; + const struct fb_videomode *mode; + struct fb_videomode m; + struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp); + int irq = platform_get_irq(hdmi->pdev, 0); + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + /* Check hdmi disp init once */ + if (hdmi_inited) { + dev_err(&hdmi->pdev->dev, + "Error only one HDMI output support now!\n"); + return -1; + } + + hdmi_get_of_property(hdmi); + + if (irq < 0) + return -ENODEV; + + /* Setting HDMI default to blank state */ + hdmi->blank = FB_BLANK_POWERDOWN; + + ret = ipu_di_to_crtc(&hdmi->pdev->dev, mxc_hdmi_ipu_id, + mxc_hdmi_disp_id, &setting->crtc); + if (ret < 0) + return ret; + + setting->if_fmt = IPU_PIX_FMT_RGB24; + + hdmi->dft_mode_str = setting->dft_mode_str; + hdmi->default_bpp = setting->default_bpp; + dev_dbg(&hdmi->pdev->dev, "%s - default mode %s bpp=%d\n", + __func__, hdmi->dft_mode_str, hdmi->default_bpp); + + hdmi->fbi = setting->fbi; + + hdmi_init_route(hdmi); + + hdmi->mipi_core_clk = clk_get(&hdmi->pdev->dev, "mipi_core"); + if (IS_ERR(hdmi->mipi_core_clk)) { + ret = PTR_ERR(hdmi->mipi_core_clk); + dev_err(&hdmi->pdev->dev, + "Unable to get mipi core clk: %d\n", ret); + goto egetclk; + } + + ret = clk_prepare_enable(hdmi->mipi_core_clk); + if (ret < 0) { + dev_err(&hdmi->pdev->dev, + "Cannot enable mipi core clock: %d\n", ret); + goto erate; + } + + hdmi->hdmi_isfr_clk = clk_get(&hdmi->pdev->dev, "hdmi_isfr"); + if (IS_ERR(hdmi->hdmi_isfr_clk)) { + ret = PTR_ERR(hdmi->hdmi_isfr_clk); + dev_err(&hdmi->pdev->dev, + "Unable to get HDMI clk: %d\n", ret); + goto egetclk1; + } + + ret = clk_prepare_enable(hdmi->hdmi_isfr_clk); + if (ret < 0) { + dev_err(&hdmi->pdev->dev, + "Cannot enable HDMI isfr clock: %d\n", ret); + goto erate1; + } + + hdmi->hdmi_iahb_clk = clk_get(&hdmi->pdev->dev, "hdmi_iahb"); + if (IS_ERR(hdmi->hdmi_iahb_clk)) { + ret = PTR_ERR(hdmi->hdmi_iahb_clk); + dev_err(&hdmi->pdev->dev, + "Unable to get HDMI clk: %d\n", ret); + goto egetclk2; + } + + ret = clk_prepare_enable(hdmi->hdmi_iahb_clk); + if (ret < 0) { + dev_err(&hdmi->pdev->dev, + "Cannot enable HDMI iahb clock: %d\n", ret); + goto erate2; + } + + dev_dbg(&hdmi->pdev->dev, "Enabled HDMI clocks\n"); + + /* Init DDC pins for HDCP */ + if (hdcp_init) { + hdmi->pinctrl = devm_pinctrl_get_select_default(&hdmi->pdev->dev); + if (IS_ERR(hdmi->pinctrl)) { + dev_err(&hdmi->pdev->dev, "can't get/select DDC pinctrl\n"); + goto erate2; + } + } + + /* Product and revision IDs */ + dev_info(&hdmi->pdev->dev, + "Detected HDMI controller 0x%x:0x%x:0x%x:0x%x\n", + hdmi_readb(HDMI_DESIGN_ID), + hdmi_readb(HDMI_REVISION_ID), + hdmi_readb(HDMI_PRODUCT_ID0), + hdmi_readb(HDMI_PRODUCT_ID1)); + + /* To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator + * N and cts values before enabling phy */ + hdmi_init_clk_regenerator(); + + INIT_LIST_HEAD(&hdmi->fbi->modelist); + + spin_lock_init(&hdmi->irq_lock); + + /* Set the default mode and modelist when disp init. */ + fb_find_mode(&hdmi->fbi->var, hdmi->fbi, + hdmi->dft_mode_str, NULL, 0, NULL, + hdmi->default_bpp); + + console_lock(); + + fb_destroy_modelist(&hdmi->fbi->modelist); + + /*Add all no interlaced CEA mode to default modelist */ + for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) { + mode = &mxc_cea_mode[i]; + if (!(mode->vmode & FB_VMODE_INTERLACED) && (mode->xres != 0)) + fb_add_videomode(mode, &hdmi->fbi->modelist); + } + + console_unlock(); + + /* Find a nearest mode in default modelist */ + fb_var_to_videomode(&m, &hdmi->fbi->var); + + hdmi->dft_mode_set = false; + mode = fb_find_nearest_mode(&m, &hdmi->fbi->modelist); + if (!mode) { + pr_err("%s: could not find mode in modelist\n", __func__); + return -1; + } + dump_fb_videomode(mode); + /* Save default video mode */ + memcpy(&hdmi->default_mode, mode, sizeof(struct fb_videomode)); + + fb_videomode_to_var(&hdmi->fbi->var, mode); + + /* update fbi mode */ + hdmi->fbi->mode = (struct fb_videomode *)mode; + + /* Default setting HDMI working in HDMI mode*/ + hdmi->edid_cfg.hdmi_cap = true; + + INIT_DELAYED_WORK(&hdmi->hotplug_work, hotplug_worker); + INIT_DELAYED_WORK(&hdmi->hdcp_hdp_work, hdcp_hdp_worker); + + /* Configure registers related to HDMI interrupt + * generation before registering IRQ. */ + hdmi_writeb(HDMI_PHY_HPD, HDMI_PHY_POL0); + + /* Clear Hotplug interrupts */ + hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0); + + hdmi->nb.notifier_call = mxc_hdmi_fb_event; + ret = fb_register_client(&hdmi->nb); + if (ret < 0) + goto efbclient; + + memset(&hdmi->hdmi_data, 0, sizeof(struct hdmi_data_info)); + + /* Default HDMI working in RGB mode */ + hdmi->hdmi_data.rgb_out_enable = true; + + ret = devm_request_irq(&hdmi->pdev->dev, irq, mxc_hdmi_hotplug, IRQF_SHARED, + dev_name(&hdmi->pdev->dev), hdmi); + if (ret < 0) { + dev_err(&hdmi->pdev->dev, + "Unable to request irq: %d\n", ret); + goto ereqirq; + } + + ret = device_create_file(&hdmi->pdev->dev, &dev_attr_fb_name); + if (ret < 0) + dev_warn(&hdmi->pdev->dev, + "cound not create sys node for fb name\n"); + ret = device_create_file(&hdmi->pdev->dev, &dev_attr_cable_state); + if (ret < 0) + dev_warn(&hdmi->pdev->dev, + "cound not create sys node for cable state\n"); + ret = device_create_file(&hdmi->pdev->dev, &dev_attr_edid); + if (ret < 0) + dev_warn(&hdmi->pdev->dev, + "cound not create sys node for edid\n"); + + ret = device_create_file(&hdmi->pdev->dev, &dev_attr_rgb_out_enable); + if (ret < 0) + dev_warn(&hdmi->pdev->dev, + "cound not create sys node for rgb out enable\n"); + + ret = device_create_file(&hdmi->pdev->dev, &dev_attr_hdcp_enable); + if (ret < 0) + dev_warn(&hdmi->pdev->dev, + "cound not create sys node for hdcp enable\n"); + + dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__); + + hdmi_inited = true; + + return ret; + +efbclient: + free_irq(irq, hdmi); +ereqirq: + clk_disable_unprepare(hdmi->hdmi_iahb_clk); +erate2: + clk_put(hdmi->hdmi_iahb_clk); +egetclk2: + clk_disable_unprepare(hdmi->hdmi_isfr_clk); +erate1: + clk_put(hdmi->hdmi_isfr_clk); +egetclk1: + clk_disable_unprepare(hdmi->mipi_core_clk); +erate: + clk_put(hdmi->mipi_core_clk); +egetclk: + dev_dbg(&hdmi->pdev->dev, "%s error exit\n", __func__); + + return ret; +} + +static void mxc_hdmi_disp_deinit(struct mxc_dispdrv_handle *disp) +{ + struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp); + + dev_dbg(&hdmi->pdev->dev, "%s\n", __func__); + + fb_unregister_client(&hdmi->nb); + + clk_disable_unprepare(hdmi->hdmi_isfr_clk); + clk_put(hdmi->hdmi_isfr_clk); + clk_disable_unprepare(hdmi->hdmi_iahb_clk); + clk_put(hdmi->hdmi_iahb_clk); + clk_disable_unprepare(hdmi->mipi_core_clk); + clk_put(hdmi->mipi_core_clk); + + platform_device_unregister(hdmi->pdev); + + hdmi_inited = false; +} + +static struct mxc_dispdrv_driver mxc_hdmi_drv = { + .name = DISPDRV_HDMI, + .init = mxc_hdmi_disp_init, + .deinit = mxc_hdmi_disp_deinit, + .enable = mxc_hdmi_power_on, + .disable = mxc_hdmi_power_off, +}; + + +static int mxc_hdmi_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static long mxc_hdmi_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int __user *argp = (void __user *)arg; + int ret = 0; + + switch (cmd) { + case HDMI_IOC_GET_RESOURCE: + ret = copy_to_user(argp, &g_hdmi->hdmi_data, + sizeof(g_hdmi->hdmi_data)) ? -EFAULT : 0; + break; + case HDMI_IOC_GET_CPU_TYPE: + ret = put_user(g_hdmi->cpu_type, argp); + break; + default: + pr_debug("Unsupport cmd %d\n", cmd); + break; + } + + return ret; +} + +static int mxc_hdmi_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations mxc_hdmi_fops = { + .owner = THIS_MODULE, + .open = mxc_hdmi_open, + .release = mxc_hdmi_release, + .unlocked_ioctl = mxc_hdmi_ioctl, +}; + + +static int mxc_hdmi_probe(struct platform_device *pdev) +{ + struct mxc_hdmi *hdmi; + struct device *temp_class; + struct resource *res; + int ret = 0; + + /* Check I2C driver is loaded and available + * check hdcp function is enable by dts */ + hdmi_hdcp_get_property(pdev); + if (!hdmi_i2c && !hdcp_init) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + hdmi = devm_kzalloc(&pdev->dev, + sizeof(struct mxc_hdmi), + GFP_KERNEL); + if (!hdmi) { + dev_err(&pdev->dev, "Cannot allocate device data\n"); + ret = -ENOMEM; + goto ealloc; + } + g_hdmi = hdmi; + + hdmi_major = register_chrdev(hdmi_major, "mxc_hdmi", &mxc_hdmi_fops); + if (hdmi_major < 0) { + printk(KERN_ERR "HDMI: unable to get a major for HDMI\n"); + ret = -EBUSY; + goto ealloc; + } + + hdmi_class = class_create(THIS_MODULE, "mxc_hdmi"); + if (IS_ERR(hdmi_class)) { + ret = PTR_ERR(hdmi_class); + goto err_out_chrdev; + } + + temp_class = device_create(hdmi_class, NULL, MKDEV(hdmi_major, 0), + NULL, "mxc_hdmi"); + if (IS_ERR(temp_class)) { + ret = PTR_ERR(temp_class); + goto err_out_class; + } + + hdmi->pdev = pdev; + + hdmi->core_pdev = platform_device_alloc("mxc_hdmi_core", -1); + if (!hdmi->core_pdev) { + pr_err("%s failed platform_device_alloc for hdmi core\n", + __func__); + ret = -ENOMEM; + goto ecore; + } + + hdmi->gpr_base = ioremap(res->start, resource_size(res)); + if (!hdmi->gpr_base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto eiomap; + } + + hdmi->gpr_hdmi_base = hdmi->gpr_base + 3; + hdmi->gpr_sdma_base = hdmi->gpr_base; + + hdmi_inited = false; + + hdmi->disp_mxc_hdmi = mxc_dispdrv_register(&mxc_hdmi_drv); + if (IS_ERR(hdmi->disp_mxc_hdmi)) { + dev_err(&pdev->dev, "Failed to register dispdrv - 0x%x\n", + (int)hdmi->disp_mxc_hdmi); + ret = (int)hdmi->disp_mxc_hdmi; + goto edispdrv; + } + mxc_dispdrv_setdata(hdmi->disp_mxc_hdmi, hdmi); + + platform_set_drvdata(pdev, hdmi); + + hdmi_regulator = devm_regulator_get(&pdev->dev, "HDMI"); + if (!IS_ERR(hdmi_regulator)) { + ret = regulator_enable(hdmi_regulator); + if (ret) { + dev_err(&pdev->dev, "enable 5v hdmi regulator failed\n"); + goto edispdrv; + } + } else { + hdmi_regulator = NULL; + dev_warn(&pdev->dev, "No hdmi 5v supply\n"); + } + + return 0; +edispdrv: + iounmap(hdmi->gpr_base); +eiomap: + platform_device_put(hdmi->core_pdev); +ecore: + kfree(hdmi); +err_out_class: + device_destroy(hdmi_class, MKDEV(hdmi_major, 0)); + class_destroy(hdmi_class); +err_out_chrdev: + unregister_chrdev(hdmi_major, "mxc_hdmi"); +ealloc: + return ret; +} + +static int mxc_hdmi_remove(struct platform_device *pdev) +{ + struct mxc_hdmi *hdmi = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + fb_unregister_client(&hdmi->nb); + + mxc_dispdrv_puthandle(hdmi->disp_mxc_hdmi); + mxc_dispdrv_unregister(hdmi->disp_mxc_hdmi); + iounmap(hdmi->gpr_base); + /* No new work will be scheduled, wait for running ISR */ + free_irq(irq, hdmi); + kfree(hdmi); + + if (hdmi_regulator) + regulator_disable(hdmi_regulator); + + g_hdmi = NULL; + + return 0; +} + +static struct platform_driver mxc_hdmi_driver = { + .probe = mxc_hdmi_probe, + .remove = mxc_hdmi_remove, + .driver = { + .name = "mxc_hdmi", + .of_match_table = imx_hdmi_dt_ids, + .owner = THIS_MODULE, + }, +}; + +static int __init mxc_hdmi_init(void) +{ + return platform_driver_register(&mxc_hdmi_driver); +} +module_init(mxc_hdmi_init); + +static void __exit mxc_hdmi_exit(void) +{ + if (hdmi_major > 0) { + device_destroy(hdmi_class, MKDEV(hdmi_major, 0)); + class_destroy(hdmi_class); + unregister_chrdev(hdmi_major, "mxc_hdmi"); + hdmi_major = 0; + } + + platform_driver_unregister(&mxc_hdmi_driver); +} +module_exit(mxc_hdmi_exit); + +static int mxc_hdmi_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) + return -ENODEV; + + hdmi_i2c = client; + + return 0; +} + +static int mxc_hdmi_i2c_remove(struct i2c_client *client) +{ + hdmi_i2c = NULL; + return 0; +} + +static const struct of_device_id imx_hdmi_i2c_match[] = { + { .compatible = "fsl,imx6-hdmi-i2c", }, + { /* sentinel */ } +}; + +static const struct i2c_device_id mxc_hdmi_i2c_id[] = { + { "mxc_hdmi_i2c", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, mxc_hdmi_i2c_id); + +static struct i2c_driver mxc_hdmi_i2c_driver = { + .driver = { + .name = "mxc_hdmi_i2c", + .of_match_table = imx_hdmi_i2c_match, + }, + .probe = mxc_hdmi_i2c_probe, + .remove = mxc_hdmi_i2c_remove, + .id_table = mxc_hdmi_i2c_id, +}; + +static int __init mxc_hdmi_i2c_init(void) +{ + return i2c_add_driver(&mxc_hdmi_i2c_driver); +} + +static void __exit mxc_hdmi_i2c_exit(void) +{ + i2c_del_driver(&mxc_hdmi_i2c_driver); +} + +module_init(mxc_hdmi_i2c_init); +module_exit(mxc_hdmi_i2c_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); diff --git a/drivers/video/fbdev/mxc/mxc_ipuv3_fb.c b/drivers/video/fbdev/mxc/mxc_ipuv3_fb.c new file mode 100644 index 000000000000..5c32176bfe6f --- /dev/null +++ b/drivers/video/fbdev/mxc/mxc_ipuv3_fb.c @@ -0,0 +1,3678 @@ +/* + * Copyright 2004-2016 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* Copyright 2019 NXP */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/fbcon.h> +#include <linux/fsl_devices.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/ipu.h> +#include <linux/ipu-v3.h> +#include <linux/ipu-v3-pre.h> +#include <linux/ipu-v3-prg.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mxcfb.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/time.h> +#include <linux/uaccess.h> + +#include "mxc_dispdrv.h" + +/* + * Driver name + */ +#define MXCFB_NAME "mxc_sdc_fb" + +/* Display port number */ +#define MXCFB_PORT_NUM 2 +/*! + * Structure containing the MXC specific framebuffer information. + */ +struct mxcfb_info { + int default_bpp; + int cur_blank; + int next_blank; + ipu_channel_t ipu_ch; + int ipu_id; + int ipu_di; + int pre_num; + u32 ipu_di_pix_fmt; + bool ipu_int_clk; + bool overlay; + bool alpha_chan_en; + bool late_init; + bool first_set_par; + bool resolve; + bool prefetch; + bool on_the_fly; + uint32_t final_pfmt; + unsigned long gpu_sec_buf_off; + unsigned long base; + uint32_t x_crop; + uint32_t y_crop; + unsigned int sec_buf_off; + unsigned int trd_buf_off; + dma_addr_t store_addr; + dma_addr_t alpha_phy_addr0; + dma_addr_t alpha_phy_addr1; + void *alpha_virt_addr0; + void *alpha_virt_addr1; + uint32_t alpha_mem_len; + uint32_t ipu_ch_irq; + uint32_t ipu_ch_nf_irq; + uint32_t ipu_alp_ch_irq; + uint32_t cur_ipu_buf; + uint32_t cur_ipu_alpha_buf; + + u32 pseudo_palette[16]; + + bool mode_found; + struct completion flip_complete; + struct completion alpha_flip_complete; + struct completion vsync_complete; + struct completion otf_complete; /* on the fly */ + + void *ipu; + struct fb_info *ovfbi; + + struct mxc_dispdrv_handle *dispdrv; + + struct fb_var_screeninfo cur_var; + uint32_t cur_ipu_pfmt; + uint32_t cur_fb_pfmt; + bool cur_prefetch; + spinlock_t spin_lock; /* for PRE small yres cases */ + struct ipu_pre_context *pre_config; +}; + +struct mxcfb_pfmt { + u32 fb_pix_fmt; + int bpp; + struct fb_bitfield red; + struct fb_bitfield green; + struct fb_bitfield blue; + struct fb_bitfield transp; +}; + +struct mxcfb_tile_block { + u32 fb_pix_fmt; + int bw; /* in pixel */ + int bh; /* in pixel */ +}; + +#define NA (~0x0UL) +static const struct mxcfb_pfmt mxcfb_pfmts[] = { + /* pixel bpp red green blue transp */ + {IPU_PIX_FMT_RGB565, 16, {11, 5, 0}, { 5, 6, 0}, { 0, 5, 0}, { 0, 0, 0} }, + {IPU_PIX_FMT_BGRA4444, 16, { 8, 4, 0}, { 4, 4, 0}, { 0, 4, 0}, { 12, 4, 0} }, + {IPU_PIX_FMT_BGRA5551, 16, {10, 5, 0}, { 5, 5, 0}, { 0, 5, 0}, { 15, 1, 0} }, + {IPU_PIX_FMT_RGB24, 24, { 0, 8, 0}, { 8, 8, 0}, {16, 8, 0}, { 0, 0, 0} }, + {IPU_PIX_FMT_BGR24, 24, {16, 8, 0}, { 8, 8, 0}, { 0, 8, 0}, { 0, 0, 0} }, + {IPU_PIX_FMT_RGB32, 32, { 0, 8, 0}, { 8, 8, 0}, {16, 8, 0}, {24, 8, 0} }, + {IPU_PIX_FMT_BGR32, 32, {16, 8, 0}, { 8, 8, 0}, { 0, 8, 0}, {24, 8, 0} }, + {IPU_PIX_FMT_ABGR32, 32, {24, 8, 0}, {16, 8, 0}, { 8, 8, 0}, { 0, 8, 0} }, + /* pixel bpp red green blue transp */ + {IPU_PIX_FMT_YUV420P2, 12, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_YUV420P, 12, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_YVU420P, 12, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_NV12, 12, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {PRE_PIX_FMT_NV21, 12, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_NV16, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {PRE_PIX_FMT_NV61, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_YUV422P, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_YVU422P, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_UYVY, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_YUYV, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_YUV444, 24, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_YUV444P, 24, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_AYUV, 32, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + /* pixel bpp red green blue transp */ + {IPU_PIX_FMT_GPU32_SB_ST, 32, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_GPU32_SB_SRT, 32, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_GPU32_ST, 32, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_GPU32_SRT, 32, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_GPU16_SB_ST, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_GPU16_SB_SRT, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_GPU16_ST, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, + {IPU_PIX_FMT_GPU16_SRT, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} }, +}; + +/* Tile fb alignment */ +static const struct mxcfb_tile_block tas[] = { + {IPU_PIX_FMT_GPU32_SB_ST, 16, 8}, + {IPU_PIX_FMT_GPU32_SB_SRT, 64, 128}, + {IPU_PIX_FMT_GPU32_ST, 16, 4}, + {IPU_PIX_FMT_GPU32_SRT, 64, 64}, + {IPU_PIX_FMT_GPU16_SB_ST, 16, 8}, + {IPU_PIX_FMT_GPU16_SB_SRT, 64, 128}, + {IPU_PIX_FMT_GPU16_ST, 16, 4}, + {IPU_PIX_FMT_GPU16_SRT, 64, 64}, +}; + +/* The block can be resolved */ +static const struct mxcfb_tile_block trs[] = { + /* pixel w h */ + {IPU_PIX_FMT_GPU32_SB_ST, 16, 4}, + {IPU_PIX_FMT_GPU32_SB_SRT, 16, 4}, + {IPU_PIX_FMT_GPU32_ST, 16, 4}, + {IPU_PIX_FMT_GPU32_SRT, 16, 4}, + {IPU_PIX_FMT_GPU16_SB_ST, 16, 4}, + {IPU_PIX_FMT_GPU16_SB_SRT, 16, 4}, + {IPU_PIX_FMT_GPU16_ST, 16, 4}, + {IPU_PIX_FMT_GPU16_SRT, 16, 4}, +}; + +struct mxcfb_alloc_list { + struct list_head list; + dma_addr_t phy_addr; + void *cpu_addr; + u32 size; +}; + +enum { + BOTH_ON, + SRC_ON, + TGT_ON, + BOTH_OFF +}; + +static bool g_dp_in_use[2]; +LIST_HEAD(fb_alloc_list); + +/* Return default standard(RGB) pixel format */ +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +static inline int bitfield_is_equal(struct fb_bitfield f1, + struct fb_bitfield f2) +{ + return !memcmp(&f1, &f2, sizeof(f1)); +} + +static int pixfmt_to_var(uint32_t pixfmt, struct fb_var_screeninfo *var) +{ + int i, ret = -1; + + for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) { + if (pixfmt == mxcfb_pfmts[i].fb_pix_fmt) { + var->red = mxcfb_pfmts[i].red; + var->green = mxcfb_pfmts[i].green; + var->blue = mxcfb_pfmts[i].blue; + var->transp = mxcfb_pfmts[i].transp; + var->bits_per_pixel = mxcfb_pfmts[i].bpp; + ret = 0; + break; + } + } + return ret; +} + +static int bpp_to_var(int bpp, struct fb_var_screeninfo *var) +{ + uint32_t pixfmt = 0; + + if (var->nonstd) + return -1; + + pixfmt = bpp_to_pixfmt(bpp); + if (pixfmt) + return pixfmt_to_var(pixfmt, var); + else + return -1; +} + +static int check_var_pixfmt(struct fb_var_screeninfo *var) +{ + int i, ret = -1; + + if (var->nonstd) { + for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) { + if (mxcfb_pfmts[i].fb_pix_fmt == var->nonstd) { + var->bits_per_pixel = mxcfb_pfmts[i].bpp; + ret = 0; + break; + } + } + return ret; + } + + for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) { + if (bitfield_is_equal(var->red, mxcfb_pfmts[i].red) && + bitfield_is_equal(var->green, mxcfb_pfmts[i].green) && + bitfield_is_equal(var->blue, mxcfb_pfmts[i].blue) && + bitfield_is_equal(var->transp, mxcfb_pfmts[i].transp) && + var->bits_per_pixel == mxcfb_pfmts[i].bpp) { + ret = 0; + break; + } + } + return ret; +} + +static uint32_t fb_to_store_pixfmt(uint32_t fb_pixfmt) +{ + switch (fb_pixfmt) { + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_ABGR32: + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_BGRA4444: + case IPU_PIX_FMT_BGRA5551: + case IPU_PIX_FMT_UYVY: + case IPU_PIX_FMT_YUYV: + case IPU_PIX_FMT_YUV444: + case IPU_PIX_FMT_AYUV: + return fb_pixfmt; + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YVU420P: + case IPU_PIX_FMT_NV12: + case PRE_PIX_FMT_NV21: + case IPU_PIX_FMT_NV16: + case PRE_PIX_FMT_NV61: + case IPU_PIX_FMT_YUV420P: + return IPU_PIX_FMT_UYVY; + case IPU_PIX_FMT_YUV444P: + return IPU_PIX_FMT_AYUV; + default: + return 0; + } +} + +static uint32_t fbi_to_pixfmt(struct fb_info *fbi, bool original_fb) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + int i; + uint32_t pixfmt = 0; + + if (fbi->var.nonstd) { + if (mxc_fbi->prefetch && !original_fb) { + if (ipu_pixel_format_is_gpu_tile(fbi->var.nonstd)) + goto next; + + return fb_to_store_pixfmt(fbi->var.nonstd); + } else { + return fbi->var.nonstd; + } + } + +next: + for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) { + if (bitfield_is_equal(fbi->var.red, mxcfb_pfmts[i].red) && + bitfield_is_equal(fbi->var.green, mxcfb_pfmts[i].green) && + bitfield_is_equal(fbi->var.blue, mxcfb_pfmts[i].blue) && + bitfield_is_equal(fbi->var.transp, mxcfb_pfmts[i].transp)) { + pixfmt = mxcfb_pfmts[i].fb_pix_fmt; + break; + } + } + + if (pixfmt == 0) + dev_err(fbi->device, "cannot get pixel format\n"); + + return pixfmt; +} + +static void fmt_to_tile_alignment(uint32_t fmt, int *bw, int *bh) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tas); i++) { + if (tas[i].fb_pix_fmt == fmt) { + *bw = tas[i].bw; + *bh = tas[i].bh; + } + } + + BUG_ON(!(*bw) || !(*bh)); +} + +static void fmt_to_tile_block(uint32_t fmt, int *bw, int *bh) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(trs); i++) { + if (trs[i].fb_pix_fmt == fmt) { + *bw = trs[i].bw; + *bh = trs[i].bh; + } + } + + BUG_ON(!(*bw) || !(*bh)); +} + +static struct fb_info *found_registered_fb(ipu_channel_t ipu_ch, int ipu_id) +{ + int i; + struct mxcfb_info *mxc_fbi; + struct fb_info *fbi = NULL; + + for (i = 0; i < num_registered_fb; i++) { + mxc_fbi = + ((struct mxcfb_info *)(registered_fb[i]->par)); + + if ((mxc_fbi->ipu_ch == ipu_ch) && + (mxc_fbi->ipu_id == ipu_id)) { + fbi = registered_fb[i]; + break; + } + } + return fbi; +} + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id); +static irqreturn_t mxcfb_nf_irq_handler(int irq, void *dev_id); +static int mxcfb_blank(int blank, struct fb_info *info); +static int mxcfb_map_video_memory(struct fb_info *fbi); +static int mxcfb_unmap_video_memory(struct fb_info *fbi); +static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, + unsigned long arg); + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ywrapstep = 1; + fix->ypanstep = 1; + + return 0; +} + +static int _setup_disp_channel1(struct fb_info *fbi) +{ + ipu_channel_params_t params; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + memset(¶ms, 0, sizeof(params)); + + if (mxc_fbi->ipu_ch == MEM_DC_SYNC) { + params.mem_dc_sync.di = mxc_fbi->ipu_di; + if (fbi->var.vmode & FB_VMODE_INTERLACED) + params.mem_dc_sync.interlaced = true; + params.mem_dc_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + params.mem_dc_sync.in_pixel_fmt = mxc_fbi->on_the_fly ? + mxc_fbi->final_pfmt : + fbi_to_pixfmt(fbi, false); + } else { + params.mem_dp_bg_sync.di = mxc_fbi->ipu_di; + if (fbi->var.vmode & FB_VMODE_INTERLACED) + params.mem_dp_bg_sync.interlaced = true; + params.mem_dp_bg_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + params.mem_dp_bg_sync.in_pixel_fmt = mxc_fbi->on_the_fly ? + mxc_fbi->final_pfmt : + fbi_to_pixfmt(fbi, false); + if (mxc_fbi->alpha_chan_en) + params.mem_dp_bg_sync.alpha_chan_en = true; + } + if (ipu_init_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, ¶ms) < 0) { + dev_err(fbi->device, "init ipu channel fail\n"); + return -EINVAL; + } + + return 0; +} + +static int _setup_disp_channel2(struct fb_info *fbi) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + int fb_stride, ipu_stride, bw = 0, bh = 0; + unsigned long base, ipu_base; + unsigned int fr_xoff, fr_yoff, fr_w, fr_h; + unsigned int prg_width; + struct ipu_pre_context pre; + bool post_pre_disable = false; + + switch (fbi_to_pixfmt(fbi, true)) { + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YVU420P: + case IPU_PIX_FMT_NV12: + case PRE_PIX_FMT_NV21: + case IPU_PIX_FMT_NV16: + case PRE_PIX_FMT_NV61: + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YUV444P: + fb_stride = fbi->var.xres_virtual; + break; + default: + fb_stride = fbi->fix.line_length; + } + + base = fbi->fix.smem_start; + fr_xoff = fbi->var.xoffset; + fr_w = fbi->var.xres_virtual; + if (!(fbi->var.vmode & FB_VMODE_YWRAP)) { + dev_dbg(fbi->device, "Y wrap disabled\n"); + fr_yoff = fbi->var.yoffset % fbi->var.yres; + fr_h = fbi->var.yres; + base += fbi->fix.line_length * fbi->var.yres * + (fbi->var.yoffset / fbi->var.yres); + if (ipu_pixel_format_is_split_gpu_tile(fbi->var.nonstd)) + base += (mxc_fbi->gpu_sec_buf_off - + fbi->fix.line_length * fbi->var.yres / 2) * + (fbi->var.yoffset / fbi->var.yres); + } else { + dev_dbg(fbi->device, "Y wrap enabled\n"); + fr_yoff = fbi->var.yoffset; + fr_h = fbi->var.yres_virtual; + } + + /* pixel block alignment for resolving cases */ + if (mxc_fbi->resolve) { + fmt_to_tile_block(fbi->var.nonstd, &bw, &bh); + } else { + base += fr_yoff * fb_stride + fr_xoff * + bytes_per_pixel(fbi_to_pixfmt(fbi, true)); + } + + if (!mxc_fbi->on_the_fly) + mxc_fbi->cur_ipu_buf = 2; + init_completion(&mxc_fbi->flip_complete); + /* + * We don't need to wait for vsync at the first time + * we do pan display after fb is initialized, as IPU will + * switch to the newly selected buffer automatically, + * so we call complete() for both mxc_fbi->flip_complete + * and mxc_fbi->alpha_flip_complete. + */ + if (!mxc_fbi->prefetch || + (mxc_fbi->prefetch && !ipu_pre_yres_is_small(fbi->var.yres))) + complete(&mxc_fbi->flip_complete); + if (mxc_fbi->alpha_chan_en) { + mxc_fbi->cur_ipu_alpha_buf = 1; + init_completion(&mxc_fbi->alpha_flip_complete); + complete(&mxc_fbi->alpha_flip_complete); + } + + if (mxc_fbi->prefetch) { + struct ipu_prg_config prg; + struct fb_var_screeninfo from_var, to_var; + + if (mxc_fbi->pre_num < 0) { + mxc_fbi->pre_num = ipu_pre_alloc(mxc_fbi->ipu_id, + mxc_fbi->ipu_ch); + if (mxc_fbi->pre_num < 0) { + dev_dbg(fbi->device, "failed to alloc PRE\n"); + mxc_fbi->prefetch = mxc_fbi->cur_prefetch; + mxc_fbi->resolve = false; + if (!mxc_fbi->on_the_fly) + mxc_fbi->cur_blank = FB_BLANK_POWERDOWN; + return mxc_fbi->pre_num; + } + } + pre.repeat = true; + pre.vflip = fbi->var.rotate ? true : false; + pre.handshake_en = true; + pre.hsk_abort_en = true; + pre.hsk_line_num = 0; + pre.sdw_update = true; + pre.cur_buf = base; + pre.next_buf = pre.cur_buf; + if (fbi->var.vmode & FB_VMODE_INTERLACED) { + pre.interlaced = 2; + if (mxc_fbi->resolve) { + pre.field_inverse = fbi->var.rotate; + pre.interlace_offset = 0; + } else { + pre.field_inverse = 0; + if (fbi->var.rotate) { + pre.interlace_offset = ~(fbi->var.xres_virtual * + bytes_per_pixel(fbi_to_pixfmt(fbi, true))) + 1; + pre.cur_buf += fbi->var.xres_virtual * bytes_per_pixel(fbi_to_pixfmt(fbi, true)); + pre.next_buf = pre.cur_buf; + } else { + pre.interlace_offset = fbi->var.xres_virtual * + bytes_per_pixel(fbi_to_pixfmt(fbi, true)); + } + } + } else { + pre.interlaced = 0; + pre.interlace_offset = 0; + } + pre.prefetch_mode = mxc_fbi->resolve ? 1 : 0; + pre.tile_fmt = mxc_fbi->resolve ? fbi->var.nonstd : 0; + pre.read_burst = mxc_fbi->resolve ? 0x4 : 0x3; + if (fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_RGB24 || + fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_BGR24 || + fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_YUV444) { + if ((fbi->var.xres * 3) % 8 == 0 && + (fbi->var.xres_virtual * 3) % 8 == 0) + pre.prefetch_input_bpp = 64; + else if ((fbi->var.xres * 3) % 4 == 0 && + (fbi->var.xres_virtual * 3) % 4 == 0) + pre.prefetch_input_bpp = 32; + else if ((fbi->var.xres * 3) % 2 == 0 && + (fbi->var.xres_virtual * 3) % 2 == 0) + pre.prefetch_input_bpp = 16; + else + pre.prefetch_input_bpp = 8; + } else { + pre.prefetch_input_bpp = + 8 * bytes_per_pixel(fbi_to_pixfmt(fbi, true)); + } + pre.prefetch_input_pixel_fmt = mxc_fbi->resolve ? + 0x1 : (fbi->var.nonstd ? fbi->var.nonstd : 0); + pre.shift_bypass = (mxc_fbi->on_the_fly && + mxc_fbi->final_pfmt != fbi_to_pixfmt(fbi, false)) ? + false : true; + pixfmt_to_var(fbi_to_pixfmt(fbi, false), &from_var); + pixfmt_to_var(mxc_fbi->final_pfmt, &to_var); + if (mxc_fbi->on_the_fly && + (format_to_colorspace(fbi_to_pixfmt(fbi, true)) == RGB) && + (bytes_per_pixel(fbi_to_pixfmt(fbi, true)) == 4)) { + pre.prefetch_shift_offset = (from_var.red.offset << to_var.red.offset) | + (from_var.green.offset << to_var.green.offset) | + (from_var.blue.offset << to_var.blue.offset) | + (from_var.transp.offset << to_var.transp.offset); + pre.prefetch_shift_width = (to_var.red.length << (to_var.red.offset/2)) | + (to_var.green.length << (to_var.green.offset/2)) | + (to_var.blue.length << (to_var.blue.offset/2)) | + (to_var.transp.length << (to_var.transp.offset/2)); + } else { + pre.prefetch_shift_offset = 0; + pre.prefetch_shift_width = 0; + } + pre.tpr_coor_offset_en = mxc_fbi->resolve ? true : false; + pre.prefetch_output_size.left = mxc_fbi->resolve ? (fr_xoff & ~(bw - 1)) : 0; + pre.prefetch_output_size.top = mxc_fbi->resolve ? (fr_yoff & ~(bh - 1)) : 0; + if (fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_RGB24 || + fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_BGR24 || + fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_YUV444) { + pre.prefetch_output_size.width = (fbi->var.xres * 3) / + (pre.prefetch_input_bpp / 8); + pre.store_output_bpp = pre.prefetch_input_bpp; + } else { + pre.prefetch_output_size.width = fbi->var.xres; + pre.store_output_bpp = 8 * + bytes_per_pixel(fbi_to_pixfmt(fbi, false)); + } + pre.prefetch_output_size.height = fbi->var.yres; + pre.prefetch_input_active_width = pre.prefetch_output_size.width; + if (fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_RGB24 || + fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_BGR24 || + fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_YUV444) + pre.prefetch_input_width = (fbi->var.xres_virtual * 3) / + (pre.prefetch_input_bpp / 8); + else + pre.prefetch_input_width = fbi->var.xres_virtual; + pre.prefetch_input_height = fbi->var.yres; + prg_width = pre.prefetch_output_size.width; + if (!(pre.prefetch_input_active_width % 32)) + pre.block_size = 0; + else if (!(pre.prefetch_input_active_width % 16)) + pre.block_size = 1; + else + pre.block_size = 0; + if (mxc_fbi->resolve) { + int bs = pre.block_size ? 16 : 32; + pre.prefetch_input_active_width += fr_xoff % bw; + if (((fr_xoff % bw) + pre.prefetch_input_active_width) % bs) + pre.prefetch_input_active_width = + ALIGN(pre.prefetch_input_active_width, bs); + pre.prefetch_output_size.width = + pre.prefetch_input_active_width; + prg_width = pre.prefetch_output_size.width; + pre.prefetch_input_height += fr_yoff % bh; + if (((fr_yoff % bh) + fbi->var.yres) % 4) { + pre.prefetch_input_height = + (fbi->var.vmode & FB_VMODE_INTERLACED) ? + ALIGN(pre.prefetch_input_height, 8) : + ALIGN(pre.prefetch_input_height, 4); + } else { + if (fbi->var.vmode & FB_VMODE_INTERLACED) + pre.prefetch_input_height = + ALIGN(pre.prefetch_input_height, 8); + } + pre.prefetch_output_size.height = pre.prefetch_input_height; + } + + /* store output pitch 8-byte aligned */ + while ((pre.store_output_bpp * prg_width) % 64) + prg_width++; + + pre.store_pitch = (pre.store_output_bpp * prg_width) / 8; + + pre.store_en = true; + pre.write_burst = 0x3; + ipu_get_channel_offset(fbi_to_pixfmt(fbi, true), + fbi->var.xres, + fr_h, + fr_w, + 0, 0, + fr_yoff, + fr_xoff, + &pre.sec_buf_off, + &pre.trd_buf_off); + if (mxc_fbi->resolve) + pre.sec_buf_off = mxc_fbi->gpu_sec_buf_off; + + ipu_pre_config(mxc_fbi->pre_num, &pre); + ipu_stride = pre.store_pitch; + ipu_base = pre.store_addr; + mxc_fbi->store_addr = ipu_base; + + if (mxc_fbi->cur_prefetch && mxc_fbi->on_the_fly) { + /* + * Make sure any pending interrupt is handled so that + * the buffer panned can start to be scanned out. + */ + if (!ipu_pre_yres_is_small(fbi->var.yres)) { + mxcfb_ioctl(fbi, MXCFB_WAIT_FOR_VSYNC, + (unsigned long)fbi->par); + mxcfb_ioctl(fbi, MXCFB_WAIT_FOR_VSYNC, + (unsigned long)fbi->par); + } + + mxc_fbi->pre_config = ⪯ + + /* + * Write the PRE control register in the flip interrupt + * handler in this on-the-fly case to workaround the + * SoC design bug recorded by errata ERR009624. + */ + init_completion(&mxc_fbi->otf_complete); + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + retval = wait_for_completion_timeout( + &mxc_fbi->otf_complete, HZ/2); + if (retval == 0) { + dev_err(fbi->device, "timeout when waiting " + "for on the fly config irq\n"); + return -ETIMEDOUT; + } else { + retval = 0; + } + } else { + retval = ipu_pre_set_ctrl(mxc_fbi->pre_num, &pre); + if (retval < 0) + return retval; + } + + if (!mxc_fbi->on_the_fly || !mxc_fbi->cur_prefetch) { + prg.id = mxc_fbi->ipu_id; + prg.pre_num = mxc_fbi->pre_num; + prg.ipu_ch = mxc_fbi->ipu_ch; + prg.so = (fbi->var.vmode & FB_VMODE_INTERLACED) ? + PRG_SO_INTERLACE : PRG_SO_PROGRESSIVE; + prg.vflip = fbi->var.rotate ? true : false; + prg.block_mode = mxc_fbi->resolve ? PRG_BLOCK_MODE : PRG_SCAN_MODE; + prg.stride = (fbi->var.vmode & FB_VMODE_INTERLACED) ? + ipu_stride * 2 : ipu_stride; + prg.ilo = (fbi->var.vmode & FB_VMODE_INTERLACED) ? + ipu_stride : 0; + prg.height = mxc_fbi->resolve ? + pre.prefetch_output_size.height : fbi->var.yres; + prg.ipu_height = fbi->var.yres; + prg.crop_line = mxc_fbi->resolve ? + ((fbi->var.vmode & FB_VMODE_INTERLACED) ? (fr_yoff % bh) / 2 : fr_yoff % bh) : 0; + prg.baddr = pre.store_addr; + prg.offset = mxc_fbi->resolve ? (prg.crop_line * prg.stride + + (fr_xoff % bw) * + bytes_per_pixel(fbi_to_pixfmt(fbi, false))) : 0; + ipu_base += prg.offset; + if (ipu_base % 8) { + dev_err(fbi->device, + "IPU base address is not 8byte aligned\n"); + return -EINVAL; + } + mxc_fbi->store_addr = ipu_base; + + if (!mxc_fbi->on_the_fly) { + retval = ipu_init_channel_buffer(mxc_fbi->ipu, + mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + fbi_to_pixfmt(fbi, false), + fbi->var.xres, fbi->var.yres, + ipu_stride, + fbi->var.rotate, + ipu_base, + ipu_base, + fbi->var.accel_flags & + FB_ACCEL_DOUBLE_FLAG ? 0 : ipu_base, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + } + + retval = ipu_prg_config(&prg); + if (retval < 0) { + dev_err(fbi->device, + "failed to configure PRG %d\n", retval); + return retval; + } + + retval = ipu_pre_enable(mxc_fbi->pre_num); + if (retval < 0) { + ipu_prg_disable(mxc_fbi->ipu_id, mxc_fbi->pre_num); + dev_err(fbi->device, + "failed to enable PRE %d\n", retval); + return retval; + } + + retval = ipu_prg_wait_buf_ready(mxc_fbi->ipu_id, + mxc_fbi->pre_num, + pre.hsk_line_num, + pre.prefetch_output_size.height); + if (retval < 0) { + ipu_prg_disable(mxc_fbi->ipu_id, mxc_fbi->pre_num); + ipu_pre_disable(mxc_fbi->pre_num); + ipu_pre_free(&mxc_fbi->pre_num); + dev_err(fbi->device, "failed to wait PRG ready %d\n", retval); + return retval; + } + } + } else { + ipu_stride = fb_stride; + ipu_base = base; + if (mxc_fbi->on_the_fly) + post_pre_disable = true; + } + + if (mxc_fbi->on_the_fly && ((mxc_fbi->cur_prefetch && !mxc_fbi->prefetch) || + (!mxc_fbi->cur_prefetch && mxc_fbi->prefetch))) { + int htotal = fbi->var.xres + fbi->var.right_margin + + fbi->var.hsync_len + fbi->var.left_margin; + int vtotal = fbi->var.yres + fbi->var.lower_margin + + fbi->var.vsync_len + fbi->var.upper_margin; + int timeout = ((htotal * vtotal) / PICOS2KHZ(fbi->var.pixclock)) * 2 ; + int cur_buf = 0; + + BUG_ON(timeout <= 0); + + ++mxc_fbi->cur_ipu_buf; + mxc_fbi->cur_ipu_buf %= 3; + if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, + ipu_base) == 0) { + if (!mxc_fbi->prefetch) + ipu_update_channel_offset(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, + fbi_to_pixfmt(fbi, true), + fr_w, + fr_h, + fr_w, + 0, 0, + fr_yoff, + fr_xoff); + ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, mxc_fbi->cur_ipu_buf); + for (; timeout > 0; timeout--) { + cur_buf = ipu_get_cur_buffer_idx(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER); + if (cur_buf == mxc_fbi->cur_ipu_buf) + break; + + udelay(1000); + } + if (!timeout) + dev_err(fbi->device, "Timeout for switch to buf %d " + "to address=0x%08lX, current buf %d, " + "buf0 ready %d, buf1 ready %d, buf2 ready " + "%d\n", mxc_fbi->cur_ipu_buf, ipu_base, + ipu_get_cur_buffer_idx(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER), + ipu_check_buffer_ready(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, 0), + ipu_check_buffer_ready(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, 1), + ipu_check_buffer_ready(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, 2)); + } + } else if (!mxc_fbi->on_the_fly && !mxc_fbi->prefetch) { + retval = ipu_init_channel_buffer(mxc_fbi->ipu, + mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->on_the_fly ? mxc_fbi->final_pfmt : + fbi_to_pixfmt(fbi, false), + fbi->var.xres, fbi->var.yres, + ipu_stride, + fbi->var.rotate, + ipu_base, + ipu_base, + fbi->var.accel_flags & + FB_ACCEL_DOUBLE_FLAG ? 0 : ipu_base, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + /* update u/v offset */ + if (!mxc_fbi->prefetch) + ipu_update_channel_offset(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, + fbi_to_pixfmt(fbi, true), + fr_w, + fr_h, + fr_w, + 0, 0, + fr_yoff, + fr_xoff); + } + + if (post_pre_disable) { + ipu_prg_disable(mxc_fbi->ipu_id, mxc_fbi->pre_num); + ipu_pre_disable(mxc_fbi->pre_num); + ipu_pre_free(&mxc_fbi->pre_num); + } + + if (mxc_fbi->alpha_chan_en) { + retval = ipu_init_channel_buffer(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + IPU_PIX_FMT_GENERIC, + fbi->var.xres, fbi->var.yres, + fbi->var.xres, + fbi->var.rotate, + mxc_fbi->alpha_phy_addr1, + mxc_fbi->alpha_phy_addr0, + 0, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + } + + return retval; +} + +static bool mxcfb_need_to_set_par(struct fb_info *fbi) +{ + struct mxcfb_info *mxc_fbi = fbi->par; + + if ((fbi->var.activate & FB_ACTIVATE_FORCE) && + (fbi->var.activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) + return true; + + /* + * Ignore xoffset and yoffset update, + * because pan display handles this case. + */ + mxc_fbi->cur_var.xoffset = fbi->var.xoffset; + mxc_fbi->cur_var.yoffset = fbi->var.yoffset; + + return !!memcmp(&mxc_fbi->cur_var, &fbi->var, + sizeof(struct fb_var_screeninfo)); +} + +static bool mxcfb_can_set_par_on_the_fly(struct fb_info *fbi, + uint32_t *final_pfmt) +{ + struct mxcfb_info *mxc_fbi = fbi->par; + struct fb_var_screeninfo cur_var = mxc_fbi->cur_var; + uint32_t cur_pfmt = mxc_fbi->cur_ipu_pfmt; + uint32_t new_pfmt = fbi_to_pixfmt(fbi, false); + uint32_t new_fb_pfmt = fbi_to_pixfmt(fbi, true); + int cur_bpp, new_bpp, cur_bw, cur_bh, new_bw, new_bh; + unsigned int mem_len; + ipu_color_space_t cur_space, new_space; + + cur_space = format_to_colorspace(cur_pfmt); + new_space = format_to_colorspace(new_pfmt); + + cur_bpp = bytes_per_pixel(cur_pfmt); + new_bpp = bytes_per_pixel(new_pfmt); + + if (mxc_fbi->first_set_par || mxc_fbi->cur_blank != FB_BLANK_UNBLANK) + return false; + + if (!mxc_fbi->prefetch && !mxc_fbi->cur_prefetch) + return false; + + if (!mxc_fbi->prefetch && cur_pfmt != new_pfmt) + return false; + + if (cur_space == RGB && (cur_bpp == 2 || cur_bpp == 3) && + cur_pfmt != new_pfmt) + return false; + + if (!(mxc_fbi->cur_prefetch && mxc_fbi->prefetch) && + ((cur_var.xres_virtual != fbi->var.xres_virtual) || + (cur_var.xres != cur_var.xres_virtual) || + (fbi->var.xres != fbi->var.xres_virtual))) + return false; + + if (cur_space != new_space || + (new_space == RGB && cur_bpp != new_bpp)) + return false; + + if (new_space == YCbCr) + return false; + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (mxc_fbi->resolve && mxc_fbi->gpu_sec_buf_off) { + if (fbi->var.vmode & FB_VMODE_YWRAP) + mem_len = mxc_fbi->gpu_sec_buf_off + mem_len / 2; + else + mem_len = mxc_fbi->gpu_sec_buf_off * + (fbi->var.yres_virtual / fbi->var.yres) + mem_len / 2; + } + if (mem_len > fbi->fix.smem_len) + return false; + + if (mxc_fbi->resolve && ipu_pixel_format_is_gpu_tile(mxc_fbi->cur_fb_pfmt)) { + fmt_to_tile_block(mxc_fbi->cur_fb_pfmt, &cur_bw, &cur_bh); + fmt_to_tile_block(new_fb_pfmt, &new_bw, &new_bh); + + if (cur_bw != new_bw || cur_bh != new_bh || + cur_var.xoffset % cur_bw != fbi->var.xoffset % new_bw || + cur_var.yoffset % cur_bh != fbi->var.yoffset % new_bh) + return false; + } else if (mxc_fbi->resolve && mxc_fbi->cur_prefetch) { + fmt_to_tile_block(new_fb_pfmt, &new_bw, &new_bh); + if (fbi->var.xoffset % new_bw || fbi->var.yoffset % new_bh || + fbi->var.xres % 16 || fbi->var.yres % + (fbi->var.vmode & FB_VMODE_INTERLACED ? 8 : 4)) + return false; + } else if (mxc_fbi->prefetch && ipu_pixel_format_is_gpu_tile(mxc_fbi->cur_fb_pfmt)) { + fmt_to_tile_block(mxc_fbi->cur_fb_pfmt, &cur_bw, &cur_bh); + if (cur_var.xoffset % cur_bw || cur_var.yoffset % cur_bh || + cur_var.xres % 16 || cur_var.yres % + (cur_var.vmode & FB_VMODE_INTERLACED ? 8 : 4)) + return false; + } + + cur_var.xres_virtual = fbi->var.xres_virtual; + cur_var.yres_virtual = fbi->var.yres_virtual; + cur_var.xoffset = fbi->var.xoffset; + cur_var.yoffset = fbi->var.yoffset; + cur_var.red = fbi->var.red; + cur_var.green = fbi->var.green; + cur_var.blue = fbi->var.blue; + cur_var.transp = fbi->var.transp; + cur_var.nonstd = fbi->var.nonstd; + if (memcmp(&cur_var, &fbi->var, + sizeof(struct fb_var_screeninfo))) + return false; + + *final_pfmt = cur_pfmt; + + return true; +} + +static void mxcfb_check_resolve(struct fb_info *fbi) +{ + struct mxcfb_info *mxc_fbi = fbi->par; + + switch (fbi->var.nonstd) { + case IPU_PIX_FMT_GPU32_ST: + case IPU_PIX_FMT_GPU32_SRT: + case IPU_PIX_FMT_GPU16_ST: + case IPU_PIX_FMT_GPU16_SRT: + mxc_fbi->gpu_sec_buf_off = 0; + /* fall-through */ + case IPU_PIX_FMT_GPU32_SB_ST: + case IPU_PIX_FMT_GPU32_SB_SRT: + case IPU_PIX_FMT_GPU16_SB_ST: + case IPU_PIX_FMT_GPU16_SB_SRT: + mxc_fbi->prefetch = true; + mxc_fbi->resolve = true; + break; + default: + mxc_fbi->resolve = false; + } +} + +static void mxcfb_check_yuv(struct fb_info *fbi) +{ + struct mxcfb_info *mxc_fbi = fbi->par; + + if (fbi->var.vmode & FB_VMODE_INTERLACED) { + if (ipu_pixel_format_is_multiplanar_yuv(fbi_to_pixfmt(fbi, true))) + mxc_fbi->prefetch = false; + } else { + if (fbi->var.nonstd == PRE_PIX_FMT_NV21 || + fbi->var.nonstd == PRE_PIX_FMT_NV61) + mxc_fbi->prefetch = true; + } +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval = 0; + u32 mem_len, alpha_mem_len; + ipu_di_signal_cfg_t sig_cfg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + uint32_t final_pfmt = 0; + int16_t ov_pos_x = 0, ov_pos_y = 0; + int ov_pos_ret = 0; + struct mxcfb_info *mxc_fbi_fg = NULL; + bool ovfbi_enable = false, on_the_fly; + + if (ipu_ch_param_bad_alpha_pos(fbi_to_pixfmt(fbi, true)) && + mxc_fbi->alpha_chan_en) { + dev_err(fbi->device, "Bad pixel format for " + "graphics plane fb\n"); + return -EINVAL; + } + + if (mxc_fbi->ovfbi) + mxc_fbi_fg = (struct mxcfb_info *)mxc_fbi->ovfbi->par; + + if (mxc_fbi->ovfbi && mxc_fbi_fg) + if (mxc_fbi_fg->next_blank == FB_BLANK_UNBLANK) + ovfbi_enable = true; + + if (!mxcfb_need_to_set_par(fbi)) + return 0; + + dev_dbg(fbi->device, "Reconfiguring framebuffer\n"); + + if (fbi->var.xres == 0 || fbi->var.yres == 0) + return 0; + + mxcfb_set_fix(fbi); + + mxcfb_check_resolve(fbi); + + mxcfb_check_yuv(fbi); + + on_the_fly = mxcfb_can_set_par_on_the_fly(fbi, &final_pfmt); + mxc_fbi->on_the_fly = on_the_fly; + mxc_fbi->final_pfmt = final_pfmt; + + if (on_the_fly) + dev_dbg(fbi->device, "Reconfiguring framebuffer on the fly\n"); + + if (ovfbi_enable) { + ov_pos_ret = ipu_disp_get_window_pos( + mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch, + &ov_pos_x, &ov_pos_y); + if (ov_pos_ret < 0) + dev_err(fbi->device, "Get overlay pos failed, dispdrv:%s.\n", + mxc_fbi->dispdrv->drv->name); + + if (!on_the_fly) { + ipu_clear_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_irq); + ipu_disable_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_irq); + ipu_clear_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_nf_irq); + ipu_disable_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_nf_irq); + ipu_disable_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch, true); + ipu_uninit_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch); + if (mxc_fbi_fg->cur_prefetch) { + ipu_prg_disable(mxc_fbi_fg->ipu_id, mxc_fbi_fg->pre_num); + ipu_pre_disable(mxc_fbi_fg->pre_num); + ipu_pre_free(&mxc_fbi_fg->pre_num); + } + } + } + + if (!on_the_fly) { + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq); + ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq); + ipu_disable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, true); + ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + if (mxc_fbi->cur_prefetch) { + ipu_prg_disable(mxc_fbi->ipu_id, mxc_fbi->pre_num); + ipu_pre_disable(mxc_fbi->pre_num); + ipu_pre_free(&mxc_fbi->pre_num); + } + } + + /* + * Disable IPU hsp clock if it is enabled for an + * additional time in ipu common driver. + */ + if (mxc_fbi->first_set_par && mxc_fbi->late_init) + ipu_disable_hsp_clk(mxc_fbi->ipu); + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (mxc_fbi->resolve && mxc_fbi->gpu_sec_buf_off) { + if (fbi->var.vmode & FB_VMODE_YWRAP) + mem_len = mxc_fbi->gpu_sec_buf_off + mem_len / 2; + else + mem_len = mxc_fbi->gpu_sec_buf_off * + (fbi->var.yres_virtual / fbi->var.yres) + mem_len / 2; + } + if (!fbi->fix.smem_start || (mem_len > fbi->fix.smem_len)) { + if (fbi->fix.smem_start) + mxcfb_unmap_video_memory(fbi); + + if (mxcfb_map_video_memory(fbi) < 0) + return -ENOMEM; + } + + if (mxc_fbi->first_set_par) { + /* + * Clear the screen in case uboot fb pixel format is not + * the same to kernel fb pixel format. + */ + if (mxc_fbi->late_init) + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + mxc_fbi->first_set_par = false; + } + + if (mxc_fbi->alpha_chan_en) { + alpha_mem_len = fbi->var.xres * fbi->var.yres; + if ((!mxc_fbi->alpha_phy_addr0 && !mxc_fbi->alpha_phy_addr1) || + (alpha_mem_len > mxc_fbi->alpha_mem_len)) { + if (mxc_fbi->alpha_phy_addr0) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr0, + mxc_fbi->alpha_phy_addr0); + if (mxc_fbi->alpha_phy_addr1) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr1, + mxc_fbi->alpha_phy_addr1); + + mxc_fbi->alpha_virt_addr0 = + dma_alloc_coherent(fbi->device, + alpha_mem_len, + &mxc_fbi->alpha_phy_addr0, + GFP_DMA | GFP_KERNEL); + + mxc_fbi->alpha_virt_addr1 = + dma_alloc_coherent(fbi->device, + alpha_mem_len, + &mxc_fbi->alpha_phy_addr1, + GFP_DMA | GFP_KERNEL); + if (mxc_fbi->alpha_virt_addr0 == NULL || + mxc_fbi->alpha_virt_addr1 == NULL) { + dev_err(fbi->device, "mxcfb: dma alloc for" + " alpha buffer failed.\n"); + if (mxc_fbi->alpha_virt_addr0) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr0, + mxc_fbi->alpha_phy_addr0); + if (mxc_fbi->alpha_virt_addr1) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr1, + mxc_fbi->alpha_phy_addr1); + return -ENOMEM; + } + mxc_fbi->alpha_mem_len = alpha_mem_len; + } + } + + if (mxc_fbi->next_blank != FB_BLANK_UNBLANK) + return retval; + + if (!on_the_fly && mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->setup) { + retval = mxc_fbi->dispdrv->drv->setup(mxc_fbi->dispdrv, fbi); + if (retval < 0) { + dev_err(fbi->device, "setup error, dispdrv:%s.\n", + mxc_fbi->dispdrv->drv->name); + return -EINVAL; + } + } + + if (!on_the_fly) { + _setup_disp_channel1(fbi); + if (ovfbi_enable) + _setup_disp_channel1(mxc_fbi->ovfbi); + } + + if (!mxc_fbi->overlay && !on_the_fly) { + uint32_t out_pixel_fmt; + + memset(&sig_cfg, 0, sizeof(sig_cfg)); + if (fbi->var.vmode & FB_VMODE_INTERLACED) + sig_cfg.interlaced = true; + out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + if (fbi->var.vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */ + sig_cfg.odd_field_first = true; + if (mxc_fbi->ipu_int_clk) + sig_cfg.int_clk = true; + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = true; + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = true; + if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL)) + sig_cfg.clk_pol = true; + if (fbi->var.sync & FB_SYNC_DATA_INVERT) + sig_cfg.data_pol = true; + if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT)) + sig_cfg.enable_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN) + sig_cfg.clkidle_en = true; + + dev_dbg(fbi->device, "pixclock = %ul Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + if (ipu_init_sync_panel(mxc_fbi->ipu, mxc_fbi->ipu_di, + (PICOS2KHZ(fbi->var.pixclock)) * 1000UL, + fbi->var.xres, fbi->var.yres, + out_pixel_fmt, + fbi->var.left_margin, + fbi->var.hsync_len, + fbi->var.right_margin, + fbi->var.upper_margin, + fbi->var.vsync_len, + fbi->var.lower_margin, + 0, sig_cfg) != 0) { + dev_err(fbi->device, + "mxcfb: Error initializing panel.\n"); + return -EINVAL; + } + + fbi->mode = + (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + + ipu_disp_set_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, 0, 0); + } + + retval = _setup_disp_channel2(fbi); + if (retval) { + if (!on_the_fly) + ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + return retval; + } + + if (ovfbi_enable && !on_the_fly) { + if (ov_pos_ret >= 0) + ipu_disp_set_window_pos( + mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch, + ov_pos_x, ov_pos_y); + mxc_fbi_fg->on_the_fly = false; + retval = _setup_disp_channel2(mxc_fbi->ovfbi); + if (retval) { + ipu_uninit_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch); + ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + return retval; + } + } + + if (!on_the_fly) { + ipu_enable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + if (ovfbi_enable) + ipu_enable_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch); + } + + if (!on_the_fly && mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->enable) { + retval = mxc_fbi->dispdrv->drv->enable(mxc_fbi->dispdrv, fbi); + if (retval < 0) { + dev_err(fbi->device, "enable error, dispdrv:%s.\n", + mxc_fbi->dispdrv->drv->name); + return -EINVAL; + } + } + + mxc_fbi->cur_var = fbi->var; + mxc_fbi->cur_ipu_pfmt = on_the_fly ? mxc_fbi->final_pfmt : + fbi_to_pixfmt(fbi, false); + mxc_fbi->cur_fb_pfmt = fbi_to_pixfmt(fbi, true); + mxc_fbi->cur_prefetch = mxc_fbi->prefetch; + + return retval; +} + +static int _swap_channels(struct fb_info *fbi_from, + struct fb_info *fbi_to, bool both_on) +{ + int retval, tmp; + ipu_channel_t old_ch; + struct fb_info *ovfbi; + struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi_from->par; + struct mxcfb_info *mxc_fbi_to = (struct mxcfb_info *)fbi_to->par; + + if (both_on) { + ipu_disable_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch, true); + ipu_uninit_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch); + } + + /* switch the mxc fbi parameters */ + old_ch = mxc_fbi_from->ipu_ch; + mxc_fbi_from->ipu_ch = mxc_fbi_to->ipu_ch; + mxc_fbi_to->ipu_ch = old_ch; + tmp = mxc_fbi_from->ipu_ch_irq; + mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq; + mxc_fbi_to->ipu_ch_irq = tmp; + tmp = mxc_fbi_from->ipu_ch_nf_irq; + mxc_fbi_from->ipu_ch_nf_irq = mxc_fbi_to->ipu_ch_nf_irq; + mxc_fbi_to->ipu_ch_nf_irq = tmp; + ovfbi = mxc_fbi_from->ovfbi; + mxc_fbi_from->ovfbi = mxc_fbi_to->ovfbi; + mxc_fbi_to->ovfbi = ovfbi; + + _setup_disp_channel1(fbi_from); + retval = _setup_disp_channel2(fbi_from); + if (retval) + return retval; + + /* switch between dp and dc, disable old idmac, enable new idmac */ + retval = ipu_swap_channel(mxc_fbi_from->ipu, old_ch, mxc_fbi_from->ipu_ch); + ipu_uninit_channel(mxc_fbi_from->ipu, old_ch); + + if (both_on) { + _setup_disp_channel1(fbi_to); + retval = _setup_disp_channel2(fbi_to); + if (retval) + return retval; + ipu_enable_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch); + } + + return retval; +} + +static int swap_channels(struct fb_info *fbi_from) +{ + int i; + int swap_mode; + ipu_channel_t ch_to; + struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi_from->par; + struct fb_info *fbi_to = NULL; + struct mxcfb_info *mxc_fbi_to; + + /* what's the target channel? */ + if (mxc_fbi_from->ipu_ch == MEM_BG_SYNC) + ch_to = MEM_DC_SYNC; + else + ch_to = MEM_BG_SYNC; + + fbi_to = found_registered_fb(ch_to, mxc_fbi_from->ipu_id); + if (!fbi_to) + return -1; + mxc_fbi_to = (struct mxcfb_info *)fbi_to->par; + + ipu_clear_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq); + ipu_clear_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq); + ipu_free_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq, fbi_from); + ipu_free_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq, fbi_to); + ipu_clear_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq); + ipu_clear_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq); + ipu_free_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq, fbi_from); + ipu_free_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq, fbi_to); + + if (mxc_fbi_from->cur_blank == FB_BLANK_UNBLANK) { + if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK) + swap_mode = BOTH_ON; + else + swap_mode = SRC_ON; + } else { + if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK) + swap_mode = TGT_ON; + else + swap_mode = BOTH_OFF; + } + + switch (swap_mode) { + case BOTH_ON: + /* disable target->switch src->enable target */ + _swap_channels(fbi_from, fbi_to, true); + break; + case SRC_ON: + /* just switch src */ + _swap_channels(fbi_from, fbi_to, false); + break; + case TGT_ON: + /* just switch target */ + _swap_channels(fbi_to, fbi_from, false); + break; + case BOTH_OFF: + /* switch directly, no more need to do */ + mxc_fbi_to->ipu_ch = mxc_fbi_from->ipu_ch; + mxc_fbi_from->ipu_ch = ch_to; + i = mxc_fbi_from->ipu_ch_irq; + mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq; + mxc_fbi_to->ipu_ch_irq = i; + i = mxc_fbi_from->ipu_ch_nf_irq; + mxc_fbi_from->ipu_ch_nf_irq = mxc_fbi_to->ipu_ch_nf_irq; + mxc_fbi_to->ipu_ch_nf_irq = i; + break; + default: + break; + } + + if (ipu_request_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq, + mxcfb_irq_handler, IPU_IRQF_ONESHOT, + MXCFB_NAME, fbi_from) != 0) { + dev_err(fbi_from->device, "Error registering irq %d\n", + mxc_fbi_from->ipu_ch_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq); + if (ipu_request_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq, + mxcfb_irq_handler, IPU_IRQF_ONESHOT, + MXCFB_NAME, fbi_to) != 0) { + dev_err(fbi_to->device, "Error registering irq %d\n", + mxc_fbi_to->ipu_ch_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq); + if (ipu_request_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq, + mxcfb_nf_irq_handler, IPU_IRQF_ONESHOT, + MXCFB_NAME, fbi_from) != 0) { + dev_err(fbi_from->device, "Error registering irq %d\n", + mxc_fbi_from->ipu_ch_nf_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq); + if (ipu_request_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq, + mxcfb_nf_irq_handler, IPU_IRQF_ONESHOT, + MXCFB_NAME, fbi_to) != 0) { + dev_err(fbi_to->device, "Error registering irq %d\n", + mxc_fbi_to->ipu_ch_nf_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq); + + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 vtotal; + u32 htotal; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + struct fb_info tmp_fbi; + unsigned int fr_xoff, fr_yoff, fr_w, fr_h, line_length; + unsigned long base = 0; + int ret, bw = 0, bh = 0; + bool triple_buffer = false; + + if (var->xres == 0 || var->yres == 0) + return 0; + + /* fg should not bigger than bg */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct fb_info *fbi_tmp; + int bg_xres = 0, bg_yres = 0; + int16_t pos_x, pos_y; + + bg_xres = var->xres; + bg_yres = var->yres; + + fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); + if (!fbi_tmp) { + dev_err(info->device, + "cannot find background fb for overlay fb\n"); + return -EINVAL; + } + + bg_xres = fbi_tmp->var.xres; + bg_yres = fbi_tmp->var.yres; + + ipu_disp_get_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, &pos_x, &pos_y); + + if ((var->xres + pos_x) > bg_xres) + var->xres = bg_xres - pos_x; + if ((var->yres + pos_y) > bg_yres) + var->yres = bg_yres - pos_y; + + if (fbi_tmp->var.vmode & FB_VMODE_INTERLACED) + var->vmode |= FB_VMODE_INTERLACED; + else + var->vmode &= ~FB_VMODE_INTERLACED; + + var->pixclock = fbi_tmp->var.pixclock; + var->right_margin = fbi_tmp->var.right_margin; + var->hsync_len = fbi_tmp->var.hsync_len; + var->left_margin = fbi_tmp->var.left_margin + + fbi_tmp->var.xres - var->xres; + var->upper_margin = fbi_tmp->var.upper_margin; + var->vsync_len = fbi_tmp->var.vsync_len; + var->lower_margin = fbi_tmp->var.lower_margin + + fbi_tmp->var.yres - var->yres; + } + + if (var->rotate > IPU_ROTATE_VERT_FLIP) + var->rotate = IPU_ROTATE_NONE; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + + if (var->yres_virtual < var->yres) { + var->yres_virtual = var->yres * 3; + triple_buffer = true; + } + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16) && (var->bits_per_pixel != 12) && + (var->bits_per_pixel != 8)) + var->bits_per_pixel = 16; + + if (check_var_pixfmt(var)) { + /* Fall back to default */ + ret = bpp_to_var(var->bits_per_pixel, var); + if (ret < 0) + return ret; + } + + if (ipu_pixel_format_is_gpu_tile(var->nonstd)) { + fmt_to_tile_alignment(var->nonstd, &bw, &bh); + var->xres_virtual = ALIGN(var->xres_virtual, bw); + if (triple_buffer) + var->yres_virtual = 3 * ALIGN(var->yres, bh); + else + var->yres_virtual = ALIGN(var->yres_virtual, bh); + } + + line_length = var->xres_virtual * var->bits_per_pixel / 8; + fr_xoff = var->xoffset; + fr_w = var->xres_virtual; + if (!(var->vmode & FB_VMODE_YWRAP)) { + fr_yoff = var->yoffset % var->yres; + fr_h = var->yres; + base = line_length * var->yres * + (var->yoffset / var->yres); + if (ipu_pixel_format_is_split_gpu_tile(var->nonstd)) + base += (mxc_fbi->gpu_sec_buf_off - + line_length * var->yres / 2) * + (var->yoffset / var->yres); + } else { + fr_yoff = var->yoffset; + fr_h = var->yres_virtual; + } + + tmp_fbi.device = info->device; + tmp_fbi.var = *var; + tmp_fbi.par = mxc_fbi; + if (ipu_pixel_format_is_gpu_tile(var->nonstd)) { + unsigned int crop_line, prg_width = var->xres, offset; + int ipu_stride, prg_stride, bs; + bool tmp_prefetch = mxc_fbi->prefetch; + + if (!(var->xres % 32)) + bs = 32; + else if (!(var->xres % 16)) + bs = 16; + else + bs = 32; + + prg_width += fr_xoff % bw; + if (((fr_xoff % bw) + prg_width) % bs) + prg_width = ALIGN(prg_width, bs); + + mxc_fbi->prefetch = true; + ipu_stride = prg_width * + bytes_per_pixel(fbi_to_pixfmt(&tmp_fbi, false)); + + if (var->vmode & FB_VMODE_INTERLACED) { + if ((fr_yoff % bh) % 2) { + dev_err(info->device, + "wrong crop value in interlaced mode\n"); + return -EINVAL; + } + crop_line = (fr_yoff % bh) / 2; + prg_stride = ipu_stride * 2; + } else { + crop_line = fr_yoff % bh; + prg_stride = ipu_stride; + } + + offset = crop_line * prg_stride + + (fr_xoff % bw) * + bytes_per_pixel(fbi_to_pixfmt(&tmp_fbi, false)); + mxc_fbi->prefetch = tmp_prefetch; + if (offset % 8) { + dev_err(info->device, + "IPU base address is not 8byte aligned\n"); + return -EINVAL; + } + } else { + unsigned int uoff = 0, voff = 0; + int fb_stride; + + switch (fbi_to_pixfmt(&tmp_fbi, true)) { + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YVU420P: + case IPU_PIX_FMT_NV12: + case PRE_PIX_FMT_NV21: + case IPU_PIX_FMT_NV16: + case PRE_PIX_FMT_NV61: + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YUV444P: + fb_stride = var->xres_virtual; + break; + default: + fb_stride = line_length; + } + base += fr_yoff * fb_stride + + fr_xoff * var->bits_per_pixel / 8; + + ipu_get_channel_offset(fbi_to_pixfmt(&tmp_fbi, true), + var->xres, + fr_h, + fr_w, + 0, 0, + fr_yoff, + fr_xoff, + &uoff, + &voff); + if (base % 8 || uoff % 8 || voff % 8) { + dev_err(info->device, + "IPU base address is not 8byte aligned\n"); + return -EINVAL; + } + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + int __user *argp = (void __user *)arg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + switch (cmd) { + case MXCFB_SET_GBL_ALPHA: + { + struct mxcfb_gbl_alpha ga; + + if (copy_from_user(&ga, (void *)arg, sizeof(ga))) { + retval = -EFAULT; + break; + } + + if (ipu_disp_set_global_alpha(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + (bool)ga.enable, + ga.alpha)) { + retval = -EINVAL; + break; + } + + if (ga.enable) + mxc_fbi->alpha_chan_en = false; + + if (ga.enable) + dev_dbg(fbi->device, + "Set global alpha of %s to %d\n", + fbi->fix.id, ga.alpha); + break; + } + case MXCFB_SET_LOC_ALPHA: + { + struct mxcfb_loc_alpha la; + bool bad_pixfmt = + ipu_ch_param_bad_alpha_pos(fbi_to_pixfmt(fbi, true)); + + if (copy_from_user(&la, (void *)arg, sizeof(la))) { + retval = -EFAULT; + break; + } + + if (la.enable && !la.alpha_in_pixel) { + struct fb_info *fbi_tmp; + ipu_channel_t ipu_ch; + + if (bad_pixfmt) { + dev_err(fbi->device, "Bad pixel format " + "for graphics plane fb\n"); + retval = -EINVAL; + break; + } + + mxc_fbi->alpha_chan_en = true; + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + ipu_ch = MEM_BG_SYNC; + else if (mxc_fbi->ipu_ch == MEM_BG_SYNC) + ipu_ch = MEM_FG_SYNC; + else { + retval = -EINVAL; + break; + } + + fbi_tmp = found_registered_fb(ipu_ch, mxc_fbi->ipu_id); + if (fbi_tmp) + ((struct mxcfb_info *)(fbi_tmp->par))->alpha_chan_en = false; + } else + mxc_fbi->alpha_chan_en = false; + + if (ipu_disp_set_global_alpha(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + !(bool)la.enable, 0)) { + retval = -EINVAL; + break; + } + + fbi->var.activate = (fbi->var.activate & ~FB_ACTIVATE_MASK) | + FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; + mxcfb_set_par(fbi); + + la.alpha_phy_addr0 = mxc_fbi->alpha_phy_addr0; + la.alpha_phy_addr1 = mxc_fbi->alpha_phy_addr1; + if (copy_to_user((void *)arg, &la, sizeof(la))) { + retval = -EFAULT; + break; + } + + if (la.enable) + dev_dbg(fbi->device, + "Enable DP local alpha for %s\n", + fbi->fix.id); + break; + } + case MXCFB_SET_LOC_ALP_BUF: + { + unsigned long base; + uint32_t ipu_alp_ch_irq; + + if (!(((mxc_fbi->ipu_ch == MEM_FG_SYNC) || + (mxc_fbi->ipu_ch == MEM_BG_SYNC)) && + (mxc_fbi->alpha_chan_en))) { + dev_err(fbi->device, + "Should use background or overlay " + "framebuffer to set the alpha buffer " + "number\n"); + return -EINVAL; + } + + if (get_user(base, argp)) + return -EFAULT; + + if (base != mxc_fbi->alpha_phy_addr0 && + base != mxc_fbi->alpha_phy_addr1) { + dev_err(fbi->device, + "Wrong alpha buffer physical address " + "%lu\n", base); + return -EINVAL; + } + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF; + else + ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + + retval = wait_for_completion_timeout( + &mxc_fbi->alpha_flip_complete, HZ/2); + if (retval == 0) { + dev_err(fbi->device, "timeout when waiting for alpha flip irq\n"); + retval = -ETIMEDOUT; + break; + } + + mxc_fbi->cur_ipu_alpha_buf = + !mxc_fbi->cur_ipu_alpha_buf; + if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi-> + cur_ipu_alpha_buf, + base) == 0) { + ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_alpha_buf); + ipu_clear_irq(mxc_fbi->ipu, ipu_alp_ch_irq); + ipu_enable_irq(mxc_fbi->ipu, ipu_alp_ch_irq); + } else { + dev_err(fbi->device, + "Error updating %s SDC alpha buf %d " + "to address=0x%08lX\n", + fbi->fix.id, + mxc_fbi->cur_ipu_alpha_buf, base); + } + break; + } + case MXCFB_SET_CLR_KEY: + { + struct mxcfb_color_key key; + if (copy_from_user(&key, (void *)arg, sizeof(key))) { + retval = -EFAULT; + break; + } + retval = ipu_disp_set_color_key(mxc_fbi->ipu, mxc_fbi->ipu_ch, + key.enable, + key.color_key); + dev_dbg(fbi->device, "Set color key to 0x%08X\n", + key.color_key); + break; + } + case MXCFB_SET_GAMMA: + { + struct mxcfb_gamma gamma; + if (copy_from_user(&gamma, (void *)arg, sizeof(gamma))) { + retval = -EFAULT; + break; + } + retval = ipu_disp_set_gamma_correction(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + gamma.enable, + gamma.constk, + gamma.slopek); + break; + } + case MXCFB_SET_GPU_SPLIT_FMT: + { + struct mxcfb_gpu_split_fmt fmt; + + if (copy_from_user(&fmt, (void *)arg, sizeof(fmt))) { + retval = -EFAULT; + break; + } + + if (fmt.var.nonstd != IPU_PIX_FMT_GPU32_SB_ST && + fmt.var.nonstd != IPU_PIX_FMT_GPU32_SB_SRT && + fmt.var.nonstd != IPU_PIX_FMT_GPU16_SB_ST && + fmt.var.nonstd != IPU_PIX_FMT_GPU16_SB_SRT) { + retval = -EINVAL; + break; + } + + mxc_fbi->gpu_sec_buf_off = fmt.offset; + fmt.var.activate = (fbi->var.activate & ~FB_ACTIVATE_MASK) | + FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; + console_lock(); + retval = fb_set_var(fbi, &fmt.var); + if (!retval) + fbcon_update_vcs(fbi, fbi->var.activate & FB_ACTIVATE_ALL); + console_unlock(); + break; + } + case MXCFB_SET_PREFETCH: + { + int enable; + + if (copy_from_user(&enable, (void *)arg, sizeof(enable))) { + retval = -EFAULT; + break; + } + + if (!enable) { + if (ipu_pixel_format_is_gpu_tile(fbi_to_pixfmt(fbi, true))) { + dev_err(fbi->device, "Cannot disable prefetch in " + "resolving mode\n"); + retval = -EINVAL; + break; + } + if (ipu_pixel_format_is_pre_yuv(fbi_to_pixfmt(fbi, true))) { + dev_err(fbi->device, "Cannot disable prefetch when " + "PRE gets NV61 or NV21\n"); + retval = -EINVAL; + break; + } + } else { + if ((fbi->var.vmode & FB_VMODE_INTERLACED) && + ipu_pixel_format_is_multiplanar_yuv(fbi_to_pixfmt(fbi, true))) { + dev_err(fbi->device, "Cannot enable prefetch when " + "PRE gets multiplanar YUV frames\n"); + retval = -EINVAL; + break; + } + } + + retval = mxcfb_check_var(&fbi->var, fbi); + if (retval) + break; + + mxc_fbi->prefetch = !!enable; + + if (mxc_fbi->cur_prefetch == mxc_fbi->prefetch) + break; + + fbi->var.activate = (fbi->var.activate & ~FB_ACTIVATE_MASK) | + FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; + retval = mxcfb_set_par(fbi); + break; + } + case MXCFB_GET_PREFETCH: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->cur_prefetch, argp)) + return -EFAULT; + break; + } + case MXCFB_WAIT_FOR_VSYNC: + { + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + /* BG should poweron */ + struct mxcfb_info *bg_mxcfbi = NULL; + struct fb_info *fbi_tmp; + + fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); + if (fbi_tmp) + bg_mxcfbi = ((struct mxcfb_info *)(fbi_tmp->par)); + + if (!bg_mxcfbi) { + retval = -EINVAL; + break; + } + if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK) { + retval = -EINVAL; + break; + } + } + if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK) { + retval = -EINVAL; + break; + } + + init_completion(&mxc_fbi->vsync_complete); + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq); + ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq); + retval = wait_for_completion_interruptible_timeout( + &mxc_fbi->vsync_complete, 1 * HZ); + if (retval == 0) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: timeout %d\n", + retval); + retval = -ETIME; + } else if (retval > 0) { + retval = 0; + } + break; + } + case FBIO_ALLOC: + { + int size; + struct mxcfb_alloc_list *mem; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (mem == NULL) + return -ENOMEM; + + if (get_user(size, argp)) { + kfree(mem); + return -EFAULT; + } + + mem->size = PAGE_ALIGN(size); + + mem->cpu_addr = dma_alloc_coherent(fbi->device, size, + &mem->phy_addr, + GFP_KERNEL); + if (mem->cpu_addr == NULL) { + kfree(mem); + return -ENOMEM; + } + + list_add(&mem->list, &fb_alloc_list); + + if (put_user(mem->phy_addr, argp)) { + list_del(&mem->list); + dma_free_coherent(fbi->device, + mem->size, + mem->cpu_addr, + mem->phy_addr); + kfree(mem); + return -EFAULT; + } + + dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n", + mem->size, mem->phy_addr); + + break; + } + case FBIO_FREE: + { + unsigned long offset; + struct mxcfb_alloc_list *mem; + + if (get_user(offset, argp)) + return -EFAULT; + + retval = -EINVAL; + list_for_each_entry(mem, &fb_alloc_list, list) { + if (mem->phy_addr == offset) { + list_del(&mem->list); + dma_free_coherent(fbi->device, + mem->size, + mem->cpu_addr, + mem->phy_addr); + kfree(mem); + retval = 0; + break; + } + } + + break; + } + case MXCFB_SET_OVERLAY_POS: + { + struct mxcfb_pos pos; + struct fb_info *bg_fbi = NULL; + struct mxcfb_info *bg_mxcfbi = NULL; + + if (mxc_fbi->ipu_ch != MEM_FG_SYNC) { + dev_err(fbi->device, "Should use the overlay " + "framebuffer to set the position of " + "the overlay window\n"); + retval = -EINVAL; + break; + } + + if (copy_from_user(&pos, (void *)arg, sizeof(pos))) { + retval = -EFAULT; + break; + } + + bg_fbi = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); + if (bg_fbi) + bg_mxcfbi = ((struct mxcfb_info *)(bg_fbi->par)); + + if (bg_fbi == NULL) { + dev_err(fbi->device, "Cannot find the " + "background framebuffer\n"); + retval = -ENOENT; + break; + } + + /* if fb is unblank, check if the pos fit the display */ + if (mxc_fbi->cur_blank == FB_BLANK_UNBLANK) { + if (fbi->var.xres + pos.x > bg_fbi->var.xres) { + if (bg_fbi->var.xres < fbi->var.xres) + pos.x = 0; + else + pos.x = bg_fbi->var.xres - fbi->var.xres; + } + if (fbi->var.yres + pos.y > bg_fbi->var.yres) { + if (bg_fbi->var.yres < fbi->var.yres) + pos.y = 0; + else + pos.y = bg_fbi->var.yres - fbi->var.yres; + } + } + + retval = ipu_disp_set_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, + pos.x, pos.y); + + if (copy_to_user((void *)arg, &pos, sizeof(pos))) { + retval = -EFAULT; + break; + } + break; + } + case MXCFB_GET_FB_IPU_CHAN: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_ch, argp)) + return -EFAULT; + break; + } + case MXCFB_GET_DIFMT: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_di_pix_fmt, argp)) + return -EFAULT; + break; + } + case MXCFB_GET_FB_IPU_DI: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_di, argp)) + return -EFAULT; + break; + } + case MXCFB_GET_FB_BLANK: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->cur_blank, argp)) + return -EFAULT; + break; + } + case MXCFB_SET_DIFMT: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (get_user(mxc_fbi->ipu_di_pix_fmt, argp)) + return -EFAULT; + + break; + } + case MXCFB_CSC_UPDATE: + { + struct mxcfb_csc_matrix csc; + + if (copy_from_user(&csc, (void *) arg, sizeof(csc))) + return -EFAULT; + + if ((mxc_fbi->ipu_ch != MEM_FG_SYNC) && + (mxc_fbi->ipu_ch != MEM_BG_SYNC) && + (mxc_fbi->ipu_ch != MEM_BG_ASYNC0)) + return -EFAULT; + ipu_set_csc_coefficients(mxc_fbi->ipu, mxc_fbi->ipu_ch, + csc.param); + break; + } + default: + retval = -EINVAL; + } + return retval; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + int ret = 0; + + dev_dbg(info->device, "blank = %d\n", blank); + + if (blank) + blank = FB_BLANK_POWERDOWN; + + if (mxc_fbi->cur_blank == blank) + return 0; + + mxc_fbi->next_blank = blank; + + if (blank == FB_BLANK_UNBLANK) { + info->var.activate = (info->var.activate & ~FB_ACTIVATE_MASK) | + FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; + ret = fb_set_var(info, &info->var); + } else { + if (mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->disable) + mxc_fbi->dispdrv->drv->disable(mxc_fbi->dispdrv, info); + ipu_disable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, true); + if (mxc_fbi->ipu_di >= 0) + ipu_uninit_sync_panel(mxc_fbi->ipu, mxc_fbi->ipu_di); + ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + if (mxc_fbi->cur_prefetch) { + ipu_prg_disable(mxc_fbi->ipu_id, mxc_fbi->pre_num); + ipu_pre_disable(mxc_fbi->pre_num); + ipu_pre_free(&mxc_fbi->pre_num); + } + } + if (!ret) + mxc_fbi->cur_blank = blank; + return ret; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + * + * @param var Variable screen buffer information + * @param info Framebuffer information pointer + */ +static int +mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par, + *mxc_graphic_fbi = NULL; + u_int y_bottom; + unsigned int fr_xoff, fr_yoff, fr_w, fr_h; + unsigned long base, ipu_base = 0, active_alpha_phy_addr = 0; + bool loc_alpha_en = false; + int fb_stride; + int bw = 0, bh = 0; + int i; + int ret; + + /* no pan display during fb blank */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct mxcfb_info *bg_mxcfbi = NULL; + struct fb_info *fbi_tmp; + + fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); + if (fbi_tmp) + bg_mxcfbi = ((struct mxcfb_info *)(fbi_tmp->par)); + if (!bg_mxcfbi) + return -EINVAL; + if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK) + return -EINVAL; + } + if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK) + return -EINVAL; + + if (mxc_fbi->resolve) { + fmt_to_tile_block(info->var.nonstd, &bw, &bh); + + if (mxc_fbi->cur_var.xoffset % bw != var->xoffset % bw || + mxc_fbi->cur_var.yoffset % bh != var->yoffset % bh) { + dev_err(info->device, "do not support panning " + "with tile crop settings changed\n"); + return -EINVAL; + } + } + + y_bottom = var->yoffset; + + if (y_bottom > info->var.yres_virtual) + return -EINVAL; + + switch (fbi_to_pixfmt(info, true)) { + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YVU420P: + case IPU_PIX_FMT_NV12: + case PRE_PIX_FMT_NV21: + case IPU_PIX_FMT_NV16: + case PRE_PIX_FMT_NV61: + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YUV444P: + fb_stride = info->var.xres_virtual; + break; + default: + fb_stride = info->fix.line_length; + } + + base = info->fix.smem_start; + fr_xoff = var->xoffset; + fr_w = info->var.xres_virtual; + if (!(var->vmode & FB_VMODE_YWRAP)) { + dev_dbg(info->device, "Y wrap disabled\n"); + fr_yoff = var->yoffset % info->var.yres; + fr_h = info->var.yres; + base += info->fix.line_length * info->var.yres * + (var->yoffset / info->var.yres); + if (ipu_pixel_format_is_split_gpu_tile(var->nonstd)) + base += (mxc_fbi->gpu_sec_buf_off - + info->fix.line_length * info->var.yres / 2) * + (var->yoffset / info->var.yres); + } else { + dev_dbg(info->device, "Y wrap enabled\n"); + fr_yoff = var->yoffset; + fr_h = info->var.yres_virtual; + } + if (!mxc_fbi->resolve) { + base += fr_yoff * fb_stride + fr_xoff * + bytes_per_pixel(fbi_to_pixfmt(info, true)); + + if (mxc_fbi->cur_prefetch && (info->var.vmode & FB_VMODE_INTERLACED)) + base += info->var.rotate ? + fr_w * bytes_per_pixel(fbi_to_pixfmt(info, true)) : 0; + } + + if (mxc_fbi->cur_prefetch) { + unsigned long lock_flags = 0; + + if (ipu_pre_yres_is_small(info->var.yres)) + /* + * Update the PRE buffer address in the flip interrupt + * handler in this case to workaround the SoC design + * bug recorded by errata ERR009624. + */ + spin_lock_irqsave(&mxc_fbi->spin_lock, lock_flags); + + if (mxc_fbi->resolve) { + mxc_fbi->x_crop = fr_xoff & ~(bw - 1); + mxc_fbi->y_crop = fr_yoff & ~(bh - 1); + } else { + mxc_fbi->x_crop = 0; + mxc_fbi->y_crop = 0; + } + + ipu_get_channel_offset(fbi_to_pixfmt(info, true), + info->var.xres, + fr_h, + fr_w, + 0, 0, + fr_yoff, + fr_xoff, + &mxc_fbi->sec_buf_off, + &mxc_fbi->trd_buf_off); + if (mxc_fbi->resolve) + mxc_fbi->sec_buf_off = mxc_fbi->gpu_sec_buf_off; + + if (ipu_pre_yres_is_small(info->var.yres)) { + mxc_fbi->base = base; + spin_unlock_irqrestore(&mxc_fbi->spin_lock, lock_flags); + } + } else { + ipu_base = base; + } + + /* Check if DP local alpha is enabled and find the graphic fb */ + if (mxc_fbi->ipu_ch == MEM_BG_SYNC || mxc_fbi->ipu_ch == MEM_FG_SYNC) { + for (i = 0; i < num_registered_fb; i++) { + char bg_id[] = "DISP3 BG"; + char fg_id[] = "DISP3 FG"; + char *idstr = registered_fb[i]->fix.id; + bg_id[4] += mxc_fbi->ipu_id; + fg_id[4] += mxc_fbi->ipu_id; + if ((strcmp(idstr, bg_id) == 0 || + strcmp(idstr, fg_id) == 0) && + ((struct mxcfb_info *) + (registered_fb[i]->par))->alpha_chan_en) { + loc_alpha_en = true; + mxc_graphic_fbi = (struct mxcfb_info *) + (registered_fb[i]->par); + active_alpha_phy_addr = + mxc_fbi->cur_ipu_alpha_buf ? + mxc_graphic_fbi->alpha_phy_addr1 : + mxc_graphic_fbi->alpha_phy_addr0; + dev_dbg(info->device, "Updating SDC alpha " + "buf %d address=0x%08lX\n", + !mxc_fbi->cur_ipu_alpha_buf, + active_alpha_phy_addr); + break; + } + } + } + + if (!mxc_fbi->cur_prefetch || + (mxc_fbi->cur_prefetch && !ipu_pre_yres_is_small(info->var.yres))) { + ret = wait_for_completion_timeout(&mxc_fbi->flip_complete, + HZ/2); + if (ret == 0) { + dev_err(info->device, "timeout when waiting for flip " + "irq\n"); + return -ETIMEDOUT; + } + } + + if (!mxc_fbi->cur_prefetch) { + ++mxc_fbi->cur_ipu_buf; + mxc_fbi->cur_ipu_buf %= 3; + dev_dbg(info->device, "Updating SDC %s buf %d address=0x%08lX\n", + info->fix.id, mxc_fbi->cur_ipu_buf, base); + } + mxc_fbi->cur_ipu_alpha_buf = !mxc_fbi->cur_ipu_alpha_buf; + + if (mxc_fbi->cur_prefetch) + goto next; + + if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, ipu_base) == 0) { +next: + /* Update the DP local alpha buffer only for graphic plane */ + if (loc_alpha_en && mxc_graphic_fbi == mxc_fbi && + ipu_update_channel_buffer(mxc_graphic_fbi->ipu, mxc_graphic_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_alpha_buf, + active_alpha_phy_addr) == 0) { + ipu_select_buffer(mxc_graphic_fbi->ipu, mxc_graphic_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_alpha_buf); + } + + /* update u/v offset */ + if (!mxc_fbi->cur_prefetch) { + ipu_update_channel_offset(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, + fbi_to_pixfmt(info, true), + fr_w, + fr_h, + fr_w, + 0, 0, + fr_yoff, + fr_xoff); + + ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, mxc_fbi->cur_ipu_buf); + } else if (!ipu_pre_yres_is_small(info->var.yres)) { + ipu_pre_set_fb_buffer(mxc_fbi->pre_num, + mxc_fbi->resolve, + base, info->var.yres, + mxc_fbi->x_crop, + mxc_fbi->y_crop, + mxc_fbi->sec_buf_off, + mxc_fbi->trd_buf_off); + } + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + } else { + dev_err(info->device, + "Error updating SDC buf %d to address=0x%08lX, " + "current buf %d, buf0 ready %d, buf1 ready %d, " + "buf2 ready %d\n", mxc_fbi->cur_ipu_buf, base, + ipu_get_cur_buffer_idx(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER), + ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, 0), + ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, 1), + ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, 2)); + if (!mxc_fbi->cur_prefetch) { + ++mxc_fbi->cur_ipu_buf; + mxc_fbi->cur_ipu_buf %= 3; + ++mxc_fbi->cur_ipu_buf; + mxc_fbi->cur_ipu_buf %= 3; + } + mxc_fbi->cur_ipu_alpha_buf = !mxc_fbi->cur_ipu_alpha_buf; + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + return -EBUSY; + } + + if (mxc_fbi->cur_prefetch && ipu_pre_yres_is_small(info->var.yres)) { + ret = wait_for_completion_timeout(&mxc_fbi->flip_complete, + HZ/2); + if (ret == 0) { + dev_err(info->device, "timeout when waiting for flip " + "irq\n"); + return -ETIMEDOUT; + } + } + + dev_dbg(info->device, "Update complete\n"); + + info->var.yoffset = var->yoffset; + mxc_fbi->cur_var.xoffset = var->xoffset; + mxc_fbi->cur_var.yoffset = var->yoffset; + + return 0; +} + +/* + * Function to handle custom mmap for MXC framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param vma Pointer to vm_area_struct + */ +static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) +{ + bool found = false; + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + struct mxcfb_alloc_list *mem; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if (offset < fbi->fix.smem_len) { + /* mapping framebuffer memory */ + len = fbi->fix.smem_len - offset; + vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT; + } else if ((vma->vm_pgoff == + (mxc_fbi->alpha_phy_addr0 >> PAGE_SHIFT)) || + (vma->vm_pgoff == + (mxc_fbi->alpha_phy_addr1 >> PAGE_SHIFT))) { + len = mxc_fbi->alpha_mem_len; + } else { + list_for_each_entry(mem, &fb_alloc_list, list) { + if (offset == mem->phy_addr) { + found = true; + len = mem->size; + break; + } + } + if (!found) + return -EINVAL; + } + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) + return -EINVAL; + + /* make buffers bufferable */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + vma->vm_flags |= VM_IO; + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(fbi->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + } + + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl, + .fb_mmap = mxcfb_mmap, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + if (mxc_fbi->pre_config) { + ipu_pre_set_ctrl(mxc_fbi->pre_num, mxc_fbi->pre_config); + mxc_fbi->pre_config = NULL; + complete(&mxc_fbi->otf_complete); + return IRQ_HANDLED; + } + + if (mxc_fbi->cur_prefetch && ipu_pre_yres_is_small(fbi->var.yres)) { + spin_lock(&mxc_fbi->spin_lock); + ipu_pre_set_fb_buffer(mxc_fbi->pre_num, + mxc_fbi->resolve, + mxc_fbi->base, fbi->var.yres, + mxc_fbi->x_crop, mxc_fbi->y_crop, + mxc_fbi->sec_buf_off, + mxc_fbi->trd_buf_off); + spin_unlock(&mxc_fbi->spin_lock); + } + + complete(&mxc_fbi->flip_complete); + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_nf_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + complete(&mxc_fbi->vsync_complete); + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_alpha_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + complete(&mxc_fbi->alpha_flip_complete); + return IRQ_HANDLED; +} + +/* + * Suspends the framebuffer and blanks the screen. Power management support + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + int saved_blank; +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + void *fbmem; +#endif + + if (mxc_fbi->ovfbi) { + struct mxcfb_info *mxc_fbi_fg = + (struct mxcfb_info *)mxc_fbi->ovfbi->par; + + console_lock(); + fb_set_suspend(mxc_fbi->ovfbi, 1); + saved_blank = mxc_fbi_fg->cur_blank; + mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi); + mxc_fbi_fg->next_blank = saved_blank; + console_unlock(); + } + + console_lock(); + fb_set_suspend(fbi, 1); + saved_blank = mxc_fbi->cur_blank; + mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + mxc_fbi->next_blank = saved_blank; + console_unlock(); + + return 0; +} + +/* + * Resumes the framebuffer and unblanks the screen. Power management support + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + console_lock(); + mxcfb_blank(mxc_fbi->next_blank, fbi); + fb_set_suspend(fbi, 0); + console_unlock(); + + if (mxc_fbi->ovfbi) { + struct mxcfb_info *mxc_fbi_fg = + (struct mxcfb_info *)mxc_fbi->ovfbi->par; + console_lock(); + mxcfb_blank(mxc_fbi_fg->next_blank, mxc_fbi->ovfbi); + fb_set_suspend(mxc_fbi->ovfbi, 0); + console_unlock(); + } + + return 0; +} + +/* + * Main framebuffer functions + */ + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if (fbi->fix.smem_len < fbi->var.yres_virtual * fbi->fix.line_length) + fbi->fix.smem_len = fbi->var.yres_virtual * + fbi->fix.line_length; + + if (mxc_fbi->resolve && mxc_fbi->gpu_sec_buf_off) { + if (fbi->var.vmode & FB_VMODE_YWRAP) + fbi->fix.smem_len = mxc_fbi->gpu_sec_buf_off + + fbi->fix.smem_len / 2; + else + fbi->fix.smem_len = mxc_fbi->gpu_sec_buf_off * + (fbi->var.yres_virtual / fbi->var.yres) + + fbi->fix.smem_len / 2; + } + + fbi->screen_base = dma_alloc_wc(fbi->device, + fbi->fix.smem_len, + (dma_addr_t *)&fbi->fix.smem_start, + GFP_DMA | GFP_KERNEL); + if (fbi->screen_base == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + return -EBUSY; + } + + dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n", + (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_size = fbi->fix.smem_len; + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ + dma_free_wc(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + fbi->screen_base = 0; + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + struct ipuv3_fb_platform_data *plat_data = dev->platform_data; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + fbi->var.activate = FB_ACTIVATE_NOW; + + bpp_to_var(plat_data->default_bpp, &fbi->var); + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +static ssize_t show_disp_chan(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par; + + if (mxcfbi->ipu_ch == MEM_BG_SYNC) + return sprintf(buf, "2-layer-fb-bg\n"); + else if (mxcfbi->ipu_ch == MEM_FG_SYNC) + return sprintf(buf, "2-layer-fb-fg\n"); + else if (mxcfbi->ipu_ch == MEM_DC_SYNC) + return sprintf(buf, "1-layer-fb\n"); + else + return sprintf(buf, "err: no display chan\n"); +} + +static ssize_t swap_disp_chan(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par; + struct mxcfb_info *fg_mxcfbi = NULL; + + console_lock(); + /* swap only happen between DP-BG and DC, while DP-FG disable */ + if (((mxcfbi->ipu_ch == MEM_BG_SYNC) && + (strstr(buf, "1-layer-fb") != NULL)) || + ((mxcfbi->ipu_ch == MEM_DC_SYNC) && + (strstr(buf, "2-layer-fb-bg") != NULL))) { + struct fb_info *fbi_fg; + + fbi_fg = found_registered_fb(MEM_FG_SYNC, mxcfbi->ipu_id); + if (fbi_fg) + fg_mxcfbi = (struct mxcfb_info *)fbi_fg->par; + + if (!fg_mxcfbi || + fg_mxcfbi->cur_blank == FB_BLANK_UNBLANK) { + dev_err(dev, + "Can not switch while fb2(fb-fg) is on.\n"); + console_unlock(); + return count; + } + + if (swap_channels(info) < 0) + dev_err(dev, "Swap display channel failed.\n"); + } + + console_unlock(); + return count; +} +static DEVICE_ATTR(fsl_disp_property, S_IWUSR | S_IRUGO, + show_disp_chan, swap_disp_chan); + +static ssize_t show_disp_dev(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par; + + if (mxcfbi->ipu_ch == MEM_FG_SYNC) + return sprintf(buf, "overlay\n"); + else + return sprintf(buf, "%s\n", mxcfbi->dispdrv->drv->name); +} +static DEVICE_ATTR(fsl_disp_dev_property, S_IRUGO, show_disp_dev, NULL); + +static int mxcfb_get_crtc(struct device *dev, struct mxcfb_info *mxcfbi, + enum crtc crtc) +{ + int i = 0; + + for (; i < ARRAY_SIZE(ipu_di_crtc_maps); i++) + if (ipu_di_crtc_maps[i].crtc == crtc) { + mxcfbi->ipu_id = ipu_di_crtc_maps[i].ipu_id; + mxcfbi->ipu_di = ipu_di_crtc_maps[i].ipu_di; + return 0; + } + + dev_err(dev, "failed to get valid crtc\n"); + return -EINVAL; +} + +static int mxcfb_dispdrv_init(struct platform_device *pdev, + struct fb_info *fbi) +{ + struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data; + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + struct mxc_dispdrv_setting setting; + char disp_dev[32], *default_dev = "lcd"; + int ret = 0; + + setting.if_fmt = plat_data->interface_pix_fmt; + setting.dft_mode_str = plat_data->mode_str; + setting.default_bpp = plat_data->default_bpp; + if (!setting.default_bpp) + setting.default_bpp = 16; + setting.fbi = fbi; + if (!strlen(plat_data->disp_dev)) { + memcpy(disp_dev, default_dev, strlen(default_dev)); + disp_dev[strlen(default_dev)] = '\0'; + } else { + memcpy(disp_dev, plat_data->disp_dev, + strlen(plat_data->disp_dev)); + disp_dev[strlen(plat_data->disp_dev)] = '\0'; + } + + mxcfbi->dispdrv = mxc_dispdrv_gethandle(disp_dev, &setting); + if (IS_ERR(mxcfbi->dispdrv)) { + ret = PTR_ERR(mxcfbi->dispdrv); + dev_err(&pdev->dev, "NO mxc display driver found!\n"); + return ret; + } else { + /* fix-up */ + mxcfbi->ipu_di_pix_fmt = setting.if_fmt; + mxcfbi->default_bpp = setting.default_bpp; + + ret = mxcfb_get_crtc(&pdev->dev, mxcfbi, setting.crtc); + if (ret) + return ret; + + dev_dbg(&pdev->dev, "di_pixfmt:0x%x, bpp:0x%x, di:%d, ipu:%d\n", + setting.if_fmt, setting.default_bpp, + mxcfbi->ipu_di, mxcfbi->ipu_id); + } + + dev_info(&pdev->dev, "registered mxc display driver %s\n", disp_dev); + + return ret; +} + +/* + * Parse user specified options (`video=trident:') + * example: + * video=mxcfb0:dev=lcd,800x480M-16@55,if=RGB565,bpp=16,noaccel + * video=mxcfb0:dev=lcd,800x480M-16@55,if=RGB565,fbpix=RGB565 + */ +static int mxcfb_option_setup(struct platform_device *pdev, struct fb_info *fbi) +{ + struct ipuv3_fb_platform_data *pdata = pdev->dev.platform_data; + char *options, *opt, *fb_mode_str = NULL; + char name[] = "mxcfb0"; + uint32_t fb_pix_fmt = 0; + + name[5] += pdev->id; + if (fb_get_options(name, &options)) { + dev_err(&pdev->dev, "Can't get fb option for %s!\n", name); + return -ENODEV; + } + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "dev=", 4)) { + memcpy(pdata->disp_dev, opt + 4, strlen(opt) - 4); + pdata->disp_dev[strlen(opt) - 4] = '\0'; + } else if (!strncmp(opt, "if=", 3)) { + if (!strncmp(opt+3, "RGB24", 5)) + pdata->interface_pix_fmt = IPU_PIX_FMT_RGB24; + else if (!strncmp(opt+3, "BGR24", 5)) + pdata->interface_pix_fmt = IPU_PIX_FMT_BGR24; + else if (!strncmp(opt+3, "GBR24", 5)) + pdata->interface_pix_fmt = IPU_PIX_FMT_GBR24; + else if (!strncmp(opt+3, "RGB565", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_RGB565; + else if (!strncmp(opt+3, "RGB666", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_RGB666; + else if (!strncmp(opt+3, "YUV444", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_YUV444; + else if (!strncmp(opt+3, "LVDS666", 7)) + pdata->interface_pix_fmt = IPU_PIX_FMT_LVDS666; + else if (!strncmp(opt+3, "YUYV16", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_YUYV; + else if (!strncmp(opt+3, "UYVY16", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_UYVY; + else if (!strncmp(opt+3, "YVYU16", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_YVYU; + else if (!strncmp(opt+3, "VYUY16", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_VYUY; + } else if (!strncmp(opt, "fbpix=", 6)) { + if (!strncmp(opt+6, "RGB24", 5)) + fb_pix_fmt = IPU_PIX_FMT_RGB24; + else if (!strncmp(opt+6, "BGR24", 5)) + fb_pix_fmt = IPU_PIX_FMT_BGR24; + else if (!strncmp(opt+6, "RGB32", 5)) + fb_pix_fmt = IPU_PIX_FMT_RGB32; + else if (!strncmp(opt+6, "BGR32", 5)) + fb_pix_fmt = IPU_PIX_FMT_BGR32; + else if (!strncmp(opt+6, "ABGR32", 6)) + fb_pix_fmt = IPU_PIX_FMT_ABGR32; + else if (!strncmp(opt+6, "RGB565", 6)) + fb_pix_fmt = IPU_PIX_FMT_RGB565; + else if (!strncmp(opt+6, "BGRA4444", 8)) + fb_pix_fmt = IPU_PIX_FMT_BGRA4444; + else if (!strncmp(opt+6, "BGRA5551", 8)) + fb_pix_fmt = IPU_PIX_FMT_BGRA5551; + + if (fb_pix_fmt) { + pixfmt_to_var(fb_pix_fmt, &fbi->var); + pdata->default_bpp = + fbi->var.bits_per_pixel; + } + } else if (!strncmp(opt, "int_clk", 7)) { + pdata->int_clk = true; + continue; + } else if (!strncmp(opt, "bpp=", 4)) { + /* bpp setting cannot overwirte fbpix setting */ + if (fb_pix_fmt) + continue; + + pdata->default_bpp = + simple_strtoul(opt + 4, NULL, 0); + + fb_pix_fmt = bpp_to_pixfmt(pdata->default_bpp); + if (fb_pix_fmt) + pixfmt_to_var(fb_pix_fmt, &fbi->var); + } else + fb_mode_str = opt; + } + + if (fb_mode_str) + pdata->mode_str = fb_mode_str; + + return 0; +} + +static int mxcfb_register(struct fb_info *fbi) +{ + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + struct fb_videomode m; + int ret = 0; + char bg0_id[] = "DISP3 BG"; + char bg1_id[] = "DISP3 BG - DI1"; + char fg_id[] = "DISP3 FG"; + + if (mxcfbi->ipu_di == 0) { + bg0_id[4] += mxcfbi->ipu_id; + strcpy(fbi->fix.id, bg0_id); + } else if (mxcfbi->ipu_di == 1) { + bg1_id[4] += mxcfbi->ipu_id; + strcpy(fbi->fix.id, bg1_id); + } else { /* Overlay */ + fg_id[4] += mxcfbi->ipu_id; + strcpy(fbi->fix.id, fg_id); + } + + mxcfb_check_var(&fbi->var, fbi); + + mxcfb_set_fix(fbi); + + /* Added first mode to fbi modelist. */ + if (!fbi->modelist.next || !fbi->modelist.prev) + INIT_LIST_HEAD(&fbi->modelist); + fb_var_to_videomode(&m, &fbi->var); + fb_add_videomode(&m, &fbi->modelist); + + if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, + mxcfb_irq_handler, IPU_IRQF_ONESHOT, MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering EOF irq handler.\n"); + ret = -EBUSY; + goto err0; + } + ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq); + if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, + mxcfb_nf_irq_handler, IPU_IRQF_ONESHOT, MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering NFACK irq handler.\n"); + ret = -EBUSY; + goto err1; + } + ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq); + + if (mxcfbi->ipu_alp_ch_irq != -1) + if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, + mxcfb_alpha_irq_handler, IPU_IRQF_ONESHOT, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering alpha irq " + "handler.\n"); + ret = -EBUSY; + goto err2; + } + + if (!mxcfbi->late_init) { + fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + ret = fb_set_var(fbi, &fbi->var); + if (!ret) + fbcon_update_vcs(fbi, fbi->var.activate & FB_ACTIVATE_ALL); + console_unlock(); + if (ret < 0) { + dev_err(fbi->device, "Error fb_set_var ret:%d\n", ret); + goto err3; + } + + if (mxcfbi->next_blank == FB_BLANK_UNBLANK) { + console_lock(); + ret = fb_blank(fbi, FB_BLANK_UNBLANK); + console_unlock(); + if (ret < 0) { + dev_err(fbi->device, + "Error fb_blank ret:%d\n", ret); + goto err4; + } + } + } else { + /* + * Setup the channel again though bootloader + * has done this, then set_par() can stop the + * channel neatly and re-initialize it . + */ + if (mxcfbi->next_blank == FB_BLANK_UNBLANK) { + console_lock(); + _setup_disp_channel1(fbi); + ipu_enable_channel(mxcfbi->ipu, mxcfbi->ipu_ch); + console_unlock(); + } + } + + + ret = register_framebuffer(fbi); + if (ret < 0) + goto err5; + + return ret; +err5: + if (mxcfbi->next_blank == FB_BLANK_UNBLANK) { + console_lock(); + if (!mxcfbi->late_init) + fb_blank(fbi, FB_BLANK_POWERDOWN); + else { + ipu_disable_channel(mxcfbi->ipu, mxcfbi->ipu_ch, + true); + ipu_uninit_channel(mxcfbi->ipu, mxcfbi->ipu_ch); + } + console_unlock(); + } +err4: +err3: + if (mxcfbi->ipu_alp_ch_irq != -1) + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi); +err2: + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, fbi); +err1: + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi); +err0: + return ret; +} + +static void mxcfb_unregister(struct fb_info *fbi) +{ + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + + if (mxcfbi->ipu_alp_ch_irq != -1) + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi); + if (mxcfbi->ipu_ch_irq) + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi); + if (mxcfbi->ipu_ch_nf_irq) + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, fbi); + + unregister_framebuffer(fbi); +} + +static int mxcfb_setup_overlay(struct platform_device *pdev, + struct fb_info *fbi_bg, struct resource *res) +{ + struct fb_info *ovfbi; + struct mxcfb_info *mxcfbi_bg = (struct mxcfb_info *)fbi_bg->par; + struct mxcfb_info *mxcfbi_fg; + int ret = 0; + + ovfbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!ovfbi) { + ret = -ENOMEM; + goto init_ovfbinfo_failed; + } + mxcfbi_fg = (struct mxcfb_info *)ovfbi->par; + + mxcfbi_fg->ipu = ipu_get_soc(mxcfbi_bg->ipu_id); + if (IS_ERR(mxcfbi_fg->ipu)) { + ret = -ENODEV; + goto get_ipu_failed; + } + mxcfbi_fg->ipu_id = mxcfbi_bg->ipu_id; + mxcfbi_fg->ipu_ch_irq = IPU_IRQ_FG_SYNC_EOF; + mxcfbi_fg->ipu_ch_nf_irq = IPU_IRQ_FG_SYNC_NFACK; + mxcfbi_fg->ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF; + mxcfbi_fg->ipu_ch = MEM_FG_SYNC; + mxcfbi_fg->ipu_di = -1; + mxcfbi_fg->ipu_di_pix_fmt = mxcfbi_bg->ipu_di_pix_fmt; + mxcfbi_fg->overlay = true; + mxcfbi_fg->cur_blank = mxcfbi_fg->next_blank = FB_BLANK_POWERDOWN; + mxcfbi_fg->prefetch = false; + mxcfbi_fg->resolve = false; + mxcfbi_fg->pre_num = -1; + + /* Need dummy values until real panel is configured */ + ovfbi->var.xres = 240; + ovfbi->var.yres = 320; + + if (res && res->start && res->end) { + ovfbi->fix.smem_len = res->end - res->start + 1; + ovfbi->fix.smem_start = res->start; + ovfbi->screen_base = ioremap( + ovfbi->fix.smem_start, + ovfbi->fix.smem_len); + } + + ret = mxcfb_register(ovfbi); + if (ret < 0) + goto register_ov_failed; + + mxcfbi_bg->ovfbi = ovfbi; + + return ret; + +register_ov_failed: +get_ipu_failed: + fb_dealloc_cmap(&ovfbi->cmap); + framebuffer_release(ovfbi); +init_ovfbinfo_failed: + return ret; +} + +static void mxcfb_unsetup_overlay(struct fb_info *fbi_bg) +{ + struct mxcfb_info *mxcfbi_bg = (struct mxcfb_info *)fbi_bg->par; + struct fb_info *ovfbi = mxcfbi_bg->ovfbi; + + mxcfb_unregister(ovfbi); + + if (&ovfbi->cmap) + fb_dealloc_cmap(&ovfbi->cmap); + framebuffer_release(ovfbi); +} + +static bool ipu_usage[2][2]; +static int ipu_test_set_usage(int ipu, int di) +{ + if (ipu_usage[ipu][di]) + return -EBUSY; + else + ipu_usage[ipu][di] = true; + return 0; +} + +static void ipu_clear_usage(int ipu, int di) +{ + ipu_usage[ipu][di] = false; +} + +static int mxcfb_get_of_property(struct platform_device *pdev, + struct ipuv3_fb_platform_data *plat_data) +{ + struct device_node *np = pdev->dev.of_node; + const char *disp_dev; + const char *mode_str = NULL; + const char *pixfmt; + int err; + int len; + u32 bpp, int_clk; + u32 late_init; + + err = of_property_read_string(np, "disp_dev", &disp_dev); + if (err < 0) { + dev_dbg(&pdev->dev, "get of property disp_dev fail\n"); + return err; + } + err = of_property_read_string(np, "mode_str", &mode_str); + if (err < 0) + dev_dbg(&pdev->dev, "get of property mode_str fail\n"); + err = of_property_read_string(np, "interface_pix_fmt", &pixfmt); + if (err) { + dev_dbg(&pdev->dev, "get of property pix fmt fail\n"); + return err; + } + err = of_property_read_u32(np, "default_bpp", &bpp); + if (err) { + dev_dbg(&pdev->dev, "get of property bpp fail\n"); + return err; + } + err = of_property_read_u32(np, "int_clk", &int_clk); + if (err) { + dev_dbg(&pdev->dev, "get of property int_clk fail\n"); + return err; + } + err = of_property_read_u32(np, "late_init", &late_init); + if (err) { + dev_dbg(&pdev->dev, "get of property late_init fail\n"); + return err; + } + + plat_data->prefetch = of_property_read_bool(np, "prefetch"); + + if (!strncmp(pixfmt, "RGB24", 5)) + plat_data->interface_pix_fmt = IPU_PIX_FMT_RGB24; + else if (!strncmp(pixfmt, "BGR24", 5)) + plat_data->interface_pix_fmt = IPU_PIX_FMT_BGR24; + else if (!strncmp(pixfmt, "GBR24", 5)) + plat_data->interface_pix_fmt = IPU_PIX_FMT_GBR24; + else if (!strncmp(pixfmt, "RGB565", 6)) + plat_data->interface_pix_fmt = IPU_PIX_FMT_RGB565; + else if (!strncmp(pixfmt, "RGB666", 6)) + plat_data->interface_pix_fmt = IPU_PIX_FMT_RGB666; + else if (!strncmp(pixfmt, "YUV444", 6)) + plat_data->interface_pix_fmt = IPU_PIX_FMT_YUV444; + else if (!strncmp(pixfmt, "LVDS666", 7)) + plat_data->interface_pix_fmt = IPU_PIX_FMT_LVDS666; + else if (!strncmp(pixfmt, "YUYV16", 6)) + plat_data->interface_pix_fmt = IPU_PIX_FMT_YUYV; + else if (!strncmp(pixfmt, "UYVY16", 6)) + plat_data->interface_pix_fmt = IPU_PIX_FMT_UYVY; + else if (!strncmp(pixfmt, "YVYU16", 6)) + plat_data->interface_pix_fmt = IPU_PIX_FMT_YVYU; + else if (!strncmp(pixfmt, "VYUY16", 6)) + plat_data->interface_pix_fmt = IPU_PIX_FMT_VYUY; + else { + dev_err(&pdev->dev, "err interface_pix_fmt!\n"); + return -ENOENT; + } + + len = min(sizeof(plat_data->disp_dev) - 1, strlen(disp_dev)); + memcpy(plat_data->disp_dev, disp_dev, len); + plat_data->disp_dev[len] = '\0'; + plat_data->mode_str = (char *)mode_str; + plat_data->default_bpp = bpp; + plat_data->int_clk = (bool)int_clk; + plat_data->late_init = (bool)late_init; + return err; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + struct ipuv3_fb_platform_data *plat_data; + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + struct resource *res; + int ret = 0; + + dev_dbg(&pdev->dev, "%s enter\n", __func__); + pdev->id = of_alias_get_id(pdev->dev.of_node, "mxcfb"); + if (pdev->id < 0) { + dev_err(&pdev->dev, "can not get alias id\n"); + return pdev->id; + } + + plat_data = devm_kzalloc(&pdev->dev, sizeof(struct + ipuv3_fb_platform_data), GFP_KERNEL); + if (!plat_data) + return -ENOMEM; + pdev->dev.platform_data = plat_data; + + ret = mxcfb_get_of_property(pdev, plat_data); + if (ret < 0) { + dev_err(&pdev->dev, "get mxcfb of property fail\n"); + return ret; + } + + /* Initialize FB structures */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto init_fbinfo_failed; + } + + ret = mxcfb_option_setup(pdev, fbi); + if (ret) + goto get_fb_option_failed; + + mxcfbi = (struct mxcfb_info *)fbi->par; + mxcfbi->ipu_int_clk = plat_data->int_clk; + mxcfbi->late_init = plat_data->late_init; + mxcfbi->first_set_par = true; + mxcfbi->prefetch = plat_data->prefetch; + mxcfbi->pre_num = -1; + spin_lock_init(&mxcfbi->spin_lock); + + ret = mxcfb_dispdrv_init(pdev, fbi); + if (ret < 0) + goto init_dispdrv_failed; + + ret = ipu_test_set_usage(mxcfbi->ipu_id, mxcfbi->ipu_di); + if (ret < 0) { + dev_err(&pdev->dev, "ipu%d-di%d already in use\n", + mxcfbi->ipu_id, mxcfbi->ipu_di); + goto ipu_in_busy; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res && res->start && res->end) { + fbi->fix.smem_len = res->end - res->start + 1; + fbi->fix.smem_start = res->start; + fbi->screen_base = ioremap(fbi->fix.smem_start, fbi->fix.smem_len); + /* Do not clear the fb content drawn in bootloader. */ + if (!mxcfbi->late_init) + memset(fbi->screen_base, 0, fbi->fix.smem_len); + } + + mxcfbi->ipu = ipu_get_soc(mxcfbi->ipu_id); + if (IS_ERR(mxcfbi->ipu)) { + ret = -ENODEV; + goto get_ipu_failed; + } + + /* first user uses DP with alpha feature */ + if (!g_dp_in_use[mxcfbi->ipu_id]) { + mxcfbi->ipu_ch_irq = IPU_IRQ_BG_SYNC_EOF; + mxcfbi->ipu_ch_nf_irq = IPU_IRQ_BG_SYNC_NFACK; + mxcfbi->ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + mxcfbi->ipu_ch = MEM_BG_SYNC; + /* Unblank the primary fb only by default */ + if (pdev->id == 0) + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_UNBLANK; + else + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN; + + ret = mxcfb_register(fbi); + if (ret < 0) + goto mxcfb_register_failed; + + ipu_disp_set_global_alpha(mxcfbi->ipu, mxcfbi->ipu_ch, + true, 0x80); + ipu_disp_set_color_key(mxcfbi->ipu, mxcfbi->ipu_ch, false, 0); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + ret = mxcfb_setup_overlay(pdev, fbi, res); + + if (ret < 0) { + mxcfb_unregister(fbi); + goto mxcfb_setupoverlay_failed; + } + + g_dp_in_use[mxcfbi->ipu_id] = true; + + ret = device_create_file(mxcfbi->ovfbi->dev, + &dev_attr_fsl_disp_property); + if (ret) + dev_err(mxcfbi->ovfbi->dev, "Error %d on creating " + "file for disp property\n", + ret); + + ret = device_create_file(mxcfbi->ovfbi->dev, + &dev_attr_fsl_disp_dev_property); + if (ret) + dev_err(mxcfbi->ovfbi->dev, "Error %d on creating " + "file for disp device " + "propety\n", ret); + } else { + mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF; + mxcfbi->ipu_ch_nf_irq = IPU_IRQ_DC_SYNC_NFACK; + mxcfbi->ipu_alp_ch_irq = -1; + mxcfbi->ipu_ch = MEM_DC_SYNC; + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN; + + ret = mxcfb_register(fbi); + if (ret < 0) + goto mxcfb_register_failed; + } + + platform_set_drvdata(pdev, fbi); + + ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_property); + if (ret) + dev_err(&pdev->dev, "Error %d on creating file for disp " + "property\n", ret); + + ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_dev_property); + if (ret) + dev_err(&pdev->dev, "Error %d on creating file for disp " + " device propety\n", ret); + + return 0; + +mxcfb_setupoverlay_failed: +mxcfb_register_failed: +get_ipu_failed: + ipu_clear_usage(mxcfbi->ipu_id, mxcfbi->ipu_di); +ipu_in_busy: +init_dispdrv_failed: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); +get_fb_option_failed: +init_fbinfo_failed: + return ret; +} + +static int mxcfb_remove(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = fbi->par; + + device_remove_file(fbi->dev, &dev_attr_fsl_disp_dev_property); + device_remove_file(fbi->dev, &dev_attr_fsl_disp_property); + mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + mxcfb_unregister(fbi); + mxcfb_unmap_video_memory(fbi); + + if (mxc_fbi->ovfbi) { + device_remove_file(mxc_fbi->ovfbi->dev, + &dev_attr_fsl_disp_dev_property); + device_remove_file(mxc_fbi->ovfbi->dev, + &dev_attr_fsl_disp_property); + mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi); + mxcfb_unsetup_overlay(fbi); + mxcfb_unmap_video_memory(mxc_fbi->ovfbi); + } + + ipu_clear_usage(mxc_fbi->ipu_id, mxc_fbi->ipu_di); + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + return 0; +} + +static const struct of_device_id imx_mxcfb_dt_ids[] = { + { .compatible = "fsl,mxc_sdc_fb"}, + { /* sentinel */ } +}; + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + .of_match_table = imx_mxcfb_dt_ids, + }, + .probe = mxcfb_probe, + .remove = mxcfb_remove, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +int __init mxcfb_init(void) +{ + return platform_driver_register(&mxcfb_driver); +} + +void mxcfb_exit(void) +{ + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/fbdev/mxc/mxc_lcdif.c b/drivers/video/fbdev/mxc/mxc_lcdif.c new file mode 100644 index 000000000000..83ef06322b11 --- /dev/null +++ b/drivers/video/fbdev/mxc/mxc_lcdif.c @@ -0,0 +1,238 @@ +/* Copyright 2020 NXP */ +/* + * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/init.h> +#include <linux/ipu.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mxcfb.h> +#include <linux/of_device.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> + +#include "mxc_dispdrv.h" + +struct mxc_lcd_platform_data { + u32 default_ifmt; + u32 ipu_id; + u32 disp_id; +}; + +struct mxc_lcdif_data { + struct platform_device *pdev; + struct mxc_dispdrv_handle *disp_lcdif; +}; + +#define DISPDRV_LCD "lcd" + +static struct fb_videomode lcdif_modedb[] = { + { + /* 800x480 @ 57 Hz , pixel clk @ 27MHz */ + "CLAA-WVGA", 57, 800, 480, 37037, 40, 60, 10, 10, 20, 10, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 800x480 @ 60 Hz , pixel clk @ 32MHz */ + "SEIKO-WVGA", 60, 800, 480, 29850, 89, 164, 23, 10, 10, 10, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, +}; +static int lcdif_modedb_sz = ARRAY_SIZE(lcdif_modedb); + +static int lcdif_init(struct mxc_dispdrv_handle *disp, + struct mxc_dispdrv_setting *setting) +{ + int ret, i; + struct mxc_lcdif_data *lcdif = mxc_dispdrv_getdata(disp); + struct device *dev = &lcdif->pdev->dev; + struct mxc_lcd_platform_data *plat_data = dev->platform_data; + struct fb_videomode *modedb = lcdif_modedb; + int modedb_sz = lcdif_modedb_sz; + + /* use platform defined ipu/di */ + ret = ipu_di_to_crtc(dev, plat_data->ipu_id, + plat_data->disp_id, &setting->crtc); + if (ret < 0) + return ret; + + ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str, + modedb, modedb_sz, NULL, setting->default_bpp); + if (!ret) { + fb_videomode_to_var(&setting->fbi->var, &modedb[0]); + setting->if_fmt = plat_data->default_ifmt; + } + + INIT_LIST_HEAD(&setting->fbi->modelist); + for (i = 0; i < modedb_sz; i++) { + struct fb_videomode m; + fb_var_to_videomode(&m, &setting->fbi->var); + if (fb_mode_is_equal(&m, &modedb[i])) { + fb_add_videomode(&modedb[i], + &setting->fbi->modelist); + break; + } + } + + return ret; +} + +void lcdif_deinit(struct mxc_dispdrv_handle *disp) +{ + /*TODO*/ +} + +static struct mxc_dispdrv_driver lcdif_drv = { + .name = DISPDRV_LCD, + .init = lcdif_init, + .deinit = lcdif_deinit, +}; + +static int lcd_get_of_property(struct platform_device *pdev, + struct mxc_lcd_platform_data *plat_data) +{ + struct device_node *np = pdev->dev.of_node; + int err; + u32 ipu_id, disp_id; + const char *default_ifmt; + + err = of_property_read_string(np, "default_ifmt", &default_ifmt); + if (err) { + dev_dbg(&pdev->dev, "get of property default_ifmt fail\n"); + return err; + } + err = of_property_read_u32(np, "ipu_id", &ipu_id); + if (err) { + dev_dbg(&pdev->dev, "get of property ipu_id fail\n"); + return err; + } + err = of_property_read_u32(np, "disp_id", &disp_id); + if (err) { + dev_dbg(&pdev->dev, "get of property disp_id fail\n"); + return err; + } + + plat_data->ipu_id = ipu_id; + plat_data->disp_id = disp_id; + if (!strncmp(default_ifmt, "RGB24", 5)) + plat_data->default_ifmt = IPU_PIX_FMT_RGB24; + else if (!strncmp(default_ifmt, "BGR24", 5)) + plat_data->default_ifmt = IPU_PIX_FMT_BGR24; + else if (!strncmp(default_ifmt, "GBR24", 5)) + plat_data->default_ifmt = IPU_PIX_FMT_GBR24; + else if (!strncmp(default_ifmt, "RGB565", 6)) + plat_data->default_ifmt = IPU_PIX_FMT_RGB565; + else if (!strncmp(default_ifmt, "RGB666", 6)) + plat_data->default_ifmt = IPU_PIX_FMT_RGB666; + else if (!strncmp(default_ifmt, "YUV444", 6)) + plat_data->default_ifmt = IPU_PIX_FMT_YUV444; + else if (!strncmp(default_ifmt, "LVDS666", 7)) + plat_data->default_ifmt = IPU_PIX_FMT_LVDS666; + else if (!strncmp(default_ifmt, "YUYV16", 6)) + plat_data->default_ifmt = IPU_PIX_FMT_YUYV; + else if (!strncmp(default_ifmt, "UYVY16", 6)) + plat_data->default_ifmt = IPU_PIX_FMT_UYVY; + else if (!strncmp(default_ifmt, "YVYU16", 6)) + plat_data->default_ifmt = IPU_PIX_FMT_YVYU; + else if (!strncmp(default_ifmt, "VYUY16", 6)) + plat_data->default_ifmt = IPU_PIX_FMT_VYUY; + else { + dev_err(&pdev->dev, "err default_ifmt!\n"); + return -ENOENT; + } + + return err; +} + +static int mxc_lcdif_probe(struct platform_device *pdev) +{ + int ret; + struct pinctrl *pinctrl; + struct mxc_lcdif_data *lcdif; + struct mxc_lcd_platform_data *plat_data; + + dev_dbg(&pdev->dev, "%s enter\n", __func__); + lcdif = devm_kzalloc(&pdev->dev, sizeof(struct mxc_lcdif_data), + GFP_KERNEL); + if (!lcdif) + return -ENOMEM; + plat_data = devm_kzalloc(&pdev->dev, + sizeof(struct mxc_lcd_platform_data), + GFP_KERNEL); + if (!plat_data) + return -ENOMEM; + pdev->dev.platform_data = plat_data; + + ret = lcd_get_of_property(pdev, plat_data); + if (ret < 0) { + dev_err(&pdev->dev, "get lcd of property fail\n"); + return ret; + } + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) { + dev_err(&pdev->dev, "can't get/select pinctrl\n"); + return PTR_ERR(pinctrl); + } + + lcdif->pdev = pdev; + lcdif->disp_lcdif = mxc_dispdrv_register(&lcdif_drv); + mxc_dispdrv_setdata(lcdif->disp_lcdif, lcdif); + + dev_set_drvdata(&pdev->dev, lcdif); + dev_dbg(&pdev->dev, "%s exit\n", __func__); + + return ret; +} + +static int mxc_lcdif_remove(struct platform_device *pdev) +{ + struct mxc_lcdif_data *lcdif = dev_get_drvdata(&pdev->dev); + + mxc_dispdrv_puthandle(lcdif->disp_lcdif); + mxc_dispdrv_unregister(lcdif->disp_lcdif); + kfree(lcdif); + return 0; +} + +static const struct of_device_id imx_lcd_dt_ids[] = { + { .compatible = "fsl,lcd"}, + { /* sentinel */ } +}; +static struct platform_driver mxc_lcdif_driver = { + .driver = { + .name = "mxc_lcdif", + .of_match_table = imx_lcd_dt_ids, + }, + .probe = mxc_lcdif_probe, + .remove = mxc_lcdif_remove, +}; + +static int __init mxc_lcdif_init(void) +{ + return platform_driver_register(&mxc_lcdif_driver); +} + +static void __exit mxc_lcdif_exit(void) +{ + platform_driver_unregister(&mxc_lcdif_driver); +} + +module_init(mxc_lcdif_init); +module_exit(mxc_lcdif_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX ipuv3 LCD extern port driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/mxc/mxcfb_hx8363_wvga.c b/drivers/video/fbdev/mxc/mxcfb_hx8363_wvga.c new file mode 100644 index 000000000000..155e8491d765 --- /dev/null +++ b/drivers/video/fbdev/mxc/mxcfb_hx8363_wvga.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/spinlock.h> +#include <linux/mipi_dsi.h> +#include <linux/mxcfb.h> +#include <linux/backlight.h> +#include <video/mipi_display.h> + +#include "mipi_dsi.h" + +#define HX8363_TWO_DATA_LANE (0x2) +#define HX8363_MAX_DPHY_CLK (800) +#define HX8363_CMD_GETHXID (0xF4) +#define HX8363_CMD_GETHXID_LEN (0x4) +#define HX8363_ID (0x84) +#define HX8363_ID_MASK (0xFF) + + +#define CHECK_RETCODE(ret) \ +do { \ + if (ret < 0) { \ + dev_err(&mipi_dsi->pdev->dev, \ + "%s ERR: ret:%d, line:%d.\n", \ + __func__, ret, __LINE__); \ + return ret; \ + } \ +} while (0) + +static void parse_variadic(int n, u8 *buf, ...) +{ + int i = 0; + va_list args; + + if (unlikely(!n)) return; + + va_start(args, buf); + + for (i = 0; i < n; i++) + buf[i + 1] = (u8)va_arg(args, int); + + va_end(args); +} + +#define TC358763_DCS_write_1A_nP(n, addr, ...) { \ + int err; \ + \ + buf[0] = addr; \ + parse_variadic(n, buf, ##__VA_ARGS__); \ + \ + if (n >= 2) \ + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, \ + MIPI_DSI_DCS_LONG_WRITE, (u32*)buf, n + 1); \ + else if (n == 1) \ + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, \ + MIPI_DSI_DCS_SHORT_WRITE_PARAM, (u32*)buf, 0); \ + else if (n == 0) \ + { \ + buf[1] = 0; \ + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, \ + MIPI_DSI_DCS_SHORT_WRITE, (u32*)buf, 0); \ + } \ + CHECK_RETCODE(err); \ +} + +#define TC358763_DCS_write_1A_0P(addr) \ + TC358763_DCS_write_1A_nP(0, addr) + +#define TC358763_DCS_write_1A_1P(addr, ...) \ + TC358763_DCS_write_1A_nP(1, addr, __VA_ARGS__) + +#define TC358763_DCS_write_1A_2P(addr, ...) \ + TC358763_DCS_write_1A_nP(2, addr, __VA_ARGS__) + +#define TC358763_DCS_write_1A_3P(addr, ...) \ + TC358763_DCS_write_1A_nP(3, addr, __VA_ARGS__) + +#define TC358763_DCS_write_1A_5P(addr, ...) \ + TC358763_DCS_write_1A_nP(5, addr, __VA_ARGS__) + +#define TC358763_DCS_write_1A_6P(addr, ...) \ + TC358763_DCS_write_1A_nP(6, addr, __VA_ARGS__) + +#define TC358763_DCS_write_1A_7P(addr, ...) \ + TC358763_DCS_write_1A_nP(7, addr, __VA_ARGS__) + +#define TC358763_DCS_write_1A_12P(addr, ...) \ + TC358763_DCS_write_1A_nP(12, addr, __VA_ARGS__) + +#define TC358763_DCS_write_1A_13P(addr, ...) \ + TC358763_DCS_write_1A_nP(13, addr, __VA_ARGS__) + +#define TC358763_DCS_write_1A_14P(addr, ...) \ + TC358763_DCS_write_1A_nP(14, addr, __VA_ARGS__) + +#define TC358763_DCS_write_1A_19P(addr, ...) \ + TC358763_DCS_write_1A_nP(19, addr, __VA_ARGS__) + +#define TC358763_DCS_write_1A_34P(addr, ...) \ + TC358763_DCS_write_1A_nP(34, addr, __VA_ARGS__) + +#define TC358763_DCS_write_1A_127P(addr, ...) \ + TC358763_DCS_write_1A_nP(127, addr, __VA_ARGS__) + +static int hx8363bl_brightness; + +#define ACTIVE_HIGH_NAME "TRUULY-WVGA-SYNC-HIGH" +#define ACTIVE_LOW_NAME "TRUULY-WVGA-SYNC-LOW" + +static struct fb_videomode truly_lcd_modedb[] = { + { + ACTIVE_HIGH_NAME, 50, 480, 854, 41042, + 40, 60, + 3, 3, + 8, 4, + 0x0, + FB_VMODE_NONINTERLACED, + 0, + }, { + ACTIVE_LOW_NAME, 50, 480, 854, 41042, + 40, 60, + 3, 3, + 8, 4, + FB_SYNC_OE_LOW_ACT, + FB_VMODE_NONINTERLACED, + 0, + }, +}; + +static struct mipi_lcd_config lcd_config = { + .virtual_ch = 0x0, + .data_lane_num = HX8363_TWO_DATA_LANE, + .max_phy_clk = HX8363_MAX_DPHY_CLK, + .dpi_fmt = MIPI_RGB888, +}; + +void mipid_hx8363_get_lcd_videomode(struct fb_videomode **mode, int *size, + struct mipi_lcd_config **data) +{ + *mode = &truly_lcd_modedb[0]; + *size = ARRAY_SIZE(truly_lcd_modedb); + *data = &lcd_config; +} + +int mipid_hx8363_lcd_setup(struct mipi_dsi_info *mipi_dsi) +{ + u8 buf[DSI_CMD_BUF_MAXSIZE]; + + dev_dbg(&mipi_dsi->pdev->dev, "MIPI DSI LCD HX8363 setup.\n"); + + TC358763_DCS_write_1A_3P(0xB9,0xFF,0x83,0x63);/* SET password */ + + TC358763_DCS_write_1A_19P(0xB1,0x01,0x00,0x44,0x08,0x01,0x10,0x10,0x36, + 0x3E,0x1A,0x1A,0x40,0x12,0x00,0xE6,0xE6,0xE6,0xE6,0xE6);/* Set Power */ + TC358763_DCS_write_1A_2P(0xB2,0x08,0x03);/* Set DISP */ + TC358763_DCS_write_1A_7P(0xB4,0x02,0x18,0x9C,0x08,0x18,0x04,0x6C); + TC358763_DCS_write_1A_1P(0xB6,0x00);/* Set VCOM */ + TC358763_DCS_write_1A_1P(0xCC,0x0B);/* Set Panel */ + TC358763_DCS_write_1A_34P(0xE0,0x0E,0x15,0x19,0x30,0x31,0x3F,0x27,0x3C,0x88,0x8F,0xD1,0xD5,0xD7,0x16,0x16, + 0x0C,0x1E,0x0E,0x15,0x19,0x30,0x31,0x3F,0x27,0x3C,0x88,0x8F, + 0xD1,0xD5,0xD7,0x16,0x16,0x0C,0x1E); + mdelay(5); + + TC358763_DCS_write_1A_1P(0x3A,0x77);/* 24bit */ + TC358763_DCS_write_1A_14P(0xBA,0x11,0x00,0x56,0xC6,0x10,0x89,0xFF,0x0F,0x32,0x6E,0x04,0x07,0x9A,0x92); + TC358763_DCS_write_1A_0P(0x21); + + TC358763_DCS_write_1A_0P(0x11); + msleep(10); + + TC358763_DCS_write_1A_0P(0x29); + msleep(120); + + return 0; +} + +static int mipid_bl_update_status(struct backlight_device *bl) +{ + return 0; +} + +static int mipid_bl_get_brightness(struct backlight_device *bl) +{ + return hx8363bl_brightness; +} + +static int mipi_bl_check_fb(struct backlight_device *bl, struct fb_info *fbi) +{ + return 0; +} + +static const struct backlight_ops mipid_lcd_bl_ops = { + .update_status = mipid_bl_update_status, + .get_brightness = mipid_bl_get_brightness, + .check_fb = mipi_bl_check_fb, +}; diff --git a/drivers/video/fbdev/mxc/mxcfb_hx8369_wvga.c b/drivers/video/fbdev/mxc/mxcfb_hx8369_wvga.c new file mode 100644 index 000000000000..d461662bf4de --- /dev/null +++ b/drivers/video/fbdev/mxc/mxcfb_hx8369_wvga.c @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/spinlock.h> +#include <linux/mipi_dsi.h> +#include <linux/mxcfb.h> +#include <linux/backlight.h> +#include <video/mipi_display.h> + +#include "mipi_dsi.h" + +#define MIPI_DSI_MAX_RET_PACK_SIZE (0x4) + +#define HX8369BL_MAX_BRIGHT (255) +#define HX8369BL_DEF_BRIGHT (255) + +#define HX8369_MAX_DPHY_CLK (800) +#define HX8369_ONE_DATA_LANE (0x1) +#define HX8369_TWO_DATA_LANE (0x2) + +#define HX8369_CMD_SETEXTC (0xB9) +#define HX8369_CMD_SETEXTC_LEN (0x4) +#define HX8369_CMD_SETEXTC_PARAM_1 (0x6983ff) + +#define HX8369_CMD_GETHXID (0xF4) +#define HX8369_CMD_GETHXID_LEN (0x4) +#define HX8369_ID (0x69) +#define HX8369_ID_MASK (0xFF) + +#define HX8369_CMD_SETDISP (0xB2) +#define HX8369_CMD_SETDISP_LEN (16) +#define HX8369_CMD_SETDISP_1_HALT (0x00) +#define HX8369_CMD_SETDISP_2_RES_MODE (0x23) +#define HX8369_CMD_SETDISP_3_BP (0x03) +#define HX8369_CMD_SETDISP_4_FP (0x03) +#define HX8369_CMD_SETDISP_5_SAP (0x70) +#define HX8369_CMD_SETDISP_6_GENON (0x00) +#define HX8369_CMD_SETDISP_7_GENOFF (0xff) +#define HX8369_CMD_SETDISP_8_RTN (0x00) +#define HX8369_CMD_SETDISP_9_TEI (0x00) +#define HX8369_CMD_SETDISP_10_TEP_UP (0x00) +#define HX8369_CMD_SETDISP_11_TEP_LOW (0x00) +#define HX8369_CMD_SETDISP_12_BP_PE (0x03) +#define HX8369_CMD_SETDISP_13_FP_PE (0x03) +#define HX8369_CMD_SETDISP_14_RTN_PE (0x00) +#define HX8369_CMD_SETDISP_15_GON (0x01) + +#define HX8369_CMD_SETCYC (0xB4) +#define HX8369_CMD_SETCYC_LEN (6) +#define HX8369_CMD_SETCYC_PARAM_1 (0x5f1d00) +#define HX8369_CMD_SETCYC_PARAM_2 (0x060e) + +#define HX8369_CMD_SETGIP (0xD5) +#define HX8369_CMD_SETGIP_LEN (27) +#define HX8369_CMD_SETGIP_PARAM_1 (0x030400) +#define HX8369_CMD_SETGIP_PARAM_2 (0x1c050100) +#define HX8369_CMD_SETGIP_PARAM_3 (0x00030170) +#define HX8369_CMD_SETGIP_PARAM_4 (0x51064000) +#define HX8369_CMD_SETGIP_PARAM_5 (0x41000007) +#define HX8369_CMD_SETGIP_PARAM_6 (0x07075006) +#define HX8369_CMD_SETGIP_PARAM_7 (0x040f) + +#define HX8369_CMD_SETPOWER (0xB1) +#define HX8369_CMD_SETPOWER_LEN (20) +#define HX8369_CMD_SETPOWER_PARAM_1 (0x340001) +#define HX8369_CMD_SETPOWER_PARAM_2 (0x0f0f0006) +#define HX8369_CMD_SETPOWER_PARAM_3 (0x3f3f322a) +#define HX8369_CMD_SETPOWER_PARAM_4 (0xe6013a07) +#define HX8369_CMD_SETPOWER_PARAM_5 (0xe6e6e6e6) + +#define HX8369_CMD_SETVCOM (0xB6) +#define HX8369_CMD_SETVCOM_LEN (3) +#define HX8369_CMD_SETVCOM_PARAM_1 (0x5656) + +#define HX8369_CMD_SETPANEL (0xCC) +#define HX8369_CMD_SETPANEL_PARAM_1 (0x02) + +#define HX8369_CMD_SETGAMMA (0xE0) +#define HX8369_CMD_SETGAMMA_LEN (35) +#define HX8369_CMD_SETGAMMA_PARAM_1 (0x221d00) +#define HX8369_CMD_SETGAMMA_PARAM_2 (0x2e3f3d38) +#define HX8369_CMD_SETGAMMA_PARAM_3 (0x0f0d064a) +#define HX8369_CMD_SETGAMMA_PARAM_4 (0x16131513) +#define HX8369_CMD_SETGAMMA_PARAM_5 (0x1d001910) +#define HX8369_CMD_SETGAMMA_PARAM_6 (0x3f3d3822) +#define HX8369_CMD_SETGAMMA_PARAM_7 (0x0d064a2e) +#define HX8369_CMD_SETGAMMA_PARAM_8 (0x1315130f) +#define HX8369_CMD_SETGAMMA_PARAM_9 (0x191016) + +#define HX8369_CMD_SETMIPI (0xBA) +#define HX8369_CMD_SETMIPI_LEN (14) +#define HX8369_CMD_SETMIPI_PARAM_1 (0xc6a000) +#define HX8369_CMD_SETMIPI_PARAM_2 (0x10000a00) +#define HX8369_CMD_SETMIPI_ONELANE (0x10 << 24) +#define HX8369_CMD_SETMIPI_TWOLANE (0x11 << 24) +#define HX8369_CMD_SETMIPI_PARAM_3 (0x00026f30) +#define HX8369_CMD_SETMIPI_PARAM_4 (0x4018) + +#define HX8369_CMD_SETPIXEL_FMT (0x3A) +#define HX8369_CMD_SETPIXEL_FMT_24BPP (0x77) +#define HX8369_CMD_SETPIXEL_FMT_18BPP (0x66) +#define HX8369_CMD_SETPIXEL_FMT_16BPP (0x55) + +#define HX8369_CMD_SETCLUMN_ADDR (0x2A) +#define HX8369_CMD_SETCLUMN_ADDR_LEN (5) +#define HX8369_CMD_SETCLUMN_ADDR_PARAM_1 (0xdf0000) +#define HX8369_CMD_SETCLUMN_ADDR_PARAM_2 (0x01) + +#define HX8369_CMD_SETPAGE_ADDR (0x2B) +#define HX8369_CMD_SETPAGE_ADDR_LEN (5) +#define HX8369_CMD_SETPAGE_ADDR_PARAM_1 (0x1f0000) +#define HX8369_CMD_SETPAGE_ADDR_PARAM_2 (0x03) + +#define HX8369_CMD_WRT_DISP_BRIGHT (0x51) +#define HX8369_CMD_WRT_DISP_BRIGHT_PARAM_1 (0xFF) + +#define HX8369_CMD_WRT_CABC_MIN_BRIGHT (0x5E) +#define HX8369_CMD_WRT_CABC_MIN_BRIGHT_PARAM_1 (0x20) + +#define HX8369_CMD_WRT_CABC_CTRL (0x55) +#define HX8369_CMD_WRT_CABC_CTRL_PARAM_1 (0x1) + +#define HX8369_CMD_WRT_CTRL_DISP (0x53) +#define HX8369_CMD_WRT_CTRL_DISP_PARAM_1 (0x24) + +#define CHECK_RETCODE(ret) \ +do { \ + if (ret < 0) { \ + dev_err(&mipi_dsi->pdev->dev, \ + "%s ERR: ret:%d, line:%d.\n", \ + __func__, ret, __LINE__); \ + return ret; \ + } \ +} while (0) + +static int hx8369bl_brightness; +static int mipid_init_backlight(struct mipi_dsi_info *mipi_dsi); + +static struct fb_videomode truly_lcd_modedb[] = { + { + "TRULY-WVGA", 64, 480, 800, 37880, + 8, 8, + 6, 6, + 8, 6, + FB_SYNC_OE_LOW_ACT, + FB_VMODE_NONINTERLACED, + 0, + }, +}; + +static struct mipi_lcd_config lcd_config = { + .virtual_ch = 0x0, + .data_lane_num = HX8369_TWO_DATA_LANE, + .max_phy_clk = HX8369_MAX_DPHY_CLK, + .dpi_fmt = MIPI_RGB888, +}; +void mipid_hx8369_get_lcd_videomode(struct fb_videomode **mode, int *size, + struct mipi_lcd_config **data) +{ + *mode = &truly_lcd_modedb[0]; + *size = ARRAY_SIZE(truly_lcd_modedb); + *data = &lcd_config; +} +EXPORT_SYMBOL(mipid_hx8369_get_lcd_videomode); + +int mipid_hx8369_lcd_setup(struct mipi_dsi_info *mipi_dsi) +{ + u32 buf[DSI_CMD_BUF_MAXSIZE]; + int err; + + dev_dbg(&mipi_dsi->pdev->dev, "MIPI DSI LCD setup.\n"); + buf[0] = HX8369_CMD_SETEXTC | (HX8369_CMD_SETEXTC_PARAM_1 << 8); + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, + buf, HX8369_CMD_SETEXTC_LEN); + CHECK_RETCODE(err); + buf[0] = MIPI_DSI_MAX_RET_PACK_SIZE; + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, + MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE, + buf, 0); + CHECK_RETCODE(err); + buf[0] = HX8369_CMD_GETHXID; + err = mipi_dsi->mipi_dsi_pkt_read(mipi_dsi, + MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM, + buf, HX8369_CMD_GETHXID_LEN); + if (!err && ((buf[0] & HX8369_ID_MASK) == HX8369_ID)) { + dev_info(&mipi_dsi->pdev->dev, + "MIPI DSI LCD ID:0x%x.\n", buf[0]); + } else { + dev_err(&mipi_dsi->pdev->dev, + "mipi_dsi_pkt_read err:%d, data:0x%x.\n", + err, buf[0]); + dev_info(&mipi_dsi->pdev->dev, + "MIPI DSI LCD not detected!\n"); + return err; + } + + /* set LCD resolution as 480RGBx800, DPI interface, + * display operation mode: RGB data bypass GRAM mode. + */ + buf[0] = HX8369_CMD_SETDISP | (HX8369_CMD_SETDISP_1_HALT << 8) | + (HX8369_CMD_SETDISP_2_RES_MODE << 16) | + (HX8369_CMD_SETDISP_3_BP << 24); + buf[1] = HX8369_CMD_SETDISP_4_FP | (HX8369_CMD_SETDISP_5_SAP << 8) | + (HX8369_CMD_SETDISP_6_GENON << 16) | + (HX8369_CMD_SETDISP_7_GENOFF << 24); + buf[2] = HX8369_CMD_SETDISP_8_RTN | (HX8369_CMD_SETDISP_9_TEI << 8) | + (HX8369_CMD_SETDISP_10_TEP_UP << 16) | + (HX8369_CMD_SETDISP_11_TEP_LOW << 24); + buf[3] = HX8369_CMD_SETDISP_12_BP_PE | + (HX8369_CMD_SETDISP_13_FP_PE << 8) | + (HX8369_CMD_SETDISP_14_RTN_PE << 16) | + (HX8369_CMD_SETDISP_15_GON << 24); + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, + buf, HX8369_CMD_SETDISP_LEN); + CHECK_RETCODE(err); + + /* Set display waveform cycle */ + buf[0] = HX8369_CMD_SETCYC | (HX8369_CMD_SETCYC_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETCYC_PARAM_2; + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, + buf, HX8369_CMD_SETCYC_LEN); + CHECK_RETCODE(err); + + /* Set GIP timing output control */ + buf[0] = HX8369_CMD_SETGIP | (HX8369_CMD_SETGIP_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETGIP_PARAM_2; + buf[2] = HX8369_CMD_SETGIP_PARAM_3; + buf[3] = HX8369_CMD_SETGIP_PARAM_4; + buf[4] = HX8369_CMD_SETGIP_PARAM_5; + buf[5] = HX8369_CMD_SETGIP_PARAM_6; + buf[6] = HX8369_CMD_SETGIP_PARAM_7; + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf, + HX8369_CMD_SETGIP_LEN); + CHECK_RETCODE(err); + + /* Set power: standby, DC etc. */ + buf[0] = HX8369_CMD_SETPOWER | (HX8369_CMD_SETPOWER_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETPOWER_PARAM_2; + buf[2] = HX8369_CMD_SETPOWER_PARAM_3; + buf[3] = HX8369_CMD_SETPOWER_PARAM_4; + buf[4] = HX8369_CMD_SETPOWER_PARAM_5; + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf, + HX8369_CMD_SETPOWER_LEN); + CHECK_RETCODE(err); + + /* Set VCOM voltage. */ + buf[0] = HX8369_CMD_SETVCOM | (HX8369_CMD_SETVCOM_PARAM_1 << 8); + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf, + HX8369_CMD_SETVCOM_LEN); + CHECK_RETCODE(err); + + /* Set Panel: BGR/RGB or Inversion. */ + buf[0] = HX8369_CMD_SETPANEL | (HX8369_CMD_SETPANEL_PARAM_1 << 8); + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, + MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, buf, 0); + CHECK_RETCODE(err); + + /* Set gamma curve related setting */ + buf[0] = HX8369_CMD_SETGAMMA | (HX8369_CMD_SETGAMMA_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETGAMMA_PARAM_2; + buf[2] = HX8369_CMD_SETGAMMA_PARAM_3; + buf[3] = HX8369_CMD_SETGAMMA_PARAM_4; + buf[4] = HX8369_CMD_SETGAMMA_PARAM_5; + buf[5] = HX8369_CMD_SETGAMMA_PARAM_6; + buf[6] = HX8369_CMD_SETGAMMA_PARAM_7; + buf[7] = HX8369_CMD_SETGAMMA_PARAM_8; + buf[8] = HX8369_CMD_SETGAMMA_PARAM_9; + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf, + HX8369_CMD_SETGAMMA_LEN); + CHECK_RETCODE(err); + + /* Set MIPI: DPHYCMD & DSICMD, data lane number */ + buf[0] = HX8369_CMD_SETMIPI | (HX8369_CMD_SETMIPI_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETMIPI_PARAM_2; + buf[2] = HX8369_CMD_SETMIPI_PARAM_3; + if (lcd_config.data_lane_num == HX8369_ONE_DATA_LANE) + buf[2] |= HX8369_CMD_SETMIPI_ONELANE; + else + buf[2] |= HX8369_CMD_SETMIPI_TWOLANE; + buf[3] = HX8369_CMD_SETMIPI_PARAM_4; + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf, + HX8369_CMD_SETMIPI_LEN); + CHECK_RETCODE(err); + + /* Set pixel format:24bpp */ + buf[0] = HX8369_CMD_SETPIXEL_FMT; + switch (lcd_config.dpi_fmt) { + case MIPI_RGB565_PACKED: + case MIPI_RGB565_LOOSELY: + case MIPI_RGB565_CONFIG3: + buf[0] |= (HX8369_CMD_SETPIXEL_FMT_16BPP << 8); + break; + + case MIPI_RGB666_LOOSELY: + case MIPI_RGB666_PACKED: + buf[0] |= (HX8369_CMD_SETPIXEL_FMT_18BPP << 8); + break; + + case MIPI_RGB888: + buf[0] |= (HX8369_CMD_SETPIXEL_FMT_24BPP << 8); + break; + + default: + buf[0] |= (HX8369_CMD_SETPIXEL_FMT_24BPP << 8); + break; + } + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, + buf, 0); + CHECK_RETCODE(err); + + /* Set column address: 0~479 */ + buf[0] = HX8369_CMD_SETCLUMN_ADDR | + (HX8369_CMD_SETCLUMN_ADDR_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETCLUMN_ADDR_PARAM_2; + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, + buf, HX8369_CMD_SETCLUMN_ADDR_LEN); + CHECK_RETCODE(err); + + /* Set page address: 0~799 */ + buf[0] = HX8369_CMD_SETPAGE_ADDR | + (HX8369_CMD_SETPAGE_ADDR_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETPAGE_ADDR_PARAM_2; + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, + buf, HX8369_CMD_SETPAGE_ADDR_LEN); + CHECK_RETCODE(err); + + /* Set display brightness related */ + buf[0] = HX8369_CMD_WRT_DISP_BRIGHT | + (HX8369_CMD_WRT_DISP_BRIGHT_PARAM_1 << 8); + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, + buf, 0); + CHECK_RETCODE(err); + + buf[0] = HX8369_CMD_WRT_CABC_CTRL | + (HX8369_CMD_WRT_CABC_CTRL_PARAM_1 << 8); + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, + buf, 0); + CHECK_RETCODE(err); + + buf[0] = HX8369_CMD_WRT_CTRL_DISP | + (HX8369_CMD_WRT_CTRL_DISP_PARAM_1 << 8); + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, + buf, 0); + CHECK_RETCODE(err); + + /* exit sleep mode and set display on */ + buf[0] = MIPI_DCS_EXIT_SLEEP_MODE; + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM, + buf, 0); + CHECK_RETCODE(err); + /* To allow time for the supply voltages + * and clock circuits to stabilize. + */ + msleep(5); + buf[0] = MIPI_DCS_SET_DISPLAY_ON; + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM, + buf, 0); + CHECK_RETCODE(err); + + err = mipid_init_backlight(mipi_dsi); + return err; +} +EXPORT_SYMBOL(mipid_hx8369_lcd_setup); + +static int mipid_bl_update_status(struct backlight_device *bl) +{ + int err; + u32 buf; + int brightness = bl->props.brightness; + struct mipi_dsi_info *mipi_dsi = bl_get_data(bl); + + if (bl->props.power != FB_BLANK_UNBLANK || + bl->props.fb_blank != FB_BLANK_UNBLANK) + brightness = 0; + + buf = HX8369_CMD_WRT_DISP_BRIGHT | + ((brightness & HX8369BL_MAX_BRIGHT) << 8); + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, + &buf, 0); + CHECK_RETCODE(err); + + hx8369bl_brightness = brightness & HX8369BL_MAX_BRIGHT; + + dev_dbg(&bl->dev, "mipid backlight bringtness:%d.\n", brightness); + return 0; +} + +static int mipid_bl_get_brightness(struct backlight_device *bl) +{ + return hx8369bl_brightness; +} + +static int mipi_bl_check_fb(struct backlight_device *bl, struct fb_info *fbi) +{ + return 0; +} + +static const struct backlight_ops mipid_lcd_bl_ops = { + .update_status = mipid_bl_update_status, + .get_brightness = mipid_bl_get_brightness, + .check_fb = mipi_bl_check_fb, +}; + +static int mipid_init_backlight(struct mipi_dsi_info *mipi_dsi) +{ + struct backlight_properties props; + struct backlight_device *bl; + + if (mipi_dsi->bl) { + pr_debug("mipid backlight already init!\n"); + return 0; + } + memset(&props, 0, sizeof(struct backlight_properties)); + props.max_brightness = HX8369BL_MAX_BRIGHT; + props.type = BACKLIGHT_RAW; + bl = backlight_device_register("mipid-bl", &mipi_dsi->pdev->dev, + mipi_dsi, &mipid_lcd_bl_ops, &props); + if (IS_ERR(bl)) { + pr_err("error %ld on backlight register\n", PTR_ERR(bl)); + return PTR_ERR(bl); + } + mipi_dsi->bl = bl; + bl->props.power = FB_BLANK_UNBLANK; + bl->props.fb_blank = FB_BLANK_UNBLANK; + bl->props.brightness = HX8369BL_DEF_BRIGHT; + + mipid_bl_update_status(bl); + return 0; +} diff --git a/drivers/video/fbdev/mxc/mxcfb_rm68191_qhd.c b/drivers/video/fbdev/mxc/mxcfb_rm68191_qhd.c new file mode 100644 index 000000000000..4faa9b6c6509 --- /dev/null +++ b/drivers/video/fbdev/mxc/mxcfb_rm68191_qhd.c @@ -0,0 +1,264 @@ +/* + * Copyright 2018 NXP + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/spinlock.h> +#include <linux/mipi_dsi.h> +#include <linux/mxcfb.h> +#include <linux/backlight.h> +#include <video/mipi_display.h> + +#include "mipi_dsi.h" + +#define RM68191_MAX_DPHY_CLK (850) + +#define CHECK_RETCODE(ret) \ +do { \ + if (ret < 0) { \ + dev_err(&mipi_dsi->pdev->dev, \ + "%s ERR: ret:%d, line:%d.\n", \ + __func__, ret, __LINE__); \ + return ret; \ + } \ +} while (0) + +static void parse_variadic(int n, u8 *buf, ...) +{ + int i = 0; + va_list args; + + if (unlikely(!n)) return; + + va_start(args, buf); + + for (i = 0; i < n; i++) + buf[i + 1] = (u8)va_arg(args, int); + + va_end(args); +} + +#define RM68191_DCS_write_1A_nP(n, addr, ...) { \ + int err; \ + \ + buf[0] = addr; \ + parse_variadic(n, buf, ##__VA_ARGS__); \ + \ + if (n >= 2) \ + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, \ + MIPI_DSI_DCS_LONG_WRITE, (u32*)buf, n + 1); \ + else if (n == 1) \ + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, \ + MIPI_DSI_DCS_SHORT_WRITE_PARAM, (u32*)buf, 0); \ + else if (n == 0) \ + { \ + buf[1] = 0; \ + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, \ + MIPI_DSI_DCS_SHORT_WRITE, (u32*)buf, 0); \ + } \ + CHECK_RETCODE(err); \ +} + +#define RM68191_DCS_write_1A_0P(addr) \ + RM68191_DCS_write_1A_nP(0, addr) + +#define RM68191_DCS_write_1A_1P(addr, ...) \ + RM68191_DCS_write_1A_nP(1, addr, __VA_ARGS__) + +#define RM68191_DCS_write_1A_2P(addr, ...) \ + RM68191_DCS_write_1A_nP(2, addr, __VA_ARGS__) + +#define RM68191_DCS_write_1A_3P(addr, ...) \ + RM68191_DCS_write_1A_nP(3, addr, __VA_ARGS__) + +#define RM68191_DCS_write_1A_4P(addr, ...) \ + RM68191_DCS_write_1A_nP(4, addr, __VA_ARGS__) + +#define RM68191_DCS_write_1A_5P(addr, ...) \ + RM68191_DCS_write_1A_nP(5, addr, __VA_ARGS__) + +#define RM68191_DCS_write_1A_6P(addr, ...) \ + RM68191_DCS_write_1A_nP(6, addr, __VA_ARGS__) + +#define RM68191_DCS_write_1A_7P(addr, ...) \ + RM68191_DCS_write_1A_nP(7, addr, __VA_ARGS__) + +#define RM68191_DCS_write_1A_8P(addr, ...) \ + RM68191_DCS_write_1A_nP(8, addr, __VA_ARGS__) + +#define RM68191_DCS_write_1A_9P(addr, ...) \ + RM68191_DCS_write_1A_nP(9, addr, __VA_ARGS__) + +#define RM68191_DCS_write_1A_10P(addr, ...) \ + RM68191_DCS_write_1A_nP(10, addr, __VA_ARGS__) + +#define RM68191_DCS_write_1A_11P(addr, ...) \ + RM68191_DCS_write_1A_nP(11, addr, __VA_ARGS__) + +#define RM68191_DCS_write_1A_16P(addr, ...) \ + RM68191_DCS_write_1A_nP(16, addr, __VA_ARGS__) + +#define ACTIVE_HIGH_NAME "RK-QHD-SYNC-HIGH" +#define ACTIVE_LOW_NAME "RK-QHD-SYNC-LOW" + +static struct fb_videomode rk_lcd_modedb[] = { + /* 540 x 960 */ + { + ACTIVE_HIGH_NAME, 60, 540, 960, 27396, + 30, 32, + 14, 16, + 2, 2, + 0x0, + FB_VMODE_NONINTERLACED, + 0, + }, { + ACTIVE_LOW_NAME, 60, 540, 960, 27396, + 30, 32, + 14, 16, + 2, 2, + FB_SYNC_OE_LOW_ACT, + FB_VMODE_NONINTERLACED, + 0, + }, +}; + +static struct mipi_lcd_config lcd_config = { + .virtual_ch = 0x0, + .data_lane_num = 2, + .max_phy_clk = RM68191_MAX_DPHY_CLK, + .dpi_fmt = MIPI_RGB888, +}; + +void mipid_rm68191_get_lcd_videomode(struct fb_videomode **mode, int *size, + struct mipi_lcd_config **data) +{ + *mode = &rk_lcd_modedb[0]; + *size = ARRAY_SIZE(rk_lcd_modedb); + *data = &lcd_config; +} + +int mipid_rm68191_lcd_setup(struct mipi_dsi_info *mipi_dsi) +{ + u8 buf[DSI_CMD_BUF_MAXSIZE]; + + dev_dbg(&mipi_dsi->pdev->dev, "MIPI DSI LCD RM68191 setup.\n"); + + RM68191_DCS_write_1A_5P(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x03); + RM68191_DCS_write_1A_9P(0x90, 0x05, 0x16, 0x09, 0x03, 0xCD, 0x00, 0x00, 0x00, 0x00); + RM68191_DCS_write_1A_9P(0x91, 0x05, 0x16, 0x0B, 0x03, 0xCF, 0x00, 0x00, 0x00, 0x00); + RM68191_DCS_write_1A_11P(0x92, 0x40, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x8F, 0x00, 0x00, 0x04, 0x08); + RM68191_DCS_write_1A_8P(0x94, 0x00, 0x08, 0x0C, 0x03, 0xD1, 0x03, 0xD2, 0x0C); + RM68191_DCS_write_1A_16P(0x95, 0x40, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13, 0x00, 0x8F, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08); + RM68191_DCS_write_1A_2P(0x99, 0x00, 0x00); + RM68191_DCS_write_1A_11P(0x9A, 0x80, 0x10, 0x03, 0xD5, 0x03, 0xD7, 0x00, 0x00, 0x00, 0x00, 0x50); + RM68191_DCS_write_1A_6P(0x9B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + RM68191_DCS_write_1A_2P(0x9C, 0x00, 0x00); + RM68191_DCS_write_1A_8P(0x9D, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00); + RM68191_DCS_write_1A_2P(0x9E, 0x00, 0x00); + RM68191_DCS_write_1A_10P(0xA0, 0x84, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x08, 0x1F, 0x0A, 0x1F); + RM68191_DCS_write_1A_10P(0xA1, 0x1F, 0x1F, 0x1F, 0x1F, 0x0C, 0x1F, 0x0E, 0x1F, 0x1F, 0x1F); + RM68191_DCS_write_1A_10P(0xA2, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x02, 0x1F, 0x06, 0x1F); + RM68191_DCS_write_1A_10P(0xA3, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F); + RM68191_DCS_write_1A_10P(0xA4, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x07, 0x1F, 0x03, 0x1F, 0x0F); + RM68191_DCS_write_1A_10P(0xA5, 0x1F, 0x0D, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x0B, 0x1F, 0x09); + RM68191_DCS_write_1A_10P(0xA6, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x01, 0x05); + RM68191_DCS_write_1A_10P(0xA7, 0x03, 0x07, 0x1F, 0x1F, 0x1F, 0x1F, 0x0B, 0x1F, 0x09, 0x1F); + RM68191_DCS_write_1A_10P(0xA8, 0x1F, 0x1F, 0x1F, 0x1F, 0x0F, 0x1F, 0x0D, 0x1F, 0x1F, 0x1F); + RM68191_DCS_write_1A_10P(0xA9, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x05, 0x1F, 0x01, 0x1F); + RM68191_DCS_write_1A_10P(0xAA, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F); + RM68191_DCS_write_1A_10P(0xAB, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x1F, 0x04, 0x1F, 0x0C); + RM68191_DCS_write_1A_10P(0xAC, 0x1F, 0x0E, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x08, 0x1F, 0x0A); + RM68191_DCS_write_1A_10P(0xAD, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x06, 0x02); + RM68191_DCS_write_1A_5P(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x02); + RM68191_DCS_write_1A_1P(0xEA, 0x7D); + RM68191_DCS_write_1A_5P(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00); + RM68191_DCS_write_1A_3P(0xBC, 0x00, 0x00, 0x00); + RM68191_DCS_write_1A_4P(0xB8, 0x01, 0xAF, 0x8F, 0x8F); + RM68191_DCS_write_1A_5P(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01); + RM68191_DCS_write_1A_16P(0xD1, 0x00, 0x00, 0x00, 0x26, 0x00, 0x5E, 0x00, 0x88, 0x00, 0xA8, 0x00, 0xDB, 0x01, 0x02, 0x01, 0x3D); + RM68191_DCS_write_1A_16P(0xD2, 0x01, 0x67, 0x01, 0xA6, 0x01, 0xD3, 0x02, 0x16, 0x02, 0x49, 0x02, 0x4B, 0x02, 0x7B, 0x02, 0xB3); + RM68191_DCS_write_1A_16P(0xD3, 0x02, 0xD9, 0x03, 0x0E, 0x03, 0x31, 0x03, 0x61, 0x03, 0x80, 0x03, 0xA5, 0x03, 0xBD, 0x03, 0xD2); + RM68191_DCS_write_1A_4P(0xD4, 0x03, 0xE5, 0x03, 0xFF); + RM68191_DCS_write_1A_16P(0xD5, 0x00, 0x00, 0x00, 0x26, 0x00, 0x5E, 0x00, 0x88, 0x00, 0xA8, 0x00, 0xDB, 0x01, 0x02, 0x01, 0x3D); + RM68191_DCS_write_1A_16P(0xD6, 0x01, 0x67, 0x01, 0xA6, 0x01, 0xD3, 0x02, 0x16, 0x02, 0x49, 0x02, 0x4B, 0x02, 0x7B, 0x02, 0xB3); + RM68191_DCS_write_1A_16P(0xD7, 0x02, 0xD9, 0x03, 0x0E, 0x03, 0x31, 0x03, 0x61, 0x03, 0x80, 0x03, 0xA5, 0x03, 0xBD, 0x03, 0xD2); + RM68191_DCS_write_1A_4P(0xD8, 0x03, 0xE5, 0x03, 0xFF); + RM68191_DCS_write_1A_16P(0xD9, 0x00, 0x00, 0x00, 0x26, 0x00, 0x5E, 0x00, 0x88, 0x00, 0xA8, 0x00, 0xDB, 0x01, 0x02, 0x01, 0x3D); + RM68191_DCS_write_1A_16P(0xDD, 0x01, 0x67, 0x01, 0xA6, 0x01, 0xD3, 0x02, 0x16, 0x02, 0x49, 0x02, 0x4B, 0x02, 0x7B, 0x02, 0xB3); + RM68191_DCS_write_1A_16P(0xDE, 0x02, 0xD9, 0x03, 0x0E, 0x03, 0x31, 0x03, 0x61, 0x03, 0x80, 0x03, 0xA5, 0x03, 0xBD, 0x03, 0xD2); + RM68191_DCS_write_1A_4P(0xDF, 0x03, 0xE5, 0x03, 0xFF); + RM68191_DCS_write_1A_16P(0xE0, 0x00, 0x00, 0x00, 0x26, 0x00, 0x5E, 0x00, 0x88, 0x00, 0xA8, 0x00, 0xDB, 0x01, 0x02, 0x01, 0x3D); + RM68191_DCS_write_1A_16P(0xE1, 0x01, 0x67, 0x01, 0xA6, 0x01, 0xD3, 0x02, 0x16, 0x02, 0x49, 0x02, 0x4B, 0x02, 0x7B, 0x02, 0xB3); + RM68191_DCS_write_1A_16P(0xE2, 0x02, 0xD9, 0x03, 0x0E, 0x03, 0x31, 0x03, 0x61, 0x03, 0x80, 0x03, 0xA5, 0x03, 0xBD, 0x03, 0xD2); + RM68191_DCS_write_1A_4P(0xE3, 0x03, 0xE5, 0x03, 0xFF); + RM68191_DCS_write_1A_16P(0xE4, 0x00, 0x00, 0x00, 0x26, 0x00, 0x5E, 0x00, 0x88, 0x00, 0xA8, 0x00, 0xDB, 0x01, 0x02, 0x01, 0x3D); + RM68191_DCS_write_1A_16P(0xE5, 0x01, 0x67, 0x01, 0xA6, 0x01, 0xD3, 0x02, 0x16, 0x02, 0x49, 0x02, 0x4B, 0x02, 0x7B, 0x02, 0xB3); + RM68191_DCS_write_1A_16P(0xE6, 0x02, 0xD9, 0x03, 0x0E, 0x03, 0x31, 0x03, 0x61, 0x03, 0x80, 0x03, 0xA5, 0x03, 0xBD, 0x03, 0xD2); + RM68191_DCS_write_1A_4P(0xE7, 0x03, 0xE5, 0x03, 0xFF); + RM68191_DCS_write_1A_16P(0xE8, 0x00, 0x00, 0x00, 0x26, 0x00, 0x5E, 0x00, 0x88, 0x00, 0xA8, 0x00, 0xDB, 0x01, 0x02, 0x01, 0x3D); + RM68191_DCS_write_1A_16P(0xE9, 0x01, 0x67, 0x01, 0xA6, 0x01, 0xD3, 0x02, 0x16, 0x02, 0x49, 0x02, 0x4B, 0x02, 0x7B, 0x02, 0xB3); + RM68191_DCS_write_1A_16P(0xEA, 0x02, 0xD9, 0x03, 0x0E, 0x03, 0x31, 0x03, 0x61, 0x03, 0x80, 0x03, 0xA5, 0x03, 0xBD, 0x03, 0xD2); + RM68191_DCS_write_1A_4P(0xEB, 0x03, 0xE5, 0x03, 0xFF); + RM68191_DCS_write_1A_3P(0xB0, 0x07, 0x07, 0x07); + RM68191_DCS_write_1A_3P(0xB1, 0x07, 0x07, 0x07); + RM68191_DCS_write_1A_3P(0xB3, 0x11, 0x11, 0x11); + RM68191_DCS_write_1A_3P(0xB4, 0x09, 0x09, 0x09); + RM68191_DCS_write_1A_3P(0xB6, 0x44, 0x44, 0x44); + RM68191_DCS_write_1A_3P(0xB7, 0x34, 0x34, 0x34); + RM68191_DCS_write_1A_3P(0xB9, 0x34, 0x34, 0x34); + RM68191_DCS_write_1A_3P(0xBA, 0x14, 0x14, 0x14); + RM68191_DCS_write_1A_3P(0xBC, 0x00, 0x98, 0x00); + RM68191_DCS_write_1A_3P(0xBD, 0x00, 0x98, 0x00); + RM68191_DCS_write_1A_1P(0xBE, 0x1D); + RM68191_DCS_write_1A_1P(0x35, 0x00); + + RM68191_DCS_write_1A_0P(0x11); + mdelay(200); + RM68191_DCS_write_1A_0P(0x29); + mdelay(200); + + return 0; +} + +static int mipid_bl_update_status(struct backlight_device *bl) +{ + return 0; +} + +static int mipid_bl_get_brightness(struct backlight_device *bl) +{ + return 255; +} + +static int mipi_bl_check_fb(struct backlight_device *bl, struct fb_info *fbi) +{ + return 0; +} + +static const struct backlight_ops mipid_lcd_bl_ops = { + .update_status = mipid_bl_update_status, + .get_brightness = mipid_bl_get_brightness, + .check_fb = mipi_bl_check_fb, +}; diff --git a/drivers/video/fbdev/mxc/mxcfb_rm68200_wxga.c b/drivers/video/fbdev/mxc/mxcfb_rm68200_wxga.c new file mode 100644 index 000000000000..9c6ef8e2dbfb --- /dev/null +++ b/drivers/video/fbdev/mxc/mxcfb_rm68200_wxga.c @@ -0,0 +1,438 @@ +/* + * Copyright 2018 NXP + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/spinlock.h> +#include <linux/mipi_dsi.h> +#include <linux/mxcfb.h> +#include <linux/backlight.h> +#include <video/mipi_display.h> + +#include "mipi_dsi.h" + +#define RM68200_MAX_DPHY_CLK (850) + +#define CHECK_RETCODE(ret) \ +do { \ + if (ret < 0) { \ + dev_err(&mipi_dsi->pdev->dev, \ + "%s ERR: ret:%d, line:%d.\n", \ + __func__, ret, __LINE__); \ + return ret; \ + } \ +} while (0) + +static void parse_variadic(int n, u8 *buf, ...) +{ + int i = 0; + va_list args; + + if (unlikely(!n)) return; + + va_start(args, buf); + + for (i = 0; i < n; i++) + buf[i + 1] = (u8)va_arg(args, int); + + va_end(args); +} + +#define RM68200_DCS_write_1A_nP(n, addr, ...) { \ + int err; \ + \ + buf[0] = addr; \ + parse_variadic(n, buf, ##__VA_ARGS__); \ + \ + if (n >= 2) \ + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, \ + MIPI_DSI_DCS_LONG_WRITE, (u32*)buf, n + 1); \ + else if (n == 1) \ + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, \ + MIPI_DSI_DCS_SHORT_WRITE_PARAM, (u32*)buf, 0); \ + else if (n == 0) \ + { \ + buf[1] = 0; \ + err = mipi_dsi->mipi_dsi_pkt_write(mipi_dsi, \ + MIPI_DSI_DCS_SHORT_WRITE, (u32*)buf, 0); \ + } \ + CHECK_RETCODE(err); \ +} + +#define RM68200_DCS_write_1A_0P(addr) \ + RM68200_DCS_write_1A_nP(0, addr) + +#define RM68200_DCS_write_1A_1P(addr, ...) \ + RM68200_DCS_write_1A_nP(1, addr, __VA_ARGS__) + +#define ACTIVE_HIGH_NAME "RK-WXGA-SYNC-HIGH" +#define ACTIVE_LOW_NAME "RK-WXGA-SYNC-LOW" + +static struct fb_videomode rk_lcd_modedb[] = { + /* 720 x 1280 */ + { + ACTIVE_HIGH_NAME, 60, 720, 1280, 16040, + 32, 32, + 14, 16, + 8, 2, + 0x0, + FB_VMODE_NONINTERLACED, + 0, + }, { + ACTIVE_LOW_NAME, 60, 720, 1280, 16040, + 32, 32, + 14, 16, + 8, 2, + FB_SYNC_OE_LOW_ACT, + FB_VMODE_NONINTERLACED, + 0, + }, +}; + +static struct mipi_lcd_config lcd_config = { + .virtual_ch = 0x0, + .data_lane_num = 2, + .max_phy_clk = RM68200_MAX_DPHY_CLK, + .dpi_fmt = MIPI_RGB888, +}; + +void mipid_rm68200_get_lcd_videomode(struct fb_videomode **mode, int *size, + struct mipi_lcd_config **data) +{ + *mode = &rk_lcd_modedb[0]; + *size = ARRAY_SIZE(rk_lcd_modedb); + *data = &lcd_config; +} + +int mipid_rm68200_lcd_setup(struct mipi_dsi_info *mipi_dsi) +{ + u8 buf[DSI_CMD_BUF_MAXSIZE]; + + dev_dbg(&mipi_dsi->pdev->dev, "MIPI DSI LCD RM68200 setup.\n"); + + /* change to MCS Page 0 */ + RM68200_DCS_write_1A_1P(0xFE, 0x01); + RM68200_DCS_write_1A_1P(0x24, 0xc0); /* External PWR IC Control */ + RM68200_DCS_write_1A_1P(0x25, 0x53); + RM68200_DCS_write_1A_1P(0x26, 0x00); + RM68200_DCS_write_1A_1P(0x2B, 0xE5); + RM68200_DCS_write_1A_1P(0x27, 0x0A); + RM68200_DCS_write_1A_1P(0x29, 0x0A); + RM68200_DCS_write_1A_1P(0x16, 0x52); + RM68200_DCS_write_1A_1P(0x2F, 0x53); + RM68200_DCS_write_1A_1P(0x34, 0x5A); + RM68200_DCS_write_1A_1P(0x1B, 0x00); + RM68200_DCS_write_1A_1P(0x12, 0x0A); + RM68200_DCS_write_1A_1P(0x1A, 0x06); + RM68200_DCS_write_1A_1P(0x46, 0x56); + RM68200_DCS_write_1A_1P(0x52, 0xA0); + RM68200_DCS_write_1A_1P(0x53, 0x00); + RM68200_DCS_write_1A_1P(0x54, 0xA0); + RM68200_DCS_write_1A_1P(0x55, 0x00); + RM68200_DCS_write_1A_1P(0x5F, 0x11); /* 2 data lanes */ + + /* change to MCS Page 2 */ + RM68200_DCS_write_1A_1P(0xFE, 0x03); + RM68200_DCS_write_1A_1P(0x00, 0x05); + RM68200_DCS_write_1A_1P(0x02, 0x0B); + RM68200_DCS_write_1A_1P(0x03, 0x0F); + RM68200_DCS_write_1A_1P(0x04, 0x7D); + RM68200_DCS_write_1A_1P(0x05, 0x00); + RM68200_DCS_write_1A_1P(0x06, 0x50); + RM68200_DCS_write_1A_1P(0x07, 0x05); + RM68200_DCS_write_1A_1P(0x08, 0x16); + RM68200_DCS_write_1A_1P(0x09, 0x0D); + RM68200_DCS_write_1A_1P(0x0A, 0x11); + RM68200_DCS_write_1A_1P(0x0B, 0x7D); + RM68200_DCS_write_1A_1P(0x0C, 0x00); + RM68200_DCS_write_1A_1P(0x0D, 0x50); + RM68200_DCS_write_1A_1P(0x0E, 0x07); + RM68200_DCS_write_1A_1P(0x0F, 0x08); + RM68200_DCS_write_1A_1P(0x10, 0x01); + RM68200_DCS_write_1A_1P(0x11, 0x02); + RM68200_DCS_write_1A_1P(0x12, 0x00); + RM68200_DCS_write_1A_1P(0x13, 0x7D); + RM68200_DCS_write_1A_1P(0x14, 0x00); + RM68200_DCS_write_1A_1P(0x15, 0x85); + RM68200_DCS_write_1A_1P(0x16, 0x08); + RM68200_DCS_write_1A_1P(0x17, 0x03); + RM68200_DCS_write_1A_1P(0x18, 0x04); + RM68200_DCS_write_1A_1P(0x19, 0x05); + RM68200_DCS_write_1A_1P(0x1A, 0x06); + RM68200_DCS_write_1A_1P(0x1B, 0x00); + RM68200_DCS_write_1A_1P(0x1C, 0x7D); + RM68200_DCS_write_1A_1P(0x1D, 0x00); + RM68200_DCS_write_1A_1P(0x1E, 0x85); + RM68200_DCS_write_1A_1P(0x1F, 0x08); + RM68200_DCS_write_1A_1P(0x20, 0x00); + RM68200_DCS_write_1A_1P(0x21, 0x00); + RM68200_DCS_write_1A_1P(0x22, 0x00); + RM68200_DCS_write_1A_1P(0x23, 0x00); + RM68200_DCS_write_1A_1P(0x24, 0x00); + RM68200_DCS_write_1A_1P(0x25, 0x00); + RM68200_DCS_write_1A_1P(0x26, 0x00); + RM68200_DCS_write_1A_1P(0x27, 0x00); + RM68200_DCS_write_1A_1P(0x28, 0x00); + RM68200_DCS_write_1A_1P(0x29, 0x00); + RM68200_DCS_write_1A_1P(0x2A, 0x07); + RM68200_DCS_write_1A_1P(0x2B, 0x08); + RM68200_DCS_write_1A_1P(0x2D, 0x01); + RM68200_DCS_write_1A_1P(0x2F, 0x02); + RM68200_DCS_write_1A_1P(0x30, 0x00); + RM68200_DCS_write_1A_1P(0x31, 0x40); + RM68200_DCS_write_1A_1P(0x32, 0x05); + RM68200_DCS_write_1A_1P(0x33, 0x08); + RM68200_DCS_write_1A_1P(0x34, 0x54); + RM68200_DCS_write_1A_1P(0x35, 0x7D); + RM68200_DCS_write_1A_1P(0x36, 0x00); + RM68200_DCS_write_1A_1P(0x37, 0x03); + RM68200_DCS_write_1A_1P(0x38, 0x04); + RM68200_DCS_write_1A_1P(0x39, 0x05); + RM68200_DCS_write_1A_1P(0x3A, 0x06); + RM68200_DCS_write_1A_1P(0x3B, 0x00); + RM68200_DCS_write_1A_1P(0x3D, 0x40); + RM68200_DCS_write_1A_1P(0x3F, 0x05); + RM68200_DCS_write_1A_1P(0x40, 0x08); + RM68200_DCS_write_1A_1P(0x41, 0x54); + RM68200_DCS_write_1A_1P(0x42, 0x7D); + RM68200_DCS_write_1A_1P(0x43, 0x00); + RM68200_DCS_write_1A_1P(0x44, 0x00); + RM68200_DCS_write_1A_1P(0x45, 0x00); + RM68200_DCS_write_1A_1P(0x46, 0x00); + RM68200_DCS_write_1A_1P(0x47, 0x00); + RM68200_DCS_write_1A_1P(0x48, 0x00); + RM68200_DCS_write_1A_1P(0x49, 0x00); + RM68200_DCS_write_1A_1P(0x4A, 0x00); + RM68200_DCS_write_1A_1P(0x4B, 0x00); + RM68200_DCS_write_1A_1P(0x4C, 0x00); + RM68200_DCS_write_1A_1P(0x4D, 0x00); + RM68200_DCS_write_1A_1P(0x4E, 0x00); + RM68200_DCS_write_1A_1P(0x4F, 0x00); + RM68200_DCS_write_1A_1P(0x50, 0x00); + RM68200_DCS_write_1A_1P(0x51, 0x00); + RM68200_DCS_write_1A_1P(0x52, 0x00); + RM68200_DCS_write_1A_1P(0x53, 0x00); + RM68200_DCS_write_1A_1P(0x54, 0x00); + RM68200_DCS_write_1A_1P(0x55, 0x00); + RM68200_DCS_write_1A_1P(0x56, 0x00); + RM68200_DCS_write_1A_1P(0x58, 0x00); + RM68200_DCS_write_1A_1P(0x59, 0x00); + RM68200_DCS_write_1A_1P(0x5A, 0x00); + RM68200_DCS_write_1A_1P(0x5B, 0x00); + RM68200_DCS_write_1A_1P(0x5C, 0x00); + RM68200_DCS_write_1A_1P(0x5D, 0x00); + RM68200_DCS_write_1A_1P(0x5E, 0x00); + RM68200_DCS_write_1A_1P(0x5F, 0x00); + RM68200_DCS_write_1A_1P(0x60, 0x00); + RM68200_DCS_write_1A_1P(0x61, 0x00); + RM68200_DCS_write_1A_1P(0x62, 0x00); + RM68200_DCS_write_1A_1P(0x63, 0x00); + RM68200_DCS_write_1A_1P(0x64, 0x00); + RM68200_DCS_write_1A_1P(0x65, 0x00); + RM68200_DCS_write_1A_1P(0x66, 0x00); + RM68200_DCS_write_1A_1P(0x67, 0x00); + RM68200_DCS_write_1A_1P(0x68, 0x00); + RM68200_DCS_write_1A_1P(0x69, 0x00); + RM68200_DCS_write_1A_1P(0x6A, 0x00); + RM68200_DCS_write_1A_1P(0x6B, 0x00); + RM68200_DCS_write_1A_1P(0x6C, 0x00); + RM68200_DCS_write_1A_1P(0x6D, 0x00); + RM68200_DCS_write_1A_1P(0x6E, 0x00); + RM68200_DCS_write_1A_1P(0x6F, 0x00); + RM68200_DCS_write_1A_1P(0x70, 0x00); + RM68200_DCS_write_1A_1P(0x71, 0x00); + RM68200_DCS_write_1A_1P(0x72, 0x20); + RM68200_DCS_write_1A_1P(0x73, 0x00); + RM68200_DCS_write_1A_1P(0x74, 0x08); + RM68200_DCS_write_1A_1P(0x75, 0x08); + RM68200_DCS_write_1A_1P(0x76, 0x08); + RM68200_DCS_write_1A_1P(0x77, 0x08); + RM68200_DCS_write_1A_1P(0x78, 0x08); + RM68200_DCS_write_1A_1P(0x79, 0x08); + RM68200_DCS_write_1A_1P(0x7A, 0x00); + RM68200_DCS_write_1A_1P(0x7B, 0x00); + RM68200_DCS_write_1A_1P(0x7C, 0x00); + RM68200_DCS_write_1A_1P(0x7D, 0x00); + RM68200_DCS_write_1A_1P(0x7E, 0xBF); + RM68200_DCS_write_1A_1P(0x7F, 0x02); + RM68200_DCS_write_1A_1P(0x80, 0x06); + RM68200_DCS_write_1A_1P(0x81, 0x14); + RM68200_DCS_write_1A_1P(0x82, 0x10); + RM68200_DCS_write_1A_1P(0x83, 0x16); + RM68200_DCS_write_1A_1P(0x84, 0x12); + RM68200_DCS_write_1A_1P(0x85, 0x08); + RM68200_DCS_write_1A_1P(0x86, 0x3F); + RM68200_DCS_write_1A_1P(0x87, 0x3F); + RM68200_DCS_write_1A_1P(0x88, 0x3F); + RM68200_DCS_write_1A_1P(0x89, 0x3F); + RM68200_DCS_write_1A_1P(0x8A, 0x3F); + RM68200_DCS_write_1A_1P(0x8B, 0x0C); + RM68200_DCS_write_1A_1P(0x8C, 0x0A); + RM68200_DCS_write_1A_1P(0x8D, 0x0E); + RM68200_DCS_write_1A_1P(0x8E, 0x3F); + RM68200_DCS_write_1A_1P(0x8F, 0x3F); + RM68200_DCS_write_1A_1P(0x90, 0x00); + RM68200_DCS_write_1A_1P(0x91, 0x04); + RM68200_DCS_write_1A_1P(0x92, 0x3F); + RM68200_DCS_write_1A_1P(0x93, 0x3F); + RM68200_DCS_write_1A_1P(0x94, 0x3F); + RM68200_DCS_write_1A_1P(0x95, 0x3F); + RM68200_DCS_write_1A_1P(0x96, 0x05); + RM68200_DCS_write_1A_1P(0x97, 0x01); + RM68200_DCS_write_1A_1P(0x98, 0x3F); + RM68200_DCS_write_1A_1P(0x99, 0x3F); + RM68200_DCS_write_1A_1P(0x9A, 0x0F); + RM68200_DCS_write_1A_1P(0x9B, 0x0B); + RM68200_DCS_write_1A_1P(0x9C, 0x0D); + RM68200_DCS_write_1A_1P(0x9D, 0x3F); + RM68200_DCS_write_1A_1P(0x9E, 0x3F); + RM68200_DCS_write_1A_1P(0x9F, 0x3F); + RM68200_DCS_write_1A_1P(0xA0, 0x3F); + RM68200_DCS_write_1A_1P(0xA2, 0x3F); + RM68200_DCS_write_1A_1P(0xA3, 0x09); + RM68200_DCS_write_1A_1P(0xA4, 0x13); + RM68200_DCS_write_1A_1P(0xA5, 0x17); + RM68200_DCS_write_1A_1P(0xA6, 0x11); + RM68200_DCS_write_1A_1P(0xA7, 0x15); + RM68200_DCS_write_1A_1P(0xA9, 0x07); + RM68200_DCS_write_1A_1P(0xAA, 0x03); + RM68200_DCS_write_1A_1P(0xAB, 0x3F); + RM68200_DCS_write_1A_1P(0xAC, 0x3F); + RM68200_DCS_write_1A_1P(0xAD, 0x05); + RM68200_DCS_write_1A_1P(0xAE, 0x01); + RM68200_DCS_write_1A_1P(0xAF, 0x17); + RM68200_DCS_write_1A_1P(0xB0, 0x13); + RM68200_DCS_write_1A_1P(0xB1, 0x15); + RM68200_DCS_write_1A_1P(0xB2, 0x11); + RM68200_DCS_write_1A_1P(0xB3, 0x0F); + RM68200_DCS_write_1A_1P(0xB4, 0x3F); + RM68200_DCS_write_1A_1P(0xB5, 0x3F); + RM68200_DCS_write_1A_1P(0xB6, 0x3F); + RM68200_DCS_write_1A_1P(0xB7, 0x3F); + RM68200_DCS_write_1A_1P(0xB8, 0x3F); + RM68200_DCS_write_1A_1P(0xB9, 0x0B); + RM68200_DCS_write_1A_1P(0xBA, 0x0D); + RM68200_DCS_write_1A_1P(0xBB, 0x09); + RM68200_DCS_write_1A_1P(0xBC, 0x3F); + RM68200_DCS_write_1A_1P(0xBD, 0x3F); + RM68200_DCS_write_1A_1P(0xBE, 0x07); + RM68200_DCS_write_1A_1P(0xBF, 0x03); + RM68200_DCS_write_1A_1P(0xC0, 0x3F); + RM68200_DCS_write_1A_1P(0xC1, 0x3F); + RM68200_DCS_write_1A_1P(0xC2, 0x3F); + RM68200_DCS_write_1A_1P(0xC3, 0x3F); + RM68200_DCS_write_1A_1P(0xC4, 0x02); + RM68200_DCS_write_1A_1P(0xC5, 0x06); + RM68200_DCS_write_1A_1P(0xC6, 0x3F); + RM68200_DCS_write_1A_1P(0xC7, 0x3F); + RM68200_DCS_write_1A_1P(0xC8, 0x08); + RM68200_DCS_write_1A_1P(0xC9, 0x0C); + RM68200_DCS_write_1A_1P(0xCA, 0x0A); + RM68200_DCS_write_1A_1P(0xCB, 0x3F); + RM68200_DCS_write_1A_1P(0xCC, 0x3F); + RM68200_DCS_write_1A_1P(0xCD, 0x3F); + RM68200_DCS_write_1A_1P(0xCE, 0x3F); + RM68200_DCS_write_1A_1P(0xCF, 0x3F); + RM68200_DCS_write_1A_1P(0xD0, 0x0E); + RM68200_DCS_write_1A_1P(0xD1, 0x10); + RM68200_DCS_write_1A_1P(0xD2, 0x14); + RM68200_DCS_write_1A_1P(0xD3, 0x12); + RM68200_DCS_write_1A_1P(0xD4, 0x16); + RM68200_DCS_write_1A_1P(0xD5, 0x00); + RM68200_DCS_write_1A_1P(0xD6, 0x04); + RM68200_DCS_write_1A_1P(0xD7, 0x3F); + RM68200_DCS_write_1A_1P(0xDC, 0x02); + RM68200_DCS_write_1A_1P(0xDE, 0x12); + RM68200_DCS_write_1A_1P(0xFE, 0x0E); + RM68200_DCS_write_1A_1P(0x01, 0x75); + + /* change to MCS Page 3: Gamma Settings */ + RM68200_DCS_write_1A_1P(0xFE, 0x04); + RM68200_DCS_write_1A_1P(0x60, 0x00); + RM68200_DCS_write_1A_1P(0x61, 0x0C); + RM68200_DCS_write_1A_1P(0x62, 0x12); + RM68200_DCS_write_1A_1P(0x63, 0x0E); + RM68200_DCS_write_1A_1P(0x64, 0x06); + RM68200_DCS_write_1A_1P(0x65, 0x12); + RM68200_DCS_write_1A_1P(0x66, 0x0E); + RM68200_DCS_write_1A_1P(0x67, 0x0B); + RM68200_DCS_write_1A_1P(0x68, 0x15); + RM68200_DCS_write_1A_1P(0x69, 0x0B); + RM68200_DCS_write_1A_1P(0x6A, 0x10); + RM68200_DCS_write_1A_1P(0x6B, 0x07); + RM68200_DCS_write_1A_1P(0x6C, 0x0F); + RM68200_DCS_write_1A_1P(0x6D, 0x12); + RM68200_DCS_write_1A_1P(0x6E, 0x0C); + RM68200_DCS_write_1A_1P(0x6F, 0x00); + RM68200_DCS_write_1A_1P(0x70, 0x00); + RM68200_DCS_write_1A_1P(0x71, 0x0C); + RM68200_DCS_write_1A_1P(0x72, 0x12); + RM68200_DCS_write_1A_1P(0x73, 0x0E); + RM68200_DCS_write_1A_1P(0x74, 0x06); + RM68200_DCS_write_1A_1P(0x75, 0x12); + RM68200_DCS_write_1A_1P(0x76, 0x0E); + RM68200_DCS_write_1A_1P(0x77, 0x0B); + RM68200_DCS_write_1A_1P(0x78, 0x15); + RM68200_DCS_write_1A_1P(0x79, 0x0B); + RM68200_DCS_write_1A_1P(0x7A, 0x10); + RM68200_DCS_write_1A_1P(0x7B, 0x07); + RM68200_DCS_write_1A_1P(0x7C, 0x0F); + RM68200_DCS_write_1A_1P(0x7D, 0x12); + RM68200_DCS_write_1A_1P(0x7E, 0x0C); + RM68200_DCS_write_1A_1P(0x7F, 0x00); + + /* change to MCS Page 0 */ + RM68200_DCS_write_1A_1P(0xFE, 0x00); + RM68200_DCS_write_1A_1P(0x11, 0x00); + mdelay(200); + RM68200_DCS_write_1A_1P(0x29, 0x00); + mdelay(100); + RM68200_DCS_write_1A_0P(0x2C); + RM68200_DCS_write_1A_1P(0x35, 0x00); + mdelay(200); + + return 0; +} + +static int mipid_bl_update_status(struct backlight_device *bl) +{ + return 0; +} + +static int mipid_bl_get_brightness(struct backlight_device *bl) +{ + return 255; +} + +static int mipi_bl_check_fb(struct backlight_device *bl, struct fb_info *fbi) +{ + return 0; +} + +static const struct backlight_ops mipid_lcd_bl_ops = { + .update_status = mipid_bl_update_status, + .get_brightness = mipid_bl_get_brightness, + .check_fb = mipi_bl_check_fb, +}; diff --git a/drivers/video/fbdev/mxc/mxsfb_sii902x.c b/drivers/video/fbdev/mxc/mxsfb_sii902x.c new file mode 100644 index 000000000000..052befe9194c --- /dev/null +++ b/drivers/video/fbdev/mxc/mxsfb_sii902x.c @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2010-2015 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for Sii902x. + */ + +/*! + * @file mxsfb_sii902x.c + * + * @brief Frame buffer driver for SII902x + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/fbcon.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/i2c.h> +#include <linux/fsl_devices.h> +#include <linux/interrupt.h> +#include <linux/reset.h> +#include <asm/mach-types.h> +#include <video/mxc_edid.h> + +#include "mxsfb_sii902x.h" + +#define DRV_NAME "sii902x" + +struct sii902x_data sii902x; + +static void sii902x_poweron(void); +static void sii902x_poweroff(void); + +static int sii902x_in_init_state; + +#ifdef DEBUG +static void dump_fb_videomode(struct fb_videomode *m) +{ + pr_debug("fb_videomode = %d %d %d %d %d %d %d %d %d %d %d %d %d\n", + m->refresh, m->xres, m->yres, m->pixclock, m->left_margin, + m->right_margin, m->upper_margin, m->lower_margin, + m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag); +} +#else +static void dump_fb_videomode(struct fb_videomode *m) +{} +#endif + +static __attribute__ ((unused)) void dump_regs(u8 reg, int len) +{ + u8 buf[50]; + int i; + + i2c_smbus_read_i2c_block_data(sii902x.client, reg, len, buf); + for (i = 0; i < len; i++) + dev_dbg(&sii902x.client->dev, "reg[0x%02X]: 0x%02X\n", + i+reg, buf[i]); +} + +static ssize_t sii902x_show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + strcpy(buf, sii902x.fbi->fix.id); + sprintf(buf+strlen(buf), "\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(fb_name, S_IRUGO, sii902x_show_name, NULL); + +static ssize_t sii902x_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (sii902x.cable_plugin == 0) + strcpy(buf, "plugout\n"); + else + strcpy(buf, "plugin\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(cable_state, S_IRUGO, sii902x_show_state, NULL); + +static ssize_t sii902x_show_edid(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, j, len = 0; + + for (j = 0; j < SII_EDID_LEN/16; j++) { + for (i = 0; i < 16; i++) + len += sprintf(buf+len, "0x%02X ", + sii902x.edid[j*16 + i]); + len += sprintf(buf+len, "\n"); + } + + return len; +} + +static DEVICE_ATTR(edid, S_IRUGO, sii902x_show_edid, NULL); + +static void sii902x_setup(struct fb_info *fbi) +{ + u16 data[4]; + u32 refresh; + u8 *tmp; + int i; + + dev_dbg(&sii902x.client->dev, "Sii902x: setup..\n"); + + /* Power up */ + i2c_smbus_write_byte_data(sii902x.client, 0x1E, 0x00); + + /* set TPI video mode */ + data[0] = PICOS2KHZ(fbi->var.pixclock) / 10; + data[2] = fbi->var.hsync_len + fbi->var.left_margin + + fbi->var.xres + fbi->var.right_margin; + data[3] = fbi->var.vsync_len + fbi->var.upper_margin + + fbi->var.yres + fbi->var.lower_margin; + refresh = data[2] * data[3]; + refresh = (PICOS2KHZ(fbi->var.pixclock) * 1000) / refresh; + data[1] = refresh * 100; + tmp = (u8 *)data; + for (i = 0; i < 8; i++) + i2c_smbus_write_byte_data(sii902x.client, i, tmp[i]); + + /* input bus/pixel: full pixel wide (24bit), rising edge */ + i2c_smbus_write_byte_data(sii902x.client, 0x08, 0x70); + /* Set input format to RGB */ + i2c_smbus_write_byte_data(sii902x.client, 0x09, 0x00); + /* set output format to RGB */ + i2c_smbus_write_byte_data(sii902x.client, 0x0A, 0x00); +} + +static void sii902x_audio_setup(void) +{ + /* audio setup */ + i2c_smbus_write_byte_data(sii902x.client, 0x25, 0x00); + i2c_smbus_write_byte_data(sii902x.client, 0x26, 0x40); + i2c_smbus_write_byte_data(sii902x.client, 0x27, 0x00); +} + +#ifdef CONFIG_FB_MODE_HELPERS +static int sii902x_read_edid(struct fb_info *fbi) +{ + int old, dat, ret, cnt = 100; + unsigned short addr = 0x50; + + dev_dbg(&sii902x.client->dev, "%s\n", __func__); + + old = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old | 0x4); + do { + cnt--; + msleep(10); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + } while ((!(dat & 0x2)) && cnt); + + if (!cnt) { + ret = -1; + goto done; + } + + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old | 0x06); + + /* edid reading */ + ret = mxc_edid_read(sii902x.client->adapter, addr, + sii902x.edid, &sii902x.edid_cfg, fbi); + + cnt = 100; + do { + cnt--; + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old & ~0x6); + msleep(10); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + } while ((dat & 0x6) && cnt); + + if (!cnt) + ret = -1; + +done: + + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old); + return ret; +} +#else +static int sii902x_read_edid(struct fb_info *fbi) +{ + return -1; +} +#endif + +static void sii902x_cable_connected(void) +{ + int i; + const struct fb_videomode *mode; + struct fb_videomode m; + + if (sii902x_read_edid(sii902x.fbi) < 0) + dev_err(&sii902x.client->dev, + "Sii902x: read edid fail\n"); + else { + if (sii902x.fbi->monspecs.modedb_len > 0) { + + fb_destroy_modelist(&sii902x.fbi->modelist); + + for (i = 0; i < sii902x.fbi->monspecs.modedb_len; i++) { + + mode = &sii902x.fbi->monspecs.modedb[i]; + + if (!(mode->vmode & FB_VMODE_INTERLACED)) { + dev_dbg(&sii902x.client->dev, "Added mode %d:", i); + dev_dbg(&sii902x.client->dev, + "xres = %d, yres = %d, freq = %d, vmode = %d, flag = %d\n", + mode->xres, mode->yres, mode->refresh, + mode->vmode, mode->flag); + + fb_add_videomode(mode, &sii902x.fbi->modelist); + } + } + + /* Set the default mode only once. */ + if (!sii902x.dft_mode_set && + sii902x.mode_str && sii902x.bits_per_pixel) { + + dev_dbg(&sii902x.client->dev, "%s: setting to default=%s bpp=%d\n", + __func__, sii902x.mode_str, sii902x.bits_per_pixel); + + fb_find_mode(&sii902x.fbi->var, sii902x.fbi, + sii902x.mode_str, NULL, 0, NULL, + sii902x.bits_per_pixel); + + sii902x.dft_mode_set = true; + } + + fb_var_to_videomode(&m, &sii902x.fbi->var); + dump_fb_videomode(&m); + + mode = fb_find_nearest_mode(&m, + &sii902x.fbi->modelist); + + /* update fbi mode */ + sii902x.fbi->mode = (struct fb_videomode *)mode; + + fb_videomode_to_var(&sii902x.fbi->var, mode); + + sii902x.fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + if (!fb_set_var(sii902x.fbi, &sii902x.fbi->var)) + fbcon_update_vcs(sii902x.fbi, sii902x.fbi->var.activate & FB_ACTIVATE_ALL); + console_unlock(); + } + /* Power on sii902x */ + sii902x_poweron(); + } +} + +static void det_worker(struct work_struct *work) +{ + int dat; + char event_string[16]; + char *envp[] = { event_string, NULL }; + + dev_dbg(&sii902x.client->dev, "%s\n", __func__); + + dat = i2c_smbus_read_byte_data(sii902x.client, 0x3D); + + /* cable connection state */ + if (dat & 0x4) { + sii902x.cable_plugin = 1; + dev_dbg(&sii902x.client->dev, "EVENT=plugin\n"); + sprintf(event_string, "EVENT=plugin"); + sii902x_cable_connected(); + } else { + sii902x.cable_plugin = 0; + dev_dbg(&sii902x.client->dev, "EVENT=plugout\n"); + sprintf(event_string, "EVENT=plugout"); + /* Power off sii902x */ + sii902x_poweroff(); + } + kobject_uevent_env(&sii902x.client->dev.kobj, KOBJ_CHANGE, envp); + + i2c_smbus_write_byte_data(sii902x.client, 0x3D, dat); + + dev_dbg(&sii902x.client->dev, "exit %s\n", __func__); + +} + +static irqreturn_t sii902x_detect_handler(int irq, void *data) +{ + if (sii902x.fbi) + schedule_delayed_work(&(sii902x.det_work), msecs_to_jiffies(50)); + + return IRQ_HANDLED; +} + +static int sii902x_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + /* Check if our FB just registered */ + if (!sii902x.fbi && val == FB_EVENT_FB_REGISTERED && + !strncmp(fbi->fix.id, "mxs-lcdif", 9)) { + pr_info("sii902x bound to %s from %s\n", + fbi->fix.id, dev_name(fbi->device)); + sii902x.fbi = fbi; + } + + /* Ignore if not our FB */ + if (fbi != sii902x.fbi) + return 0; + + /* Ignore if driver did not probe yet */ + if (sii902x_in_init_state) + return 0; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + /* Manually trigger a plugin/plugout interrupter */ + schedule_delayed_work(&(sii902x.det_work), 0); + /* Dealy 20ms to wait cable states detected */ + msleep(20); + fb_show_logo(fbi, 0); + + break; + case FB_EVENT_MODE_CHANGE: + sii902x_setup(fbi); + break; + case FB_EVENT_BLANK: + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + dev_dbg(&sii902x.client->dev, "FB_BLANK_UNBLANK\n"); + sii902x_poweron(); + } else { + dev_dbg(&sii902x.client->dev, "FB_BLANK_BLANK\n"); + sii902x_poweroff(); + } + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = sii902x_fb_event, +}; + +static int mxsfb_get_of_property(void) +{ + struct device_node *np = sii902x.client->dev.of_node; + const char *mode_str; + int bits_per_pixel, ret; + + ret = of_property_read_string(np, "mode_str", &mode_str); + if (ret < 0) { + dev_warn(&sii902x.client->dev, "get of property mode_str fail\n"); + return ret; + } + ret = of_property_read_u32(np, "bits-per-pixel", &bits_per_pixel); + if (ret) { + dev_warn(&sii902x.client->dev, "get of property bpp fail\n"); + return ret; + } + + sii902x.mode_str = mode_str; + sii902x.bits_per_pixel = bits_per_pixel; + + return ret; +} + +static int sii902x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i, dat, ret; + struct fb_info edid_fbi; + struct fb_info *init_fbi = sii902x.fbi; + + memset(&sii902x, 0, sizeof(sii902x)); + + sii902x.client = client; + + dev_dbg(&sii902x.client->dev, "%s\n", __func__);; + + /* Reset sii902x */ + ret = device_reset(&sii902x.client->dev); + if (ret) + dev_warn(&sii902x.client->dev, "No reset pin found\n"); + if (ret == -EPROBE_DEFER) + return ret; + + /* Set 902x in hardware TPI mode on and jump out of D3 state */ + if (i2c_smbus_write_byte_data(sii902x.client, 0xc7, 0x00) < 0) { + dev_err(&sii902x.client->dev, + "Sii902x: cound not find device\n"); + return -ENODEV; + } + + /* read device ID */ + for (i = 10; i > 0; i--) { + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1B); + dev_dbg(&sii902x.client->dev, "Sii902x: read id = 0x%02X", dat); + if (dat == 0xb0) { + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1C); + dev_dbg(&sii902x.client->dev, "-0x%02X", dat); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1D); + dev_dbg(&sii902x.client->dev, "-0x%02X", dat); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x30); + dev_dbg(&sii902x.client->dev, "-0x%02X\n", dat); + break; + } + } + if (i == 0) { + dev_err(&sii902x.client->dev, + "Sii902x: cound not find device\n"); + return -ENODEV; + } + + /* enable hmdi audio */ + sii902x_audio_setup(); + + /* try to read edid, only if cable is plugged in */ + dat = i2c_smbus_read_byte_data(sii902x.client, 0x3D); + if (dat & 0x04) { + ret = sii902x_read_edid(&edid_fbi); + if (ret < 0) + dev_warn(&sii902x.client->dev, "Can not read edid\n"); + } + + if (sii902x.client->irq) { + ret = request_irq(sii902x.client->irq, sii902x_detect_handler, + IRQF_TRIGGER_FALLING, + "SII902x_det", &sii902x); + if (ret < 0) + dev_warn(&sii902x.client->dev, + "Sii902x: cound not request det irq %d\n", + sii902x.client->irq); + else { + /*enable cable hot plug irq*/ + i2c_smbus_write_byte_data(sii902x.client, 0x3c, 0x01); + INIT_DELAYED_WORK(&(sii902x.det_work), det_worker); + } + ret = device_create_file(&sii902x.client->dev, &dev_attr_fb_name); + if (ret < 0) + dev_warn(&sii902x.client->dev, + "Sii902x: cound not create sys node for fb name\n"); + ret = device_create_file(&sii902x.client->dev, &dev_attr_cable_state); + if (ret < 0) + dev_warn(&sii902x.client->dev, + "Sii902x: cound not create sys node for cable state\n"); + ret = device_create_file(&sii902x.client->dev, &dev_attr_edid); + if (ret < 0) + dev_warn(&sii902x.client->dev, + "Sii902x: cound not create sys node for edid\n"); + + } + + mxsfb_get_of_property(); + + if (init_fbi) { + sii902x.fbi = init_fbi; + + /* Manually trigger a plugin/plugout interrupter to check cable state */ + schedule_delayed_work(&(sii902x.det_work), msecs_to_jiffies(50)); + } + + sii902x_in_init_state = 0; + + dev_set_drvdata(&sii902x.client->dev, &sii902x); + + sii902x_register_audio_driver(&sii902x.client->dev); + + return 0; +} + +static int sii902x_remove(struct i2c_client *client) +{ + fb_unregister_client(&nb); + sii902x_poweroff(); + + return 0; +} + +static void sii902x_poweron(void) +{ + /* Turn on DVI or HDMI */ + if (sii902x.edid_cfg.hdmi_cap) + i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x01); + else + i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x00); + return; +} + +static void sii902x_poweroff(void) +{ + /* disable tmds before changing resolution */ + if (sii902x.edid_cfg.hdmi_cap) + i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x11); + else + i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x10); + + return; +} + +static int __init sii902x_init(void) +{ + sii902x_in_init_state = 1; + + return fb_register_client(&nb); +} +fs_initcall_sync(sii902x_init); + +static const struct i2c_device_id sii902x_id[] = { + { DRV_NAME, 0}, + { }, +}; +MODULE_DEVICE_TABLE(i2c, sii902x_id); + +static const struct of_device_id sii902x_dt_ids[] = { + { .compatible = "SiI,sii902x", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sii902x_dt_ids); + +static struct i2c_driver sii902x_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = sii902x_dt_ids, + }, + .probe = sii902x_probe, + .remove = sii902x_remove, + .id_table = sii902x_id, +}; + +module_i2c_driver(sii902x_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SII902x DVI/HDMI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/mxc/mxsfb_sii902x.h b/drivers/video/fbdev/mxc/mxsfb_sii902x.h new file mode 100644 index 000000000000..5f6376a37c32 --- /dev/null +++ b/drivers/video/fbdev/mxc/mxsfb_sii902x.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +// Copyright 2019 NXP + + +#ifndef _MXSFB_SII902X_H +#define _MXSFB_SII902X_H + +#include <video/mxc_edid.h> + +#define SII_EDID_LEN 512 + +struct sii902x_data { + struct i2c_client *client; + struct delayed_work det_work; + struct fb_info *fbi; + struct mxc_edid_cfg edid_cfg; + u8 cable_plugin; + u8 edid[SII_EDID_LEN]; + bool dft_mode_set; + const char *mode_str; + int bits_per_pixel; +}; + +void sii902x_register_audio_driver(struct device *dev); + +#endif /* _MXSFB_SII902X_H */ diff --git a/drivers/video/fbdev/mxc/mxsfb_sii902x_audio.c b/drivers/video/fbdev/mxc/mxsfb_sii902x_audio.c new file mode 100644 index 000000000000..a4f1d58d8c43 --- /dev/null +++ b/drivers/video/fbdev/mxc/mxsfb_sii902x_audio.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP + +#include <linux/clk.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/component.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/irq.h> +#include <linux/of_device.h> +#include <sound/hdmi-codec.h> +#include <drm/drm_edid.h> + +#include "mxsfb_sii902x.h" + +static const unsigned int audio_rates[] = { + 32000, + 44100, + 48000, + 88200, + 96000, + 176400, + 192000, +}; + +/* + * HDMI audio codec callbacks + */ +static int sii902x_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct sii902x_data *sii902x = dev_get_drvdata(dev); + unsigned char reg; + + /* sii90sx hdmi audio setup */ + i2c_smbus_write_byte_data(sii902x->client, 0x26, 0x90); + i2c_smbus_write_byte_data(sii902x->client, 0x20, 0x2d); + i2c_smbus_write_byte_data(sii902x->client, 0x1f, 0x88); + i2c_smbus_write_byte_data(sii902x->client, 0x1f, 0x91); + i2c_smbus_write_byte_data(sii902x->client, 0x1f, 0xa2); + i2c_smbus_write_byte_data(sii902x->client, 0x1f, 0xb3); + i2c_smbus_write_byte_data(sii902x->client, 0x27, 0); + + switch (params->sample_rate) { + case 44100: + reg = 0; + break; + case 48000: + reg = 0x2; + break; + case 32000: + reg = 0x3; + break; + case 88200: + reg = 0x8; + break; + case 96000: + reg = 0xa; + break; + case 176400: + reg = 0xc; + break; + case 192000: + reg = 0xe; + break; + default: + reg = 0x1; + break; + } + + i2c_smbus_write_byte_data(sii902x->client, 0x24, reg); + i2c_smbus_write_byte_data(sii902x->client, 0x25, 0x0b); + i2c_smbus_write_byte_data(sii902x->client, 0x26, 0x80); + + return 0; +} + +static void sii902x_audio_shutdown(struct device *dev, void *data) +{ + struct sii902x_data *sii902x = dev_get_drvdata(dev); + + i2c_smbus_write_byte_data(sii902x->client, 0x26, 0x10); +} + + +/* Most of these function is copy from the drivers/gpu/drm/drm_edid.c */ +typedef void detailed_cb(struct detailed_timing *timing, void *closure); +static void +cea_for_detailed_block(u8 *ext, detailed_cb *cb, void *closure) +{ + int i, n = 0; + u8 d = ext[0x02]; + u8 *det_base = ext + d; + + n = (127 - d) / 18; + for (i = 0; i < n; i++) + cb((struct detailed_timing *)(det_base + 18 * i), closure); +} + +static void +vtb_for_detailed_block(u8 *ext, detailed_cb *cb, void *closure) +{ + unsigned int i, n = min_t(int, ext[0x02], 6); + u8 *det_base = ext + 5; + + if (ext[0x01] != 1) + return; /* unknown version */ + + for (i = 0; i < n; i++) + cb((struct detailed_timing *)(det_base + 18 * i), closure); +} + +#define EDID_DETAILED_TIMINGS 4 +#define AUDIO_BLOCK 0x01 +#define VIDEO_BLOCK 0x02 +#define VENDOR_BLOCK 0x03 +#define SPEAKER_BLOCK 0x04 + +static void +eld_for_detailed_block(u8 *raw_edid, detailed_cb *cb, void *closure) +{ + int i; + struct edid *edid = (struct edid *)raw_edid; + + if (edid == NULL) + return; + + for (i = 0; i < EDID_DETAILED_TIMINGS; i++) + cb(&(edid->detailed_timings[i]), closure); + + for (i = 1; i <= raw_edid[0x7e]; i++) { + u8 *ext = raw_edid + (i * EDID_LENGTH); + + switch (*ext) { + case CEA_EXT: + cea_for_detailed_block(ext, cb, closure); + break; + case VTB_EXT: + vtb_for_detailed_block(ext, cb, closure); + break; + default: + break; + } + } +} + +static void +monitor_name(struct detailed_timing *t, void *data) +{ + if (t->data.other_data.type == EDID_DETAIL_MONITOR_NAME) + *(u8 **)data = t->data.other_data.data.str.str; +} + +static int get_monitor_name(struct edid *edid, char name[13]) +{ + char *edid_name = NULL; + int mnl; + + if (!edid || !name) + return 0; + + eld_for_detailed_block((u8 *)edid, monitor_name, &edid_name); + for (mnl = 0; edid_name && mnl < 13; mnl++) { + if (edid_name[mnl] == 0x0a) + break; + + name[mnl] = edid_name[mnl]; + } + + return mnl; +} + +static int +cea_revision(const u8 *cea) +{ + return cea[1]; +} + +static int +cea_db_offsets(const u8 *cea, int *start, int *end) +{ + /* Data block offset in CEA extension block */ + *start = 4; + *end = cea[2]; + if (*end == 0) + *end = 127; + if (*end < 4 || *end > 127) + return -ERANGE; + return 0; +} + +static int +cea_db_payload_len(const u8 *db) +{ + return db[0] & 0x1f; +} + +static int +cea_db_tag(const u8 *db) +{ + return db[0] >> 5; +} + +#define HDMI_IEEE_OUI 0x000c03 +static bool cea_db_is_hdmi_vsdb(const u8 *db) +{ + int hdmi_id; + + if (cea_db_tag(db) != VENDOR_BLOCK) + return false; + + if (cea_db_payload_len(db) < 5) + return false; + + hdmi_id = db[1] | (db[2] << 8) | (db[3] << 16); + + return hdmi_id == HDMI_IEEE_OUI; +} + +#define for_each_cea_db(cea, i, start, end) \ + for ((i) = (start); (i) < (end) && (i) + cea_db_payload_len(&(cea)[(i)]) < (end); (i) += cea_db_payload_len(&(cea)[(i)]) + 1) + +static u8 *find_cea_extension(const struct edid *edid, int ext_id) +{ + u8 *edid_ext = NULL; + int i; + + /* No EDID or EDID extensions */ + if (edid == NULL || edid->extensions == 0) + return NULL; + + /* Find CEA extension */ + for (i = 0; i < edid->extensions; i++) { + edid_ext = (u8 *)edid + EDID_LENGTH * (i + 1); + if (edid_ext[0] == ext_id) + break; + } + + if (i == edid->extensions) + return NULL; + + return edid_ext; +} + +static void edid_to_eld(char *eld, struct edid *edid) +{ + u8 *cea; + u8 *db; + int total_sad_count = 0; + int mnl; + int dbl; + + if (!edid) + return; + + cea = find_cea_extension(edid, CEA_EXT); + if (!cea) + return; + + mnl = get_monitor_name(edid, &eld[DRM_ELD_MONITOR_NAME_STRING]); + + eld[DRM_ELD_CEA_EDID_VER_MNL] = cea[1] << DRM_ELD_CEA_EDID_VER_SHIFT; + eld[DRM_ELD_CEA_EDID_VER_MNL] |= mnl; + + eld[DRM_ELD_VER] = DRM_ELD_VER_CEA861D; + + eld[DRM_ELD_MANUFACTURER_NAME0] = edid->mfg_id[0]; + eld[DRM_ELD_MANUFACTURER_NAME1] = edid->mfg_id[1]; + eld[DRM_ELD_PRODUCT_CODE0] = edid->prod_code[0]; + eld[DRM_ELD_PRODUCT_CODE1] = edid->prod_code[1]; + + if (cea_revision(cea) >= 3) { + int i, start, end; + + if (cea_db_offsets(cea, &start, &end)) { + start = 0; + end = 0; + } + + for_each_cea_db(cea, i, start, end) { + db = &cea[i]; + dbl = cea_db_payload_len(db); + + switch (cea_db_tag(db)) { + int sad_count; + + case AUDIO_BLOCK: + /* Audio Data Block, contains SADs */ + sad_count = min(dbl / 3, 15 - total_sad_count); + if (sad_count >= 1) + memcpy(&eld[DRM_ELD_CEA_SAD(mnl, total_sad_count)], + &db[1], sad_count * 3); + total_sad_count += sad_count; + break; + case SPEAKER_BLOCK: + /* Speaker Allocation Data Block */ + if (dbl >= 1) + eld[DRM_ELD_SPEAKER] = db[1]; + break; + case VENDOR_BLOCK: + /* HDMI Vendor-Specific Data Block */ + if (cea_db_is_hdmi_vsdb(db)) { + u8 len = cea_db_payload_len(db); + + if (len >= 6 && (db[6] & (1 << 7))) + eld[DRM_ELD_SAD_COUNT_CONN_TYPE] |= DRM_ELD_SUPPORTS_AI; + + } + break; + default: + break; + } + } + } + + eld[DRM_ELD_SAD_COUNT_CONN_TYPE] |= total_sad_count << DRM_ELD_SAD_COUNT_SHIFT; + + eld[DRM_ELD_SAD_COUNT_CONN_TYPE] |= DRM_ELD_CONN_TYPE_HDMI; + + eld[DRM_ELD_BASELINE_ELD_LEN] = + DIV_ROUND_UP(drm_eld_calc_baseline_block_size(eld), 4); +} + +static int sii902x_audio_get_eld(struct device *dev, void *data, uint8_t *buf, size_t len) +{ + struct sii902x_data *sii902x = dev_get_drvdata(dev); + uint8_t eld[128]; + + memset(eld, 0, 128); + + edid_to_eld(eld, (struct edid *)sii902x->edid); + + memcpy(buf, eld, min(sizeof(eld), len)); + + return 0; +} + +static const struct hdmi_codec_ops sii902x_audio_codec_ops = { + .hw_params = sii902x_audio_hw_params, + .audio_shutdown = sii902x_audio_shutdown, + .get_eld = sii902x_audio_get_eld, +}; + +void sii902x_register_audio_driver(struct device *dev) +{ + struct hdmi_codec_pdata codec_data = { + .ops = &sii902x_audio_codec_ops, + .max_i2s_channels = 8, + .i2s = 1, + }; + struct platform_device *pdev; + + pdev = platform_device_register_data(dev, HDMI_CODEC_DRV_NAME, + 1, &codec_data, + sizeof(codec_data)); + if (IS_ERR(pdev)) + return; +} |