diff options
-rw-r--r-- | drivers/video/fbdev/mxc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/video/fbdev/mxc/Makefile | 2 | ||||
-rw-r--r-- | drivers/video/fbdev/mxc/mipi_dsi.h | 167 | ||||
-rw-r--r-- | drivers/video/fbdev/mxc/mipi_dsi_northwest.c | 1556 | ||||
-rw-r--r-- | drivers/video/fbdev/mxc/mxc_edid.c | 771 | ||||
-rw-r--r-- | include/linux/mipi_dsi_northwest.h | 164 | ||||
-rw-r--r-- | include/video/mxc_edid.h | 107 |
7 files changed, 2777 insertions, 0 deletions
diff --git a/drivers/video/fbdev/mxc/Kconfig b/drivers/video/fbdev/mxc/Kconfig index e861613554d5..d0bc4a2bb1a8 100644 --- a/drivers/video/fbdev/mxc/Kconfig +++ b/drivers/video/fbdev/mxc/Kconfig @@ -29,3 +29,13 @@ config FB_MXC_OVERLAY 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 diff --git a/drivers/video/fbdev/mxc/Makefile b/drivers/video/fbdev/mxc/Makefile index 3898338bfd1f..136ecd815e36 100644 --- a/drivers/video/fbdev/mxc/Makefile +++ b/drivers/video/fbdev/mxc/Makefile @@ -1 +1,3 @@ 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 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/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/include/linux/mipi_dsi_northwest.h b/include/linux/mipi_dsi_northwest.h new file mode 100644 index 000000000000..8cb379c85ef5 --- /dev/null +++ b/include/linux/mipi_dsi_northwest.h @@ -0,0 +1,164 @@ +/* + * 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. + */ + +#ifndef __INCLUDE_MIPI_DSI_NORTHWEST_H +#define __INCLUDE_MIPI_DSI_NORTHWEST_H + +/* ---------------------------- register offsets --------------------------- */ + +/* sim */ +#define SIM_SOPT1 0x0 +#define MIPI_ISO_DISABLE 0x8 + +#define SIM_SOPT1CFG 0x4 +#define DSI_RST_DPI_N 0x80000000 +#define DSI_RST_ESC_N 0x40000000 +#define DSI_RST_BYTE_N 0x20000000 +#define DSI_SD 0x200 +#define DSI_CM 0x100 +#define DSI_PLL_EN 0x80 + +/* SRC */ +#define SRC_MIPIPHY_RCR 0x28 +#define MIPI_DSI_RESET_BYTE_N 0x2 +#define MIPI_DSI_RESET_N 0x4 +#define MIPI_DSI_DPI_RESET_N 0x8 +#define MIPI_DSI_ESC_RESET_N 0x10 +#define MIPI_DSI_PCLK_RESET_N 0x20 + +/* GPR */ +#define IOMUXC_GPR_GPR13 0x34 +#define GPR_MIPI_MUX_SEL 0x4 + +/* dphy */ +#define DPHY_PD_DPHY 0x300 +#define DPHY_M_PRG_HS_PREPARE 0x304 +#define DPHY_MC_PRG_HS_PREPARE 0x308 +#define DPHY_M_PRG_HS_ZERO 0x30c +#define DPHY_MC_PRG_HS_ZERO 0x310 +#define DPHY_M_PRG_HS_TRAIL 0x314 +#define DPHY_MC_PRG_HS_TRAIL 0x318 +#define DPHY_PD_PLL 0x31c +#define DPHY_TST 0x320 +#define DPHY_CN 0x324 +#define DPHY_CM 0x328 +#define DPHY_CO 0x32c +#define DPHY_LOCK 0x330 +#define DPHY_LOCK_BYP 0x334 +#define DPHY_RTERM_SEL 0x338 +#define DPHY_AUTO_PD_EN 0x33c +#define DPHY_RXLPRP 0x340 +#define DPHY_RXCDRP 0x344 + +/* host */ +#define HOST_CFG_NUM_LANES 0x0 +#define HOST_CFG_NONCONTINUOUS_CLK 0x4 +#define HOST_CFG_T_PRE 0x8 +#define HOST_CFG_T_POST 0xc +#define HOST_CFG_TX_GAP 0x10 +#define HOST_CFG_AUTOINSERT_EOTP 0x14 +#define HOST_CFG_EXTRA_CMDS_AFTER_EOTP 0x18 +#define HOST_CFG_HTX_TO_COUNT 0x1c +#define HOST_CFG_LRX_H_TO_COUNT 0x20 +#define HOST_CFG_BTA_H_TO_COUNT 0x24 +#define HOST_CFG_TWAKEUP 0x28 +#define HOST_CFG_STATUS_OUT 0x2c +#define HOST_RX_ERROR_STATUS 0x30 + +/* dpi */ +#define DPI_PIXEL_PAYLOAD_SIZE 0x200 +#define DPI_PIXEL_FIFO_SEND_LEVEL 0x204 +#define DPI_INTERFACE_COLOR_CODING 0x208 +#define DPI_PIXEL_FORMAT 0x20c +#define DPI_VSYNC_POLARITY 0x210 +#define DPI_HSYNC_POLARITY 0x214 +#define DPI_VIDEO_MODE 0x218 +#define DPI_HFP 0x21c +#define DPI_HBP 0x220 +#define DPI_HSA 0x224 +#define DPI_ENABLE_MULT_PKTS 0x228 +#define DPI_VBP 0x22c +#define DPI_VFP 0x230 +#define DPI_BLLP_MODE 0x234 +#define DPI_USE_NULL_PKT_BLLP 0x238 +#define DPI_VACTIVE 0x23c +#define DPI_VC 0x240 + +/* apb pkt */ +#define HOST_TX_PAYLOAD 0x280 + +#define HOST_PKT_CONTROL 0x284 +#define HOST_PKT_CONTROL_WC(x) (((x) & 0xffff) << 0) +#define HOST_PKT_CONTROL_VC(x) (((x) & 0x3) << 16) +#define HOST_PKT_CONTROL_DT(x) (((x) & 0x3f) << 18) +#define HOST_PKT_CONTROL_HS_SEL(x) (((x) & 0x1) << 24) +#define HOST_PKT_CONTROL_BTA_TX(x) (((x) & 0x1) << 25) +#define HOST_PKT_CONTROL_BTA_NO_TX(x) (((x) & 0x1) << 26) + +#define HOST_SEND_PACKET 0x288 +#define HOST_PKT_STATUS 0x28c +#define HOST_PKT_FIFO_WR_LEVEL 0x290 +#define HOST_PKT_FIFO_RD_LEVEL 0x294 +#define HOST_PKT_RX_PAYLOAD 0x298 + +#define HOST_PKT_RX_PKT_HEADER 0x29c +#define HOST_PKT_RX_PKT_HEADER_WC(x) (((x) & 0xffff) << 0) +#define HOST_PKT_RX_PKT_HEADER_DT(x) (((x) & 0x3f) << 16) +#define HOST_PKT_RX_PKT_HEADER_VC(x) (((x) & 0x3) << 22) + +#define HOST_IRQ_STATUS 0x2a0 +#define HOST_IRQ_STATUS_SM_NOT_IDLE (1 << 0) +#define HOST_IRQ_STATUS_TX_PKT_DONE (1 << 1) +#define HOST_IRQ_STATUS_DPHY_DIRECTION (1 << 2) +#define HOST_IRQ_STATUS_TX_FIFO_OVFLW (1 << 3) +#define HOST_IRQ_STATUS_TX_FIFO_UDFLW (1 << 4) +#define HOST_IRQ_STATUS_RX_FIFO_OVFLW (1 << 5) +#define HOST_IRQ_STATUS_RX_FIFO_UDFLW (1 << 6) +#define HOST_IRQ_STATUS_RX_PKT_HDR_RCVD (1 << 7) +#define HOST_IRQ_STATUS_RX_PKT_PAYLOAD_DATA_RCVD (1 << 8) +#define HOST_IRQ_STATUS_HOST_BTA_TIMEOUT (1 << 29) +#define HOST_IRQ_STATUS_LP_RX_TIMEOUT (1 << 30) +#define HOST_IRQ_STATUS_HS_TX_TIMEOUT (1 << 31) + +#define HOST_IRQ_STATUS2 0x2a4 +#define HOST_IRQ_STATUS2_SINGLE_BIT_ECC_ERR (1 << 0) +#define HOST_IRQ_STATUS2_MULTI_BIT_ECC_ERR (1 << 1) +#define HOST_IRQ_STATUS2_CRC_ERR (1 << 2) + +#define HOST_IRQ_MASK 0x2a8 +#define HOST_IRQ_MASK_SM_NOT_IDLE_MASK (1 << 0) +#define HOST_IRQ_MASK_TX_PKT_DONE_MASK (1 << 1) +#define HOST_IRQ_MASK_DPHY_DIRECTION_MASK (1 << 2) +#define HOST_IRQ_MASK_TX_FIFO_OVFLW_MASK (1 << 3) +#define HOST_IRQ_MASK_TX_FIFO_UDFLW_MASK (1 << 4) +#define HOST_IRQ_MASK_RX_FIFO_OVFLW_MASK (1 << 5) +#define HOST_IRQ_MASK_RX_FIFO_UDFLW_MASK (1 << 6) +#define HOST_IRQ_MASK_RX_PKT_HDR_RCVD_MASK (1 << 7) +#define HOST_IRQ_MASK_RX_PKT_PAYLOAD_DATA_RCVD_MASK (1 << 8) +#define HOST_IRQ_MASK_HOST_BTA_TIMEOUT_MASK (1 << 29) +#define HOST_IRQ_MASK_LP_RX_TIMEOUT_MASK (1 << 30) +#define HOST_IRQ_MASK_HS_TX_TIMEOUT_MASK (1 << 31) + +#define HOST_IRQ_MASK2 0x2ac +#define HOST_IRQ_MASK2_SINGLE_BIT_ECC_ERR_MASK (1 << 0) +#define HOST_IRQ_MASK2_MULTI_BIT_ECC_ERR_MASK (1 << 1) +#define HOST_IRQ_MASK2_CRC_ERR_MASK (1 << 2) + +/* ------------------------------------- end -------------------------------- */ + +#endif diff --git a/include/video/mxc_edid.h b/include/video/mxc_edid.h new file mode 100644 index 000000000000..ff7ddd92a3bc --- /dev/null +++ b/include/video/mxc_edid.h @@ -0,0 +1,107 @@ +/* + * 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.h + * + * @brief MXC EDID tools + * + * @ingroup Framebuffer + */ + +#ifndef MXC_EDID_H +#define MXC_EDID_H + +#include <linux/fb.h> + +#define FB_VMODE_ASPECT_4_3 0x10 +#define FB_VMODE_ASPECT_16_9 0x20 +#define FB_VMODE_ASPECT_MASK (FB_VMODE_ASPECT_4_3 | FB_VMODE_ASPECT_16_9) + +enum cea_audio_coding_types { + AUDIO_CODING_TYPE_REF_STREAM_HEADER = 0, + AUDIO_CODING_TYPE_LPCM = 1, + AUDIO_CODING_TYPE_AC3 = 2, + AUDIO_CODING_TYPE_MPEG1 = 3, + AUDIO_CODING_TYPE_MP3 = 4, + AUDIO_CODING_TYPE_MPEG2 = 5, + AUDIO_CODING_TYPE_AACLC = 6, + AUDIO_CODING_TYPE_DTS = 7, + AUDIO_CODING_TYPE_ATRAC = 8, + AUDIO_CODING_TYPE_SACD = 9, + AUDIO_CODING_TYPE_EAC3 = 10, + AUDIO_CODING_TYPE_DTS_HD = 11, + AUDIO_CODING_TYPE_MLP = 12, + AUDIO_CODING_TYPE_DST = 13, + AUDIO_CODING_TYPE_WMAPRO = 14, + AUDIO_CODING_TYPE_RESERVED = 15, +}; + +struct mxc_hdmi_3d_format { + unsigned char vic_order_2d; + unsigned char struct_3d; + unsigned char detail_3d; + unsigned char reserved; +}; + +struct mxc_edid_cfg { + bool cea_underscan; + bool cea_basicaudio; + bool cea_ycbcr444; + bool cea_ycbcr422; + bool hdmi_cap; + + /*VSD*/ + bool vsd_support_ai; + bool vsd_dc_48bit; + bool vsd_dc_36bit; + bool vsd_dc_30bit; + bool vsd_dc_y444; + bool vsd_dvi_dual; + + bool vsd_cnc0; + bool vsd_cnc1; + bool vsd_cnc2; + bool vsd_cnc3; + + u8 vsd_video_latency; + u8 vsd_audio_latency; + u8 vsd_I_video_latency; + u8 vsd_I_audio_latency; + + u8 physical_address[4]; + u8 hdmi_vic[64]; + struct mxc_hdmi_3d_format hdmi_3d_format[64]; + u16 hdmi_3d_mask_all; + u16 hdmi_3d_struct_all; + u32 vsd_max_tmdsclk_rate; + + u8 max_channels; + u8 sample_sizes; + u8 sample_rates; + u8 speaker_alloc; +}; + +extern const struct fb_videomode mxc_cea_mode[64]; + +int mxc_edid_var_to_vic(struct fb_var_screeninfo *var); +int mxc_edid_mode_to_vic(const struct fb_videomode *mode); +int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, struct mxc_edid_cfg *cfg, struct fb_info *fbi); +int mxc_edid_parse_ext_blk(unsigned char *edid, struct mxc_edid_cfg *cfg, + struct fb_monspecs *specs); +#endif |