summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/clock/qcom,virtclk-front.txt14
-rw-r--r--drivers/clk/msm/Kconfig11
-rw-r--r--drivers/clk/msm/Makefile3
-rw-r--r--drivers/clk/msm/virtclk-front-8996.c407
-rw-r--r--drivers/clk/msm/virtclk-front.c460
-rw-r--r--include/linux/clk/msm-clock-generic.h14
6 files changed, 908 insertions, 1 deletions
diff --git a/Documentation/devicetree/bindings/clock/qcom,virtclk-front.txt b/Documentation/devicetree/bindings/clock/qcom,virtclk-front.txt
new file mode 100644
index 000000000000..a863c802120a
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/qcom,virtclk-front.txt
@@ -0,0 +1,14 @@
+QTI Virtual Clock Frontend Binding
+------------------------------------------------
+
+Required properties :
+- compatible : shall contain:
+ "qcom,virtclk-frontend-8996"
+
+- #clock-cells : shall contain 1
+
+Example:
+ virtclk-frontend@0 {
+ compatible = "qcom,virtclk-frontend-8996";
+ #clock-cells = <1>;
+ };
diff --git a/drivers/clk/msm/Kconfig b/drivers/clk/msm/Kconfig
index 3829f6aec124..a7501f5d446f 100644
--- a/drivers/clk/msm/Kconfig
+++ b/drivers/clk/msm/Kconfig
@@ -16,4 +16,15 @@ config MSM_CLK_CONTROLLER_V2
Generate clock data structures from definitions found in
device tree.
+config MSM_VIRTCLK_FRONTEND
+ bool
+
+config MSM_VIRTCLK_FRONTEND_8996
+ tristate "QTI msm8996 virtual clock frontend driver"
+ depends on COMMON_CLK_MSM && MSM_HAB
+ select MSM_VIRTCLK_FRONTEND
+ ---help---
+ This is the virtual clock frontend driver for the QTI msm8996
+ virtual platform.
+
source "drivers/clk/msm/mdss/Kconfig"
diff --git a/drivers/clk/msm/Makefile b/drivers/clk/msm/Makefile
index 27e07eb12205..5f50890704da 100644
--- a/drivers/clk/msm/Makefile
+++ b/drivers/clk/msm/Makefile
@@ -29,3 +29,6 @@ endif
obj-$(CONFIG_COMMON_CLK_MSM) += gdsc.o
obj-$(CONFIG_COMMON_CLK_MSM) += mdss/
+
+obj-$(CONFIG_MSM_VIRTCLK_FRONTEND) += virtclk-front.o
+obj-$(CONFIG_MSM_VIRTCLK_FRONTEND_8996) += virtclk-front-8996.o
diff --git a/drivers/clk/msm/virtclk-front-8996.c b/drivers/clk/msm/virtclk-front-8996.c
new file mode 100644
index 000000000000..84cd55bd85bf
--- /dev/null
+++ b/drivers/clk/msm/virtclk-front-8996.c
@@ -0,0 +1,407 @@
+/* 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.
+ */
+
+#include <linux/clk/msm-clock-generic.h>
+#include <linux/clk/msm-clk-provider.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <dt-bindings/clock/msm-clocks-8996.h>
+
+static struct virtclk_front gcc_blsp1_ahb_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_ahb_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_ahb_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_qup1_spi_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_qup1_spi_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_qup1_spi_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_qup1_i2c_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_qup1_i2c_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_qup1_i2c_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_uart1_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_uart1_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_uart1_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_qup2_spi_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_qup2_spi_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_qup2_spi_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_qup2_i2c_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_qup2_i2c_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_qup2_i2c_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_uart2_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_uart2_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_uart2_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_qup3_spi_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_qup3_spi_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_qup3_spi_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_qup3_i2c_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_qup3_i2c_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_qup3_i2c_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_uart3_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_uart3_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_uart3_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_qup4_spi_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_qup4_spi_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_qup4_spi_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_qup4_i2c_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_qup4_i2c_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_qup4_i2c_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_uart4_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_uart4_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_uart4_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_qup5_spi_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_qup5_spi_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_qup5_spi_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_qup5_i2c_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_qup5_i2c_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_qup5_i2c_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_uart5_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_uart5_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_uart5_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_qup6_spi_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_qup6_spi_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_qup6_spi_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_qup6_i2c_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_qup6_i2c_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_qup6_i2c_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp1_uart6_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp1_uart6_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp1_uart6_apps_clk.c),
+ },
+};
+
+
+static struct virtclk_front gcc_blsp2_ahb_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_ahb_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_ahb_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_qup1_spi_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_qup1_spi_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_qup1_spi_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_qup1_i2c_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_qup1_i2c_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_qup1_i2c_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_uart1_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_uart1_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_uart1_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_qup2_spi_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_qup2_spi_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_qup2_spi_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_qup2_i2c_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_qup2_i2c_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_qup2_i2c_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_uart2_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_uart2_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_uart2_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_qup3_spi_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_qup3_spi_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_qup3_spi_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_qup3_i2c_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_qup3_i2c_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_qup3_i2c_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_uart3_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_uart3_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_uart3_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_qup4_spi_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_qup4_spi_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_qup4_spi_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_qup4_i2c_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_qup4_i2c_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_qup4_i2c_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_uart4_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_uart4_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_uart4_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_qup5_spi_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_qup5_spi_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_qup5_spi_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_qup5_i2c_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_qup5_i2c_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_qup5_i2c_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_uart5_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_uart5_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_uart5_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_qup6_spi_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_qup6_spi_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_qup6_spi_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_qup6_i2c_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_qup6_i2c_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_qup6_i2c_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_blsp2_uart6_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_blsp2_uart6_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_blsp2_uart6_apps_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_sdcc2_ahb_clk = {
+ .c = {
+ .dbg_name = "gcc_sdcc2_ahb_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_sdcc2_ahb_clk.c),
+ },
+};
+
+static struct virtclk_front gcc_sdcc2_apps_clk = {
+ .c = {
+ .dbg_name = "gcc_sdcc2_apps_clk",
+ .ops = &virtclk_front_ops,
+ CLK_INIT(gcc_sdcc2_apps_clk.c),
+ },
+};
+
+static struct clk_lookup msm_clocks_8996[] = {
+ CLK_LIST(gcc_blsp1_ahb_clk),
+ CLK_LIST(gcc_blsp1_qup1_spi_apps_clk),
+ CLK_LIST(gcc_blsp1_qup1_i2c_apps_clk),
+ CLK_LIST(gcc_blsp1_uart1_apps_clk),
+ CLK_LIST(gcc_blsp1_qup2_spi_apps_clk),
+ CLK_LIST(gcc_blsp1_qup2_i2c_apps_clk),
+ CLK_LIST(gcc_blsp1_uart2_apps_clk),
+ CLK_LIST(gcc_blsp1_qup3_spi_apps_clk),
+ CLK_LIST(gcc_blsp1_qup3_i2c_apps_clk),
+ CLK_LIST(gcc_blsp1_uart3_apps_clk),
+ CLK_LIST(gcc_blsp1_qup4_spi_apps_clk),
+ CLK_LIST(gcc_blsp1_qup4_i2c_apps_clk),
+ CLK_LIST(gcc_blsp1_uart4_apps_clk),
+ CLK_LIST(gcc_blsp1_qup5_spi_apps_clk),
+ CLK_LIST(gcc_blsp1_qup5_i2c_apps_clk),
+ CLK_LIST(gcc_blsp1_uart5_apps_clk),
+ CLK_LIST(gcc_blsp1_qup6_spi_apps_clk),
+ CLK_LIST(gcc_blsp1_qup6_i2c_apps_clk),
+ CLK_LIST(gcc_blsp1_uart6_apps_clk),
+ CLK_LIST(gcc_blsp2_ahb_clk),
+ CLK_LIST(gcc_blsp2_qup1_spi_apps_clk),
+ CLK_LIST(gcc_blsp2_qup1_i2c_apps_clk),
+ CLK_LIST(gcc_blsp2_uart1_apps_clk),
+ CLK_LIST(gcc_blsp2_qup2_spi_apps_clk),
+ CLK_LIST(gcc_blsp2_qup2_i2c_apps_clk),
+ CLK_LIST(gcc_blsp2_uart2_apps_clk),
+ CLK_LIST(gcc_blsp2_qup3_spi_apps_clk),
+ CLK_LIST(gcc_blsp2_qup3_i2c_apps_clk),
+ CLK_LIST(gcc_blsp2_uart3_apps_clk),
+ CLK_LIST(gcc_blsp2_qup4_spi_apps_clk),
+ CLK_LIST(gcc_blsp2_qup4_i2c_apps_clk),
+ CLK_LIST(gcc_blsp2_uart4_apps_clk),
+ CLK_LIST(gcc_blsp2_qup5_spi_apps_clk),
+ CLK_LIST(gcc_blsp2_qup5_i2c_apps_clk),
+ CLK_LIST(gcc_blsp2_uart5_apps_clk),
+ CLK_LIST(gcc_blsp2_qup6_spi_apps_clk),
+ CLK_LIST(gcc_blsp2_qup6_i2c_apps_clk),
+ CLK_LIST(gcc_blsp2_uart6_apps_clk),
+ CLK_LIST(gcc_sdcc2_ahb_clk),
+ CLK_LIST(gcc_sdcc2_apps_clk),
+};
+
+static const struct of_device_id msm8996_virtclk_front_match_table[] = {
+ { .compatible = "qcom,virtclk-frontend-8996" },
+ {}
+};
+
+static int msm8996_virtclk_front_probe(struct platform_device *pdev)
+{
+ return msm_virtclk_front_probe(pdev, msm_clocks_8996,
+ ARRAY_SIZE(msm_clocks_8996));
+}
+
+static struct platform_driver msm8996_virtclk_front_driver = {
+ .probe = msm8996_virtclk_front_probe,
+ .driver = {
+ .name = "virtclk-front-8996",
+ .of_match_table = msm8996_virtclk_front_match_table,
+ .owner = THIS_MODULE,
+ },
+};
+
+int __init msm8996_virtclk_front_init(void)
+{
+ return platform_driver_register(&msm8996_virtclk_front_driver);
+}
+arch_initcall(msm8996_virtclk_front_init);
diff --git a/drivers/clk/msm/virtclk-front.c b/drivers/clk/msm/virtclk-front.c
new file mode 100644
index 000000000000..08c7e5aaa7f4
--- /dev/null
+++ b/drivers/clk/msm/virtclk-front.c
@@ -0,0 +1,460 @@
+/* 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/msm-clock-generic.h>
+#include <linux/clk/msm-clk-provider.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/habmm.h>
+#include <soc/qcom/msm-clock-controller.h>
+
+struct virtclk_front_data {
+ int handle;
+ struct rt_mutex lock;
+};
+
+enum virtclk_cmd {
+ CLK_MSG_GETID = 1,
+ CLK_MSG_ENABLE,
+ CLK_MSG_DISABLE,
+ CLK_MSG_RESET,
+ CLK_MSG_SETFREQ,
+ CLK_MSG_GETFREQ,
+ CLK_MSG_MAX
+};
+
+struct clk_msg_header {
+ u32 cmd;
+ u32 len;
+ u32 clk_id;
+} __packed;
+
+struct clk_msg_rsp {
+ struct clk_msg_header header;
+ u32 rsp;
+} __packed;
+
+struct clk_msg_setfreq {
+ struct clk_msg_header header;
+ u32 freq;
+} __packed;
+
+struct clk_msg_getid {
+ struct clk_msg_header header;
+ char name[32];
+} __packed;
+
+struct clk_msg_getfreq {
+ struct clk_msg_rsp rsp;
+ u32 freq;
+} __packed;
+
+static struct virtclk_front_data virtclk_front_ctx;
+
+static inline struct virtclk_front *to_virtclk_front(struct clk *clk)
+{
+ return container_of(clk, struct virtclk_front, c);
+}
+
+static int virtclk_front_init_iface(void)
+{
+ int ret = 0;
+ int handle;
+
+ rt_mutex_lock(&virtclk_front_ctx.lock);
+
+ if (virtclk_front_ctx.handle)
+ goto out;
+
+ ret = habmm_socket_open(&handle, MM_CLK_VM1, 0, 0);
+ if (ret) {
+ pr_err("open habmm socket failed (%d)\n", ret);
+ goto out;
+ }
+
+ virtclk_front_ctx.handle = handle;
+
+out:
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+ return ret;
+}
+
+static int virtclk_front_get_id(struct clk *clk)
+{
+ struct virtclk_front *v = to_virtclk_front(clk);
+ struct clk_msg_getid msg;
+ struct clk_msg_rsp rsp;
+ u32 rsp_size = sizeof(rsp);
+ int handle;
+ int ret = 0;
+
+ if (v->id)
+ return ret;
+
+ msg.header.cmd = CLK_MSG_GETID;
+ msg.header.len = sizeof(msg);
+ strlcpy(msg.name, clk->dbg_name, sizeof(msg.name));
+
+ rt_mutex_lock(&virtclk_front_ctx.lock);
+
+ handle = virtclk_front_ctx.handle;
+ ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
+ if (ret) {
+ pr_err("%s: habmm socket send failed (%d)\n", clk->dbg_name,
+ ret);
+ goto err_out;
+ }
+
+ ret = habmm_socket_recv(handle, &rsp, &rsp_size,
+ UINT_MAX, 0);
+ if (ret) {
+ pr_err("%s: habmm socket receive failed (%d)\n", clk->dbg_name,
+ ret);
+ goto err_out;
+ }
+
+ if (rsp.rsp) {
+ pr_err("%s: error response (%d)\n", clk->dbg_name, rsp.rsp);
+ ret = -EIO;
+ } else
+ v->id = rsp.header.clk_id;
+
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+
+ return ret;
+
+err_out:
+ habmm_socket_close(handle);
+ virtclk_front_ctx.handle = 0;
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+ return ret;
+}
+
+static int virtclk_front_prepare(struct clk *clk)
+{
+ struct virtclk_front *v = to_virtclk_front(clk);
+ struct clk_msg_header msg;
+ struct clk_msg_rsp rsp;
+ u32 rsp_size = sizeof(rsp);
+ int handle;
+ int ret = 0;
+
+ ret = virtclk_front_init_iface();
+ if (ret)
+ return ret;
+
+ ret = virtclk_front_get_id(clk);
+ if (ret)
+ return ret;
+
+ msg.clk_id = v->id;
+ msg.cmd = CLK_MSG_ENABLE;
+ msg.len = sizeof(struct clk_msg_header);
+
+ rt_mutex_lock(&virtclk_front_ctx.lock);
+
+ handle = virtclk_front_ctx.handle;
+ ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
+ if (ret) {
+ pr_err("%s: habmm socket send failed (%d)\n", clk->dbg_name,
+ ret);
+ goto err_out;
+ }
+
+ ret = habmm_socket_recv(handle, &rsp, &rsp_size, UINT_MAX, 0);
+ if (ret) {
+ pr_err("%s: habmm socket receive failed (%d)\n", clk->dbg_name,
+ ret);
+ goto err_out;
+ }
+
+ if (rsp.rsp) {
+ pr_err("%s: error response (%d)\n", clk->dbg_name, rsp.rsp);
+ ret = -EIO;
+ }
+
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+ return ret;
+
+err_out:
+ habmm_socket_close(handle);
+ virtclk_front_ctx.handle = 0;
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+ return ret;
+}
+
+static void virtclk_front_unprepare(struct clk *clk)
+{
+ struct virtclk_front *v = to_virtclk_front(clk);
+ struct clk_msg_header msg;
+ struct clk_msg_rsp rsp;
+ u32 rsp_size = sizeof(rsp);
+ int handle;
+ int ret = 0;
+
+ ret = virtclk_front_init_iface();
+ if (ret)
+ return;
+
+ ret = virtclk_front_get_id(clk);
+ if (ret)
+ return;
+
+ msg.clk_id = v->id;
+ msg.cmd = CLK_MSG_DISABLE;
+ msg.len = sizeof(struct clk_msg_header);
+
+ rt_mutex_lock(&virtclk_front_ctx.lock);
+
+ handle = virtclk_front_ctx.handle;
+ ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
+ if (ret) {
+ pr_err("%s: habmm socket send failed (%d)\n", clk->dbg_name,
+ ret);
+ goto err_out;
+ }
+
+ ret = habmm_socket_recv(handle, &rsp, &rsp_size, UINT_MAX, 0);
+ if (ret) {
+ pr_err("%s: habmm socket receive failed (%d)\n", clk->dbg_name,
+ ret);
+ goto err_out;
+ }
+
+ if (rsp.rsp)
+ pr_err("%s: error response (%d)\n", clk->dbg_name, rsp.rsp);
+
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+ return;
+
+err_out:
+ habmm_socket_close(handle);
+ virtclk_front_ctx.handle = 0;
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+}
+
+static int virtclk_front_reset(struct clk *clk, enum clk_reset_action action)
+{
+ struct virtclk_front *v = to_virtclk_front(clk);
+ struct clk_msg_header msg;
+ struct clk_msg_rsp rsp;
+ u32 rsp_size = sizeof(rsp);
+ int handle;
+ int ret = 0;
+
+ ret = virtclk_front_init_iface();
+ if (ret)
+ return ret;
+
+ ret = virtclk_front_get_id(clk);
+ if (ret)
+ return ret;
+
+ msg.clk_id = v->id;
+ msg.cmd = CLK_MSG_RESET;
+ msg.len = sizeof(struct clk_msg_header);
+
+ rt_mutex_lock(&virtclk_front_ctx.lock);
+
+ handle = virtclk_front_ctx.handle;
+ ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
+ if (ret) {
+ pr_err("%s: habmm socket send failed (%d)\n", clk->dbg_name,
+ ret);
+ goto err_out;
+ }
+
+ ret = habmm_socket_recv(handle, &rsp, &rsp_size, UINT_MAX, 0);
+ if (ret) {
+ pr_err("%s: habmm socket receive failed (%d)\n", clk->dbg_name,
+ ret);
+ goto err_out;
+ }
+
+ if (rsp.rsp) {
+ pr_err("%s: error response (%d)\n", clk->dbg_name, rsp.rsp);
+ ret = -EIO;
+ }
+
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+ return ret;
+
+err_out:
+ habmm_socket_close(handle);
+ virtclk_front_ctx.handle = 0;
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+ return ret;
+}
+
+static int virtclk_front_set_rate(struct clk *clk, unsigned long rate)
+{
+ struct virtclk_front *v = to_virtclk_front(clk);
+ struct clk_msg_setfreq msg;
+ struct clk_msg_rsp rsp;
+ u32 rsp_size = sizeof(rsp);
+ int handle;
+ int ret = 0;
+
+ ret = virtclk_front_init_iface();
+ if (ret)
+ return ret;
+
+ ret = virtclk_front_get_id(clk);
+ if (ret)
+ return ret;
+
+ msg.header.clk_id = v->id;
+ msg.header.cmd = CLK_MSG_SETFREQ;
+ msg.header.len = sizeof(msg);
+ msg.freq = (u32)rate;
+
+ rt_mutex_lock(&virtclk_front_ctx.lock);
+
+ handle = virtclk_front_ctx.handle;
+ ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
+ if (ret) {
+ pr_err("%s: habmm socket send failed (%d)\n", clk->dbg_name,
+ ret);
+ goto err_out;
+ }
+
+ ret = habmm_socket_recv(handle, &rsp, &rsp_size, UINT_MAX, 0);
+ if (ret) {
+ pr_err("%s: habmm socket receive failed (%d)\n", clk->dbg_name,
+ ret);
+ goto err_out;
+ }
+
+ if (rsp.rsp) {
+ pr_err("%s (%luHz): error response (%d)\n", clk->dbg_name,
+ rate, rsp.rsp);
+ ret = -EIO;
+ }
+
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+ return ret;
+
+err_out:
+ habmm_socket_close(handle);
+ virtclk_front_ctx.handle = 0;
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+ return ret;
+}
+
+static int virtclk_front_set_max_rate(struct clk *clk, unsigned long rate)
+{
+ return 0;
+}
+
+static int virtclk_front_is_enabled(struct clk *clk)
+{
+ struct virtclk_front *v = to_virtclk_front(clk);
+
+ return !!v->c.prepare_count;
+}
+
+static int virtclk_front_set_flags(struct clk *clk, unsigned flags)
+{
+ return 0;
+}
+
+static unsigned long virtclk_front_get_rate(struct clk *clk)
+{
+ struct virtclk_front *v = to_virtclk_front(clk);
+ struct clk_msg_header msg;
+ struct clk_msg_getfreq rsp;
+ u32 rsp_size = sizeof(rsp);
+ int handle;
+ int ret = 0;
+
+ ret = virtclk_front_init_iface();
+ if (ret)
+ return 0;
+
+ ret = virtclk_front_get_id(clk);
+ if (ret)
+ return 0;
+
+ msg.clk_id = v->id;
+ msg.cmd = CLK_MSG_GETFREQ;
+ msg.len = sizeof(msg);
+
+ rt_mutex_lock(&virtclk_front_ctx.lock);
+
+ handle = virtclk_front_ctx.handle;
+ ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
+ if (ret) {
+ ret = 0;
+ pr_err("%s: habmm socket send failed (%d)\n", clk->dbg_name,
+ ret);
+ goto err_out;
+ }
+
+ ret = habmm_socket_recv(handle, &rsp, &rsp_size, UINT_MAX, 0);
+ if (ret) {
+ ret = 0;
+ pr_err("%s: habmm socket receive failed (%d)\n", clk->dbg_name,
+ ret);
+ goto err_out;
+ }
+
+ if (rsp.rsp.rsp) {
+ pr_err("%s: error response (%d)\n", clk->dbg_name, rsp.rsp.rsp);
+ ret = 0;
+ } else
+ ret = rsp.freq;
+
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+ return ret;
+
+err_out:
+ habmm_socket_close(handle);
+ virtclk_front_ctx.handle = 0;
+ rt_mutex_unlock(&virtclk_front_ctx.lock);
+ return ret;
+}
+
+static long virtclk_front_round_rate(struct clk *clk, unsigned long rate)
+{
+ return rate;
+}
+
+struct clk_ops virtclk_front_ops = {
+ .prepare = virtclk_front_prepare,
+ .unprepare = virtclk_front_unprepare,
+ .reset = virtclk_front_reset,
+ .set_rate = virtclk_front_set_rate,
+ .set_max_rate = virtclk_front_set_max_rate,
+ .is_enabled = virtclk_front_is_enabled,
+ .set_flags = virtclk_front_set_flags,
+ .get_rate = virtclk_front_get_rate,
+ .round_rate = virtclk_front_round_rate,
+};
+
+int msm_virtclk_front_probe(struct platform_device *pdev,
+ struct clk_lookup *table,
+ size_t size)
+{
+ int ret;
+
+ ret = of_msm_clock_register(pdev->dev.of_node, table, size);
+ if (ret)
+ return ret;
+
+ rt_mutex_init(&virtclk_front_ctx.lock);
+
+ dev_info(&pdev->dev, "Registered virtual clock provider.\n");
+
+ return ret;
+}
+EXPORT_SYMBOL(msm_virtclk_front_probe);
diff --git a/include/linux/clk/msm-clock-generic.h b/include/linux/clk/msm-clock-generic.h
index d7186a363a3f..fe019d366d0b 100644
--- a/include/linux/clk/msm-clock-generic.h
+++ b/include/linux/clk/msm-clock-generic.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2013-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
@@ -307,4 +307,16 @@ static inline struct mux_div_clk *to_mux_div_clk(struct clk *clk)
extern struct clk_ops clk_ops_mux_div_clk;
+/* ==================== Virtual clock ==================== */
+struct virtclk_front {
+ int id;
+ struct clk c;
+};
+
+extern struct clk_ops virtclk_front_ops;
+
+int msm_virtclk_front_probe(struct platform_device *pdev,
+ struct clk_lookup *table,
+ size_t size);
+
#endif