1166 lines
28 KiB
C
1166 lines
28 KiB
C
/* Copyright (c) 2018, 2020, The Linux Foundation. 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 version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/err.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/msm-bus.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/spi/spi.h>
|
|
|
|
#include "mdss.h"
|
|
#include "mdss_panel.h"
|
|
#include "mdss_rgb.h"
|
|
#include "mdss_debug.h"
|
|
|
|
static struct mdss_rgb_data *mdss_rgb_res;
|
|
static struct spi_device *spi_dev;
|
|
|
|
static int mdss_rgb_spi_write(struct mdss_rgb_data *rgb_data,
|
|
bool cmd, u8 data)
|
|
{
|
|
struct spi_transfer xfer = { 0 };
|
|
struct spi_message msg;
|
|
u16 txbuf;
|
|
|
|
if (cmd)
|
|
txbuf = (1 << 8) | data;
|
|
else
|
|
txbuf = data;
|
|
|
|
spi_message_init(&msg);
|
|
|
|
xfer.tx_buf = &txbuf;
|
|
xfer.bits_per_word = 9;
|
|
xfer.len = sizeof(txbuf);
|
|
|
|
spi_message_add_tail(&xfer, &msg);
|
|
return spi_sync(rgb_data->spi, &msg);
|
|
}
|
|
|
|
int mdss_rgb_write_command(struct mdss_rgb_data *rgb_data, u8 cmd)
|
|
{
|
|
int rc;
|
|
|
|
rc = mdss_rgb_spi_write(rgb_data, false, cmd);
|
|
if (rc)
|
|
pr_err("%s: spi write failed. cmd = 0x%x rc = %d\n",
|
|
__func__, cmd, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int mdss_rgb_write_data(struct mdss_rgb_data *rgb_data, u8 data)
|
|
{
|
|
int rc;
|
|
|
|
rc = mdss_rgb_spi_write(rgb_data, true, data);
|
|
if (rc)
|
|
pr_err("%s: spi write failed. data = 0x%x rc = %d\n",
|
|
__func__, data, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
int mdss_rgb_read_command(struct mdss_rgb_data *rgb_data,
|
|
u8 cmd, u8 *data, u8 data_len)
|
|
{
|
|
int rc;
|
|
|
|
rc = spi_write_then_read(spi_dev, &cmd, 1, data, data_len);
|
|
if (rc)
|
|
pr_err("%s: spi read failed. rc = %d\n", __func__, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_rgb_core_clk_init(struct platform_device *pdev,
|
|
struct mdss_rgb_data *rgb_data)
|
|
{
|
|
struct device *dev = NULL;
|
|
int rc = 0;
|
|
|
|
dev = &pdev->dev;
|
|
|
|
rgb_data->mdp_core_clk = devm_clk_get(dev, "mdp_core_clk");
|
|
if (IS_ERR(rgb_data->mdp_core_clk)) {
|
|
rc = PTR_ERR(rgb_data->mdp_core_clk);
|
|
pr_err("%s: Unable to get mdp core clk. rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rgb_data->ahb_clk = devm_clk_get(dev, "iface_clk");
|
|
if (IS_ERR(rgb_data->ahb_clk)) {
|
|
rc = PTR_ERR(rgb_data->ahb_clk);
|
|
pr_err("%s: Unable to get mdss ahb clk. rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rgb_data->axi_clk = devm_clk_get(dev, "bus_clk");
|
|
if (IS_ERR(rgb_data->axi_clk)) {
|
|
rc = PTR_ERR(rgb_data->axi_clk);
|
|
pr_err("%s: Unable to get axi bus clk. rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rgb_data->ext_byte0_clk = devm_clk_get(dev, "ext_byte0_clk");
|
|
if (IS_ERR(rgb_data->ext_byte0_clk)) {
|
|
pr_debug("%s: unable to get byte0 clk rcg. rc=%d\n",
|
|
__func__, rc);
|
|
rgb_data->ext_byte0_clk = NULL;
|
|
}
|
|
|
|
rgb_data->ext_pixel0_clk = devm_clk_get(dev, "ext_pixel0_clk");
|
|
if (IS_ERR(rgb_data->ext_pixel0_clk)) {
|
|
pr_debug("%s: unable to get pixel0 clk rcg. rc=%d\n",
|
|
__func__, rc);
|
|
rgb_data->ext_pixel0_clk = NULL;
|
|
}
|
|
|
|
rgb_data->mmss_misc_ahb_clk = devm_clk_get(dev, "core_mmss_clk");
|
|
if (IS_ERR(rgb_data->mmss_misc_ahb_clk)) {
|
|
rgb_data->mmss_misc_ahb_clk = NULL;
|
|
pr_debug("%s: Unable to get mmss misc ahb clk\n",
|
|
__func__);
|
|
}
|
|
|
|
rgb_data->mnoc_clk = devm_clk_get(dev, "mnoc_clk");
|
|
if (IS_ERR(rgb_data->mnoc_clk)) {
|
|
pr_debug("%s: Unable to get mnoc clk\n", __func__);
|
|
rgb_data->mnoc_clk = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mdss_rgb_bus_scale_init(struct platform_device *pdev,
|
|
struct mdss_rgb_data *rgb_data)
|
|
{
|
|
int rc = 0;
|
|
|
|
rgb_data->bus_scale_table = msm_bus_cl_get_pdata(pdev);
|
|
if (IS_ERR_OR_NULL(rgb_data->bus_scale_table)) {
|
|
rc = PTR_ERR(rgb_data->bus_scale_table);
|
|
pr_err("%s: msm_bus_cl_get_pdata() failed, rc=%d\n", __func__,
|
|
rc);
|
|
rgb_data->bus_scale_table = NULL;
|
|
return rc;
|
|
}
|
|
|
|
rgb_data->bus_handle =
|
|
msm_bus_scale_register_client(rgb_data->bus_scale_table);
|
|
|
|
if (!rgb_data->bus_handle) {
|
|
rc = -EINVAL;
|
|
pr_err("%sbus_client register failed\n", __func__);
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
static void mdss_rgb_bus_scale_deinit(struct mdss_rgb_data *sdata)
|
|
{
|
|
if (sdata->bus_handle) {
|
|
if (sdata->bus_refcount)
|
|
msm_bus_scale_client_update_request(sdata->bus_handle,
|
|
0);
|
|
|
|
sdata->bus_refcount = 0;
|
|
msm_bus_scale_unregister_client(sdata->bus_handle);
|
|
sdata->bus_handle = 0;
|
|
}
|
|
}
|
|
|
|
static int mdss_rgb_regulator_init(struct platform_device *pdev,
|
|
struct mdss_rgb_data *rgb_data)
|
|
{
|
|
int rc = 0, i = 0, j = 0;
|
|
|
|
for (i = DSI_CORE_PM; !rc && (i < DSI_MAX_PM); i++) {
|
|
rc = msm_dss_config_vreg(&pdev->dev,
|
|
rgb_data->power_data[i].vreg_config,
|
|
rgb_data->power_data[i].num_vreg, 1);
|
|
if (rc) {
|
|
pr_err("%s: failed to init vregs for %s\n",
|
|
__func__, __mdss_dsi_pm_name(i));
|
|
for (j = i-1; j >= DSI_CORE_PM; j--) {
|
|
msm_dss_config_vreg(&pdev->dev,
|
|
rgb_data->power_data[j].vreg_config,
|
|
rgb_data->power_data[j].num_vreg, 0);
|
|
}
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static void mdss_rgb_res_deinit(struct platform_device *pdev)
|
|
{
|
|
int i;
|
|
struct mdss_rgb_data *sdata = platform_get_drvdata(pdev);
|
|
|
|
for (i = (DSI_MAX_PM - 1); i >= DSI_CORE_PM; i--) {
|
|
if (msm_dss_config_vreg(&pdev->dev,
|
|
sdata->power_data[i].vreg_config,
|
|
sdata->power_data[i].num_vreg, 1) < 0)
|
|
pr_err("%s: failed to de-init vregs for %s\n",
|
|
__func__, __mdss_dsi_pm_name(i));
|
|
mdss_dsi_put_dt_vreg_data(&pdev->dev,
|
|
&sdata->power_data[i]);
|
|
}
|
|
|
|
mdss_rgb_bus_scale_deinit(sdata);
|
|
}
|
|
|
|
static int mdss_rgb_res_init(struct platform_device *pdev)
|
|
{
|
|
int rc = 0, i = 0;
|
|
|
|
mdss_rgb_res = platform_get_drvdata(pdev);
|
|
if (!mdss_rgb_res) {
|
|
mdss_rgb_res = devm_kzalloc(&pdev->dev,
|
|
sizeof(*mdss_rgb_res),
|
|
GFP_KERNEL);
|
|
if (!mdss_rgb_res) {
|
|
rc = -ENOMEM;
|
|
goto mem_fail;
|
|
}
|
|
|
|
mdss_rgb_res->spi = spi_dev;
|
|
mdss_rgb_res->panel_data.panel_info.pdest = DISPLAY_1;
|
|
mdss_rgb_res->ndx = DSI_CTRL_0;
|
|
|
|
rc = mdss_rgb_core_clk_init(pdev, mdss_rgb_res);
|
|
if (rc)
|
|
goto mem_fail;
|
|
|
|
/* Parse the ctrl regulator information */
|
|
for (i = DSI_CORE_PM; i < DSI_MAX_PM; i++) {
|
|
rc = mdss_dsi_get_dt_vreg_data(&pdev->dev,
|
|
pdev->dev.of_node,
|
|
&mdss_rgb_res->power_data[i], i);
|
|
if (rc) {
|
|
pr_err("%s: '%s' get_dt_vreg_data failed.rc=%d\n",
|
|
__func__, __mdss_dsi_pm_name(i), rc);
|
|
i--;
|
|
for (; i >= DSI_CORE_PM; i--)
|
|
mdss_dsi_put_dt_vreg_data(&pdev->dev,
|
|
&mdss_rgb_res->power_data[i]);
|
|
goto mem_fail;
|
|
}
|
|
}
|
|
rc = mdss_rgb_regulator_init(pdev, mdss_rgb_res);
|
|
if (rc)
|
|
goto mem_fail;
|
|
|
|
rc = mdss_rgb_bus_scale_init(pdev, mdss_rgb_res);
|
|
if (rc)
|
|
goto mem_fail;
|
|
|
|
platform_set_drvdata(pdev, mdss_rgb_res);
|
|
}
|
|
|
|
mdss_rgb_res->pdev = pdev;
|
|
pr_debug("%s: Setting up mdss_rgb_res=%pK\n", __func__, mdss_rgb_res);
|
|
|
|
return 0;
|
|
|
|
mem_fail:
|
|
mdss_rgb_res_deinit(pdev);
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_rgb_get_panel_cfg(char *panel_cfg, size_t panel_cfg_len)
|
|
{
|
|
int rc;
|
|
struct mdss_panel_cfg *pan_cfg = NULL;
|
|
struct mdss_util_intf *util;
|
|
|
|
util = mdss_get_util_intf();
|
|
|
|
pan_cfg = util->panel_intf_type(MDSS_PANEL_INTF_RGB);
|
|
if (IS_ERR(pan_cfg)) {
|
|
return PTR_ERR(pan_cfg);
|
|
} else if (!pan_cfg) {
|
|
panel_cfg[0] = 0;
|
|
return 0;
|
|
}
|
|
|
|
pr_debug("%s:%d: cfg:[%s]\n", __func__, __LINE__,
|
|
pan_cfg->arg_cfg);
|
|
rc = strlcpy(panel_cfg, pan_cfg->arg_cfg,
|
|
min(panel_cfg_len, sizeof(pan_cfg->arg_cfg)));
|
|
|
|
return rc;
|
|
}
|
|
|
|
static struct device_node *mdss_rgb_pref_prim_panel(
|
|
struct platform_device *pdev)
|
|
{
|
|
struct device_node *dsi_pan_node = NULL;
|
|
|
|
pr_debug("%s:%d: Select primary panel from dt\n",
|
|
__func__, __LINE__);
|
|
dsi_pan_node = of_parse_phandle(pdev->dev.of_node,
|
|
"qcom,rgb-panel", 0);
|
|
if (!dsi_pan_node)
|
|
pr_err("%s:can't find panel phandle\n", __func__);
|
|
|
|
return dsi_pan_node;
|
|
}
|
|
|
|
static struct device_node *mdss_rgb_find_panel_of_node(
|
|
struct platform_device *pdev, char *panel_cfg)
|
|
{
|
|
int len;
|
|
char panel_name[MDSS_MAX_PANEL_LEN] = "";
|
|
struct device_node *dsi_pan_node = NULL;
|
|
struct mdss_rgb_data *rgb_data = platform_get_drvdata(pdev);
|
|
|
|
len = strlen(panel_cfg);
|
|
rgb_data->panel_data.dsc_cfg_np_name[0] = '\0';
|
|
if (!len) {
|
|
/* no panel cfg chg, parse dt */
|
|
pr_err("%s:%d: no cmd line cfg present\n",
|
|
__func__, __LINE__);
|
|
goto end;
|
|
}
|
|
|
|
end:
|
|
if (strcmp(panel_name, NONE_PANEL))
|
|
dsi_pan_node = mdss_rgb_pref_prim_panel(pdev);
|
|
|
|
return dsi_pan_node;
|
|
}
|
|
|
|
static struct device_node *mdss_rgb_config_panel(struct platform_device *pdev)
|
|
{
|
|
char panel_cfg[MDSS_MAX_PANEL_LEN] = { 0 };
|
|
struct device_node *dsi_pan_node = NULL;
|
|
struct mdss_rgb_data *rgb_data = platform_get_drvdata(pdev);
|
|
int rc = 0;
|
|
|
|
rc = mdss_rgb_get_panel_cfg(panel_cfg, MDSS_MAX_PANEL_LEN);
|
|
if (!rc)
|
|
pr_warn("%s:%d:dsi specific cfg not present\n",
|
|
__func__, __LINE__);
|
|
|
|
dsi_pan_node = mdss_rgb_find_panel_of_node(pdev, panel_cfg);
|
|
if (!dsi_pan_node) {
|
|
pr_err("%s: can't find panel node %s\n", __func__, panel_cfg);
|
|
return NULL;
|
|
}
|
|
|
|
rc = mdss_rgb_panel_init(dsi_pan_node, rgb_data);
|
|
if (rc) {
|
|
pr_err("%s: dsi panel init failed\n", __func__);
|
|
of_node_put(dsi_pan_node);
|
|
return NULL;
|
|
}
|
|
|
|
return dsi_pan_node;
|
|
}
|
|
|
|
static int mdss_rgb_set_clk_rates(struct mdss_rgb_data *rgb_data)
|
|
{
|
|
int rc;
|
|
|
|
rc = mdss_dsi_clk_set_link_rate(rgb_data->clk_handle,
|
|
MDSS_DSI_LINK_BYTE_CLK,
|
|
rgb_data->byte_clk_rate,
|
|
MDSS_DSI_CLK_UPDATE_CLK_RATE_AT_ON);
|
|
if (rc) {
|
|
pr_err("%s: dsi_byte_clk - clk_set_rate failed\n",
|
|
__func__);
|
|
return rc;
|
|
}
|
|
rc = mdss_dsi_clk_set_link_rate(rgb_data->clk_handle,
|
|
MDSS_DSI_LINK_PIX_CLK,
|
|
rgb_data->pclk_rate,
|
|
MDSS_DSI_CLK_UPDATE_CLK_RATE_AT_ON);
|
|
if (rc)
|
|
pr_err("%s: dsi_pixel_clk - clk_set_rate failed\n",
|
|
__func__);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int mdss_rgb_pix_clk_init(struct platform_device *pdev,
|
|
struct mdss_rgb_data *rgb_data)
|
|
{
|
|
struct device *dev = NULL;
|
|
int rc = 0;
|
|
|
|
dev = &pdev->dev;
|
|
rgb_data->byte_clk_rgb = devm_clk_get(dev, "byte_clk");
|
|
if (IS_ERR(rgb_data->byte_clk_rgb)) {
|
|
rc = PTR_ERR(rgb_data->byte_clk_rgb);
|
|
pr_err("%s: can't find dsi_byte_clk. rc=%d\n",
|
|
__func__, rc);
|
|
rgb_data->byte_clk_rgb = NULL;
|
|
return rc;
|
|
}
|
|
|
|
rgb_data->pixel_clk_rgb = devm_clk_get(dev, "pixel_clk");
|
|
if (IS_ERR(rgb_data->pixel_clk_rgb)) {
|
|
rc = PTR_ERR(rgb_data->pixel_clk_rgb);
|
|
pr_err("%s: can't find rgb_pixel_clk. rc=%d\n",
|
|
__func__, rc);
|
|
rgb_data->pixel_clk_rgb = NULL;
|
|
return rc;
|
|
}
|
|
|
|
rgb_data->byte_clk_rcg = devm_clk_get(dev, "byte_clk_rcg");
|
|
if (IS_ERR(rgb_data->byte_clk_rcg)) {
|
|
pr_debug("%s: can't find byte clk rcg. rc=%d\n", __func__, rc);
|
|
rgb_data->byte_clk_rcg = NULL;
|
|
}
|
|
|
|
rgb_data->pixel_clk_rcg = devm_clk_get(dev, "pixel_clk_rcg");
|
|
if (IS_ERR(rgb_data->pixel_clk_rcg)) {
|
|
pr_debug("%s: can't find pixel clk rcg. rc=%d\n", __func__, rc);
|
|
rgb_data->pixel_clk_rcg = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mdss_rgb_ctrl_clock_init(struct platform_device *ctrl_pdev,
|
|
struct mdss_rgb_data *rgb_data)
|
|
{
|
|
int rc = 0;
|
|
struct mdss_dsi_clk_info info;
|
|
struct mdss_dsi_clk_client client1 = {"dsi_clk_client"};
|
|
struct mdss_dsi_clk_client client2 = {"mdp_event_client"};
|
|
void *handle;
|
|
|
|
|
|
if (mdss_rgb_pix_clk_init(ctrl_pdev, rgb_data))
|
|
return -EPERM;
|
|
|
|
memset(&info, 0x0, sizeof(info));
|
|
|
|
info.core_clks.mdp_core_clk = rgb_data->mdp_core_clk;
|
|
info.core_clks.mmss_misc_ahb_clk = rgb_data->mmss_misc_ahb_clk;
|
|
info.link_hs_clks.byte_clk = rgb_data->byte_clk_rgb;
|
|
info.link_hs_clks.pixel_clk = rgb_data->pixel_clk_rgb;
|
|
|
|
info.priv_data = rgb_data;
|
|
snprintf(info.name, sizeof(info.name), "DSI0");
|
|
rgb_data->clk_mngr = mdss_dsi_clk_init(&info);
|
|
|
|
if (IS_ERR_OR_NULL(rgb_data->clk_mngr)) {
|
|
rc = PTR_ERR(rgb_data->clk_mngr);
|
|
rgb_data->clk_mngr = NULL;
|
|
pr_err("dsi clock registration failed, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
handle = mdss_dsi_clk_register(rgb_data->clk_mngr, &client1);
|
|
if (IS_ERR_OR_NULL(handle)) {
|
|
rc = PTR_ERR(handle);
|
|
pr_err("failed to register %s client, rc = %d\n",
|
|
client1.client_name, rc);
|
|
return rc;
|
|
}
|
|
rgb_data->clk_handle = handle;
|
|
|
|
handle = mdss_dsi_clk_register(rgb_data->clk_mngr, &client2);
|
|
if (IS_ERR_OR_NULL(handle)) {
|
|
rc = PTR_ERR(handle);
|
|
pr_err("failed to register %s client, rc = %d\n",
|
|
client2.client_name, rc);
|
|
goto error_clk_client_deregister;
|
|
} else {
|
|
rgb_data->mdp_clk_handle = handle;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error_clk_client_deregister:
|
|
mdss_dsi_clk_deregister(rgb_data->clk_handle);
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_rgb_pinctrl_init(struct platform_device *pdev)
|
|
{
|
|
struct mdss_rgb_data *rgb_data;
|
|
|
|
rgb_data = platform_get_drvdata(pdev);
|
|
rgb_data->pin_res.pinctrl = devm_pinctrl_get(&pdev->dev);
|
|
if (IS_ERR_OR_NULL(rgb_data->pin_res.pinctrl)) {
|
|
pr_err("%s: failed to get pinctrl\n", __func__);
|
|
return PTR_ERR(rgb_data->pin_res.pinctrl);
|
|
}
|
|
|
|
rgb_data->pin_res.gpio_state_active
|
|
= pinctrl_lookup_state(rgb_data->pin_res.pinctrl,
|
|
MDSS_PINCTRL_STATE_DEFAULT);
|
|
if (IS_ERR_OR_NULL(rgb_data->pin_res.gpio_state_active))
|
|
pr_warn("%s: can not get default pinstate\n", __func__);
|
|
|
|
rgb_data->pin_res.gpio_state_suspend
|
|
= pinctrl_lookup_state(rgb_data->pin_res.pinctrl,
|
|
MDSS_PINCTRL_STATE_SLEEP);
|
|
if (IS_ERR_OR_NULL(rgb_data->pin_res.gpio_state_suspend))
|
|
pr_warn("%s: can not get sleep pinstate\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mdss_rgb_config_clk_src(struct platform_device *pdev)
|
|
{
|
|
struct mdss_rgb_data *rgb_res = platform_get_drvdata(pdev);
|
|
|
|
if (!rgb_res->ext_pixel0_clk) {
|
|
pr_err("%s: RGB ext. clocks not present\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (rgb_res->pll_src_config == PLL_SRC_DEFAULT) {
|
|
rgb_res->byte0_parent = rgb_res->ext_byte0_clk;
|
|
rgb_res->pixel0_parent = rgb_res->ext_pixel0_clk;
|
|
}
|
|
pr_debug("%s: default: DSI0 <--> PLL0\n", __func__);
|
|
}
|
|
|
|
static int mdss_rgb_set_clk_src(struct mdss_rgb_data *ctrl)
|
|
{
|
|
int rc;
|
|
struct clk *byte_parent, *pixel_parent = NULL;
|
|
|
|
if (!ctrl->byte_clk_rcg || !ctrl->pixel_clk_rcg) {
|
|
pr_debug("%s: set_clk_src not needed\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
byte_parent = ctrl->byte0_parent;
|
|
pixel_parent = ctrl->pixel0_parent;
|
|
|
|
if (pixel_parent == NULL || byte_parent == NULL)
|
|
pr_debug("%s : clk_parent is null\n", __func__);
|
|
|
|
rc = clk_set_parent(ctrl->byte_clk_rcg, byte_parent);
|
|
if (rc) {
|
|
pr_err("%s: failed to set parent for byte clk for rgb. rc=%d\n",
|
|
__func__, rc);
|
|
goto error;
|
|
}
|
|
|
|
rc = clk_set_parent(ctrl->pixel_clk_rcg, pixel_parent);
|
|
if (rc) {
|
|
pr_err("%s: failed to set parent for pixel clk for rgb. rc=%d\n",
|
|
__func__, rc);
|
|
goto error;
|
|
}
|
|
|
|
pr_debug("%s: rgb clock source set to pixel", __func__);
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_rgb_probe(struct spi_device *spi, struct platform_device *pdev)
|
|
{
|
|
struct mdss_panel_cfg *pan_cfg = NULL;
|
|
struct mdss_util_intf *util;
|
|
char *panel_cfg;
|
|
struct device_node *dsi_pan_node = NULL;
|
|
struct mdss_panel_info *pinfo = NULL;
|
|
int rc = 0;
|
|
|
|
if (spi) {
|
|
spi_dev = spi;
|
|
spi_dev->bits_per_word = 9;
|
|
return 0;
|
|
}
|
|
|
|
if (!spi_dev) {
|
|
pr_err("%s: SPI controller not probed yet\n", __func__);
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
if (!pdev || !pdev->dev.of_node) {
|
|
pr_err("%s: RGB driver only supports device tree probe\n",
|
|
__func__);
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
util = mdss_get_util_intf();
|
|
if (util == NULL) {
|
|
pr_err("%s: Failed to get mdss utility functions\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!util->mdp_probe_done) {
|
|
pr_err("%s: MDP not probed yet\n", __func__);
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
pan_cfg = util->panel_intf_type(MDSS_PANEL_INTF_RGB);
|
|
if (IS_ERR_OR_NULL(pan_cfg)) {
|
|
rc = PTR_ERR(pan_cfg);
|
|
return rc;
|
|
}
|
|
panel_cfg = pan_cfg->arg_cfg;
|
|
|
|
rc = mdss_rgb_res_init(pdev);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = mdss_rgb_pinctrl_init(pdev);
|
|
|
|
rc = mdss_rgb_ctrl_clock_init(pdev, mdss_rgb_res);
|
|
if (rc)
|
|
return rc;
|
|
|
|
dsi_pan_node = mdss_rgb_config_panel(pdev);
|
|
if (!dsi_pan_node)
|
|
return -EINVAL;
|
|
|
|
rc = rgb_panel_device_register(pdev, dsi_pan_node, mdss_rgb_res);
|
|
if (rc)
|
|
return rc;
|
|
|
|
pinfo = &(mdss_rgb_res->panel_data.panel_info);
|
|
rc = mdss_rgb_set_clk_rates(mdss_rgb_res);
|
|
if (rc)
|
|
return rc;
|
|
|
|
mdss_rgb_config_clk_src(pdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mdss_rgb_clk_div_config(struct mdss_rgb_data *rgb_data,
|
|
struct mdss_panel_info *panel_info, int frame_rate)
|
|
{
|
|
u64 h_period, v_period, clk_rate;
|
|
u8 bpp;
|
|
|
|
if (!panel_info)
|
|
return -EINVAL;
|
|
|
|
pr_debug("mipi.dst_format = %d\n", panel_info->mipi.dst_format);
|
|
switch (panel_info->mipi.dst_format) {
|
|
case DSI_CMD_DST_FORMAT_RGB888:
|
|
case DSI_VIDEO_DST_FORMAT_RGB888:
|
|
case DSI_VIDEO_DST_FORMAT_RGB666:
|
|
bpp = 3;
|
|
break;
|
|
case DSI_CMD_DST_FORMAT_RGB565:
|
|
case DSI_VIDEO_DST_FORMAT_RGB565:
|
|
bpp = 2;
|
|
break;
|
|
default:
|
|
bpp = 3; /* Default format set to RGB888 */
|
|
break;
|
|
}
|
|
|
|
h_period = mdss_panel_get_htotal(panel_info, true);
|
|
v_period = mdss_panel_get_vtotal(panel_info);
|
|
|
|
panel_info->clk_rate = h_period * v_period * frame_rate * bpp * 8;
|
|
|
|
clk_rate = panel_info->clk_rate;
|
|
do_div(clk_rate, 8 * bpp);
|
|
|
|
panel_info->mipi.dsi_pclk_rate = (u32) clk_rate;
|
|
|
|
return 0;
|
|
}
|
|
static int mdss_rgb_pinctrl_set_state(struct mdss_rgb_data *rgb_data,
|
|
bool active)
|
|
{
|
|
struct pinctrl_state *pin_state;
|
|
int ret = 0;
|
|
|
|
pin_state = active ? rgb_data->pin_res.gpio_state_active
|
|
: rgb_data->pin_res.gpio_state_suspend;
|
|
if (!IS_ERR_OR_NULL(pin_state)) {
|
|
ret = pinctrl_select_state(rgb_data->pin_res.pinctrl,
|
|
pin_state);
|
|
if (ret)
|
|
pr_err("%s: can not set %s pins\n", __func__,
|
|
active ? MDSS_PINCTRL_STATE_DEFAULT
|
|
: MDSS_PINCTRL_STATE_SLEEP);
|
|
} else {
|
|
pr_err("%s: invalid '%s' pinstate\n", __func__,
|
|
active ? MDSS_PINCTRL_STATE_DEFAULT
|
|
: MDSS_PINCTRL_STATE_SLEEP);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int mdss_rgb_panel_power_off(struct mdss_panel_data *pdata)
|
|
{
|
|
int ret = 0;
|
|
struct mdss_rgb_data *ctrl_pdata = NULL;
|
|
|
|
ctrl_pdata = container_of(pdata, struct mdss_rgb_data,
|
|
panel_data);
|
|
|
|
ret = mdss_rgb_pinctrl_set_state(ctrl_pdata, false);
|
|
|
|
ret = msm_dss_enable_vreg(
|
|
ctrl_pdata->panel_power_data.vreg_config,
|
|
ctrl_pdata->panel_power_data.num_vreg, 0);
|
|
if (ret)
|
|
pr_err("%s: failed to disable vregs for %s\n",
|
|
__func__, __mdss_dsi_pm_name(DSI_PANEL_PM));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mdss_rgb_panel_power_on(struct mdss_panel_data *pdata)
|
|
{
|
|
int ret = 0;
|
|
struct mdss_rgb_data *ctrl_pdata = NULL;
|
|
|
|
ctrl_pdata = container_of(pdata, struct mdss_rgb_data,
|
|
panel_data);
|
|
|
|
ret = msm_dss_enable_vreg(
|
|
ctrl_pdata->panel_power_data.vreg_config,
|
|
ctrl_pdata->panel_power_data.num_vreg, 1);
|
|
if (ret) {
|
|
pr_err("%s: failed to enable vregs for %s\n",
|
|
__func__, __mdss_dsi_pm_name(DSI_PANEL_PM));
|
|
return ret;
|
|
}
|
|
|
|
ret = mdss_rgb_pinctrl_set_state(ctrl_pdata, true);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int mdss_rgb_panel_power_ctrl(struct mdss_panel_data *pdata, int power_state)
|
|
{
|
|
int ret = 0;
|
|
struct mdss_panel_info *pinfo;
|
|
|
|
if (pdata == NULL) {
|
|
pr_err("%s: Invalid input data\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pinfo = &pdata->panel_info;
|
|
pr_debug("%s: cur_power_state=%d req_power_state=%d\n", __func__,
|
|
pinfo->panel_power_state, power_state);
|
|
|
|
if (pinfo->panel_power_state == power_state) {
|
|
pr_debug("%s: no change needed\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
switch (power_state) {
|
|
case MDSS_PANEL_POWER_ON:
|
|
ret = mdss_rgb_panel_power_on(pdata);
|
|
break;
|
|
case MDSS_PANEL_POWER_OFF:
|
|
ret = mdss_rgb_panel_power_off(pdata);
|
|
break;
|
|
default:
|
|
pr_err("%s: unknown panel power state requested (%d)\n",
|
|
__func__, power_state);
|
|
ret = -EINVAL;
|
|
}
|
|
if (!ret)
|
|
pinfo->panel_power_state = power_state;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void mdss_rgb_phy_config(struct mdss_rgb_data *ctrl, bool phy_enable)
|
|
{
|
|
if (phy_enable) {
|
|
/* clk_en */
|
|
MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_CMN_GLBL_TEST_CTRL, 0x1);
|
|
MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_PLL_CLKBUFLR_EN, 0x01);
|
|
} else {
|
|
MIPI_OUTP(ctrl->phy_io.base + DSIPHY_PLL_CLKBUFLR_EN, 0);
|
|
MIPI_OUTP(ctrl->phy_io.base + DSIPHY_CMN_GLBL_TEST_CTRL, 0);
|
|
}
|
|
}
|
|
|
|
static int mdss_rgb_unblank(struct mdss_panel_data *pdata)
|
|
{
|
|
struct mdss_rgb_data *ctrl_pdata = NULL;
|
|
|
|
ctrl_pdata = container_of(pdata, struct mdss_rgb_data,
|
|
panel_data);
|
|
|
|
ctrl_pdata->on(pdata);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mdss_rgb_blank(struct mdss_panel_data *pdata, int power_state)
|
|
{
|
|
struct mdss_rgb_data *ctrl_pdata = NULL;
|
|
|
|
ctrl_pdata = container_of(pdata, struct mdss_rgb_data,
|
|
panel_data);
|
|
|
|
ctrl_pdata->off(pdata);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mdss_rgb_on(struct mdss_panel_data *pdata)
|
|
{
|
|
int rc = 0;
|
|
struct mdss_panel_info *pinfo;
|
|
struct mipi_panel_info *mipi;
|
|
struct mdss_rgb_data *ctrl_pdata = NULL;
|
|
int cur_power_state;
|
|
|
|
ctrl_pdata = container_of(pdata, struct mdss_rgb_data,
|
|
panel_data);
|
|
|
|
cur_power_state = pdata->panel_info.panel_power_state;
|
|
pr_debug("%s+: ctrl=%pK cur_power_state=%d\n", __func__,
|
|
ctrl_pdata, cur_power_state);
|
|
|
|
pinfo = &pdata->panel_info;
|
|
mipi = &pdata->panel_info.mipi;
|
|
|
|
rc = mdss_rgb_panel_power_ctrl(pdata, MDSS_PANEL_POWER_ON);
|
|
|
|
if (rc)
|
|
goto end;
|
|
|
|
if (mdss_panel_is_power_on(cur_power_state)) {
|
|
pr_debug("%s: rgb_on from panel low power state\n", __func__);
|
|
goto end;
|
|
}
|
|
|
|
rc = mdss_rgb_set_clk_src(ctrl_pdata);
|
|
|
|
rc = mdss_dsi_clk_req_state(ctrl_pdata->clk_handle,
|
|
MDSS_DSI_CORE_CLK, MDSS_DSI_CLK_ON, 0);
|
|
if (rc) {
|
|
pr_err("%s: failed set clk state for Core Clks, rc = %d\n",
|
|
__func__, rc);
|
|
goto end;
|
|
}
|
|
mdss_rgb_phy_config(ctrl_pdata, 1);
|
|
|
|
rc = mdss_dsi_clk_req_state(ctrl_pdata->clk_handle,
|
|
MDSS_DSI_LINK_CLK, MDSS_DSI_CLK_ON, 0);
|
|
if (rc)
|
|
pr_err("%s: failed set clk state for Link Clks, rc = %d\n",
|
|
__func__, rc);
|
|
|
|
end:
|
|
pr_debug("%s-:\n", __func__);
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_rgb_off(struct mdss_panel_data *pdata, int power_state)
|
|
{
|
|
int ret = 0;
|
|
struct mdss_rgb_data *ctrl_pdata = NULL;
|
|
struct mdss_panel_info *panel_info = NULL;
|
|
|
|
ctrl_pdata = container_of(pdata, struct mdss_rgb_data,
|
|
panel_data);
|
|
|
|
panel_info = &ctrl_pdata->panel_data.panel_info;
|
|
|
|
pr_debug("%s+: ctrl=%pK power_state=%d\n",
|
|
__func__, ctrl_pdata, power_state);
|
|
|
|
if (power_state == panel_info->panel_power_state) {
|
|
pr_debug("%s: No change in power state %d -> %d\n", __func__,
|
|
panel_info->panel_power_state, power_state);
|
|
goto end;
|
|
}
|
|
|
|
if (mdss_panel_is_power_on(power_state)) {
|
|
pr_debug("%s: dsi_off with panel always on\n", __func__);
|
|
goto panel_power_ctrl;
|
|
}
|
|
|
|
/*
|
|
* Link clocks should be turned off before PHY can be disabled.
|
|
* For command mode panels, all clocks are turned off prior to reaching
|
|
* here, so core clocks should be turned on before accessing hardware
|
|
* registers. For video mode panel, turn off link clocks and then
|
|
* disable PHY
|
|
*/
|
|
if (pdata->panel_info.type == MIPI_CMD_PANEL)
|
|
ret = mdss_dsi_clk_req_state(ctrl_pdata->clk_handle,
|
|
MDSS_DSI_CORE_CLK, MDSS_DSI_CLK_ON, 0);
|
|
else
|
|
ret = mdss_dsi_clk_req_state(ctrl_pdata->clk_handle,
|
|
MDSS_DSI_LINK_CLK, MDSS_DSI_CLK_OFF, 0);
|
|
|
|
/* disable phy */
|
|
mdss_rgb_phy_config(ctrl_pdata, 0);
|
|
|
|
ret = mdss_dsi_clk_req_state(ctrl_pdata->clk_handle,
|
|
MDSS_DSI_CORE_CLK, MDSS_DSI_CLK_OFF, 0);
|
|
|
|
panel_power_ctrl:
|
|
mdss_rgb_panel_power_ctrl(pdata, power_state);
|
|
end:
|
|
pr_debug("%s-:\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static int mdss_rgb_event_handler(struct mdss_panel_data *pdata,
|
|
int event, void *arg)
|
|
{
|
|
int rc = 0;
|
|
struct mdss_rgb_data *ctrl_pdata = NULL;
|
|
struct mdss_panel_info *pinfo;
|
|
int power_state;
|
|
|
|
if (pdata == NULL) {
|
|
pr_err("%s: Invalid input data\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pinfo = &pdata->panel_info;
|
|
ctrl_pdata = container_of(pdata, struct mdss_rgb_data,
|
|
panel_data);
|
|
|
|
switch (event) {
|
|
case MDSS_EVENT_LINK_READY:
|
|
if (ctrl_pdata->refresh_clk_rate)
|
|
rc = mdss_rgb_clk_refresh(ctrl_pdata);
|
|
|
|
rc = mdss_rgb_on(pdata);
|
|
break;
|
|
|
|
case MDSS_EVENT_UNBLANK:
|
|
rc = mdss_rgb_unblank(pdata);
|
|
break;
|
|
case MDSS_EVENT_BLANK:
|
|
power_state = (int) (unsigned long) arg;
|
|
rc = mdss_rgb_blank(pdata, power_state);
|
|
break;
|
|
case MDSS_EVENT_PANEL_OFF:
|
|
power_state = (int) (unsigned long) arg;
|
|
rc = mdss_rgb_blank(pdata, power_state);
|
|
rc = mdss_rgb_off(pdata, power_state);
|
|
break;
|
|
}
|
|
pr_debug("%s+: event=%d\n", __func__, event);
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_rgb_parse_gpio_params(struct platform_device *ctrl_pdev,
|
|
struct mdss_rgb_data *rgb_data)
|
|
{
|
|
int rc = 0;
|
|
|
|
rgb_data->rst_gpio = of_get_named_gpio(ctrl_pdev->dev.of_node,
|
|
"qcom,platform-reset-gpio", 0);
|
|
if (!gpio_is_valid(rgb_data->rst_gpio)) {
|
|
pr_err("%s:%d, reset gpio not specified\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = gpio_request(rgb_data->rst_gpio, "disp_rst_n");
|
|
if (rc) {
|
|
pr_err("request reset gpio failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = gpio_direction_output(rgb_data->rst_gpio, 1);
|
|
if (rc) {
|
|
pr_err("%s: unable to set dir for rst gpio\n",
|
|
__func__);
|
|
return rc;
|
|
}
|
|
|
|
pr_debug("%s: reset gpio set\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
int rgb_panel_device_register(struct platform_device *ctrl_pdev,
|
|
struct device_node *pan_node, struct mdss_rgb_data *rgb_data)
|
|
{
|
|
struct mipi_panel_info *mipi;
|
|
int rc = 0;
|
|
struct mdss_panel_info *pinfo = &(rgb_data->panel_data.panel_info);
|
|
struct mdss_util_intf *util;
|
|
u64 clk_rate;
|
|
|
|
mipi = &(pinfo->mipi);
|
|
util = mdss_get_util_intf();
|
|
pinfo->type = RGB_PANEL;
|
|
|
|
mdss_rgb_clk_div_config(rgb_data, pinfo, mipi->frame_rate);
|
|
|
|
rgb_data->pclk_rate = mipi->dsi_pclk_rate;
|
|
clk_rate = pinfo->clk_rate;
|
|
do_div(clk_rate, 8U);
|
|
rgb_data->byte_clk_rate = (u32)clk_rate;
|
|
pr_debug("%s: pclk=%d, bclk=%d\n", __func__,
|
|
rgb_data->pclk_rate, rgb_data->byte_clk_rate);
|
|
|
|
rc = mdss_dsi_get_dt_vreg_data(&ctrl_pdev->dev, pan_node,
|
|
&rgb_data->panel_power_data, DSI_PANEL_PM);
|
|
if (rc) {
|
|
pr_err("%s: '%s' get_dt_vreg_data failed.rc=%d\n",
|
|
__func__, __mdss_dsi_pm_name(DSI_PANEL_PM), rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = msm_dss_config_vreg(&ctrl_pdev->dev,
|
|
rgb_data->panel_power_data.vreg_config,
|
|
rgb_data->panel_power_data.num_vreg, 1);
|
|
if (rc) {
|
|
pr_err("%s: failed to init regulator, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
pinfo->panel_max_fps = mdss_panel_get_framerate(pinfo);
|
|
pinfo->panel_max_vtotal = mdss_panel_get_vtotal(pinfo);
|
|
|
|
rc = mdss_rgb_parse_gpio_params(ctrl_pdev, rgb_data);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = msm_dss_ioremap_byname(ctrl_pdev, &rgb_data->phy_io, "dsi_phy");
|
|
if (rc) {
|
|
pr_err("%s:%d unable to remap dsi phy resources\n",
|
|
__func__, __LINE__);
|
|
return rc;
|
|
}
|
|
|
|
rgb_data->panel_data.event_handler = mdss_rgb_event_handler;
|
|
|
|
pinfo->is_prim_panel = true;
|
|
|
|
pinfo->cont_splash_enabled =
|
|
util->panel_intf_status(pinfo->pdest,
|
|
MDSS_PANEL_INTF_RGB) ? true : false;
|
|
|
|
pr_info("%s: Continuous splash %s\n", __func__,
|
|
pinfo->cont_splash_enabled ? "enabled" : "disabled");
|
|
|
|
rc = mdss_register_panel(ctrl_pdev, &(rgb_data->panel_data));
|
|
|
|
|
|
if (rc) {
|
|
pr_err("%s: unable to register RGB panel\n", __func__);
|
|
return rc;
|
|
}
|
|
panel_debug_register_base("panel",
|
|
rgb_data->ctrl_base, rgb_data->reg_size);
|
|
|
|
pr_debug("%s: Panel data initialized\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mdss_rgb_remove(struct spi_device *spi, struct platform_device *pdev)
|
|
{
|
|
if (pdev)
|
|
mdss_rgb_res_deinit(pdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mdss_rgb_probe_pdev(struct platform_device *pdev)
|
|
{
|
|
return mdss_rgb_probe(NULL, pdev);
|
|
}
|
|
|
|
static int mdss_rgb_remove_pdev(struct platform_device *pdev)
|
|
{
|
|
return mdss_rgb_remove(NULL, pdev);
|
|
}
|
|
|
|
static int mdss_rgb_probe_spi(struct spi_device *spi)
|
|
{
|
|
return mdss_rgb_probe(spi, NULL);
|
|
}
|
|
|
|
static int mdss_rgb_remove_spi(struct spi_device *spi)
|
|
{
|
|
return mdss_rgb_remove(spi, NULL);
|
|
}
|
|
|
|
static const struct of_device_id mdss_rgb_spi_dt_match[] = {
|
|
{.compatible = "qcom,mdss-rgb-spi"},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mdss_rgb_spi_dt_match);
|
|
|
|
static const struct of_device_id mdss_rgb_dt_match[] = {
|
|
{.compatible = "qcom,mdss-rgb"},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mdss_rgb_dt_match);
|
|
|
|
static struct spi_driver mdss_rgb_spi_driver = {
|
|
.probe = mdss_rgb_probe_spi,
|
|
.remove = mdss_rgb_remove_spi,
|
|
.driver = {
|
|
.name = "mdss_rgb_spi",
|
|
.of_match_table = mdss_rgb_spi_dt_match,
|
|
},
|
|
};
|
|
|
|
static struct platform_driver mdss_rgb_driver = {
|
|
.probe = mdss_rgb_probe_pdev,
|
|
.remove = mdss_rgb_remove_pdev,
|
|
.shutdown = NULL,
|
|
.driver = {
|
|
.name = "mdss_rgb",
|
|
.of_match_table = mdss_rgb_dt_match,
|
|
},
|
|
};
|
|
|
|
static int __init mdss_rgb_driver_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = spi_register_driver(&mdss_rgb_spi_driver);
|
|
if (ret < 0) {
|
|
pr_err("failed to register mdss_rgb spi driver\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = platform_driver_register(&mdss_rgb_driver);
|
|
if (ret)
|
|
pr_err("failed to register mdss_rgb platform driver\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
module_init(mdss_rgb_driver_init);
|
|
|
|
static void __exit mdss_rgb_driver_cleanup(void)
|
|
{
|
|
platform_driver_unregister(&mdss_rgb_driver);
|
|
spi_unregister_driver(&mdss_rgb_spi_driver);
|
|
}
|
|
module_exit(mdss_rgb_driver_cleanup);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("RGB driver");
|