blob: f29c9b1dacdd58d60c65eb3551a9f1bdf1d41844 [file] [log] [blame]
/*
* Copyright 2019, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the GNU General Public License version 2. Note that NO WARRANTY is provided.
* See "LICENSE_GPLv2.txt" for details.
*
* @TAG(DATA61_GPL)
*/
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <platsupport/clock.h>
#include <platsupport/plat/clock.h>
/* NVIDIA interface */
#include <tx2bpmp/bpmp.h> /* struct mrq_clk_request, struct mrq_clk_response */
#define TX2_CLKCAR_PADDR 0x5000000
#define TX2_CLKCAR_SIZE 0x1000000
/*
* Having the parent pointer filled in for the clk_t structure currently
* doesn't mean anything at the moment. Recalibration is handled by the BPMP
* co-processor and it all happens behind the scenes.
*/
extern uint32_t mrq_clk_id_map[];
extern uint32_t mrq_gate_id_map[];
typedef struct tx2_clk {
ps_io_ops_t *io_ops;
void *car_vaddr;
struct tx2_bpmp *bpmp;
} tx2_clk_t;
static inline bool check_valid_gate(enum clock_gate gate)
{
return (CLK_GATE_FUSE <= gate && gate < NCLKGATES);
}
static inline bool check_valid_clk_id(enum clk_id id)
{
return (CLK_PLLC_OUT_ISP <= id && id < NCLOCKS);
}
static int tx2_car_gate_enable(clock_sys_t *clock_sys, enum clock_gate gate, enum clock_gate_mode mode)
{
if (!check_valid_gate(gate)) {
ZF_LOGE("Invalid clock gate!");
return -EINVAL;
}
if (mode == CLKGATE_IDLE || mode == CLKGATE_SLEEP) {
ZF_LOGE("Idle and sleep gate modes are not supported");
return -EINVAL;
}
uint32_t command = (mode == CLKGATE_ON ? CMD_CLK_ENABLE : CMD_CLK_DISABLE);
uint32_t bpmp_gate_id = mrq_gate_id_map[gate];
/* Setup the message and make a call to BPMP */
struct mrq_clk_request req = { .cmd_and_id = (command << 24) | bpmp_gate_id };
struct mrq_clk_response res = {0};
tx2_clk_t *clk = clock_sys->priv;
int bytes_recvd = tx2_bpmp_call(clk->bpmp, MRQ_CLK, &req, sizeof(req), &res, sizeof(res));
if (bytes_recvd < 0) {
return -EIO;
}
return 0;
}
static freq_t tx2_car_get_freq(clk_t *clk)
{
struct mrq_clk_request req = { .cmd_and_id = (CMD_CLK_GET_RATE << 24) | clk->id };
struct mrq_clk_response res = {0};
tx2_clk_t *tx2_clk = clk->clk_sys->priv;
int bytes_recvd = tx2_bpmp_call(tx2_clk->bpmp, MRQ_CLK, &req, sizeof(req), &res, sizeof(&res));
if (bytes_recvd < 0) {
return 0;
}
return (freq_t) res.clk_get_rate.rate;
}
static freq_t tx2_car_set_freq(clk_t *clk, freq_t hz)
{
struct mrq_clk_request req = { .cmd_and_id = (CMD_CLK_SET_RATE << 24) | clk->id };
req.clk_set_rate.rate = hz;
struct mrq_clk_response res = {0};
tx2_clk_t *tx2_clk = clk->clk_sys->priv;
int bytes_recvd = tx2_bpmp_call(tx2_clk->bpmp, MRQ_CLK, &req, sizeof(req), &res, sizeof(&res));
if (bytes_recvd < 0) {
return 0;
}
clk->req_freq = hz;
return (freq_t) res.clk_set_rate.rate;
}
static clk_t *tx2_car_get_clock(clock_sys_t *clock_sys, enum clk_id id)
{
if (!check_valid_clk_id(id)) {
ZF_LOGE("Invalid clock ID");
return NULL;
}
clk_t *ret_clk = NULL;
tx2_clk_t *tx2_clk = clock_sys->priv;
size_t clk_name_len = 0;
int error = ps_calloc(&tx2_clk->io_ops->malloc_ops, 1, sizeof(*ret_clk), (void **) &ret_clk);
if (error) {
ZF_LOGE("Failed to allocate memory for the clock structure");
return NULL;
}
bool clock_initialised = false;
/* Enable the clock while we're at it, clk_id is also a substitute for clock_gate */
error = tx2_car_gate_enable(clock_sys, id, CLKGATE_ON);
if (error) {
goto fail;
}
clock_initialised = true;
uint32_t bpmp_clk_id = mrq_clk_id_map[id];
/* Get info about this clock so we can fill it in */
struct mrq_clk_request req = { .cmd_and_id = (CMD_CLK_GET_ALL_INFO << 24) | bpmp_clk_id };
struct mrq_clk_response res = {0};
char *clock_name = NULL;
int bytes_recvd = tx2_bpmp_call(tx2_clk->bpmp, MRQ_CLK, &req, sizeof(req), &res, sizeof(res));
if (bytes_recvd < 0) {
ZF_LOGE("Failed to initialise the clock");
goto fail;
}
clk_name_len = strlen((char *) res.clk_get_all_info.name) + 1;
error = ps_calloc(&tx2_clk->io_ops->malloc_ops, 1, sizeof(char) * clk_name_len, (void **) &clock_name);
if (error) {
ZF_LOGE("Failed to allocate memory for the name of the clock");
goto fail;
}
strncpy(clock_name, (char *) res.clk_get_all_info.name, clk_name_len);
ret_clk->name = (const char *) clock_name;
/* There's no need for the init nor the recal functions as we're already
* doing it now and that the BPMP handles the recalibration for us */
ret_clk->get_freq = tx2_car_get_freq;
ret_clk->set_freq = tx2_car_set_freq;
ret_clk->id = id;
ret_clk->clk_sys = clock_sys;
return ret_clk;
fail:
if (ret_clk) {
if (ret_clk->name) {
ps_free(&tx2_clk->io_ops->malloc_ops, sizeof(char) * clk_name_len, (void *) ret_clk->name);
}
ps_free(&tx2_clk->io_ops->malloc_ops, sizeof(*ret_clk), (void *) ret_clk);
}
if (clock_initialised) {
ZF_LOGF_IF(tx2_car_gate_enable(clock_sys, id, CLKGATE_OFF),
"Failed to disable clock following failed clock initialisation operation");
}
return NULL;
}
static int interface_search_handler(void *handler_data, void *interface_instance, char **properties)
{
/* Select the first one that is registered */
tx2_clk_t *clk = handler_data;
clk->bpmp = (struct tx2_bpmp *) interface_instance;
return PS_INTERFACE_FOUND_MATCH;
}
int clock_sys_init(ps_io_ops_t *io_ops, clock_sys_t *clock_sys)
{
if (!io_ops || !clock_sys) {
if (!io_ops) {
ZF_LOGE("null io_ops argument");
}
if (!clock_sys) {
ZF_LOGE("null clock_sys argument");
}
return -EINVAL;
}
int error = 0;
tx2_clk_t *clk = NULL;
void *car_vaddr = NULL;
error = ps_calloc(&io_ops->malloc_ops, 1, sizeof(tx2_clk_t), (void **) &clock_sys->priv);
if (error) {
ZF_LOGE("Failed to allocate memory for clock sys internal structure");
error = -ENOMEM;
goto fail;
}
clk = clock_sys->priv;
car_vaddr = ps_io_map(&io_ops->io_mapper, TX2_CLKCAR_PADDR, TX2_CLKCAR_SIZE, 0, PS_MEM_NORMAL);
if (car_vaddr == NULL) {
ZF_LOGE("Failed to map tx2 CAR registers");
error = -ENOMEM;
goto fail;
}
clk->car_vaddr = car_vaddr;
/* See if there's a registered interface for the BPMP, if not, create one
* ourselves */
error = ps_interface_find(&io_ops->interface_registration_ops, TX2_BPMP_INTERFACE,
interface_search_handler, clk);
if (error) {
error = ps_calloc(&io_ops->malloc_ops, 1, sizeof(struct tx2_bpmp), (void **) &clk->bpmp);
if (error) {
ZF_LOGE("Failed to allocate memory for the BPMP structure");
goto fail;
}
error = tx2_bpmp_init(io_ops, clk->bpmp);
if (error) {
ZF_LOGE("Failed to initialise BPMP interface");
goto fail;
}
}
clk->io_ops = io_ops;
clock_sys->gate_enable = &tx2_car_gate_enable;
clock_sys->get_clock = &tx2_car_get_clock;
return 0;
fail:
if (car_vaddr) {
ps_io_unmap(&io_ops->io_mapper, car_vaddr, TX2_CLKCAR_SIZE);
}
if (clock_sys->priv) {
if (clk->bpmp) {
ZF_LOGF_IF(ps_free(&io_ops->malloc_ops, sizeof(struct tx2_bpmp), (void *) clk->bpmp),
"Failed to free the BPMP structure after failing to initialise");
}
ZF_LOGF_IF(ps_free(&io_ops->malloc_ops, sizeof(tx2_clk_t), (void *) clock_sys->priv),
"Failed to free the clock private structure after failing to initialise");
}
return error;
}
void clk_print_clock_tree(clock_sys_t *sys)
{
/* TODO Implement this function. The manual doesn't really give us a nice
* diagram of the clock hierarchy, but there is information about it. It's
* kind of hard to find however and would require scrolling through the
* manual and constructing it by hand. */
ZF_LOGE("Unimplemented");
}