From patchwork Tue Dec 15 12:30:02 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Georgi Djakov X-Patchwork-Id: 58431 Delivered-To: patch@linaro.org Received: by 10.112.89.199 with SMTP id bq7csp2638lbb; Tue, 15 Dec 2015 04:30:45 -0800 (PST) X-Received: by 10.98.66.216 with SMTP id h85mr24548822pfd.118.1450182631377; Tue, 15 Dec 2015 04:30:31 -0800 (PST) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id fm8si1730230pab.17.2015.12.15.04.30.28; Tue, 15 Dec 2015 04:30:31 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dkim=neutral (body hash did not verify) header.i=@linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753954AbbLOMaZ (ORCPT + 28 others); Tue, 15 Dec 2015 07:30:25 -0500 Received: from mail-wm0-f54.google.com ([74.125.82.54]:35944 "EHLO mail-wm0-f54.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753858AbbLOMaP (ORCPT ); Tue, 15 Dec 2015 07:30:15 -0500 Received: by mail-wm0-f54.google.com with SMTP id n186so162532277wmn.1 for ; Tue, 15 Dec 2015 04:30:15 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=ePMvHwdSg1n9CAxwuJ7TVId+RIWP4GSvavi2+Nybir8=; b=Ku/MnauFb0HgDlayNMhGT5qJK2U0vp43UrcjfHzHIA9mrP/jgN/3Rv98vLjECaWWSp EP/EypO0rsxqM+nDrdDxO6ZVH8DfkD/Ya8BPcDInm+b/kL8IEsjAAthuwLDMDiMzsdlq Y1LWdpWj40HzzztgGc3AOKG3m3yfDvf+T6Zs0= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=ePMvHwdSg1n9CAxwuJ7TVId+RIWP4GSvavi2+Nybir8=; b=PhN2XGPROzMEJC6if5vHlp4ffIhUV0aC3tCaeS7QUp3suRM3GtAWgVpT7zNBQnUqBN 0UITFzu7wOcHoHGTdIRBEJk4oEakEMLLo8atNwzvdqwnbRwkjG2CRbu++qaVWdsIZ+g4 iB/vU8jWaJXrAeOYfToY+8eGxM9q3+ok+7B7G3ZnuOwErG5NB1w6q13szI9sHLfaoM4E vflUaKnFdi5ovOpQV0rnJ4hI/rXml/gUqixhrc/WLzHPf2TvSgbIIn7snizG+MNl1L38 7iXgXJHeKrCTtoVHbzOWI+Aerj459JzcUS6Z6s0BMC736YV06i1ceCKNPaV7qoG+Vw/v e51g== X-Gm-Message-State: ALoCoQkQ3QWzPDErlp3dyz9cURXGEYp50KuS2sgDHedo7UW+lggTqo1WmzA7uhnAh/Znpj6fNG25ccHz31A49/OTBqxoW7BNbA== X-Received: by 10.194.242.195 with SMTP id ws3mr43433118wjc.131.1450182614445; Tue, 15 Dec 2015 04:30:14 -0800 (PST) Received: from mms.qualcomm.mm-sol.com ([37.157.136.206]) by smtp.googlemail.com with ESMTPSA id t3sm1241072wjz.11.2015.12.15.04.30.12 (version=TLSv1/SSLv3 cipher=OTHER); Tue, 15 Dec 2015 04:30:13 -0800 (PST) From: Georgi Djakov To: sboyd@codeaurora.org Cc: mturquette@baylibre.com, linux-clk@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, georgi.djakov@linaro.org Subject: [PATCH v6 2/2] clk: qcom: Add support for RPM Clocks Date: Tue, 15 Dec 2015 14:30:02 +0200 Message-Id: <1450182602-6996-3-git-send-email-georgi.djakov@linaro.org> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1450182602-6996-1-git-send-email-georgi.djakov@linaro.org> References: <1450182602-6996-1-git-send-email-georgi.djakov@linaro.org> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This adds initial support for clocks controlled by the Resource Power Manager (RPM) processor on some Qualcomm SoCs, which use the qcom_rpm driver to communicate with RPM. Such platforms are apq8064 and msm8960. Signed-off-by: Georgi Djakov --- .../devicetree/bindings/clock/qcom,rpmcc.txt | 1 + drivers/clk/qcom/Kconfig | 13 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/clk-rpm.c | 290 ++++++++++++++++++++ drivers/clk/qcom/clk-rpm.h | 71 +++++ 5 files changed, 376 insertions(+) create mode 100644 drivers/clk/qcom/clk-rpm.c create mode 100644 drivers/clk/qcom/clk-rpm.h -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/ diff --git a/Documentation/devicetree/bindings/clock/qcom,rpmcc.txt b/Documentation/devicetree/bindings/clock/qcom,rpmcc.txt index 91be034ea75b..7f7ce1f042fd 100644 --- a/Documentation/devicetree/bindings/clock/qcom,rpmcc.txt +++ b/Documentation/devicetree/bindings/clock/qcom,rpmcc.txt @@ -11,6 +11,7 @@ Required properties : compatible "qcom,rpmcc" should be also included. "qcom,rpmcc-msm8916", "qcom,rpmcc" + "qcom,rpmcc-apq8064", "qcom,rpmcc" - #clock-cells : shall contain 1 diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 5963d36f0c45..748123893958 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -25,6 +25,19 @@ config QCOM_CLK_SMD_RPM Say Y if you want to support the clocks exposed by the RPM on platforms such as apq8016, apq8084, msm8974 etc. +config QCOM_CLK_RPM + tristate "RPM based Clock Controller" + depends on COMMON_CLK_QCOM && MFD_QCOM_RPM + select QCOM_RPMCC + help + The RPM (Resource Power Manager) is a dedicated hardware engine for + managing the shared SoC resources in order to keep the lowest power + profile. It communicates with other hardware subsystems via shared + memory and accepts clock requests, aggregates the requests and turns + the clocks on/off or scales them on demand. + Say Y if you want to support the clocks exposed by the RPM on + platforms such as apq8064, msm8660, msm8960 etc. + config APQ_GCC_8084 tristate "APQ8084 Global Clock Controller" select QCOM_GDSC diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 5f4673b7b03a..25bf2f81d58a 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o +obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o diff --git a/drivers/clk/qcom/clk-rpm.c b/drivers/clk/qcom/clk-rpm.c new file mode 100644 index 000000000000..7b0e85eefe70 --- /dev/null +++ b/drivers/clk/qcom/clk-rpm.c @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clk-rpm.h" +#include + +#define to_clk_rpm(_hw) container_of(_hw, struct clk_rpm, hw) + +static DEFINE_MUTEX(rpm_clk_lock); + +static int clk_rpm_set_rate_active(struct clk_rpm *r, unsigned long rate) +{ + u32 value = DIV_ROUND_UP(rate, 1000); /* to kHz */ + + return qcom_rpm_write(r->rpm, QCOM_RPM_ACTIVE_STATE, + r->rpm_clk_id, &value, 1); +} + +static int clk_rpm_prepare(struct clk_hw *hw) +{ + struct clk_rpm *r = to_clk_rpm(hw); + unsigned long rate = r->rate; + int ret = 0; + + mutex_lock(&rpm_clk_lock); + + if (!rate) + goto out; + + if (r->branch) + rate = !!rate; + + ret = clk_rpm_set_rate_active(r, rate); + + if (ret) + goto out; + +out: + if (!ret) + r->enabled = true; + + mutex_unlock(&rpm_clk_lock); + + return ret; +} + +static void clk_rpm_unprepare(struct clk_hw *hw) +{ + struct clk_rpm *r = to_clk_rpm(hw); + int ret; + + mutex_lock(&rpm_clk_lock); + + if (!r->rate) + goto out; + + ret = clk_rpm_set_rate_active(r, r->rate); + if (ret) + goto out; + + r->enabled = false; + +out: + mutex_unlock(&rpm_clk_lock); +} + +static int clk_rpm_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct clk_rpm *r = to_clk_rpm(hw); + int ret = 0; + + mutex_lock(&rpm_clk_lock); + + if (r->enabled) + ret = clk_rpm_set_rate_active(r, rate); + + if (!ret) + r->rate = rate; + + mutex_unlock(&rpm_clk_lock); + + return ret; +} + +static long clk_rpm_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + /* + * RPM handles rate rounding and we don't have a way to + * know what the rate will be, so just return whatever + * rate is requested. + */ + return rate; +} + +static unsigned long clk_rpm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_rpm *r = to_clk_rpm(hw); + + /* + * RPM handles rate rounding and we don't have a way to + * know what the rate will be, so just return whatever + * rate was set. + */ + return r->rate; +} + +const struct clk_ops clk_rpm_ops = { + .prepare = clk_rpm_prepare, + .unprepare = clk_rpm_unprepare, + .set_rate = clk_rpm_set_rate, + .round_rate = clk_rpm_round_rate, + .recalc_rate = clk_rpm_recalc_rate, +}; +EXPORT_SYMBOL_GPL(clk_rpm_ops); + +const struct clk_ops clk_rpm_branch_ops = { + .prepare = clk_rpm_prepare, + .unprepare = clk_rpm_unprepare, + .round_rate = clk_rpm_round_rate, + .recalc_rate = clk_rpm_recalc_rate, +}; +EXPORT_SYMBOL_GPL(clk_rpm_branch_ops); + +struct rpm_cc { + struct qcom_rpm *rpm; + struct clk_onecell_data data; + struct clk *clks[]; +}; + +struct rpm_clk_desc { + struct clk_rpm **clks; + size_t num_clks; +}; + +/* apq8064 */ +DEFINE_CLK_RPM_PXO_BRANCH(apq8064, pxo, QCOM_RPM_PXO_CLK, 27000000); +DEFINE_CLK_RPM_CXO_BRANCH(apq8064, cxo, QCOM_RPM_CXO_CLK, 19200000); +DEFINE_CLK_RPM(apq8064, afab_clk, QCOM_RPM_APPS_FABRIC_CLK); +DEFINE_CLK_RPM(apq8064, cfpb_clk, QCOM_RPM_CFPB_CLK); +DEFINE_CLK_RPM(apq8064, daytona_clk, QCOM_RPM_DAYTONA_FABRIC_CLK); +DEFINE_CLK_RPM(apq8064, ebi1_clk, QCOM_RPM_EBI1_CLK); +DEFINE_CLK_RPM(apq8064, mmfab_clk, QCOM_RPM_MM_FABRIC_CLK); +DEFINE_CLK_RPM(apq8064, mmfpb_clk, QCOM_RPM_MMFPB_CLK); +DEFINE_CLK_RPM(apq8064, sfab_clk, QCOM_RPM_SYS_FABRIC_CLK); +DEFINE_CLK_RPM(apq8064, sfpb_clk, QCOM_RPM_SFPB_CLK); +DEFINE_CLK_RPM(apq8064, qdss_clk, QCOM_RPM_QDSS_CLK); + +static struct clk_rpm *apq8064_clks[] = { + [QCOM_RPM_PXO_CLK] = &apq8064_pxo, + [QCOM_RPM_CXO_CLK] = &apq8064_cxo, + [QCOM_RPM_APPS_FABRIC_CLK] = &apq8064_afab_clk, + [QCOM_RPM_CFPB_CLK] = &apq8064_cfpb_clk, + [QCOM_RPM_DAYTONA_FABRIC_CLK] = &apq8064_daytona_clk, + [QCOM_RPM_EBI1_CLK] = &apq8064_ebi1_clk, + [QCOM_RPM_MM_FABRIC_CLK] = &apq8064_mmfab_clk, + [QCOM_RPM_MMFPB_CLK] = &apq8064_mmfpb_clk, + [QCOM_RPM_SYS_FABRIC_CLK] = &apq8064_sfab_clk, + [QCOM_RPM_SFPB_CLK] = &apq8064_sfpb_clk, + [QCOM_RPM_QDSS_CLK] = &apq8064_qdss_clk, +}; + +static const struct rpm_clk_desc rpm_clk_apq8064 = { + .clks = apq8064_clks, + .num_clks = ARRAY_SIZE(apq8064_clks), +}; + +static const struct of_device_id rpm_clk_match_table[] = { + { .compatible = "qcom,rpmcc-apq8064", .data = &rpm_clk_apq8064}, + { } +}; +MODULE_DEVICE_TABLE(of, rpm_clk_match_table); + +static int rpm_clk_probe(struct platform_device *pdev) +{ + struct clk **clks; + struct clk *clk; + struct rpm_cc *rcc; + struct clk_onecell_data *data; + int ret; + size_t num_clks, i; + struct qcom_rpm *rpm; + struct clk_rpm **rpm_clks; + const struct rpm_clk_desc *desc; + + rpm = dev_get_drvdata(pdev->dev.parent); + if (!rpm) { + dev_err(&pdev->dev, "Unable to retrieve handle to RPM\n"); + return -ENODEV; + } + + desc = of_device_get_match_data(&pdev->dev); + if (!desc) + return -EINVAL; + + rpm_clks = desc->clks; + num_clks = desc->num_clks; + + rcc = devm_kzalloc(&pdev->dev, sizeof(*rcc) + sizeof(*clks) * num_clks, + GFP_KERNEL); + if (!rcc) + return -ENOMEM; + + clks = rcc->clks; + data = &rcc->data; + data->clks = clks; + data->clk_num = num_clks; + + for (i = 0; i < num_clks; i++) { + if (!rpm_clks[i]) { + clks[i] = ERR_PTR(-ENOENT); + continue; + } + + rpm_clks[i]->rpm = rpm; + clk = devm_clk_register(&pdev->dev, &rpm_clks[i]->hw); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + goto err; + } + + clks[i] = clk; + } + + ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get, + data); + if (ret) + goto err; + + return 0; +err: + dev_err(&pdev->dev, "Error registering RPM Clock driver (%d)\n", ret); + return ret; +} + +static int rpm_clk_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + return 0; +} + +static struct platform_driver rpm_clk_driver = { + .driver = { + .name = "qcom-clk-rpm", + .of_match_table = rpm_clk_match_table, + }, + .probe = rpm_clk_probe, + .remove = rpm_clk_remove, +}; + +static int __init rpm_clk_init(void) +{ + return platform_driver_register(&rpm_clk_driver); +} +core_initcall(rpm_clk_init); + +static void __exit rpm_clk_exit(void) +{ + platform_driver_unregister(&rpm_clk_driver); +} +module_exit(rpm_clk_exit); + +MODULE_DESCRIPTION("Qualcomm RPM Clock Controller Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:qcom-clk-rpm"); diff --git a/drivers/clk/qcom/clk-rpm.h b/drivers/clk/qcom/clk-rpm.h new file mode 100644 index 000000000000..c0ac30f806b5 --- /dev/null +++ b/drivers/clk/qcom/clk-rpm.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __QCOM_CLK_RPM_H__ +#define __QCOM_CLK_RPM_H__ + +#include + +struct qcom_rpm; + +struct clk_rpm { + const int rpm_clk_id; + unsigned long rate; + bool enabled; + bool branch; + struct clk_hw hw; + struct qcom_rpm *rpm; +}; + +extern const struct clk_ops clk_rpm_ops; +extern const struct clk_ops clk_rpm_branch_ops; + +#define DEFINE_CLK_RPM(_platform, _name, r_id) \ + static struct clk_rpm _platform##_##_name = { \ + .rpm_clk_id = (r_id), \ + .rate = INT_MAX, \ + .hw.init = &(struct clk_init_data){ \ + .name = #_name, \ + .parent_names = (const char *[]){ "pxo_board" }, \ + .num_parents = 1, \ + .ops = &clk_rpm_ops, \ + }, \ + } + +#define DEFINE_CLK_RPM_PXO_BRANCH(_platform, _name, r_id, r) \ + static struct clk_rpm _platform##_##_name = { \ + .rpm_clk_id = (r_id), \ + .branch = true, \ + .rate = (r), \ + .hw.init = &(struct clk_init_data){ \ + .name = #_name, \ + .parent_names = (const char *[]){ "pxo_board" }, \ + .num_parents = 1, \ + .ops = &clk_rpm_branch_ops, \ + }, \ + } + +#define DEFINE_CLK_RPM_CXO_BRANCH(_platform, _name, r_id, r) \ + static struct clk_rpm _platform##_##_name = { \ + .rpm_clk_id = (r_id), \ + .branch = true, \ + .rate = (r), \ + .hw.init = &(struct clk_init_data){ \ + .name = #_name, \ + .parent_names = (const char *[]){ "cxo_board" }, \ + .num_parents = 1, \ + .ops = &clk_rpm_branch_ops, \ + }, \ + } +#endif