312 lines
6.9 KiB
C
312 lines
6.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
#include <linux/clk-provider.h>
|
|
|
|
#define CLK_SEL_EXTERNAL_32K 0
|
|
#define CLK_SEL_INTERNAL_PVTM 1
|
|
|
|
#define wr_msk_bit(v, off, msk) ((v) << (off) | (msk << (16 + (off))))
|
|
|
|
struct rockchip_clock_pvtm;
|
|
|
|
struct rockchip_clock_pvtm_info {
|
|
u32 con;
|
|
u32 sta;
|
|
u32 sel_con;
|
|
u32 sel_shift;
|
|
u32 sel_value;
|
|
u32 sel_mask;
|
|
u32 div_shift;
|
|
u32 div_mask;
|
|
|
|
u32 (*get_value)(struct rockchip_clock_pvtm *pvtm,
|
|
unsigned int time_us);
|
|
int (*init_freq)(struct rockchip_clock_pvtm *pvtm);
|
|
int (*sel_enable)(struct rockchip_clock_pvtm *pvtm);
|
|
};
|
|
|
|
struct rockchip_clock_pvtm {
|
|
const struct rockchip_clock_pvtm_info *info;
|
|
struct regmap *grf;
|
|
struct clk *pvtm_clk;
|
|
struct clk *clk;
|
|
unsigned long rate;
|
|
};
|
|
|
|
static unsigned long xin32k_pvtm_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
return 32768;
|
|
}
|
|
|
|
static const struct clk_ops xin32k_pvtm = {
|
|
.recalc_rate = xin32k_pvtm_recalc_rate,
|
|
};
|
|
|
|
static void rockchip_clock_pvtm_delay(unsigned int delay)
|
|
{
|
|
unsigned int ms = delay / 1000;
|
|
unsigned int us = delay % 1000;
|
|
|
|
if (ms > 0) {
|
|
if (ms < 20)
|
|
us += ms * 1000;
|
|
else
|
|
msleep(ms);
|
|
}
|
|
|
|
if (us >= 10)
|
|
usleep_range(us, us + 100);
|
|
else
|
|
udelay(us);
|
|
}
|
|
|
|
static int rockchip_clock_sel_internal_pvtm(struct rockchip_clock_pvtm *pvtm)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = regmap_write(pvtm->grf, pvtm->info->sel_con,
|
|
wr_msk_bit(pvtm->info->sel_value,
|
|
pvtm->info->sel_shift,
|
|
pvtm->info->sel_mask));
|
|
if (ret != 0)
|
|
pr_err("%s: fail to write register\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* get pmu pvtm value */
|
|
static u32 rockchip_clock_pvtm_get_value(struct rockchip_clock_pvtm *pvtm,
|
|
u32 time_us)
|
|
{
|
|
const struct rockchip_clock_pvtm_info *info = pvtm->info;
|
|
u32 val = 0, sta = 0;
|
|
u32 clk_cnt, check_cnt;
|
|
|
|
/* 24m clk ,24cnt=1us */
|
|
clk_cnt = time_us * 24;
|
|
|
|
regmap_write(pvtm->grf, info->con + 0x4, clk_cnt);
|
|
regmap_write(pvtm->grf, info->con, wr_msk_bit(3, 0, 0x3));
|
|
|
|
rockchip_clock_pvtm_delay(time_us);
|
|
|
|
check_cnt = 100;
|
|
while (check_cnt) {
|
|
regmap_read(pvtm->grf, info->sta, &sta);
|
|
if (sta & 0x1)
|
|
break;
|
|
udelay(4);
|
|
check_cnt--;
|
|
}
|
|
|
|
if (check_cnt) {
|
|
regmap_read(pvtm->grf, info->sta + 0x4, &val);
|
|
} else {
|
|
pr_err("%s: wait pvtm_done timeout!\n", __func__);
|
|
val = 0;
|
|
}
|
|
|
|
regmap_write(pvtm->grf, info->con, wr_msk_bit(0, 0, 0x3));
|
|
|
|
return val;
|
|
}
|
|
|
|
static int rockchip_clock_pvtm_init_freq(struct rockchip_clock_pvtm *pvtm)
|
|
{
|
|
u32 pvtm_cnt = 0;
|
|
u32 div, time_us;
|
|
int ret = 0;
|
|
|
|
time_us = 1000;
|
|
pvtm_cnt = pvtm->info->get_value(pvtm, time_us);
|
|
pr_debug("get pvtm_cnt = %d\n", pvtm_cnt);
|
|
|
|
/* set pvtm_div to get rate */
|
|
div = DIV_ROUND_UP(1000 * pvtm_cnt, pvtm->rate);
|
|
if (div > pvtm->info->div_mask) {
|
|
pr_err("pvtm_div out of bounary! set max instead\n");
|
|
div = pvtm->info->div_mask;
|
|
}
|
|
|
|
pr_debug("set div %d, rate %luKHZ\n", div, pvtm->rate);
|
|
ret = regmap_write(pvtm->grf, pvtm->info->con,
|
|
wr_msk_bit(div, pvtm->info->div_shift,
|
|
pvtm->info->div_mask));
|
|
if (ret != 0)
|
|
goto out;
|
|
|
|
/* pmu pvtm oscilator enable */
|
|
ret = regmap_write(pvtm->grf, pvtm->info->con,
|
|
wr_msk_bit(1, 1, 0x1));
|
|
if (ret != 0)
|
|
goto out;
|
|
|
|
ret = pvtm->info->sel_enable(pvtm);
|
|
out:
|
|
if (ret != 0)
|
|
pr_err("%s: fail to write register\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int clock_pvtm_regitstor(struct device *dev,
|
|
struct rockchip_clock_pvtm *pvtm)
|
|
{
|
|
struct clk_init_data init = {};
|
|
struct clk_hw *clk_hw;
|
|
|
|
/* Init the xin32k_pvtm */
|
|
pvtm->info->init_freq(pvtm);
|
|
|
|
init.parent_names = NULL;
|
|
init.num_parents = 0;
|
|
init.name = "xin32k_pvtm";
|
|
init.ops = &xin32k_pvtm;
|
|
|
|
clk_hw = devm_kzalloc(dev, sizeof(*clk_hw), GFP_KERNEL);
|
|
if (!clk_hw)
|
|
return -ENOMEM;
|
|
clk_hw->init = &init;
|
|
|
|
/* optional override of the clockname */
|
|
of_property_read_string_index(dev->of_node, "clock-output-names",
|
|
0, &init.name);
|
|
pvtm->clk = devm_clk_register(dev, clk_hw);
|
|
if (IS_ERR(pvtm->clk))
|
|
return PTR_ERR(pvtm->clk);
|
|
|
|
return of_clk_add_provider(dev->of_node, of_clk_src_simple_get,
|
|
pvtm->clk);
|
|
}
|
|
|
|
static const struct rockchip_clock_pvtm_info rk3368_pvtm_data = {
|
|
.con = 0x180,
|
|
.sta = 0x190,
|
|
.sel_con = 0x100,
|
|
.sel_shift = 6,
|
|
.sel_value = CLK_SEL_INTERNAL_PVTM,
|
|
.sel_mask = 0x1,
|
|
.div_shift = 2,
|
|
.div_mask = 0x3f,
|
|
|
|
.sel_enable = rockchip_clock_sel_internal_pvtm,
|
|
.get_value = rockchip_clock_pvtm_get_value,
|
|
.init_freq = rockchip_clock_pvtm_init_freq,
|
|
};
|
|
|
|
static const struct of_device_id rockchip_clock_pvtm_match[] = {
|
|
{
|
|
.compatible = "rockchip,rk3368-pvtm-clock",
|
|
.data = (void *)&rk3368_pvtm_data,
|
|
},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, rockchip_clock_pvtm_match);
|
|
|
|
static int rockchip_clock_pvtm_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
const struct of_device_id *match;
|
|
struct rockchip_clock_pvtm *pvtm;
|
|
int error;
|
|
u32 rate;
|
|
|
|
pvtm = devm_kzalloc(dev, sizeof(*pvtm), GFP_KERNEL);
|
|
if (!pvtm)
|
|
return -ENOMEM;
|
|
|
|
match = of_match_node(rockchip_clock_pvtm_match, np);
|
|
if (!match)
|
|
return -ENXIO;
|
|
|
|
pvtm->info = (const struct rockchip_clock_pvtm_info *)match->data;
|
|
if (!pvtm->info)
|
|
return -EINVAL;
|
|
|
|
if (!dev->parent || !dev->parent->of_node)
|
|
return -EINVAL;
|
|
|
|
pvtm->grf = syscon_node_to_regmap(dev->parent->of_node);
|
|
if (IS_ERR(pvtm->grf))
|
|
return PTR_ERR(pvtm->grf);
|
|
|
|
if (!of_property_read_u32(np, "pvtm-rate", &rate))
|
|
pvtm->rate = rate;
|
|
else
|
|
pvtm->rate = 32768;
|
|
|
|
pvtm->pvtm_clk = devm_clk_get(&pdev->dev, "pvtm_pmu_clk");
|
|
if (IS_ERR(pvtm->pvtm_clk)) {
|
|
error = PTR_ERR(pvtm->pvtm_clk);
|
|
if (error != -EPROBE_DEFER)
|
|
dev_err(&pdev->dev,
|
|
"failed to get pvtm core clock: %d\n",
|
|
error);
|
|
goto out_probe;
|
|
}
|
|
|
|
error = clk_prepare_enable(pvtm->pvtm_clk);
|
|
if (error) {
|
|
dev_err(&pdev->dev, "failed to enable the clock: %d\n",
|
|
error);
|
|
goto out_probe;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, pvtm);
|
|
|
|
error = clock_pvtm_regitstor(&pdev->dev, pvtm);
|
|
if (error) {
|
|
dev_err(&pdev->dev, "failed to registor clock: %d\n",
|
|
error);
|
|
goto out_clk_put;
|
|
}
|
|
|
|
return error;
|
|
|
|
out_clk_put:
|
|
clk_disable_unprepare(pvtm->pvtm_clk);
|
|
out_probe:
|
|
return error;
|
|
}
|
|
|
|
static int rockchip_clock_pvtm_remove(struct platform_device *pdev)
|
|
{
|
|
struct rockchip_clock_pvtm *pvtm = platform_get_drvdata(pdev);
|
|
struct device_node *np = pdev->dev.of_node;
|
|
|
|
of_clk_del_provider(np);
|
|
clk_disable_unprepare(pvtm->pvtm_clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver rockchip_clock_pvtm_driver = {
|
|
.driver = {
|
|
.name = "rockchip-clcok-pvtm",
|
|
.of_match_table = rockchip_clock_pvtm_match,
|
|
},
|
|
.probe = rockchip_clock_pvtm_probe,
|
|
.remove = rockchip_clock_pvtm_remove,
|
|
};
|
|
|
|
module_platform_driver(rockchip_clock_pvtm_driver);
|
|
|
|
MODULE_DESCRIPTION("Rockchip Clock Pvtm Driver");
|
|
MODULE_LICENSE("GPL v2");
|