| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * DesignWare MIPI DSI Host Controller v1.02 driver |
| * |
| * Copyright (c) 2016 Linaro Limited. |
| * Copyright (c) 2014-2016 Hisilicon Limited. |
| * |
| * Author: |
| * <shizongxuan@huawei.com> |
| * <zhangxiubin@huawei.com> |
| * <lvda3@hisilicon.com> |
| */ |
| #include <linux/clk.h> |
| #include <linux/component.h> |
| #include <linux/delay.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| |
| #include <drm/drm_atomic_helper.h> |
| #include <drm/drm_device.h> |
| #include <drm/drm_encoder_slave.h> |
| #include <drm/drm_mipi_dsi.h> |
| #include <drm/drm_of.h> |
| #include <drm/drm_print.h> |
| #include <drm/drm_probe_helper.h> |
| #include <drm/drm_sysfs.h> |
| |
| #include "kirin_drm_dsi.h" |
| #include "dw_dsi_reg.h" |
| |
| static struct kirin_dsi_ops *hisi_dsi_ops; |
| |
| void dsi_set_output_client(struct drm_device *dev) |
| { |
| enum dsi_output_client client; |
| struct drm_connector *connector; |
| struct drm_encoder *encoder; |
| struct drm_connector_list_iter conn_iter; |
| struct dw_dsi *dsi; |
| |
| mutex_lock(&dev->mode_config.mutex); |
| |
| /* find dsi encoder */ |
| drm_for_each_encoder(encoder, dev) |
| if (encoder->encoder_type == DRM_MODE_ENCODER_DSI) |
| break; |
| dsi = encoder_to_dsi(encoder); |
| |
| /* find HDMI connector */ |
| drm_connector_list_iter_begin(dev, &conn_iter); |
| drm_for_each_connector_iter(connector, &conn_iter) |
| if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA) |
| break; |
| drm_connector_list_iter_end(&conn_iter); |
| |
| /* |
| * set the proper dsi output client |
| */ |
| client = connector->status == connector_status_connected ? OUT_HDMI : |
| OUT_PANEL; |
| if (client != dsi->cur_client) { |
| /* |
| * set the switch ic to select the HDMI or MIPI_DSI |
| */ |
| if (hisi_dsi_ops->version == KIRIN960_DSI) |
| gpiod_set_value_cansleep(dsi->gpio_mux, client); |
| |
| dsi->cur_client = client; |
| /* let the userspace know panel connector status has changed */ |
| drm_sysfs_hotplug_event(dev); |
| DRM_INFO("client change to %s\n", |
| client == OUT_HDMI ? "HDMI" : "panel"); |
| } |
| |
| mutex_unlock(&dev->mode_config.mutex); |
| } |
| EXPORT_SYMBOL_GPL(dsi_set_output_client); |
| /************************for the panel attach to dsi*****************************/ |
| static int dsi_connector_get_modes(struct drm_connector *connector) |
| { |
| struct dw_dsi *dsi = connector_to_dsi(connector); |
| |
| return drm_panel_get_modes(dsi->panel, connector); |
| } |
| |
| static enum drm_mode_status |
| dsi_connector_mode_valid(struct drm_connector *connector, |
| struct drm_display_mode *mode) |
| { |
| enum drm_mode_status mode_status = MODE_OK; |
| |
| return mode_status; |
| } |
| |
| static struct drm_encoder * |
| dsi_connector_best_encoder(struct drm_connector *connector) |
| { |
| struct dw_dsi *dsi = connector_to_dsi(connector); |
| |
| return &dsi->encoder; |
| } |
| |
| static struct drm_connector_helper_funcs dsi_connector_helper_funcs = { |
| .get_modes = dsi_connector_get_modes, |
| .mode_valid = dsi_connector_mode_valid, |
| .best_encoder = dsi_connector_best_encoder, |
| }; |
| |
| static enum drm_connector_status |
| dsi_connector_detect(struct drm_connector *connector, bool force) |
| { |
| struct dw_dsi *dsi = connector_to_dsi(connector); |
| enum drm_connector_status status; |
| |
| status = dsi->cur_client == OUT_PANEL ? connector_status_connected : |
| connector_status_disconnected; |
| |
| return status; |
| } |
| |
| static void dsi_connector_destroy(struct drm_connector *connector) |
| { |
| drm_connector_unregister(connector); |
| drm_connector_cleanup(connector); |
| } |
| |
| static struct drm_connector_funcs dsi_atomic_connector_funcs = { |
| .fill_modes = drm_helper_probe_single_connector_modes, |
| .detect = dsi_connector_detect, |
| .destroy = dsi_connector_destroy, |
| .reset = drm_atomic_helper_connector_reset, |
| .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
| .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
| }; |
| |
| static int dsi_connector_init(struct drm_device *dev, struct dw_dsi *dsi) |
| { |
| struct drm_encoder *encoder = &dsi->encoder; |
| struct drm_connector *connector = &dsi->connector; |
| int ret; |
| |
| connector->polled = DRM_CONNECTOR_POLL_HPD; |
| drm_connector_helper_add(connector, &dsi_connector_helper_funcs); |
| |
| ret = drm_connector_init(dev, &dsi->connector, |
| &dsi_atomic_connector_funcs, |
| DRM_MODE_CONNECTOR_DSI); |
| if (ret) |
| return ret; |
| |
| ret = drm_connector_attach_encoder(connector, encoder); |
| if (ret) |
| return ret; |
| |
| DRM_INFO("connector init\n"); |
| return 0; |
| } |
| |
| /****************************************************************************/ |
| |
| /***************************for the encoder_helper_funcs****************************************/ |
| static const struct drm_encoder_funcs dw_encoder_funcs = { |
| .destroy = drm_encoder_cleanup, |
| }; |
| |
| static int dsi_encoder_atomic_check(struct drm_encoder *encoder, |
| struct drm_crtc_state *crtc_state, |
| struct drm_connector_state *conn_state) |
| { |
| /* do nothing */ |
| return 0; |
| } |
| |
| static enum drm_mode_status |
| dsi_encoder_mode_valid(struct drm_encoder *encoder, |
| const struct drm_display_mode *mode) |
| |
| { |
| return hisi_dsi_ops->encoder_valid(encoder, mode); |
| } |
| |
| static void dsi_encoder_mode_set(struct drm_encoder *encoder, |
| struct drm_display_mode *mode, |
| struct drm_display_mode *adj_mode) |
| { |
| struct dw_dsi *dsi = encoder_to_dsi(encoder); |
| |
| drm_mode_copy(&dsi->cur_mode, adj_mode); |
| } |
| |
| static void dsi_encoder_enable(struct drm_encoder *encoder) |
| { |
| struct dw_dsi *dsi = encoder_to_dsi(encoder); |
| |
| if (dsi->enable) |
| return; |
| |
| hisi_dsi_ops->encoder_enable(encoder); |
| |
| if (hisi_dsi_ops->version == KIRIN960_DSI) { |
| /* turn on panel */ |
| if (dsi->panel && drm_panel_prepare(dsi->panel)) |
| DRM_ERROR("failed to prepare panel\n"); |
| |
| /*dw_dsi_set_mode(dsi, DSI_VIDEO_MODE);*/ |
| |
| /* turn on panel's back light */ |
| if (dsi->panel && drm_panel_enable(dsi->panel)) |
| DRM_ERROR("failed to enable panel\n"); |
| } |
| |
| dsi->enable = true; |
| } |
| |
| static void dw_dsi_set_mode(struct dw_dsi *dsi, enum dsi_work_mode mode) |
| { |
| struct dsi_hw_ctx *ctx = dsi->ctx; |
| void __iomem *base = ctx->base; |
| |
| writel(RESET, base + PWR_UP); |
| writel(mode, base + MODE_CFG); |
| writel(POWERUP, base + PWR_UP); |
| } |
| |
| static void dsi_encoder_disable(struct drm_encoder *encoder) |
| { |
| struct dw_dsi *dsi = encoder_to_dsi(encoder); |
| struct dsi_hw_ctx *ctx = dsi->ctx; |
| |
| if (!dsi->enable) |
| return; |
| |
| dw_dsi_set_mode(dsi, DSI_COMMAND_MODE); |
| |
| if (hisi_dsi_ops->version == KIRIN960_DSI) { |
| /* turn off panel's backlight */ |
| if (dsi->panel && drm_panel_disable(dsi->panel)) |
| DRM_ERROR("failed to disable panel\n"); |
| |
| /* turn off panel */ |
| if (dsi->panel && drm_panel_unprepare(dsi->panel)) |
| DRM_ERROR("failed to unprepare panel\n"); |
| |
| clk_disable_unprepare(ctx->dss_dphy0_ref_clk); |
| clk_disable_unprepare(ctx->dss_dphy0_cfg_clk); |
| clk_disable_unprepare(ctx->dss_pclk_dsi0_clk); |
| } |
| |
| dsi->enable = false; |
| } |
| |
| static const struct drm_encoder_helper_funcs dw_encoder_helper_funcs = { |
| .atomic_check = dsi_encoder_atomic_check, |
| .mode_valid = dsi_encoder_mode_valid, |
| .mode_set = dsi_encoder_mode_set, |
| .enable = dsi_encoder_enable, |
| .disable = dsi_encoder_disable |
| }; |
| |
| /****************************************************************************/ |
| static int dsi_bridge_init(struct drm_device *dev, struct dw_dsi *dsi) |
| { |
| struct drm_encoder *encoder = &dsi->encoder; |
| struct drm_bridge *bridge = dsi->bridge; |
| int ret; |
| |
| /* associate the bridge to dsi encoder */ |
| ret = drm_bridge_attach(encoder, bridge, NULL, 0); |
| |
| if (ret) { |
| DRM_ERROR("failed to attach external bridge\n"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int dw_drm_encoder_init(struct device *dev, struct drm_device *drm_dev, |
| struct drm_encoder *encoder) |
| { |
| int ret; |
| u32 crtc_mask = drm_of_find_possible_crtcs(drm_dev, dev->of_node); |
| |
| if (!crtc_mask) { |
| DRM_ERROR("failed to find crtc mask\n"); |
| return -EINVAL; |
| } |
| |
| encoder->possible_crtcs = crtc_mask; |
| ret = drm_encoder_init(drm_dev, encoder, &dw_encoder_funcs, |
| DRM_MODE_ENCODER_DSI, NULL); |
| if (ret) { |
| DRM_ERROR("failed to init dsi encoder\n"); |
| return ret; |
| } |
| |
| drm_encoder_helper_add(encoder, &dw_encoder_helper_funcs); |
| |
| return 0; |
| } |
| |
| static int dsi_bind(struct device *dev, struct device *master, void *data) |
| { |
| struct dsi_data *ddata = dev_get_drvdata(dev); |
| struct dw_dsi *dsi = &ddata->dsi; |
| struct drm_device *drm_dev = data; |
| int ret; |
| |
| DRM_INFO("+.\n"); |
| ret = dw_drm_encoder_init(dev, drm_dev, &dsi->encoder); |
| if (ret) |
| return ret; |
| |
| if (dsi->bridge) { |
| ret = dsi_bridge_init(drm_dev, dsi); |
| if (ret) |
| return ret; |
| } |
| |
| if (hisi_dsi_ops->version == KIRIN960_DSI) { |
| if (dsi->panel) { |
| ret = dsi_connector_init(drm_dev, dsi); |
| if (ret) |
| return ret; |
| } |
| } else if (hisi_dsi_ops->version == KIRIN620_DSI) { |
| /*the panel for the kirin620 drm have not support*/ |
| } |
| |
| DRM_INFO("-.\n"); |
| return 0; |
| } |
| |
| static void dsi_unbind(struct device *dev, struct device *master, void *data) |
| { |
| /* do nothing */ |
| } |
| |
| static const struct component_ops dsi_ops = { |
| .bind = dsi_bind, |
| .unbind = dsi_unbind, |
| }; |
| |
| static int dsi_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct dsi_data *data; |
| struct dw_dsi *dsi; |
| struct dsi_hw_ctx *ctx; |
| int ret; |
| |
| hisi_dsi_ops = (struct kirin_dsi_ops *)of_device_get_match_data(dev); |
| |
| DRM_INFO("+.\n"); |
| data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); |
| if (!data) { |
| DRM_ERROR("failed to allocate dsi data.\n"); |
| return -ENOMEM; |
| } |
| dsi = &data->dsi; |
| ctx = &data->ctx; |
| dsi->ctx = ctx; |
| |
| if (hisi_dsi_ops == NULL) |
| DRM_ERROR("hisi_dsi_ops is not bind\n"); |
| ret = hisi_dsi_ops->host_init(dev, dsi); |
| if (ret) |
| return ret; |
| |
| ret = hisi_dsi_ops->parse_dt(pdev, dsi); |
| if (ret) |
| goto err_host_unregister; |
| |
| platform_set_drvdata(pdev, data); |
| |
| ret = component_add(dev, &dsi_ops); |
| if (ret) |
| goto err_host_unregister; |
| |
| DRM_INFO("-.\n"); |
| return 0; |
| |
| err_host_unregister: |
| mipi_dsi_host_unregister(&dsi->host); |
| return ret; |
| } |
| |
| static int dsi_remove(struct platform_device *pdev) |
| { |
| component_del(&pdev->dev, &dsi_ops); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id dsi_of_match[] = { |
| #ifdef CONFIG_DRM_HISI_KIRIN960 |
| { |
| .compatible = "hisilicon,hi3660-dsi", |
| .data = &kirin_dsi_960, |
| }, |
| #endif |
| #ifdef CONFIG_DRM_HISI_KIRIN620 |
| { |
| .compatible = "hisilicon,hi6220-dsi", |
| .data = &kirin_dsi_620, |
| }, |
| #endif |
| { /* end node */ } |
| }; |
| MODULE_DEVICE_TABLE(of, dsi_of_match); |
| |
| static struct platform_driver dsi_driver = { |
| .probe = dsi_probe, |
| .remove = dsi_remove, |
| .driver = { |
| .name = "dw-dsi", |
| .of_match_table = dsi_of_match, |
| }, |
| }; |
| |
| module_platform_driver(dsi_driver); |
| |
| MODULE_DESCRIPTION("DesignWare MIPI DSI Host Controller v1.02 driver"); |
| MODULE_LICENSE("GPL v2"); |