| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright (c) 2018, 2020, The Linux Foundation. All rights reserved. */ |
| |
| |
| #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_clks.byte_clk = rgb_data->byte_clk_rgb; |
| info.link_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\n", __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"); |