blob: b8ad05050ec09863b8751e955ef73c06cb49b41a [file] [log] [blame]
/*
* Copyright (C) 2016 Freescale Semiconductor, Inc.
* Copyright 2017 NXP
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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.h>
#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <soc/imx8/sc/sci.h>
#include "clk-imx8.h"
struct clk_divider_scu {
struct clk_divider div;
sc_rsrc_t rsrc_id;
sc_pm_clk_t clk_type;
};
struct clk_divider3_scu {
struct clk_divider div;
sc_rsrc_t rsrc_id;
sc_ctrl_t gpr_id;
};
static inline struct clk_divider3_scu *to_clk_divider3_scu(struct clk_hw *hw)
{
struct clk_divider *div = container_of(hw, struct clk_divider, hw);
return container_of(div, struct clk_divider3_scu, div);
}
static inline struct clk_divider_scu *to_clk_divider_scu(struct clk_hw *hw)
{
struct clk_divider *div = container_of(hw, struct clk_divider, hw);
return container_of(div, struct clk_divider_scu, div);
}
static unsigned long clk_divider_scu_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_divider_scu *clk = to_clk_divider_scu(hw);
sc_err_t sci_err;
sc_pm_clock_rate_t rate = 0;
if (!ccm_ipc_handle)
return 0;
sci_err = sc_pm_get_clock_rate(ccm_ipc_handle, clk->rsrc_id,
clk->clk_type, &rate);
return sci_err ? 0 : rate;
}
static long clk_divider_scu_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
/* TODO */
*prate = rate;
return rate;
}
static int clk_divider_scu_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_divider_scu *clk = to_clk_divider_scu(hw);
sc_err_t sci_err;
if (!ccm_ipc_handle) {
return -EAGAIN;
}
sci_err = sc_pm_set_clock_rate(ccm_ipc_handle, clk->rsrc_id,
clk->clk_type, (sc_pm_clock_rate_t *)&rate);
return sci_err ? -EINVAL : 0;
}
static struct clk_ops clk_divider_scu_ops = {
.recalc_rate = clk_divider_scu_recalc_rate,
.round_rate = clk_divider_scu_round_rate,
.set_rate = clk_divider_scu_set_rate,
};
struct clk *imx_clk_divider_scu(const char *name,
sc_rsrc_t rsrc_id, sc_pm_clk_t clk_type)
{
struct clk_divider_scu *div_clk;
struct clk *clk;
struct clk_init_data init;
if (!imx8_clk_is_resource_owned(rsrc_id)) {
pr_debug("skip clk %s rsrc %d not owned\n", name, rsrc_id);
return ERR_PTR(-ENODEV);
}
div_clk = kzalloc(sizeof(*div_clk), GFP_KERNEL);
if (!div_clk)
return ERR_PTR(-ENOMEM);
div_clk->rsrc_id = rsrc_id;
div_clk->clk_type = clk_type;
init.name = name;
init.ops = &clk_divider_scu_ops;
init.flags = CLK_GET_RATE_NOCACHE;
init.num_parents = 0;
div_clk->div.hw.init = &init;
clk = clk_register(NULL, &div_clk->div.hw);
if (IS_ERR(clk))
kfree(div_clk);
return clk;
}
struct clk *imx_clk_divider2_scu(const char *name, const char *parent_name,
sc_rsrc_t rsrc_id, sc_pm_clk_t clk_type)
{
struct clk_divider_scu *div_clk;
struct clk *clk;
struct clk_init_data init;
if (!imx8_clk_is_resource_owned(rsrc_id)) {
pr_debug("skip clk %s rsrc %d not owned\n", name, rsrc_id);
return ERR_PTR(-ENODEV);
}
div_clk = kzalloc(sizeof(*div_clk), GFP_KERNEL);
if (!div_clk)
return ERR_PTR(-ENOMEM);
div_clk->rsrc_id = rsrc_id;
div_clk->clk_type = clk_type;
init.name = name;
init.ops = &clk_divider_scu_ops;
init.flags = CLK_GET_RATE_NOCACHE;
init.parent_names = parent_name ? &parent_name : NULL;
init.num_parents = parent_name ? 1 : 0;
div_clk->div.hw.init = &init;
clk = clk_register(NULL, &div_clk->div.hw);
if (IS_ERR(clk))
kfree(div_clk);
return clk;
}
static unsigned long clk_divider3_scu_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_divider3_scu *clk = to_clk_divider3_scu(hw);
uint32_t val;
sc_err_t sci_err;
sc_pm_clock_rate_t rate = 0;
if (!ccm_ipc_handle)
return 0;
sci_err = sc_misc_get_control(ccm_ipc_handle, clk->rsrc_id,
clk->gpr_id, &val);
rate = (val) ? parent_rate / 2 : parent_rate;
return sci_err ? 0 : rate;
}
static long clk_divider3_scu_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
if (rate < *prate)
rate = *prate / 2;
else
rate = *prate;
return rate;
}
static int clk_divider3_scu_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_divider3_scu *clk = to_clk_divider3_scu(hw);
uint32_t val;
sc_err_t sci_err;
if (!ccm_ipc_handle) {
return -EAGAIN;
}
val = (rate < parent_rate) ? 1 : 0;
sci_err = sc_misc_set_control(ccm_ipc_handle, clk->rsrc_id,
clk->gpr_id, val);
return sci_err ? -EINVAL : 0;
}
static struct clk_ops clk_divider3_scu_ops = {
.recalc_rate = clk_divider3_scu_recalc_rate,
.round_rate = clk_divider3_scu_round_rate,
.set_rate = clk_divider3_scu_set_rate,
};
struct clk *imx_clk_divider3_scu(const char *name, const char *parent_name,
sc_rsrc_t rsrc_id, sc_ctrl_t gpr_id)
{
struct clk_divider3_scu *div;
struct clk *clk;
struct clk_init_data init;
if (!imx8_clk_is_resource_owned(rsrc_id)) {
pr_debug("skip clk %s rsrc %d not owned\n", name, rsrc_id);
return ERR_PTR(-ENODEV);
}
div = kzalloc(sizeof(struct clk_divider3_scu), GFP_KERNEL);
if (!div)
return ERR_PTR(-ENOMEM);
div->rsrc_id = rsrc_id;
div->gpr_id = gpr_id;
init.name = name;
init.ops = &clk_divider3_scu_ops;
init.flags = 0;
init.parent_names = parent_name ? &parent_name : NULL;
init.num_parents = parent_name ? 1 : 0;
div->div.hw.init = &init;
clk = clk_register(NULL, &div->div.hw);
if (IS_ERR(clk))
kfree(div);
return clk;
}