| /* Copyright (c) 2012-2015, 2017-2018, 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/clk.h> |
| #include <linux/clk/clk-conf.h> |
| #include <linux/err.h> |
| #include <linux/delay.h> |
| |
| #include "dpu_io_util.h" |
| |
| void msm_dss_put_clk(struct dss_clk *clk_arry, int num_clk) |
| { |
| int i; |
| |
| for (i = num_clk - 1; i >= 0; i--) { |
| if (clk_arry[i].clk) |
| clk_put(clk_arry[i].clk); |
| clk_arry[i].clk = NULL; |
| } |
| } |
| |
| int msm_dss_get_clk(struct device *dev, struct dss_clk *clk_arry, int num_clk) |
| { |
| int i, rc = 0; |
| |
| for (i = 0; i < num_clk; i++) { |
| clk_arry[i].clk = clk_get(dev, clk_arry[i].clk_name); |
| rc = PTR_ERR_OR_ZERO(clk_arry[i].clk); |
| if (rc) { |
| DEV_ERR("%pS->%s: '%s' get failed. rc=%d\n", |
| __builtin_return_address(0), __func__, |
| clk_arry[i].clk_name, rc); |
| goto error; |
| } |
| } |
| |
| return rc; |
| |
| error: |
| for (i--; i >= 0; i--) { |
| if (clk_arry[i].clk) |
| clk_put(clk_arry[i].clk); |
| clk_arry[i].clk = NULL; |
| } |
| |
| return rc; |
| } |
| |
| int msm_dss_clk_set_rate(struct dss_clk *clk_arry, int num_clk) |
| { |
| int i, rc = 0; |
| |
| for (i = 0; i < num_clk; i++) { |
| if (clk_arry[i].clk) { |
| if (clk_arry[i].type != DSS_CLK_AHB) { |
| DEV_DBG("%pS->%s: '%s' rate %ld\n", |
| __builtin_return_address(0), __func__, |
| clk_arry[i].clk_name, |
| clk_arry[i].rate); |
| rc = clk_set_rate(clk_arry[i].clk, |
| clk_arry[i].rate); |
| if (rc) { |
| DEV_ERR("%pS->%s: %s failed. rc=%d\n", |
| __builtin_return_address(0), |
| __func__, |
| clk_arry[i].clk_name, rc); |
| break; |
| } |
| } |
| } else { |
| DEV_ERR("%pS->%s: '%s' is not available\n", |
| __builtin_return_address(0), __func__, |
| clk_arry[i].clk_name); |
| rc = -EPERM; |
| break; |
| } |
| } |
| |
| return rc; |
| } |
| |
| int msm_dss_enable_clk(struct dss_clk *clk_arry, int num_clk, int enable) |
| { |
| int i, rc = 0; |
| |
| if (enable) { |
| for (i = 0; i < num_clk; i++) { |
| DEV_DBG("%pS->%s: enable '%s'\n", |
| __builtin_return_address(0), __func__, |
| clk_arry[i].clk_name); |
| if (clk_arry[i].clk) { |
| rc = clk_prepare_enable(clk_arry[i].clk); |
| if (rc) |
| DEV_ERR("%pS->%s: %s en fail. rc=%d\n", |
| __builtin_return_address(0), |
| __func__, |
| clk_arry[i].clk_name, rc); |
| } else { |
| DEV_ERR("%pS->%s: '%s' is not available\n", |
| __builtin_return_address(0), __func__, |
| clk_arry[i].clk_name); |
| rc = -EPERM; |
| } |
| |
| if (rc) { |
| msm_dss_enable_clk(&clk_arry[i], |
| i, false); |
| break; |
| } |
| } |
| } else { |
| for (i = num_clk - 1; i >= 0; i--) { |
| DEV_DBG("%pS->%s: disable '%s'\n", |
| __builtin_return_address(0), __func__, |
| clk_arry[i].clk_name); |
| |
| if (clk_arry[i].clk) |
| clk_disable_unprepare(clk_arry[i].clk); |
| else |
| DEV_ERR("%pS->%s: '%s' is not available\n", |
| __builtin_return_address(0), __func__, |
| clk_arry[i].clk_name); |
| } |
| } |
| |
| return rc; |
| } |
| |
| int msm_dss_parse_clock(struct platform_device *pdev, |
| struct dss_module_power *mp) |
| { |
| u32 i, rc = 0; |
| const char *clock_name; |
| int num_clk = 0; |
| |
| if (!pdev || !mp) |
| return -EINVAL; |
| |
| mp->num_clk = 0; |
| num_clk = of_property_count_strings(pdev->dev.of_node, "clock-names"); |
| if (num_clk <= 0) { |
| pr_debug("clocks are not defined\n"); |
| return 0; |
| } |
| |
| mp->clk_config = devm_kzalloc(&pdev->dev, |
| sizeof(struct dss_clk) * num_clk, |
| GFP_KERNEL); |
| if (!mp->clk_config) |
| return -ENOMEM; |
| |
| for (i = 0; i < num_clk; i++) { |
| rc = of_property_read_string_index(pdev->dev.of_node, |
| "clock-names", i, |
| &clock_name); |
| if (rc) { |
| dev_err(&pdev->dev, "Failed to get clock name for %d\n", |
| i); |
| break; |
| } |
| strlcpy(mp->clk_config[i].clk_name, clock_name, |
| sizeof(mp->clk_config[i].clk_name)); |
| |
| mp->clk_config[i].type = DSS_CLK_AHB; |
| } |
| |
| rc = msm_dss_get_clk(&pdev->dev, mp->clk_config, num_clk); |
| if (rc) { |
| dev_err(&pdev->dev, "Failed to get clock refs %d\n", rc); |
| goto err; |
| } |
| |
| rc = of_clk_set_defaults(pdev->dev.of_node, false); |
| if (rc) { |
| dev_err(&pdev->dev, "Failed to set clock defaults %d\n", rc); |
| goto err; |
| } |
| |
| for (i = 0; i < num_clk; i++) { |
| u32 rate = clk_get_rate(mp->clk_config[i].clk); |
| if (!rate) |
| continue; |
| mp->clk_config[i].rate = rate; |
| mp->clk_config[i].type = DSS_CLK_PCLK; |
| } |
| |
| mp->num_clk = num_clk; |
| return 0; |
| |
| err: |
| msm_dss_put_clk(mp->clk_config, num_clk); |
| return rc; |
| } |