From 6950dffaa388c9e839649bb14abaf55d1c2dec16 Mon Sep 17 00:00:00 2001 From: Elaine Zhang Date: Tue, 4 Dec 2018 09:36:49 +0800 Subject: [PATCH] clk: rockchip: add support for pvtm clk add pvtm 32K internal clock setting and select enable. Change-Id: I60225d29e16c5b96f285623260bea475c78a026a Signed-off-by: Elaine Zhang --- drivers/clk/rockchip/Makefile | 1 + drivers/clk/rockchip/clk-pvtm.c | 310 ++++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 drivers/clk/rockchip/clk-pvtm.c diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index 6ebdf84a6ac8..62ab9c5e9961 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -11,6 +11,7 @@ obj-y += clk-mmc-phase.o obj-y += clk-muxgrf.o obj-y += clk-ddr.o obj-y += clk-half-divider.o +obj-y += clk-pvtm.o obj-$(CONFIG_RESET_CONTROLLER) += softrst.o obj-$(CONFIG_CPU_PX30) += clk-px30.o diff --git a/drivers/clk/rockchip/clk-pvtm.c b/drivers/clk/rockchip/clk-pvtm.c new file mode 100644 index 000000000000..503523c18dfa --- /dev/null +++ b/drivers/clk/rockchip/clk-pvtm.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); + } + + 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.flags = CLK_IS_ROOT; + 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, +}; +MODULE_DEVICE_TABLE(of, rockchip_clock_pvtm_match); + +static const struct of_device_id rockchip_clock_pvtm_match[] = { + { + .compatible = "rockchip,rk3368-pvtm-clock", + .data = (void *)&rk3368_pvtm_data, + }, +}; + +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");