| /* |
| * Copyright © 2010 Intel Corporation |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| * DEALINGS IN THE SOFTWARE. |
| * |
| * Authors: |
| * jim liu <jim.liu@intel.com> |
| * Jackie Li<yaodong.li@intel.com> |
| */ |
| |
| #include <linux/module.h> |
| |
| #include "mdfld_dsi_output.h" |
| #include "mdfld_dsi_dpi.h" |
| #include "mdfld_output.h" |
| #include "mdfld_dsi_pkg_sender.h" |
| #include "tc35876x-dsi-lvds.h" |
| #include <linux/pm_runtime.h> |
| #include <asm/intel_scu_ipc.h> |
| |
| /* get the LABC from command line. */ |
| static int LABC_control = 1; |
| |
| #ifdef MODULE |
| module_param(LABC_control, int, 0644); |
| #else |
| |
| static int __init parse_LABC_control(char *arg) |
| { |
| /* LABC control can be passed in as a cmdline parameter */ |
| /* to enable this feature add LABC=1 to cmdline */ |
| /* to disable this feature add LABC=0 to cmdline */ |
| if (!arg) |
| return -EINVAL; |
| |
| if (!strcasecmp(arg, "0")) |
| LABC_control = 0; |
| else if (!strcasecmp(arg, "1")) |
| LABC_control = 1; |
| |
| return 0; |
| } |
| early_param("LABC", parse_LABC_control); |
| #endif |
| |
| /** |
| * Check and see if the generic control or data buffer is empty and ready. |
| */ |
| void mdfld_dsi_gen_fifo_ready(struct drm_device *dev, u32 gen_fifo_stat_reg, |
| u32 fifo_stat) |
| { |
| u32 GEN_BF_time_out_count; |
| |
| /* Check MIPI Adatper command registers */ |
| for (GEN_BF_time_out_count = 0; |
| GEN_BF_time_out_count < GEN_FB_TIME_OUT; |
| GEN_BF_time_out_count++) { |
| if ((REG_READ(gen_fifo_stat_reg) & fifo_stat) == fifo_stat) |
| break; |
| udelay(100); |
| } |
| |
| if (GEN_BF_time_out_count == GEN_FB_TIME_OUT) |
| DRM_ERROR("mdfld_dsi_gen_fifo_ready, Timeout. gen_fifo_stat_reg = 0x%x.\n", |
| gen_fifo_stat_reg); |
| } |
| |
| /** |
| * Manage the DSI MIPI keyboard and display brightness. |
| * FIXME: this is exported to OSPM code. should work out an specific |
| * display interface to OSPM. |
| */ |
| |
| void mdfld_dsi_brightness_init(struct mdfld_dsi_config *dsi_config, int pipe) |
| { |
| struct mdfld_dsi_pkg_sender *sender = |
| mdfld_dsi_get_pkg_sender(dsi_config); |
| struct drm_device *dev = sender->dev; |
| struct drm_psb_private *dev_priv = dev->dev_private; |
| u32 gen_ctrl_val; |
| |
| if (!sender) { |
| DRM_ERROR("No sender found\n"); |
| return; |
| } |
| |
| /* Set default display backlight value to 85% (0xd8)*/ |
| mdfld_dsi_send_mcs_short(sender, write_display_brightness, 0xd8, 1, |
| true); |
| |
| /* Set minimum brightness setting of CABC function to 20% (0x33)*/ |
| mdfld_dsi_send_mcs_short(sender, write_cabc_min_bright, 0x33, 1, true); |
| |
| /* Enable backlight or/and LABC */ |
| gen_ctrl_val = BRIGHT_CNTL_BLOCK_ON | DISPLAY_DIMMING_ON | |
| BACKLIGHT_ON; |
| if (LABC_control == 1) |
| gen_ctrl_val |= DISPLAY_DIMMING_ON | DISPLAY_BRIGHTNESS_AUTO |
| | GAMMA_AUTO; |
| |
| if (LABC_control == 1) |
| gen_ctrl_val |= AMBIENT_LIGHT_SENSE_ON; |
| |
| dev_priv->mipi_ctrl_display = gen_ctrl_val; |
| |
| mdfld_dsi_send_mcs_short(sender, write_ctrl_display, (u8)gen_ctrl_val, |
| 1, true); |
| |
| mdfld_dsi_send_mcs_short(sender, write_ctrl_cabc, UI_IMAGE, 1, true); |
| } |
| |
| void mdfld_dsi_brightness_control(struct drm_device *dev, int pipe, int level) |
| { |
| struct mdfld_dsi_pkg_sender *sender; |
| struct drm_psb_private *dev_priv; |
| struct mdfld_dsi_config *dsi_config; |
| u32 gen_ctrl_val = 0; |
| int p_type = TMD_VID; |
| |
| if (!dev || (pipe != 0 && pipe != 2)) { |
| DRM_ERROR("Invalid parameter\n"); |
| return; |
| } |
| |
| p_type = mdfld_get_panel_type(dev, 0); |
| |
| dev_priv = dev->dev_private; |
| |
| if (pipe) |
| dsi_config = dev_priv->dsi_configs[1]; |
| else |
| dsi_config = dev_priv->dsi_configs[0]; |
| |
| sender = mdfld_dsi_get_pkg_sender(dsi_config); |
| |
| if (!sender) { |
| DRM_ERROR("No sender found\n"); |
| return; |
| } |
| |
| gen_ctrl_val = (level * 0xff / MDFLD_DSI_BRIGHTNESS_MAX_LEVEL) & 0xff; |
| |
| dev_dbg(sender->dev->dev, "pipe = %d, gen_ctrl_val = %d.\n", |
| pipe, gen_ctrl_val); |
| |
| if (p_type == TMD_VID) { |
| /* Set display backlight value */ |
| mdfld_dsi_send_mcs_short(sender, tmd_write_display_brightness, |
| (u8)gen_ctrl_val, 1, true); |
| } else { |
| /* Set display backlight value */ |
| mdfld_dsi_send_mcs_short(sender, write_display_brightness, |
| (u8)gen_ctrl_val, 1, true); |
| |
| /* Enable backlight control */ |
| if (level == 0) |
| gen_ctrl_val = 0; |
| else |
| gen_ctrl_val = dev_priv->mipi_ctrl_display; |
| |
| mdfld_dsi_send_mcs_short(sender, write_ctrl_display, |
| (u8)gen_ctrl_val, 1, true); |
| } |
| } |
| |
| static int mdfld_dsi_get_panel_status(struct mdfld_dsi_config *dsi_config, |
| u8 dcs, u32 *data, bool hs) |
| { |
| struct mdfld_dsi_pkg_sender *sender |
| = mdfld_dsi_get_pkg_sender(dsi_config); |
| |
| if (!sender || !data) { |
| DRM_ERROR("Invalid parameter\n"); |
| return -EINVAL; |
| } |
| |
| return mdfld_dsi_read_mcs(sender, dcs, data, 1, hs); |
| } |
| |
| int mdfld_dsi_get_power_mode(struct mdfld_dsi_config *dsi_config, u32 *mode, |
| bool hs) |
| { |
| if (!dsi_config || !mode) { |
| DRM_ERROR("Invalid parameter\n"); |
| return -EINVAL; |
| } |
| |
| return mdfld_dsi_get_panel_status(dsi_config, 0x0a, mode, hs); |
| } |
| |
| /* |
| * NOTE: this function was used by OSPM. |
| * TODO: will be removed later, should work out display interfaces for OSPM |
| */ |
| void mdfld_dsi_controller_init(struct mdfld_dsi_config *dsi_config, int pipe) |
| { |
| if (!dsi_config || ((pipe != 0) && (pipe != 2))) { |
| DRM_ERROR("Invalid parameters\n"); |
| return; |
| } |
| |
| mdfld_dsi_dpi_controller_init(dsi_config, pipe); |
| } |
| |
| static void mdfld_dsi_connector_save(struct drm_connector *connector) |
| { |
| } |
| |
| static void mdfld_dsi_connector_restore(struct drm_connector *connector) |
| { |
| } |
| |
| /* FIXME: start using the force parameter */ |
| static enum drm_connector_status |
| mdfld_dsi_connector_detect(struct drm_connector *connector, bool force) |
| { |
| struct mdfld_dsi_connector *dsi_connector |
| = mdfld_dsi_connector(connector); |
| |
| dsi_connector->status = connector_status_connected; |
| |
| return dsi_connector->status; |
| } |
| |
| static int mdfld_dsi_connector_set_property(struct drm_connector *connector, |
| struct drm_property *property, |
| uint64_t value) |
| { |
| struct drm_encoder *encoder = connector->encoder; |
| struct backlight_device *psb_bd; |
| |
| if (!strcmp(property->name, "scaling mode") && encoder) { |
| struct psb_intel_crtc *psb_crtc = |
| to_psb_intel_crtc(encoder->crtc); |
| bool centerechange; |
| uint64_t val; |
| |
| if (!psb_crtc) |
| goto set_prop_error; |
| |
| switch (value) { |
| case DRM_MODE_SCALE_FULLSCREEN: |
| break; |
| case DRM_MODE_SCALE_NO_SCALE: |
| break; |
| case DRM_MODE_SCALE_ASPECT: |
| break; |
| default: |
| goto set_prop_error; |
| } |
| |
| if (drm_connector_property_get_value(connector, property, &val)) |
| goto set_prop_error; |
| |
| if (val == value) |
| goto set_prop_done; |
| |
| if (drm_connector_property_set_value(connector, |
| property, value)) |
| goto set_prop_error; |
| |
| centerechange = (val == DRM_MODE_SCALE_NO_SCALE) || |
| (value == DRM_MODE_SCALE_NO_SCALE); |
| |
| if (psb_crtc->saved_mode.hdisplay != 0 && |
| psb_crtc->saved_mode.vdisplay != 0) { |
| if (centerechange) { |
| if (!drm_crtc_helper_set_mode(encoder->crtc, |
| &psb_crtc->saved_mode, |
| encoder->crtc->x, |
| encoder->crtc->y, |
| encoder->crtc->fb)) |
| goto set_prop_error; |
| } else { |
| struct drm_encoder_helper_funcs *funcs = |
| encoder->helper_private; |
| funcs->mode_set(encoder, |
| &psb_crtc->saved_mode, |
| &psb_crtc->saved_adjusted_mode); |
| } |
| } |
| } else if (!strcmp(property->name, "backlight") && encoder) { |
| if (drm_connector_property_set_value(connector, property, |
| value)) |
| goto set_prop_error; |
| else { |
| psb_bd = mdfld_get_backlight_device(); |
| if (psb_bd) { |
| psb_bd->props.brightness = value; |
| mdfld_set_brightness(psb_bd); |
| } |
| } |
| } |
| set_prop_done: |
| return 0; |
| set_prop_error: |
| return -1; |
| } |
| |
| static void mdfld_dsi_connector_destroy(struct drm_connector *connector) |
| { |
| struct mdfld_dsi_connector *dsi_connector = |
| mdfld_dsi_connector(connector); |
| struct mdfld_dsi_pkg_sender *sender; |
| |
| if (!dsi_connector) |
| return; |
| drm_sysfs_connector_remove(connector); |
| drm_connector_cleanup(connector); |
| sender = dsi_connector->pkg_sender; |
| mdfld_dsi_pkg_sender_destroy(sender); |
| kfree(dsi_connector); |
| } |
| |
| static int mdfld_dsi_connector_get_modes(struct drm_connector *connector) |
| { |
| struct mdfld_dsi_connector *dsi_connector = |
| mdfld_dsi_connector(connector); |
| struct mdfld_dsi_config *dsi_config = |
| mdfld_dsi_get_config(dsi_connector); |
| struct drm_display_mode *fixed_mode = dsi_config->fixed_mode; |
| struct drm_display_mode *dup_mode = NULL; |
| struct drm_device *dev = connector->dev; |
| |
| connector->display_info.min_vfreq = 0; |
| connector->display_info.max_vfreq = 200; |
| connector->display_info.min_hfreq = 0; |
| connector->display_info.max_hfreq = 200; |
| |
| if (fixed_mode) { |
| dev_dbg(dev->dev, "fixed_mode %dx%d\n", |
| fixed_mode->hdisplay, fixed_mode->vdisplay); |
| dup_mode = drm_mode_duplicate(dev, fixed_mode); |
| drm_mode_probed_add(connector, dup_mode); |
| return 1; |
| } |
| DRM_ERROR("Didn't get any modes!\n"); |
| return 0; |
| } |
| |
| static int mdfld_dsi_connector_mode_valid(struct drm_connector *connector, |
| struct drm_display_mode *mode) |
| { |
| struct mdfld_dsi_connector *dsi_connector = |
| mdfld_dsi_connector(connector); |
| struct mdfld_dsi_config *dsi_config = |
| mdfld_dsi_get_config(dsi_connector); |
| struct drm_display_mode *fixed_mode = dsi_config->fixed_mode; |
| |
| if (mode->flags & DRM_MODE_FLAG_DBLSCAN) |
| return MODE_NO_DBLESCAN; |
| |
| if (mode->flags & DRM_MODE_FLAG_INTERLACE) |
| return MODE_NO_INTERLACE; |
| |
| /** |
| * FIXME: current DC has no fitting unit, reject any mode setting |
| * request |
| * Will figure out a way to do up-scaling(pannel fitting) later. |
| **/ |
| if (fixed_mode) { |
| if (mode->hdisplay != fixed_mode->hdisplay) |
| return MODE_PANEL; |
| |
| if (mode->vdisplay != fixed_mode->vdisplay) |
| return MODE_PANEL; |
| } |
| |
| return MODE_OK; |
| } |
| |
| static void mdfld_dsi_connector_dpms(struct drm_connector *connector, int mode) |
| { |
| if (mode == connector->dpms) |
| return; |
| |
| /*first, execute dpms*/ |
| |
| drm_helper_connector_dpms(connector, mode); |
| } |
| |
| static struct drm_encoder *mdfld_dsi_connector_best_encoder( |
| struct drm_connector *connector) |
| { |
| struct mdfld_dsi_connector *dsi_connector = |
| mdfld_dsi_connector(connector); |
| struct mdfld_dsi_config *dsi_config = |
| mdfld_dsi_get_config(dsi_connector); |
| return &dsi_config->encoder->base.base; |
| } |
| |
| /*DSI connector funcs*/ |
| static const struct drm_connector_funcs mdfld_dsi_connector_funcs = { |
| .dpms = /*drm_helper_connector_dpms*/mdfld_dsi_connector_dpms, |
| .save = mdfld_dsi_connector_save, |
| .restore = mdfld_dsi_connector_restore, |
| .detect = mdfld_dsi_connector_detect, |
| .fill_modes = drm_helper_probe_single_connector_modes, |
| .set_property = mdfld_dsi_connector_set_property, |
| .destroy = mdfld_dsi_connector_destroy, |
| }; |
| |
| /*DSI connector helper funcs*/ |
| static const struct drm_connector_helper_funcs |
| mdfld_dsi_connector_helper_funcs = { |
| .get_modes = mdfld_dsi_connector_get_modes, |
| .mode_valid = mdfld_dsi_connector_mode_valid, |
| .best_encoder = mdfld_dsi_connector_best_encoder, |
| }; |
| |
| static int mdfld_dsi_get_default_config(struct drm_device *dev, |
| struct mdfld_dsi_config *config, int pipe) |
| { |
| if (!dev || !config) { |
| DRM_ERROR("Invalid parameters"); |
| return -EINVAL; |
| } |
| |
| config->bpp = 24; |
| if (mdfld_get_panel_type(dev, pipe) == TC35876X) |
| config->lane_count = 4; |
| else |
| config->lane_count = 2; |
| config->channel_num = 0; |
| |
| if (mdfld_get_panel_type(dev, pipe) == TMD_VID) |
| config->video_mode = MDFLD_DSI_VIDEO_NON_BURST_MODE_SYNC_PULSE; |
| else if (mdfld_get_panel_type(dev, pipe) == TC35876X) |
| config->video_mode = |
| MDFLD_DSI_VIDEO_NON_BURST_MODE_SYNC_EVENTS; |
| else |
| config->video_mode = MDFLD_DSI_VIDEO_BURST_MODE; |
| |
| return 0; |
| } |
| |
| int mdfld_dsi_panel_reset(int pipe) |
| { |
| unsigned gpio; |
| int ret = 0; |
| |
| switch (pipe) { |
| case 0: |
| gpio = 128; |
| break; |
| case 2: |
| gpio = 34; |
| break; |
| default: |
| DRM_ERROR("Invalid output\n"); |
| return -EINVAL; |
| } |
| |
| ret = gpio_request(gpio, "gfx"); |
| if (ret) { |
| DRM_ERROR("gpio_rqueset failed\n"); |
| return ret; |
| } |
| |
| ret = gpio_direction_output(gpio, 1); |
| if (ret) { |
| DRM_ERROR("gpio_direction_output failed\n"); |
| goto gpio_error; |
| } |
| |
| gpio_get_value(128); |
| |
| gpio_error: |
| if (gpio_is_valid(gpio)) |
| gpio_free(gpio); |
| |
| return ret; |
| } |
| |
| /* |
| * MIPI output init |
| * @dev drm device |
| * @pipe pipe number. 0 or 2 |
| * @config |
| * |
| * Do the initialization of a MIPI output, including create DRM mode objects |
| * initialization of DSI output on @pipe |
| */ |
| void mdfld_dsi_output_init(struct drm_device *dev, |
| int pipe, |
| const struct panel_funcs *p_vid_funcs) |
| { |
| struct mdfld_dsi_config *dsi_config; |
| struct mdfld_dsi_connector *dsi_connector; |
| struct drm_connector *connector; |
| struct mdfld_dsi_encoder *encoder; |
| struct drm_psb_private *dev_priv = dev->dev_private; |
| struct panel_info dsi_panel_info; |
| u32 width_mm, height_mm; |
| |
| dev_dbg(dev->dev, "init DSI output on pipe %d\n", pipe); |
| |
| if (!dev || ((pipe != 0) && (pipe != 2))) { |
| DRM_ERROR("Invalid parameter\n"); |
| return; |
| } |
| |
| /*create a new connetor*/ |
| dsi_connector = kzalloc(sizeof(struct mdfld_dsi_connector), GFP_KERNEL); |
| if (!dsi_connector) { |
| DRM_ERROR("No memory"); |
| return; |
| } |
| |
| dsi_connector->pipe = pipe; |
| |
| dsi_config = kzalloc(sizeof(struct mdfld_dsi_config), |
| GFP_KERNEL); |
| if (!dsi_config) { |
| DRM_ERROR("cannot allocate memory for DSI config\n"); |
| goto dsi_init_err0; |
| } |
| mdfld_dsi_get_default_config(dev, dsi_config, pipe); |
| |
| dsi_connector->private = dsi_config; |
| |
| dsi_config->changed = 1; |
| dsi_config->dev = dev; |
| |
| dsi_config->fixed_mode = p_vid_funcs->get_config_mode(dev); |
| if (p_vid_funcs->get_panel_info(dev, pipe, &dsi_panel_info)) |
| goto dsi_init_err0; |
| |
| width_mm = dsi_panel_info.width_mm; |
| height_mm = dsi_panel_info.height_mm; |
| |
| dsi_config->mode = dsi_config->fixed_mode; |
| dsi_config->connector = dsi_connector; |
| |
| if (!dsi_config->fixed_mode) { |
| DRM_ERROR("No pannel fixed mode was found\n"); |
| goto dsi_init_err0; |
| } |
| |
| if (pipe && dev_priv->dsi_configs[0]) { |
| dsi_config->dvr_ic_inited = 0; |
| dev_priv->dsi_configs[1] = dsi_config; |
| } else if (pipe == 0) { |
| dsi_config->dvr_ic_inited = 1; |
| dev_priv->dsi_configs[0] = dsi_config; |
| } else { |
| DRM_ERROR("Trying to init MIPI1 before MIPI0\n"); |
| goto dsi_init_err0; |
| } |
| |
| |
| connector = &dsi_connector->base.base; |
| drm_connector_init(dev, connector, &mdfld_dsi_connector_funcs, |
| DRM_MODE_CONNECTOR_LVDS); |
| drm_connector_helper_add(connector, &mdfld_dsi_connector_helper_funcs); |
| |
| connector->display_info.subpixel_order = SubPixelHorizontalRGB; |
| connector->display_info.width_mm = width_mm; |
| connector->display_info.height_mm = height_mm; |
| connector->interlace_allowed = false; |
| connector->doublescan_allowed = false; |
| |
| /*attach properties*/ |
| drm_connector_attach_property(connector, |
| dev->mode_config.scaling_mode_property, |
| DRM_MODE_SCALE_FULLSCREEN); |
| drm_connector_attach_property(connector, |
| dev_priv->backlight_property, |
| MDFLD_DSI_BRIGHTNESS_MAX_LEVEL); |
| |
| /*init DSI package sender on this output*/ |
| if (mdfld_dsi_pkg_sender_init(dsi_connector, pipe)) { |
| DRM_ERROR("Package Sender initialization failed on pipe %d\n", |
| pipe); |
| goto dsi_init_err0; |
| } |
| |
| encoder = mdfld_dsi_dpi_init(dev, dsi_connector, p_vid_funcs); |
| if (!encoder) { |
| DRM_ERROR("Create DPI encoder failed\n"); |
| goto dsi_init_err1; |
| } |
| encoder->private = dsi_config; |
| dsi_config->encoder = encoder; |
| encoder->base.type = (pipe == 0) ? INTEL_OUTPUT_MIPI : |
| INTEL_OUTPUT_MIPI2; |
| drm_sysfs_connector_add(connector); |
| return; |
| |
| /*TODO: add code to destroy outputs on error*/ |
| dsi_init_err1: |
| /*destroy sender*/ |
| mdfld_dsi_pkg_sender_destroy(dsi_connector->pkg_sender); |
| |
| drm_connector_cleanup(connector); |
| |
| kfree(dsi_config->fixed_mode); |
| kfree(dsi_config); |
| dsi_init_err0: |
| kfree(dsi_connector); |
| } |