Loading Documentation/devicetree/bindings/clock/qcom,aop-qmp.txt 0 → 100644 +17 −0 Original line number Diff line number Diff line Qualcomm Technologies, Inc. Always On Processor Clock controller Binding ------------------------------------------------------------------------ Required properties : - compatible : must be "qcom,aop-qmp-clk" - #clock-cells : must contain 1 - mboxes : list of QMP mailbox phandle and channel identifier tuples. - mbox-names: List of identifier strings for each mailbox channel. Must contain "qdss_clk". Example : clock_qdss: qcom,aopclk { compatible = "qcom,aop-qmp-clk"; #clock-cells = <1>; mboxes = <&qmp_aop 0>; mbox-names = "qdss_clk"; }; drivers/clk/qcom/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -196,3 +196,12 @@ config MSM_MMCC_8996 Support for the multimedia clock controller on msm8996 devices. Say Y if you want to support multimedia devices such as display, graphics, video encode/decode, camera, etc. config MSM_CLK_AOP_QMP tristate "AOP QMP Clock Driver" depends on COMMON_CLK_QCOM && MSM_QMP help Always On Processor manages few shared clocks on some Qualcomm Technologies, Inc. SoCs. It accepts requests from other hardware subsystems via QMP mailboxes. Say Y to support the clocks managed by AOP on platforms such as sdm845. drivers/clk/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ obj-$(CONFIG_IPQ_GCC_8074) += gcc-ipq8074.o obj-$(CONFIG_IPQ_LCC_806X) += lcc-ipq806x.o obj-$(CONFIG_MDM_GCC_9615) += gcc-mdm9615.o obj-$(CONFIG_MDM_LCC_9615) += lcc-mdm9615.o obj-$(CONFIG_MSM_CLK_AOP_QMP) += clk-aop-qmp.o obj-$(CONFIG_MSM_GCC_8660) += gcc-msm8660.o obj-$(CONFIG_MSM_GCC_8916) += gcc-msm8916.o obj-$(CONFIG_MSM_GCC_8960) += gcc-msm8960.o Loading drivers/clk/qcom/clk-aop-qmp.c 0 → 100644 +327 −0 Original line number Diff line number Diff line /* * Copyright (c) 2017, 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. */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include <linux/clk-provider.h> #include <linux/clk.h> #include <linux/err.h> #include <linux/kernel.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/mailbox_client.h> #include <dt-bindings/clock/qcom,aop-qmp.h> #define MAX_LEN 96 #define MBOX_TOUT_MS 1000 struct qmp_pkt { u32 size; void *data; }; #define DEFINE_CLK_AOP_QMP(_name, _class, _res, _estate, _dstate, _flags) \ static struct clk_aop_qmp _name = { \ .msg.class = #_class, \ .msg.res = #_res, \ .enable_state = _estate, \ .disable_state = _dstate, \ .hw.init = &(struct clk_init_data){ \ .ops = &aop_qmp_clk_ops, \ .name = #_name, \ .num_parents = 0, \ .flags = _flags, \ }, \ } #define to_aop_qmp_clk(hw) container_of(hw, struct clk_aop_qmp, hw) /* * struct qmp_mbox_msg - mailbox data to QMP * @class: identifies the class. * @res: identifies the resource in the class * @level: identifies the level for the resource. */ struct qmp_mbox_msg { char class[MAX_LEN]; char res[MAX_LEN]; int level; }; /* * struct clk_aop_qmp - AOP clock * @dev: The device that corresponds to this clock. * @hw: The clock hardware for this clock. * @cl: The client mailbox for this clock. * @mbox: The mbox controller for this clock. * @level: The clock level for this clock. * @enable_state: The clock state when this clock is prepared. * @disable_state: The clock state when this clock is unprepared. * @msg: QMP data associated with this clock. * @enabled: Status of the clock enable. */ struct clk_aop_qmp { struct device *dev; struct clk_hw hw; struct mbox_client cl; struct mbox_chan *mbox; int level; int enable_state; int disable_state; struct qmp_mbox_msg msg; bool enabled; }; static DEFINE_MUTEX(clk_aop_lock); static unsigned long clk_aop_qmp_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct clk_aop_qmp *clk = to_aop_qmp_clk(hw); return clk->level; } static long clk_aop_qmp_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { return rate; } static int clk_aop_qmp_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { char mbox_msg[MAX_LEN]; struct qmp_pkt pkt; struct clk_aop_qmp *clk = to_aop_qmp_clk(hw); int ret = 0; mutex_lock(&clk_aop_lock); snprintf(mbox_msg, MAX_LEN, "{class: %s, res: %s, val: %ld}", clk->msg.class, clk->msg.res, rate); pkt.size = MAX_LEN; pkt.data = mbox_msg; ret = mbox_send_message(clk->mbox, &pkt); if (ret < 0) { pr_err("Failed to send set rate request of %lu for %s, ret %d\n", rate, clk_hw_get_name(hw), ret); goto err; } /* update the current clock level once the mailbox message is sent */ clk->level = rate; err: mutex_unlock(&clk_aop_lock); return ret < 0 ? ret : 0; } static int clk_aop_qmp_prepare(struct clk_hw *hw) { char mbox_msg[MAX_LEN]; unsigned long rate; int ret = 0; struct qmp_pkt pkt; struct clk_aop_qmp *clk = to_aop_qmp_clk(hw); mutex_lock(&clk_aop_lock); if (clk->level) rate = clk->level; else rate = clk->enable_state; snprintf(mbox_msg, MAX_LEN, "{class: %s, res: %s, val: %ld}", clk->msg.class, clk->msg.res, rate); pkt.size = MAX_LEN; pkt.data = mbox_msg; ret = mbox_send_message(clk->mbox, &pkt); if (ret < 0) { pr_err("Failed to send clk prepare request for %s, ret %d\n", clk_hw_get_name(hw), ret); goto err; } /* update the current clock level once the mailbox message is sent */ clk->level = rate; clk->enabled = true; err: mutex_unlock(&clk_aop_lock); return ret < 0 ? ret : 0; } static void clk_aop_qmp_unprepare(struct clk_hw *hw) { char mbox_msg[MAX_LEN]; unsigned long rate; int ret = 0; struct qmp_pkt pkt; struct clk_aop_qmp *clk = to_aop_qmp_clk(hw); mutex_lock(&clk_aop_lock); rate = clk->disable_state; snprintf(mbox_msg, MAX_LEN, "{class: %s, res: %s, val: %ld}", clk->msg.class, clk->msg.res, rate); pkt.size = MAX_LEN; pkt.data = mbox_msg; ret = mbox_send_message(clk->mbox, &pkt); if (ret < 0) { pr_err("Failed to send clk unprepare request for %s, ret %d\n", clk_hw_get_name(hw), ret); goto err; } clk->enabled = false; err: mutex_unlock(&clk_aop_lock); } static int clk_aop_qmp_is_enabled(struct clk_hw *hw) { struct clk_aop_qmp *clk = to_aop_qmp_clk(hw); return clk->enabled; } static const struct clk_ops aop_qmp_clk_ops = { .prepare = clk_aop_qmp_prepare, .unprepare = clk_aop_qmp_unprepare, .recalc_rate = clk_aop_qmp_recalc_rate, .set_rate = clk_aop_qmp_set_rate, .round_rate = clk_aop_qmp_round_rate, .is_enabled = clk_aop_qmp_is_enabled, }; DEFINE_CLK_AOP_QMP(qdss_qmp_clk, clock, qdss, QDSS_CLK_LEVEL_DYNAMIC, QDSS_CLK_LEVEL_OFF, CLK_ENABLE_HAND_OFF); DEFINE_CLK_AOP_QMP(qdss_ao_qmp_clk, clock, qdss_ao, QDSS_CLK_LEVEL_DYNAMIC, QDSS_CLK_LEVEL_OFF, 0); static struct clk_hw *aop_qmp_clk_hws[] = { [QDSS_CLK] = &qdss_qmp_clk.hw, [QDSS_AO_CLK] = &qdss_ao_qmp_clk.hw, }; static int qmp_update_client(struct clk_hw *hw, struct device *dev, struct mbox_chan *mbox) { struct clk_aop_qmp *clk_aop = to_aop_qmp_clk(hw); /* Use mailbox client with blocking mode */ clk_aop->cl.dev = dev; clk_aop->cl.tx_block = true; clk_aop->cl.tx_tout = MBOX_TOUT_MS; clk_aop->cl.knows_txdone = false; if (mbox) { clk_aop->mbox = mbox; return 0; } /* Allocate mailbox channel */ mbox = clk_aop->mbox = mbox_request_channel(&clk_aop->cl, 0); if (IS_ERR(clk_aop->mbox) && PTR_ERR(clk_aop->mbox) != -EPROBE_DEFER) { dev_err(dev, "Failed to get mailbox channel %pK %ld\n", mbox, PTR_ERR(mbox)); return PTR_ERR(clk_aop->mbox); } return 0; } static int aop_qmp_clk_probe(struct platform_device *pdev) { struct clk *clk = NULL; struct device_node *np = pdev->dev.of_node; struct mbox_chan *mbox = NULL; int num_clks = ARRAY_SIZE(aop_qmp_clk_hws); int ret = 0, i = 0; /* * Allocate mbox channel for the first clock client. The same channel * would be used for the rest of the clock clients. */ ret = qmp_update_client(aop_qmp_clk_hws[i], &pdev->dev, mbox); if (ret < 0) return ret; for (i = 1; i < num_clks; i++) { if (!aop_qmp_clk_hws[i]) continue; ret = qmp_update_client(aop_qmp_clk_hws[i], &pdev->dev, mbox); if (ret < 0) { dev_err(&pdev->dev, "Failed to update QMP client %d\n", ret); goto fail; } } /* * Proxy vote on the QDSS clock. This is needed to avoid issues with * excessive requests on the QMP layer during the QDSS driver probe. */ ret = clk_aop_qmp_prepare(&qdss_qmp_clk.hw); if (ret < 0) goto fail; for (i = 0; i < num_clks; i++) { if (!aop_qmp_clk_hws[i]) continue; clk = devm_clk_register(&pdev->dev, aop_qmp_clk_hws[i]); if (IS_ERR(clk)) { ret = PTR_ERR(clk); goto fail; } } ret = of_clk_add_provider(np, of_clk_src_simple_get, clk); if (ret) { dev_err(&pdev->dev, "Failed to register clock provider\n"); goto fail; } dev_info(&pdev->dev, "Registered clocks with AOP\n"); return ret; fail: mbox_free_channel(mbox); return ret; } static const struct of_device_id aop_qmp_clk_of_match[] = { { .compatible = "qcom,aop-qmp-clk", }, {} }; static struct platform_driver aop_qmp_clk_driver = { .driver = { .name = "qmp-aop-clk", .of_match_table = aop_qmp_clk_of_match, }, .probe = aop_qmp_clk_probe, }; static int __init aop_qmp_clk_init(void) { return platform_driver_register(&aop_qmp_clk_driver); } subsys_initcall(aop_qmp_clk_init); include/dt-bindings/clock/qcom,aop-qmp.h +1 −1 Original line number Diff line number Diff line Loading @@ -25,5 +25,5 @@ /* clocks id */ #define QDSS_CLK 0 #define QDSS_AO_CLK 1 #endif Loading
Documentation/devicetree/bindings/clock/qcom,aop-qmp.txt 0 → 100644 +17 −0 Original line number Diff line number Diff line Qualcomm Technologies, Inc. Always On Processor Clock controller Binding ------------------------------------------------------------------------ Required properties : - compatible : must be "qcom,aop-qmp-clk" - #clock-cells : must contain 1 - mboxes : list of QMP mailbox phandle and channel identifier tuples. - mbox-names: List of identifier strings for each mailbox channel. Must contain "qdss_clk". Example : clock_qdss: qcom,aopclk { compatible = "qcom,aop-qmp-clk"; #clock-cells = <1>; mboxes = <&qmp_aop 0>; mbox-names = "qdss_clk"; };
drivers/clk/qcom/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -196,3 +196,12 @@ config MSM_MMCC_8996 Support for the multimedia clock controller on msm8996 devices. Say Y if you want to support multimedia devices such as display, graphics, video encode/decode, camera, etc. config MSM_CLK_AOP_QMP tristate "AOP QMP Clock Driver" depends on COMMON_CLK_QCOM && MSM_QMP help Always On Processor manages few shared clocks on some Qualcomm Technologies, Inc. SoCs. It accepts requests from other hardware subsystems via QMP mailboxes. Say Y to support the clocks managed by AOP on platforms such as sdm845.
drivers/clk/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ obj-$(CONFIG_IPQ_GCC_8074) += gcc-ipq8074.o obj-$(CONFIG_IPQ_LCC_806X) += lcc-ipq806x.o obj-$(CONFIG_MDM_GCC_9615) += gcc-mdm9615.o obj-$(CONFIG_MDM_LCC_9615) += lcc-mdm9615.o obj-$(CONFIG_MSM_CLK_AOP_QMP) += clk-aop-qmp.o obj-$(CONFIG_MSM_GCC_8660) += gcc-msm8660.o obj-$(CONFIG_MSM_GCC_8916) += gcc-msm8916.o obj-$(CONFIG_MSM_GCC_8960) += gcc-msm8960.o Loading
drivers/clk/qcom/clk-aop-qmp.c 0 → 100644 +327 −0 Original line number Diff line number Diff line /* * Copyright (c) 2017, 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. */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include <linux/clk-provider.h> #include <linux/clk.h> #include <linux/err.h> #include <linux/kernel.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/mailbox_client.h> #include <dt-bindings/clock/qcom,aop-qmp.h> #define MAX_LEN 96 #define MBOX_TOUT_MS 1000 struct qmp_pkt { u32 size; void *data; }; #define DEFINE_CLK_AOP_QMP(_name, _class, _res, _estate, _dstate, _flags) \ static struct clk_aop_qmp _name = { \ .msg.class = #_class, \ .msg.res = #_res, \ .enable_state = _estate, \ .disable_state = _dstate, \ .hw.init = &(struct clk_init_data){ \ .ops = &aop_qmp_clk_ops, \ .name = #_name, \ .num_parents = 0, \ .flags = _flags, \ }, \ } #define to_aop_qmp_clk(hw) container_of(hw, struct clk_aop_qmp, hw) /* * struct qmp_mbox_msg - mailbox data to QMP * @class: identifies the class. * @res: identifies the resource in the class * @level: identifies the level for the resource. */ struct qmp_mbox_msg { char class[MAX_LEN]; char res[MAX_LEN]; int level; }; /* * struct clk_aop_qmp - AOP clock * @dev: The device that corresponds to this clock. * @hw: The clock hardware for this clock. * @cl: The client mailbox for this clock. * @mbox: The mbox controller for this clock. * @level: The clock level for this clock. * @enable_state: The clock state when this clock is prepared. * @disable_state: The clock state when this clock is unprepared. * @msg: QMP data associated with this clock. * @enabled: Status of the clock enable. */ struct clk_aop_qmp { struct device *dev; struct clk_hw hw; struct mbox_client cl; struct mbox_chan *mbox; int level; int enable_state; int disable_state; struct qmp_mbox_msg msg; bool enabled; }; static DEFINE_MUTEX(clk_aop_lock); static unsigned long clk_aop_qmp_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct clk_aop_qmp *clk = to_aop_qmp_clk(hw); return clk->level; } static long clk_aop_qmp_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { return rate; } static int clk_aop_qmp_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { char mbox_msg[MAX_LEN]; struct qmp_pkt pkt; struct clk_aop_qmp *clk = to_aop_qmp_clk(hw); int ret = 0; mutex_lock(&clk_aop_lock); snprintf(mbox_msg, MAX_LEN, "{class: %s, res: %s, val: %ld}", clk->msg.class, clk->msg.res, rate); pkt.size = MAX_LEN; pkt.data = mbox_msg; ret = mbox_send_message(clk->mbox, &pkt); if (ret < 0) { pr_err("Failed to send set rate request of %lu for %s, ret %d\n", rate, clk_hw_get_name(hw), ret); goto err; } /* update the current clock level once the mailbox message is sent */ clk->level = rate; err: mutex_unlock(&clk_aop_lock); return ret < 0 ? ret : 0; } static int clk_aop_qmp_prepare(struct clk_hw *hw) { char mbox_msg[MAX_LEN]; unsigned long rate; int ret = 0; struct qmp_pkt pkt; struct clk_aop_qmp *clk = to_aop_qmp_clk(hw); mutex_lock(&clk_aop_lock); if (clk->level) rate = clk->level; else rate = clk->enable_state; snprintf(mbox_msg, MAX_LEN, "{class: %s, res: %s, val: %ld}", clk->msg.class, clk->msg.res, rate); pkt.size = MAX_LEN; pkt.data = mbox_msg; ret = mbox_send_message(clk->mbox, &pkt); if (ret < 0) { pr_err("Failed to send clk prepare request for %s, ret %d\n", clk_hw_get_name(hw), ret); goto err; } /* update the current clock level once the mailbox message is sent */ clk->level = rate; clk->enabled = true; err: mutex_unlock(&clk_aop_lock); return ret < 0 ? ret : 0; } static void clk_aop_qmp_unprepare(struct clk_hw *hw) { char mbox_msg[MAX_LEN]; unsigned long rate; int ret = 0; struct qmp_pkt pkt; struct clk_aop_qmp *clk = to_aop_qmp_clk(hw); mutex_lock(&clk_aop_lock); rate = clk->disable_state; snprintf(mbox_msg, MAX_LEN, "{class: %s, res: %s, val: %ld}", clk->msg.class, clk->msg.res, rate); pkt.size = MAX_LEN; pkt.data = mbox_msg; ret = mbox_send_message(clk->mbox, &pkt); if (ret < 0) { pr_err("Failed to send clk unprepare request for %s, ret %d\n", clk_hw_get_name(hw), ret); goto err; } clk->enabled = false; err: mutex_unlock(&clk_aop_lock); } static int clk_aop_qmp_is_enabled(struct clk_hw *hw) { struct clk_aop_qmp *clk = to_aop_qmp_clk(hw); return clk->enabled; } static const struct clk_ops aop_qmp_clk_ops = { .prepare = clk_aop_qmp_prepare, .unprepare = clk_aop_qmp_unprepare, .recalc_rate = clk_aop_qmp_recalc_rate, .set_rate = clk_aop_qmp_set_rate, .round_rate = clk_aop_qmp_round_rate, .is_enabled = clk_aop_qmp_is_enabled, }; DEFINE_CLK_AOP_QMP(qdss_qmp_clk, clock, qdss, QDSS_CLK_LEVEL_DYNAMIC, QDSS_CLK_LEVEL_OFF, CLK_ENABLE_HAND_OFF); DEFINE_CLK_AOP_QMP(qdss_ao_qmp_clk, clock, qdss_ao, QDSS_CLK_LEVEL_DYNAMIC, QDSS_CLK_LEVEL_OFF, 0); static struct clk_hw *aop_qmp_clk_hws[] = { [QDSS_CLK] = &qdss_qmp_clk.hw, [QDSS_AO_CLK] = &qdss_ao_qmp_clk.hw, }; static int qmp_update_client(struct clk_hw *hw, struct device *dev, struct mbox_chan *mbox) { struct clk_aop_qmp *clk_aop = to_aop_qmp_clk(hw); /* Use mailbox client with blocking mode */ clk_aop->cl.dev = dev; clk_aop->cl.tx_block = true; clk_aop->cl.tx_tout = MBOX_TOUT_MS; clk_aop->cl.knows_txdone = false; if (mbox) { clk_aop->mbox = mbox; return 0; } /* Allocate mailbox channel */ mbox = clk_aop->mbox = mbox_request_channel(&clk_aop->cl, 0); if (IS_ERR(clk_aop->mbox) && PTR_ERR(clk_aop->mbox) != -EPROBE_DEFER) { dev_err(dev, "Failed to get mailbox channel %pK %ld\n", mbox, PTR_ERR(mbox)); return PTR_ERR(clk_aop->mbox); } return 0; } static int aop_qmp_clk_probe(struct platform_device *pdev) { struct clk *clk = NULL; struct device_node *np = pdev->dev.of_node; struct mbox_chan *mbox = NULL; int num_clks = ARRAY_SIZE(aop_qmp_clk_hws); int ret = 0, i = 0; /* * Allocate mbox channel for the first clock client. The same channel * would be used for the rest of the clock clients. */ ret = qmp_update_client(aop_qmp_clk_hws[i], &pdev->dev, mbox); if (ret < 0) return ret; for (i = 1; i < num_clks; i++) { if (!aop_qmp_clk_hws[i]) continue; ret = qmp_update_client(aop_qmp_clk_hws[i], &pdev->dev, mbox); if (ret < 0) { dev_err(&pdev->dev, "Failed to update QMP client %d\n", ret); goto fail; } } /* * Proxy vote on the QDSS clock. This is needed to avoid issues with * excessive requests on the QMP layer during the QDSS driver probe. */ ret = clk_aop_qmp_prepare(&qdss_qmp_clk.hw); if (ret < 0) goto fail; for (i = 0; i < num_clks; i++) { if (!aop_qmp_clk_hws[i]) continue; clk = devm_clk_register(&pdev->dev, aop_qmp_clk_hws[i]); if (IS_ERR(clk)) { ret = PTR_ERR(clk); goto fail; } } ret = of_clk_add_provider(np, of_clk_src_simple_get, clk); if (ret) { dev_err(&pdev->dev, "Failed to register clock provider\n"); goto fail; } dev_info(&pdev->dev, "Registered clocks with AOP\n"); return ret; fail: mbox_free_channel(mbox); return ret; } static const struct of_device_id aop_qmp_clk_of_match[] = { { .compatible = "qcom,aop-qmp-clk", }, {} }; static struct platform_driver aop_qmp_clk_driver = { .driver = { .name = "qmp-aop-clk", .of_match_table = aop_qmp_clk_of_match, }, .probe = aop_qmp_clk_probe, }; static int __init aop_qmp_clk_init(void) { return platform_driver_register(&aop_qmp_clk_driver); } subsys_initcall(aop_qmp_clk_init);
include/dt-bindings/clock/qcom,aop-qmp.h +1 −1 Original line number Diff line number Diff line Loading @@ -25,5 +25,5 @@ /* clocks id */ #define QDSS_CLK 0 #define QDSS_AO_CLK 1 #endif