mirror of https://github.com/Qortal/Brooklyn
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.
771 lines
19 KiB
771 lines
19 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Zynq UltraScale+ MPSoC clock controller |
|
* |
|
* Copyright (C) 2016-2019 Xilinx |
|
* |
|
* Based on drivers/clk/zynq/clkc.c |
|
*/ |
|
|
|
#include <linux/bitfield.h> |
|
#include <linux/clk.h> |
|
#include <linux/clk-provider.h> |
|
#include <linux/module.h> |
|
#include <linux/of_platform.h> |
|
#include <linux/slab.h> |
|
#include <linux/string.h> |
|
|
|
#include "clk-zynqmp.h" |
|
|
|
#define MAX_PARENT 100 |
|
#define MAX_NODES 6 |
|
#define MAX_NAME_LEN 50 |
|
|
|
/* Flags for parents */ |
|
#define PARENT_CLK_SELF 0 |
|
#define PARENT_CLK_NODE1 1 |
|
#define PARENT_CLK_NODE2 2 |
|
#define PARENT_CLK_NODE3 3 |
|
#define PARENT_CLK_NODE4 4 |
|
#define PARENT_CLK_EXTERNAL 5 |
|
|
|
#define END_OF_CLK_NAME "END_OF_CLK" |
|
#define END_OF_TOPOLOGY_NODE 1 |
|
#define END_OF_PARENTS 1 |
|
#define RESERVED_CLK_NAME "" |
|
|
|
#define CLK_GET_NAME_RESP_LEN 16 |
|
#define CLK_GET_TOPOLOGY_RESP_WORDS 3 |
|
#define CLK_GET_PARENTS_RESP_WORDS 3 |
|
#define CLK_GET_ATTR_RESP_WORDS 1 |
|
|
|
enum clk_type { |
|
CLK_TYPE_OUTPUT, |
|
CLK_TYPE_EXTERNAL, |
|
}; |
|
|
|
/** |
|
* struct clock_parent - Clock parent |
|
* @name: Parent name |
|
* @id: Parent clock ID |
|
* @flag: Parent flags |
|
*/ |
|
struct clock_parent { |
|
char name[MAX_NAME_LEN]; |
|
int id; |
|
u32 flag; |
|
}; |
|
|
|
/** |
|
* struct zynqmp_clock - Clock |
|
* @clk_name: Clock name |
|
* @valid: Validity flag of clock |
|
* @type: Clock type (Output/External) |
|
* @node: Clock topology nodes |
|
* @num_nodes: Number of nodes present in topology |
|
* @parent: Parent of clock |
|
* @num_parents: Number of parents of clock |
|
* @clk_id: Clock id |
|
*/ |
|
struct zynqmp_clock { |
|
char clk_name[MAX_NAME_LEN]; |
|
u32 valid; |
|
enum clk_type type; |
|
struct clock_topology node[MAX_NODES]; |
|
u32 num_nodes; |
|
struct clock_parent parent[MAX_PARENT]; |
|
u32 num_parents; |
|
u32 clk_id; |
|
}; |
|
|
|
struct name_resp { |
|
char name[CLK_GET_NAME_RESP_LEN]; |
|
}; |
|
|
|
struct topology_resp { |
|
#define CLK_TOPOLOGY_TYPE GENMASK(3, 0) |
|
#define CLK_TOPOLOGY_CUSTOM_TYPE_FLAGS GENMASK(7, 4) |
|
#define CLK_TOPOLOGY_FLAGS GENMASK(23, 8) |
|
#define CLK_TOPOLOGY_TYPE_FLAGS GENMASK(31, 24) |
|
u32 topology[CLK_GET_TOPOLOGY_RESP_WORDS]; |
|
}; |
|
|
|
struct parents_resp { |
|
#define NA_PARENT 0xFFFFFFFF |
|
#define DUMMY_PARENT 0xFFFFFFFE |
|
#define CLK_PARENTS_ID GENMASK(15, 0) |
|
#define CLK_PARENTS_FLAGS GENMASK(31, 16) |
|
u32 parents[CLK_GET_PARENTS_RESP_WORDS]; |
|
}; |
|
|
|
struct attr_resp { |
|
#define CLK_ATTR_VALID BIT(0) |
|
#define CLK_ATTR_TYPE BIT(2) |
|
#define CLK_ATTR_NODE_INDEX GENMASK(13, 0) |
|
#define CLK_ATTR_NODE_TYPE GENMASK(19, 14) |
|
#define CLK_ATTR_NODE_SUBCLASS GENMASK(25, 20) |
|
#define CLK_ATTR_NODE_CLASS GENMASK(31, 26) |
|
u32 attr[CLK_GET_ATTR_RESP_WORDS]; |
|
}; |
|
|
|
static const char clk_type_postfix[][10] = { |
|
[TYPE_INVALID] = "", |
|
[TYPE_MUX] = "_mux", |
|
[TYPE_GATE] = "", |
|
[TYPE_DIV1] = "_div1", |
|
[TYPE_DIV2] = "_div2", |
|
[TYPE_FIXEDFACTOR] = "_ff", |
|
[TYPE_PLL] = "" |
|
}; |
|
|
|
static struct clk_hw *(* const clk_topology[]) (const char *name, u32 clk_id, |
|
const char * const *parents, |
|
u8 num_parents, |
|
const struct clock_topology *nodes) |
|
= { |
|
[TYPE_INVALID] = NULL, |
|
[TYPE_MUX] = zynqmp_clk_register_mux, |
|
[TYPE_PLL] = zynqmp_clk_register_pll, |
|
[TYPE_FIXEDFACTOR] = zynqmp_clk_register_fixed_factor, |
|
[TYPE_DIV1] = zynqmp_clk_register_divider, |
|
[TYPE_DIV2] = zynqmp_clk_register_divider, |
|
[TYPE_GATE] = zynqmp_clk_register_gate |
|
}; |
|
|
|
static struct zynqmp_clock *clock; |
|
static struct clk_hw_onecell_data *zynqmp_data; |
|
static unsigned int clock_max_idx; |
|
|
|
/** |
|
* zynqmp_is_valid_clock() - Check whether clock is valid or not |
|
* @clk_id: Clock index |
|
* |
|
* Return: 1 if clock is valid, 0 if clock is invalid else error code |
|
*/ |
|
static inline int zynqmp_is_valid_clock(u32 clk_id) |
|
{ |
|
if (clk_id >= clock_max_idx) |
|
return -ENODEV; |
|
|
|
return clock[clk_id].valid; |
|
} |
|
|
|
/** |
|
* zynqmp_get_clock_name() - Get name of clock from Clock index |
|
* @clk_id: Clock index |
|
* @clk_name: Name of clock |
|
* |
|
* Return: 0 on success else error code |
|
*/ |
|
static int zynqmp_get_clock_name(u32 clk_id, char *clk_name) |
|
{ |
|
int ret; |
|
|
|
ret = zynqmp_is_valid_clock(clk_id); |
|
if (ret == 1) { |
|
strncpy(clk_name, clock[clk_id].clk_name, MAX_NAME_LEN); |
|
return 0; |
|
} |
|
|
|
return ret == 0 ? -EINVAL : ret; |
|
} |
|
|
|
/** |
|
* zynqmp_get_clock_type() - Get type of clock |
|
* @clk_id: Clock index |
|
* @type: Clock type: CLK_TYPE_OUTPUT or CLK_TYPE_EXTERNAL |
|
* |
|
* Return: 0 on success else error code |
|
*/ |
|
static int zynqmp_get_clock_type(u32 clk_id, u32 *type) |
|
{ |
|
int ret; |
|
|
|
ret = zynqmp_is_valid_clock(clk_id); |
|
if (ret == 1) { |
|
*type = clock[clk_id].type; |
|
return 0; |
|
} |
|
|
|
return ret == 0 ? -EINVAL : ret; |
|
} |
|
|
|
/** |
|
* zynqmp_pm_clock_get_num_clocks() - Get number of clocks in system |
|
* @nclocks: Number of clocks in system/board. |
|
* |
|
* Call firmware API to get number of clocks. |
|
* |
|
* Return: 0 on success else error code. |
|
*/ |
|
static int zynqmp_pm_clock_get_num_clocks(u32 *nclocks) |
|
{ |
|
struct zynqmp_pm_query_data qdata = {0}; |
|
u32 ret_payload[PAYLOAD_ARG_CNT]; |
|
int ret; |
|
|
|
qdata.qid = PM_QID_CLOCK_GET_NUM_CLOCKS; |
|
|
|
ret = zynqmp_pm_query_data(qdata, ret_payload); |
|
*nclocks = ret_payload[1]; |
|
|
|
return ret; |
|
} |
|
|
|
/** |
|
* zynqmp_pm_clock_get_name() - Get the name of clock for given id |
|
* @clock_id: ID of the clock to be queried |
|
* @response: Name of the clock with the given id |
|
* |
|
* This function is used to get name of clock specified by given |
|
* clock ID. |
|
* |
|
* Return: Returns 0 |
|
*/ |
|
static int zynqmp_pm_clock_get_name(u32 clock_id, |
|
struct name_resp *response) |
|
{ |
|
struct zynqmp_pm_query_data qdata = {0}; |
|
u32 ret_payload[PAYLOAD_ARG_CNT]; |
|
|
|
qdata.qid = PM_QID_CLOCK_GET_NAME; |
|
qdata.arg1 = clock_id; |
|
|
|
zynqmp_pm_query_data(qdata, ret_payload); |
|
memcpy(response, ret_payload, sizeof(*response)); |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* zynqmp_pm_clock_get_topology() - Get the topology of clock for given id |
|
* @clock_id: ID of the clock to be queried |
|
* @index: Node index of clock topology |
|
* @response: Buffer used for the topology response |
|
* |
|
* This function is used to get topology information for the clock |
|
* specified by given clock ID. |
|
* |
|
* This API will return 3 node of topology with a single response. To get |
|
* other nodes, master should call same API in loop with new |
|
* index till error is returned. E.g First call should have |
|
* index 0 which will return nodes 0,1 and 2. Next call, index |
|
* should be 3 which will return nodes 3,4 and 5 and so on. |
|
* |
|
* Return: 0 on success else error+reason |
|
*/ |
|
static int zynqmp_pm_clock_get_topology(u32 clock_id, u32 index, |
|
struct topology_resp *response) |
|
{ |
|
struct zynqmp_pm_query_data qdata = {0}; |
|
u32 ret_payload[PAYLOAD_ARG_CNT]; |
|
int ret; |
|
|
|
qdata.qid = PM_QID_CLOCK_GET_TOPOLOGY; |
|
qdata.arg1 = clock_id; |
|
qdata.arg2 = index; |
|
|
|
ret = zynqmp_pm_query_data(qdata, ret_payload); |
|
memcpy(response, &ret_payload[1], sizeof(*response)); |
|
|
|
return ret; |
|
} |
|
|
|
/** |
|
* zynqmp_clk_register_fixed_factor() - Register fixed factor with the |
|
* clock framework |
|
* @name: Name of this clock |
|
* @clk_id: Clock ID |
|
* @parents: Name of this clock's parents |
|
* @num_parents: Number of parents |
|
* @nodes: Clock topology node |
|
* |
|
* Return: clock hardware to the registered clock |
|
*/ |
|
struct clk_hw *zynqmp_clk_register_fixed_factor(const char *name, u32 clk_id, |
|
const char * const *parents, |
|
u8 num_parents, |
|
const struct clock_topology *nodes) |
|
{ |
|
u32 mult, div; |
|
struct clk_hw *hw; |
|
struct zynqmp_pm_query_data qdata = {0}; |
|
u32 ret_payload[PAYLOAD_ARG_CNT]; |
|
int ret; |
|
|
|
qdata.qid = PM_QID_CLOCK_GET_FIXEDFACTOR_PARAMS; |
|
qdata.arg1 = clk_id; |
|
|
|
ret = zynqmp_pm_query_data(qdata, ret_payload); |
|
if (ret) |
|
return ERR_PTR(ret); |
|
|
|
mult = ret_payload[1]; |
|
div = ret_payload[2]; |
|
|
|
hw = clk_hw_register_fixed_factor(NULL, name, |
|
parents[0], |
|
nodes->flag, mult, |
|
div); |
|
|
|
return hw; |
|
} |
|
|
|
/** |
|
* zynqmp_pm_clock_get_parents() - Get the first 3 parents of clock for given id |
|
* @clock_id: Clock ID |
|
* @index: Parent index |
|
* @response: Parents of the given clock |
|
* |
|
* This function is used to get 3 parents for the clock specified by |
|
* given clock ID. |
|
* |
|
* This API will return 3 parents with a single response. To get |
|
* other parents, master should call same API in loop with new |
|
* parent index till error is returned. E.g First call should have |
|
* index 0 which will return parents 0,1 and 2. Next call, index |
|
* should be 3 which will return parent 3,4 and 5 and so on. |
|
* |
|
* Return: 0 on success else error+reason |
|
*/ |
|
static int zynqmp_pm_clock_get_parents(u32 clock_id, u32 index, |
|
struct parents_resp *response) |
|
{ |
|
struct zynqmp_pm_query_data qdata = {0}; |
|
u32 ret_payload[PAYLOAD_ARG_CNT]; |
|
int ret; |
|
|
|
qdata.qid = PM_QID_CLOCK_GET_PARENTS; |
|
qdata.arg1 = clock_id; |
|
qdata.arg2 = index; |
|
|
|
ret = zynqmp_pm_query_data(qdata, ret_payload); |
|
memcpy(response, &ret_payload[1], sizeof(*response)); |
|
|
|
return ret; |
|
} |
|
|
|
/** |
|
* zynqmp_pm_clock_get_attributes() - Get the attributes of clock for given id |
|
* @clock_id: Clock ID |
|
* @response: Clock attributes response |
|
* |
|
* This function is used to get clock's attributes(e.g. valid, clock type, etc). |
|
* |
|
* Return: 0 on success else error+reason |
|
*/ |
|
static int zynqmp_pm_clock_get_attributes(u32 clock_id, |
|
struct attr_resp *response) |
|
{ |
|
struct zynqmp_pm_query_data qdata = {0}; |
|
u32 ret_payload[PAYLOAD_ARG_CNT]; |
|
int ret; |
|
|
|
qdata.qid = PM_QID_CLOCK_GET_ATTRIBUTES; |
|
qdata.arg1 = clock_id; |
|
|
|
ret = zynqmp_pm_query_data(qdata, ret_payload); |
|
memcpy(response, &ret_payload[1], sizeof(*response)); |
|
|
|
return ret; |
|
} |
|
|
|
/** |
|
* __zynqmp_clock_get_topology() - Get topology data of clock from firmware |
|
* response data |
|
* @topology: Clock topology |
|
* @response: Clock topology data received from firmware |
|
* @nnodes: Number of nodes |
|
* |
|
* Return: 0 on success else error+reason |
|
*/ |
|
static int __zynqmp_clock_get_topology(struct clock_topology *topology, |
|
struct topology_resp *response, |
|
u32 *nnodes) |
|
{ |
|
int i; |
|
u32 type; |
|
|
|
for (i = 0; i < ARRAY_SIZE(response->topology); i++) { |
|
type = FIELD_GET(CLK_TOPOLOGY_TYPE, response->topology[i]); |
|
if (type == TYPE_INVALID) |
|
return END_OF_TOPOLOGY_NODE; |
|
topology[*nnodes].type = type; |
|
topology[*nnodes].flag = FIELD_GET(CLK_TOPOLOGY_FLAGS, |
|
response->topology[i]); |
|
topology[*nnodes].type_flag = |
|
FIELD_GET(CLK_TOPOLOGY_TYPE_FLAGS, |
|
response->topology[i]); |
|
topology[*nnodes].custom_type_flag = |
|
FIELD_GET(CLK_TOPOLOGY_CUSTOM_TYPE_FLAGS, |
|
response->topology[i]); |
|
(*nnodes)++; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* zynqmp_clock_get_topology() - Get topology of clock from firmware using |
|
* PM_API |
|
* @clk_id: Clock index |
|
* @topology: Clock topology |
|
* @num_nodes: Number of nodes |
|
* |
|
* Return: 0 on success else error+reason |
|
*/ |
|
static int zynqmp_clock_get_topology(u32 clk_id, |
|
struct clock_topology *topology, |
|
u32 *num_nodes) |
|
{ |
|
int j, ret; |
|
struct topology_resp response = { }; |
|
|
|
*num_nodes = 0; |
|
for (j = 0; j <= MAX_NODES; j += ARRAY_SIZE(response.topology)) { |
|
ret = zynqmp_pm_clock_get_topology(clock[clk_id].clk_id, j, |
|
&response); |
|
if (ret) |
|
return ret; |
|
ret = __zynqmp_clock_get_topology(topology, &response, |
|
num_nodes); |
|
if (ret == END_OF_TOPOLOGY_NODE) |
|
return 0; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* __zynqmp_clock_get_parents() - Get parents info of clock from firmware |
|
* response data |
|
* @parents: Clock parents |
|
* @response: Clock parents data received from firmware |
|
* @nparent: Number of parent |
|
* |
|
* Return: 0 on success else error+reason |
|
*/ |
|
static int __zynqmp_clock_get_parents(struct clock_parent *parents, |
|
struct parents_resp *response, |
|
u32 *nparent) |
|
{ |
|
int i; |
|
struct clock_parent *parent; |
|
|
|
for (i = 0; i < ARRAY_SIZE(response->parents); i++) { |
|
if (response->parents[i] == NA_PARENT) |
|
return END_OF_PARENTS; |
|
|
|
parent = &parents[i]; |
|
parent->id = FIELD_GET(CLK_PARENTS_ID, response->parents[i]); |
|
if (response->parents[i] == DUMMY_PARENT) { |
|
strcpy(parent->name, "dummy_name"); |
|
parent->flag = 0; |
|
} else { |
|
parent->flag = FIELD_GET(CLK_PARENTS_FLAGS, |
|
response->parents[i]); |
|
if (zynqmp_get_clock_name(parent->id, parent->name)) |
|
continue; |
|
} |
|
*nparent += 1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* zynqmp_clock_get_parents() - Get parents info from firmware using PM_API |
|
* @clk_id: Clock index |
|
* @parents: Clock parents |
|
* @num_parents: Total number of parents |
|
* |
|
* Return: 0 on success else error+reason |
|
*/ |
|
static int zynqmp_clock_get_parents(u32 clk_id, struct clock_parent *parents, |
|
u32 *num_parents) |
|
{ |
|
int j = 0, ret; |
|
struct parents_resp response = { }; |
|
|
|
*num_parents = 0; |
|
do { |
|
/* Get parents from firmware */ |
|
ret = zynqmp_pm_clock_get_parents(clock[clk_id].clk_id, j, |
|
&response); |
|
if (ret) |
|
return ret; |
|
|
|
ret = __zynqmp_clock_get_parents(&parents[j], &response, |
|
num_parents); |
|
if (ret == END_OF_PARENTS) |
|
return 0; |
|
j += ARRAY_SIZE(response.parents); |
|
} while (*num_parents <= MAX_PARENT); |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* zynqmp_get_parent_list() - Create list of parents name |
|
* @np: Device node |
|
* @clk_id: Clock index |
|
* @parent_list: List of parent's name |
|
* @num_parents: Total number of parents |
|
* |
|
* Return: 0 on success else error+reason |
|
*/ |
|
static int zynqmp_get_parent_list(struct device_node *np, u32 clk_id, |
|
const char **parent_list, u32 *num_parents) |
|
{ |
|
int i = 0, ret; |
|
u32 total_parents = clock[clk_id].num_parents; |
|
struct clock_topology *clk_nodes; |
|
struct clock_parent *parents; |
|
|
|
clk_nodes = clock[clk_id].node; |
|
parents = clock[clk_id].parent; |
|
|
|
for (i = 0; i < total_parents; i++) { |
|
if (!parents[i].flag) { |
|
parent_list[i] = parents[i].name; |
|
} else if (parents[i].flag == PARENT_CLK_EXTERNAL) { |
|
ret = of_property_match_string(np, "clock-names", |
|
parents[i].name); |
|
if (ret < 0) |
|
strcpy(parents[i].name, "dummy_name"); |
|
parent_list[i] = parents[i].name; |
|
} else { |
|
strcat(parents[i].name, |
|
clk_type_postfix[clk_nodes[parents[i].flag - 1]. |
|
type]); |
|
parent_list[i] = parents[i].name; |
|
} |
|
} |
|
|
|
*num_parents = total_parents; |
|
return 0; |
|
} |
|
|
|
/** |
|
* zynqmp_register_clk_topology() - Register clock topology |
|
* @clk_id: Clock index |
|
* @clk_name: Clock Name |
|
* @num_parents: Total number of parents |
|
* @parent_names: List of parents name |
|
* |
|
* Return: Returns either clock hardware or error+reason |
|
*/ |
|
static struct clk_hw *zynqmp_register_clk_topology(int clk_id, char *clk_name, |
|
int num_parents, |
|
const char **parent_names) |
|
{ |
|
int j; |
|
u32 num_nodes, clk_dev_id; |
|
char *clk_out[MAX_NODES]; |
|
struct clock_topology *nodes; |
|
struct clk_hw *hw = NULL; |
|
|
|
nodes = clock[clk_id].node; |
|
num_nodes = clock[clk_id].num_nodes; |
|
clk_dev_id = clock[clk_id].clk_id; |
|
|
|
for (j = 0; j < num_nodes; j++) { |
|
/* |
|
* Clock name received from firmware is output clock name. |
|
* Intermediate clock names are postfixed with type of clock. |
|
*/ |
|
if (j != (num_nodes - 1)) { |
|
clk_out[j] = kasprintf(GFP_KERNEL, "%s%s", clk_name, |
|
clk_type_postfix[nodes[j].type]); |
|
} else { |
|
clk_out[j] = kasprintf(GFP_KERNEL, "%s", clk_name); |
|
} |
|
|
|
if (!clk_topology[nodes[j].type]) |
|
continue; |
|
|
|
hw = (*clk_topology[nodes[j].type])(clk_out[j], clk_dev_id, |
|
parent_names, |
|
num_parents, |
|
&nodes[j]); |
|
if (IS_ERR(hw)) |
|
pr_warn_once("%s() 0x%x: %s register fail with %ld\n", |
|
__func__, clk_dev_id, clk_name, |
|
PTR_ERR(hw)); |
|
|
|
parent_names[0] = clk_out[j]; |
|
} |
|
|
|
for (j = 0; j < num_nodes; j++) |
|
kfree(clk_out[j]); |
|
|
|
return hw; |
|
} |
|
|
|
/** |
|
* zynqmp_register_clocks() - Register clocks |
|
* @np: Device node |
|
* |
|
* Return: 0 on success else error code |
|
*/ |
|
static int zynqmp_register_clocks(struct device_node *np) |
|
{ |
|
int ret; |
|
u32 i, total_parents = 0, type = 0; |
|
const char *parent_names[MAX_PARENT]; |
|
|
|
for (i = 0; i < clock_max_idx; i++) { |
|
char clk_name[MAX_NAME_LEN]; |
|
|
|
/* get clock name, continue to next clock if name not found */ |
|
if (zynqmp_get_clock_name(i, clk_name)) |
|
continue; |
|
|
|
/* Check if clock is valid and output clock. |
|
* Do not register invalid or external clock. |
|
*/ |
|
ret = zynqmp_get_clock_type(i, &type); |
|
if (ret || type != CLK_TYPE_OUTPUT) |
|
continue; |
|
|
|
/* Get parents of clock*/ |
|
if (zynqmp_get_parent_list(np, i, parent_names, |
|
&total_parents)) { |
|
WARN_ONCE(1, "No parents found for %s\n", |
|
clock[i].clk_name); |
|
continue; |
|
} |
|
|
|
zynqmp_data->hws[i] = |
|
zynqmp_register_clk_topology(i, clk_name, |
|
total_parents, |
|
parent_names); |
|
} |
|
|
|
for (i = 0; i < clock_max_idx; i++) { |
|
if (IS_ERR(zynqmp_data->hws[i])) { |
|
pr_err("Zynq Ultrascale+ MPSoC clk %s: register failed with %ld\n", |
|
clock[i].clk_name, PTR_ERR(zynqmp_data->hws[i])); |
|
WARN_ON(1); |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
/** |
|
* zynqmp_get_clock_info() - Get clock information from firmware using PM_API |
|
*/ |
|
static void zynqmp_get_clock_info(void) |
|
{ |
|
int i, ret; |
|
u32 type = 0; |
|
u32 nodetype, subclass, class; |
|
struct attr_resp attr; |
|
struct name_resp name; |
|
|
|
for (i = 0; i < clock_max_idx; i++) { |
|
ret = zynqmp_pm_clock_get_attributes(i, &attr); |
|
if (ret) |
|
continue; |
|
|
|
clock[i].valid = FIELD_GET(CLK_ATTR_VALID, attr.attr[0]); |
|
/* skip query for Invalid clock */ |
|
ret = zynqmp_is_valid_clock(i); |
|
if (ret != CLK_ATTR_VALID) |
|
continue; |
|
|
|
clock[i].type = FIELD_GET(CLK_ATTR_TYPE, attr.attr[0]) ? |
|
CLK_TYPE_EXTERNAL : CLK_TYPE_OUTPUT; |
|
|
|
nodetype = FIELD_GET(CLK_ATTR_NODE_TYPE, attr.attr[0]); |
|
subclass = FIELD_GET(CLK_ATTR_NODE_SUBCLASS, attr.attr[0]); |
|
class = FIELD_GET(CLK_ATTR_NODE_CLASS, attr.attr[0]); |
|
|
|
clock[i].clk_id = FIELD_PREP(CLK_ATTR_NODE_CLASS, class) | |
|
FIELD_PREP(CLK_ATTR_NODE_SUBCLASS, subclass) | |
|
FIELD_PREP(CLK_ATTR_NODE_TYPE, nodetype) | |
|
FIELD_PREP(CLK_ATTR_NODE_INDEX, i); |
|
|
|
zynqmp_pm_clock_get_name(clock[i].clk_id, &name); |
|
if (!strcmp(name.name, RESERVED_CLK_NAME)) |
|
continue; |
|
strncpy(clock[i].clk_name, name.name, MAX_NAME_LEN); |
|
} |
|
|
|
/* Get topology of all clock */ |
|
for (i = 0; i < clock_max_idx; i++) { |
|
ret = zynqmp_get_clock_type(i, &type); |
|
if (ret || type != CLK_TYPE_OUTPUT) |
|
continue; |
|
|
|
ret = zynqmp_clock_get_topology(i, clock[i].node, |
|
&clock[i].num_nodes); |
|
if (ret) |
|
continue; |
|
|
|
ret = zynqmp_clock_get_parents(i, clock[i].parent, |
|
&clock[i].num_parents); |
|
if (ret) |
|
continue; |
|
} |
|
} |
|
|
|
/** |
|
* zynqmp_clk_setup() - Setup the clock framework and register clocks |
|
* @np: Device node |
|
* |
|
* Return: 0 on success else error code |
|
*/ |
|
static int zynqmp_clk_setup(struct device_node *np) |
|
{ |
|
int ret; |
|
|
|
ret = zynqmp_pm_clock_get_num_clocks(&clock_max_idx); |
|
if (ret) |
|
return ret; |
|
|
|
zynqmp_data = kzalloc(struct_size(zynqmp_data, hws, clock_max_idx), |
|
GFP_KERNEL); |
|
if (!zynqmp_data) |
|
return -ENOMEM; |
|
|
|
clock = kcalloc(clock_max_idx, sizeof(*clock), GFP_KERNEL); |
|
if (!clock) { |
|
kfree(zynqmp_data); |
|
return -ENOMEM; |
|
} |
|
|
|
zynqmp_get_clock_info(); |
|
zynqmp_register_clocks(np); |
|
|
|
zynqmp_data->num = clock_max_idx; |
|
of_clk_add_hw_provider(np, of_clk_hw_onecell_get, zynqmp_data); |
|
|
|
return 0; |
|
} |
|
|
|
static int zynqmp_clock_probe(struct platform_device *pdev) |
|
{ |
|
int ret; |
|
struct device *dev = &pdev->dev; |
|
|
|
ret = zynqmp_clk_setup(dev->of_node); |
|
|
|
return ret; |
|
} |
|
|
|
static const struct of_device_id zynqmp_clock_of_match[] = { |
|
{.compatible = "xlnx,zynqmp-clk"}, |
|
{.compatible = "xlnx,versal-clk"}, |
|
{}, |
|
}; |
|
MODULE_DEVICE_TABLE(of, zynqmp_clock_of_match); |
|
|
|
static struct platform_driver zynqmp_clock_driver = { |
|
.driver = { |
|
.name = "zynqmp_clock", |
|
.of_match_table = zynqmp_clock_of_match, |
|
}, |
|
.probe = zynqmp_clock_probe, |
|
}; |
|
module_platform_driver(zynqmp_clock_driver);
|
|
|