blob: 2aa57662d15c006b804046209a4429728acd775d [file] [log] [blame]
/*
* 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>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/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) {
/* associate bridge and dsi encoder */
if (client == OUT_HDMI)
encoder->bridge = dsi->bridge;
else
encoder->bridge = NULL;
/*
* set the switch ic to select the HDMI or MIPI_DSI
*/
if (KIRIN960_DSI == hisi_dsi_ops->version) {
gpiod_set_value_cansleep(dsi->gpio_mux, client);
}else if (KIRIN620_DSI == hisi_dsi_ops->version) {
/*the gpio0_1*/
}
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);
}
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;
ret = drm_panel_attach(dsi->panel, connector);
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 (KIRIN960_DSI == hisi_dsi_ops->version) {
/* 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 (KIRIN960_DSI == hisi_dsi_ops->version) {
/* 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);
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 (KIRIN960_DSI == hisi_dsi_ops->version) {
if (dsi->panel) {
ret = dsi_connector_init(drm_dev, dsi);
if (ret)
return ret;
}
}else if (KIRIN620_DSI == hisi_dsi_ops->version) {
/*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 (NULL == hisi_dsi_ops) {
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");