You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
732 lines
15 KiB
732 lines
15 KiB
/* Copyright (c) 2018-2019, 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/kernel.h>
|
|
#include <linux/err.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/reset-controller.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/habmm.h>
|
|
#include "clk-virt.h"
|
|
|
|
enum virtclk_cmd {
|
|
CLK_MSG_GETID = 1,
|
|
CLK_MSG_ENABLE,
|
|
CLK_MSG_DISABLE,
|
|
CLK_MSG_RESET,
|
|
CLK_MSG_SETFREQ,
|
|
CLK_MSG_GETFREQ,
|
|
CLK_MSG_ROUNDFREQ = 13,
|
|
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_reset {
|
|
struct clk_msg_header header;
|
|
u32 reset;
|
|
} __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;
|
|
|
|
struct clk_msg_roundfreq {
|
|
struct clk_msg_header header;
|
|
u32 freq;
|
|
} __packed;
|
|
|
|
struct clk_msg_roundfreq_rsp {
|
|
struct clk_msg_rsp rsp;
|
|
u32 freq;
|
|
} __packed;
|
|
|
|
struct virt_cc {
|
|
struct reset_controller_dev rcdev;
|
|
struct virt_reset_map *resets;
|
|
struct clk_onecell_data data;
|
|
struct clk *clks[];
|
|
};
|
|
|
|
static DEFINE_MUTEX(virt_clk_lock);
|
|
static int hab_handle;
|
|
|
|
static inline struct clk_virt *to_clk_virt(struct clk_hw *_hw)
|
|
{
|
|
return container_of(_hw, struct clk_virt, hw);
|
|
}
|
|
|
|
static int clk_virt_init_iface(void)
|
|
{
|
|
int ret = 0;
|
|
int handle;
|
|
|
|
mutex_lock(&virt_clk_lock);
|
|
|
|
if (hab_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;
|
|
}
|
|
|
|
hab_handle = handle;
|
|
|
|
out:
|
|
mutex_unlock(&virt_clk_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int clk_virt_get_id(struct clk_hw *hw)
|
|
{
|
|
struct clk_virt *v = to_clk_virt(hw);
|
|
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 | v->flag;
|
|
msg.header.len = sizeof(msg);
|
|
strlcpy(msg.name, clk_hw_get_name(hw), sizeof(msg.name));
|
|
|
|
mutex_lock(&virt_clk_lock);
|
|
|
|
handle = hab_handle;
|
|
ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
|
|
if (ret) {
|
|
pr_err("%s: habmm socket send failed (%d)\n",
|
|
clk_hw_get_name(hw), ret);
|
|
goto err_out;
|
|
}
|
|
|
|
ret = habmm_socket_recv(handle, &rsp, &rsp_size,
|
|
UINT_MAX, HABMM_SOCKET_RECV_FLAGS_UNINTERRUPTIBLE);
|
|
if (ret) {
|
|
pr_err("%s: habmm socket receive failed (%d)\n",
|
|
clk_hw_get_name(hw), ret);
|
|
goto err_out;
|
|
}
|
|
|
|
if (rsp.rsp) {
|
|
pr_err("%s: error response (%d)\n", clk_hw_get_name(hw),
|
|
rsp.rsp);
|
|
ret = -EIO;
|
|
} else
|
|
v->id = rsp.header.clk_id;
|
|
|
|
mutex_unlock(&virt_clk_lock);
|
|
|
|
return ret;
|
|
|
|
err_out:
|
|
habmm_socket_close(handle);
|
|
hab_handle = 0;
|
|
mutex_unlock(&virt_clk_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int clk_virt_prepare(struct clk_hw *hw)
|
|
{
|
|
struct clk_virt *v = to_clk_virt(hw);
|
|
struct clk_msg_header msg;
|
|
struct clk_msg_rsp rsp;
|
|
u32 rsp_size = sizeof(rsp);
|
|
int handle;
|
|
int ret = 0;
|
|
|
|
ret = clk_virt_init_iface();
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_virt_get_id(hw);
|
|
if (ret)
|
|
return ret;
|
|
|
|
msg.clk_id = v->id;
|
|
msg.cmd = CLK_MSG_ENABLE | v->flag;
|
|
msg.len = sizeof(struct clk_msg_header);
|
|
|
|
mutex_lock(&virt_clk_lock);
|
|
|
|
handle = hab_handle;
|
|
ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
|
|
if (ret) {
|
|
pr_err("%s: habmm socket send failed (%d)\n",
|
|
clk_hw_get_name(hw), ret);
|
|
goto err_out;
|
|
}
|
|
|
|
ret = habmm_socket_recv(handle, &rsp, &rsp_size, UINT_MAX,
|
|
HABMM_SOCKET_RECV_FLAGS_UNINTERRUPTIBLE);
|
|
if (ret) {
|
|
pr_err("%s: habmm socket receive failed (%d)\n",
|
|
clk_hw_get_name(hw), ret);
|
|
goto err_out;
|
|
}
|
|
|
|
if (rsp.rsp) {
|
|
pr_err("%s: error response (%d)\n", clk_hw_get_name(hw),
|
|
rsp.rsp);
|
|
ret = -EIO;
|
|
}
|
|
|
|
mutex_unlock(&virt_clk_lock);
|
|
return ret;
|
|
|
|
err_out:
|
|
habmm_socket_close(handle);
|
|
hab_handle = 0;
|
|
mutex_unlock(&virt_clk_lock);
|
|
return ret;
|
|
}
|
|
|
|
static void clk_virt_unprepare(struct clk_hw *hw)
|
|
{
|
|
struct clk_virt *v = to_clk_virt(hw);
|
|
struct clk_msg_header msg;
|
|
struct clk_msg_rsp rsp;
|
|
u32 rsp_size = sizeof(rsp);
|
|
int handle;
|
|
int ret = 0;
|
|
|
|
ret = clk_virt_init_iface();
|
|
if (ret)
|
|
return;
|
|
|
|
ret = clk_virt_get_id(hw);
|
|
if (ret)
|
|
return;
|
|
|
|
msg.clk_id = v->id;
|
|
msg.cmd = CLK_MSG_DISABLE | v->flag;
|
|
msg.len = sizeof(struct clk_msg_header);
|
|
|
|
mutex_lock(&virt_clk_lock);
|
|
|
|
handle = hab_handle;
|
|
ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
|
|
if (ret) {
|
|
pr_err("%s: habmm socket send failed (%d)\n",
|
|
clk_hw_get_name(hw), ret);
|
|
goto err_out;
|
|
}
|
|
|
|
ret = habmm_socket_recv(handle, &rsp, &rsp_size, UINT_MAX,
|
|
HABMM_SOCKET_RECV_FLAGS_UNINTERRUPTIBLE);
|
|
if (ret) {
|
|
pr_err("%s: habmm socket receive failed (%d)\n",
|
|
clk_hw_get_name(hw), ret);
|
|
goto err_out;
|
|
}
|
|
|
|
if (rsp.rsp)
|
|
pr_err("%s: error response (%d)\n", clk_hw_get_name(hw),
|
|
rsp.rsp);
|
|
|
|
mutex_unlock(&virt_clk_lock);
|
|
return;
|
|
|
|
err_out:
|
|
habmm_socket_close(handle);
|
|
hab_handle = 0;
|
|
mutex_unlock(&virt_clk_lock);
|
|
}
|
|
|
|
static int clk_virt_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct clk_virt *v = to_clk_virt(hw);
|
|
struct clk_msg_setfreq msg;
|
|
struct clk_msg_rsp rsp;
|
|
u32 rsp_size = sizeof(rsp);
|
|
int handle;
|
|
int ret = 0;
|
|
|
|
ret = clk_virt_init_iface();
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_virt_get_id(hw);
|
|
if (ret)
|
|
return ret;
|
|
|
|
msg.header.clk_id = v->id;
|
|
msg.header.cmd = CLK_MSG_SETFREQ | v->flag;
|
|
msg.header.len = sizeof(msg);
|
|
msg.freq = (u32)rate;
|
|
|
|
mutex_lock(&virt_clk_lock);
|
|
|
|
handle = hab_handle;
|
|
ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
|
|
if (ret) {
|
|
pr_err("%s: habmm socket send failed (%d)\n",
|
|
clk_hw_get_name(hw), ret);
|
|
goto err_out;
|
|
}
|
|
|
|
ret = habmm_socket_recv(handle, &rsp, &rsp_size, UINT_MAX,
|
|
HABMM_SOCKET_RECV_FLAGS_UNINTERRUPTIBLE);
|
|
if (ret) {
|
|
pr_err("%s: habmm socket receive failed (%d)\n",
|
|
clk_hw_get_name(hw),
|
|
ret);
|
|
goto err_out;
|
|
}
|
|
|
|
if (rsp.rsp) {
|
|
pr_err("%s (%luHz): error response (%d)\n", clk_hw_get_name(hw),
|
|
rate, rsp.rsp);
|
|
ret = -EIO;
|
|
}
|
|
|
|
mutex_unlock(&virt_clk_lock);
|
|
return ret;
|
|
|
|
err_out:
|
|
habmm_socket_close(handle);
|
|
hab_handle = 0;
|
|
mutex_unlock(&virt_clk_lock);
|
|
return ret;
|
|
}
|
|
|
|
static long clk_virt_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *parent_rate)
|
|
{
|
|
struct clk_virt *v = to_clk_virt(hw);
|
|
struct clk_msg_roundfreq msg;
|
|
struct clk_msg_roundfreq_rsp rsp;
|
|
u32 rsp_size = sizeof(rsp);
|
|
int handle;
|
|
int ret = 0;
|
|
|
|
ret = clk_virt_init_iface();
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_virt_get_id(hw);
|
|
if (ret)
|
|
return ret;
|
|
|
|
msg.header.clk_id = v->id;
|
|
msg.header.cmd = CLK_MSG_ROUNDFREQ | v->flag;
|
|
msg.header.len = sizeof(msg);
|
|
msg.freq = (u32)rate;
|
|
|
|
mutex_lock(&virt_clk_lock);
|
|
|
|
handle = hab_handle;
|
|
ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
|
|
if (ret) {
|
|
pr_err("%s: habmm socket send failed (%d)\n",
|
|
clk_hw_get_name(hw), ret);
|
|
goto err_out;
|
|
}
|
|
|
|
ret = habmm_socket_recv(handle, &rsp, &rsp_size, UINT_MAX,
|
|
HABMM_SOCKET_RECV_FLAGS_UNINTERRUPTIBLE);
|
|
if (ret) {
|
|
pr_err("%s: habmm socket receive failed (%d)\n",
|
|
clk_hw_get_name(hw),
|
|
ret);
|
|
goto err_out;
|
|
}
|
|
|
|
if (rsp.rsp.rsp) {
|
|
pr_err("%s (%luHz): error response (%d)\n", clk_hw_get_name(hw),
|
|
rate, rsp.rsp.rsp);
|
|
ret = 0;
|
|
} else
|
|
ret = rsp.freq;
|
|
|
|
mutex_unlock(&virt_clk_lock);
|
|
return ret;
|
|
|
|
err_out:
|
|
habmm_socket_close(handle);
|
|
hab_handle = 0;
|
|
mutex_unlock(&virt_clk_lock);
|
|
return ret;
|
|
}
|
|
|
|
static unsigned long clk_virt_get_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct clk_virt *v = to_clk_virt(hw);
|
|
struct clk_msg_header msg;
|
|
struct clk_msg_getfreq rsp;
|
|
u32 rsp_size = sizeof(rsp);
|
|
int handle;
|
|
int ret = 0;
|
|
|
|
ret = clk_virt_init_iface();
|
|
if (ret)
|
|
return 0;
|
|
|
|
ret = clk_virt_get_id(hw);
|
|
if (ret)
|
|
return 0;
|
|
|
|
msg.clk_id = v->id;
|
|
msg.cmd = CLK_MSG_GETFREQ | v->flag;
|
|
msg.len = sizeof(msg);
|
|
|
|
mutex_lock(&virt_clk_lock);
|
|
|
|
handle = hab_handle;
|
|
ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
|
|
if (ret) {
|
|
ret = 0;
|
|
pr_err("%s: habmm socket send failed (%d)\n",
|
|
clk_hw_get_name(hw), ret);
|
|
goto err_out;
|
|
}
|
|
|
|
ret = habmm_socket_recv(handle, &rsp, &rsp_size, UINT_MAX,
|
|
HABMM_SOCKET_RECV_FLAGS_UNINTERRUPTIBLE);
|
|
if (ret) {
|
|
ret = 0;
|
|
pr_err("%s: habmm socket receive failed (%d)\n",
|
|
clk_hw_get_name(hw),
|
|
ret);
|
|
goto err_out;
|
|
}
|
|
|
|
if (rsp.rsp.rsp) {
|
|
pr_err("%s: error response (%d)\n", clk_hw_get_name(hw),
|
|
rsp.rsp.rsp);
|
|
ret = 0;
|
|
} else
|
|
ret = rsp.freq;
|
|
|
|
mutex_unlock(&virt_clk_lock);
|
|
return ret;
|
|
|
|
err_out:
|
|
habmm_socket_close(handle);
|
|
hab_handle = 0;
|
|
mutex_unlock(&virt_clk_lock);
|
|
return ret;
|
|
}
|
|
|
|
const struct clk_ops clk_virt_ops = {
|
|
.prepare = clk_virt_prepare,
|
|
.unprepare = clk_virt_unprepare,
|
|
.set_rate = clk_virt_set_rate,
|
|
.round_rate = clk_virt_round_rate,
|
|
.recalc_rate = clk_virt_get_rate,
|
|
};
|
|
|
|
#define rc_to_vcc(r) \
|
|
container_of(r, struct virt_cc, rcdev)
|
|
|
|
static int virtrc_get_clk_id(struct reset_controller_dev *rcdev,
|
|
unsigned long id)
|
|
{
|
|
struct virt_cc *vcc;
|
|
struct virt_reset_map *map;
|
|
struct clk_msg_getid msg;
|
|
struct clk_msg_rsp rsp;
|
|
u32 rsp_size = sizeof(rsp);
|
|
int handle;
|
|
int ret = 0;
|
|
|
|
vcc = rc_to_vcc(rcdev);
|
|
map = &vcc->resets[id];
|
|
msg.header.cmd = CLK_MSG_GETID;
|
|
msg.header.len = sizeof(msg);
|
|
strlcpy(msg.name, map->clk_name, sizeof(msg.name));
|
|
|
|
mutex_lock(&virt_clk_lock);
|
|
|
|
handle = hab_handle;
|
|
ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
|
|
if (ret) {
|
|
pr_err("%s: habmm socket send failed (%d)\n", map->clk_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", map->clk_name,
|
|
ret);
|
|
goto err_out;
|
|
}
|
|
|
|
if (rsp.rsp) {
|
|
pr_err("%s: error response (%d)\n", map->clk_name, rsp.rsp);
|
|
ret = -EIO;
|
|
} else
|
|
map->clk_id = rsp.header.clk_id;
|
|
|
|
mutex_unlock(&virt_clk_lock);
|
|
|
|
return ret;
|
|
|
|
err_out:
|
|
habmm_socket_close(handle);
|
|
hab_handle = 0;
|
|
mutex_unlock(&virt_clk_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int __virtrc_reset(struct reset_controller_dev *rcdev,
|
|
unsigned long id, unsigned int action)
|
|
{
|
|
struct virt_cc *vcc;
|
|
struct virt_reset_map *map;
|
|
struct clk_msg_reset msg;
|
|
struct clk_msg_rsp rsp;
|
|
u32 rsp_size = sizeof(rsp);
|
|
int handle;
|
|
int ret = 0;
|
|
|
|
vcc = rc_to_vcc(rcdev);
|
|
map = &vcc->resets[id];
|
|
|
|
ret = clk_virt_init_iface();
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = virtrc_get_clk_id(rcdev, id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
msg.header.clk_id = map->clk_id;
|
|
msg.header.cmd = CLK_MSG_RESET;
|
|
msg.header.len = sizeof(struct clk_msg_header);
|
|
msg.reset = action;
|
|
|
|
mutex_lock(&virt_clk_lock);
|
|
|
|
handle = hab_handle;
|
|
ret = habmm_socket_send(handle, &msg, sizeof(msg), 0);
|
|
if (ret) {
|
|
pr_err("%s: habmm socket send failed (%d)\n", map->clk_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", map->clk_name,
|
|
ret);
|
|
goto err_out;
|
|
}
|
|
|
|
if (rsp.rsp) {
|
|
pr_err("%s: error response (%d)\n", map->clk_name, rsp.rsp);
|
|
ret = -EIO;
|
|
}
|
|
|
|
mutex_unlock(&virt_clk_lock);
|
|
|
|
pr_debug("%s(%lu): do %s\n", map->clk_name, id,
|
|
action == 1 ? "assert" : "deassert");
|
|
|
|
return ret;
|
|
|
|
err_out:
|
|
habmm_socket_close(handle);
|
|
hab_handle = 0;
|
|
mutex_unlock(&virt_clk_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int virtrc_reset(struct reset_controller_dev *rcdev,
|
|
unsigned long id)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = __virtrc_reset(rcdev, id, 1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
udelay(1);
|
|
|
|
return __virtrc_reset(rcdev, id, 0);
|
|
}
|
|
|
|
static int virtrc_reset_assert(struct reset_controller_dev *rcdev,
|
|
unsigned long id)
|
|
{
|
|
return __virtrc_reset(rcdev, id, 1);
|
|
}
|
|
|
|
static int virtrc_reset_deassert(struct reset_controller_dev *rcdev,
|
|
unsigned long id)
|
|
{
|
|
return __virtrc_reset(rcdev, id, 0);
|
|
}
|
|
|
|
static const struct reset_control_ops virtrc_ops = {
|
|
.reset = virtrc_reset,
|
|
.assert = virtrc_reset_assert,
|
|
.deassert = virtrc_reset_deassert,
|
|
};
|
|
|
|
static const struct of_device_id clk_virt_match_table[] = {
|
|
{
|
|
.compatible = "qcom,virt-clk-sm8150-gcc",
|
|
.data = &clk_virt_sm8150_gcc
|
|
},
|
|
{
|
|
.compatible = "qcom,virt-clk-sm8150-scc",
|
|
.data = &clk_virt_sm8150_scc
|
|
},
|
|
{
|
|
.compatible = "qcom,virt-clk-sm6150-gcc",
|
|
.data = &clk_virt_sm6150_gcc
|
|
},
|
|
{
|
|
.compatible = "qcom,virt-clk-sm6150-scc",
|
|
.data = &clk_virt_sm6150_scc
|
|
},
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, clk_virt_match_table);
|
|
|
|
static int clk_virt_probe(struct platform_device *pdev)
|
|
{
|
|
struct clk **clks;
|
|
struct clk *clk;
|
|
struct virt_cc *vcc;
|
|
struct clk_onecell_data *data;
|
|
int ret;
|
|
size_t num_clks, num_resets, i;
|
|
struct clk_hw **hw_clks;
|
|
struct virt_reset_map *resets;
|
|
struct clk_virt *virt_clk;
|
|
const struct clk_virt_desc *desc;
|
|
|
|
desc = of_device_get_match_data(&pdev->dev);
|
|
if (!desc) {
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
hw_clks = desc->clks;
|
|
num_clks = desc->num_clks;
|
|
resets = desc->resets;
|
|
num_resets = desc->num_resets;
|
|
|
|
vcc = devm_kzalloc(&pdev->dev, sizeof(*vcc) + sizeof(*clks) * num_clks,
|
|
GFP_KERNEL);
|
|
if (!vcc) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
clks = vcc->clks;
|
|
data = &vcc->data;
|
|
data->clks = clks;
|
|
data->clk_num = num_clks;
|
|
|
|
for (i = 0; i < num_clks; i++) {
|
|
if (!hw_clks[i]) {
|
|
clks[i] = ERR_PTR(-ENOENT);
|
|
continue;
|
|
}
|
|
|
|
virt_clk = to_clk_virt(hw_clks[i]);
|
|
|
|
clk = devm_clk_register(&pdev->dev, hw_clks[i]);
|
|
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;
|
|
|
|
vcc->rcdev.of_node = pdev->dev.of_node;
|
|
vcc->rcdev.ops = &virtrc_ops;
|
|
vcc->rcdev.owner = pdev->dev.driver->owner;
|
|
vcc->rcdev.nr_resets = desc->num_resets;
|
|
vcc->resets = desc->resets;
|
|
|
|
ret = devm_reset_controller_register(&pdev->dev, &vcc->rcdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
dev_info(&pdev->dev, "Registered virtual clocks\n");
|
|
|
|
return ret;
|
|
err:
|
|
dev_err(&pdev->dev, "Error registering virtual clock driver (%d)\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
static struct platform_driver clk_virt_driver = {
|
|
.probe = clk_virt_probe,
|
|
.driver = {
|
|
.name = "clk-virt",
|
|
.of_match_table = clk_virt_match_table,
|
|
},
|
|
};
|
|
|
|
static int __init clk_virt_init(void)
|
|
{
|
|
return platform_driver_register(&clk_virt_driver);
|
|
}
|
|
subsys_initcall_sync(clk_virt_init);
|
|
|
|
static void __exit clk_virt_exit(void)
|
|
{
|
|
platform_driver_unregister(&clk_virt_driver);
|
|
}
|
|
module_exit(clk_virt_exit);
|
|
|
|
MODULE_DESCRIPTION("QTI Virtual Clock Driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:clk-virt");
|
|
|