| /* |
| * 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/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"); |