From d04dec1a5405e2e6f2dc964b7934768cea09677b Mon Sep 17 00:00:00 2001 From: Wyon Bi Date: Wed, 25 Oct 2017 15:45:34 +0800 Subject: [PATCH] clk/rockchip: add cru support for rk618 Change-Id: I223c85194f62fec2c22c2a013466b767a1128f9c Signed-off-by: Wyon Bi --- .../bindings/clock/rockchip,rk618-cru.txt | 29 + drivers/clk/rockchip/Makefile | 1 + drivers/clk/rockchip/rk618/Makefile | 11 + .../clk/rockchip/rk618/clk-regmap-composite.c | 351 ++++++++++++ .../clk/rockchip/rk618/clk-regmap-divider.c | 104 ++++ drivers/clk/rockchip/rk618/clk-regmap-gate.c | 82 +++ drivers/clk/rockchip/rk618/clk-regmap-mux.c | 79 +++ drivers/clk/rockchip/rk618/clk-regmap-pll.c | 341 +++++++++++ drivers/clk/rockchip/rk618/clk-regmap.h | 92 +++ drivers/clk/rockchip/rk618/clk-rk618.c | 532 ++++++++++++++++++ include/dt-bindings/clock/rk618-cru.h | 38 ++ 11 files changed, 1660 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/rockchip,rk618-cru.txt create mode 100644 drivers/clk/rockchip/rk618/Makefile create mode 100644 drivers/clk/rockchip/rk618/clk-regmap-composite.c create mode 100644 drivers/clk/rockchip/rk618/clk-regmap-divider.c create mode 100644 drivers/clk/rockchip/rk618/clk-regmap-gate.c create mode 100644 drivers/clk/rockchip/rk618/clk-regmap-mux.c create mode 100644 drivers/clk/rockchip/rk618/clk-regmap-pll.c create mode 100644 drivers/clk/rockchip/rk618/clk-regmap.h create mode 100644 drivers/clk/rockchip/rk618/clk-rk618.c create mode 100644 include/dt-bindings/clock/rk618-cru.h diff --git a/Documentation/devicetree/bindings/clock/rockchip,rk618-cru.txt b/Documentation/devicetree/bindings/clock/rockchip,rk618-cru.txt new file mode 100644 index 000000000000..14e3d010d50d --- /dev/null +++ b/Documentation/devicetree/bindings/clock/rockchip,rk618-cru.txt @@ -0,0 +1,29 @@ +Rockchip RK618 Clock and Reset Unit + +This binding uses the common clock binding: +Documentation/devicetree/bindings/clock/clock-bindings.txt + +Required properties : +- compatible : Should be "rockchip,rk618-cru" +- clocks : Should contain phandle and clock specifiers for the input clock: + the AP I2S master clock output(mclk) "clkin", and the AP LCDC master + dclk output(dclk) "lcdc0_dclkp". +- #clock-cells : Should be 1. + +Example: + +&rk618 { + CRU: cru { + compatible = "rockchip,rk618-cru"; + clocks = <&cru SCLK_I2S_8CH_OUT>, <&cru DCLK_VOP>; + clock-names = "clkin", "lcdc0_dclkp"; + assigned-clocks = <&CRU SCALER_PLLIN_CLK>, <&CRU VIF_PLLIN_CLK>, + <&CRU HDMI_CLK>, <&CRU SCALER_CLK>, + <&CRU CODEC_CLK>; + assigned-clock-parents = <&CRU LCDC0_CLK>, <&CRU LCDC0_CLK>, + <&CRU VIF0_CLK>, <&CRU SCALER_PLL_CLK>, + <&cru SCLK_I2S_8CH_OUT>; + #clock-cells = <1>; + status = "okay"; + }; +}; diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index dd4651bcd287..51631bede1ff 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -22,3 +22,4 @@ obj-y += clk-rk3328.o obj-y += clk-rk3366.o obj-y += clk-rk3368.o obj-y += clk-rk3399.o +obj-$(CONFIG_MFD_RK618) += rk618/ diff --git a/drivers/clk/rockchip/rk618/Makefile b/drivers/clk/rockchip/rk618/Makefile new file mode 100644 index 000000000000..01e59d6f63b8 --- /dev/null +++ b/drivers/clk/rockchip/rk618/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the Rockchip RK618 CRU driver. +# + +obj-$(CONFIG_MFD_RK618) += clk-regmap-mux.o \ + clk-regmap-divider.o \ + clk-regmap-gate.o \ + clk-regmap-composite.o \ + clk-regmap-pll.o \ + clk-rk618.o diff --git a/drivers/clk/rockchip/rk618/clk-regmap-composite.c b/drivers/clk/rockchip/rk618/clk-regmap-composite.c new file mode 100644 index 000000000000..6b1b1025e7ec --- /dev/null +++ b/drivers/clk/rockchip/rk618/clk-regmap-composite.c @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2017 Rockchip Electronics Co. Ltd. + * + * Base on code in drivers/clk/clk-composite.c. + * See clk-composite.c for further copyright information. + * + * 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 "clk-regmap.h" + +struct clk_regmap_composite { + struct device *dev; + struct clk_hw hw; + struct clk_ops ops; + + struct clk_hw *mux_hw; + struct clk_hw *rate_hw; + struct clk_hw *gate_hw; + + const struct clk_ops *mux_ops; + const struct clk_ops *rate_ops; + const struct clk_ops *gate_ops; +}; + +#define to_clk_regmap_composite(_hw) \ + container_of(_hw, struct clk_regmap_composite, hw) + +static u8 clk_regmap_composite_get_parent(struct clk_hw *hw) +{ + struct clk_regmap_composite *composite = to_clk_regmap_composite(hw); + const struct clk_ops *mux_ops = composite->mux_ops; + struct clk_hw *mux_hw = composite->mux_hw; + + __clk_hw_set_clk(mux_hw, hw); + + return mux_ops->get_parent(mux_hw); +} + +static int clk_regmap_composite_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_regmap_composite *composite = to_clk_regmap_composite(hw); + const struct clk_ops *mux_ops = composite->mux_ops; + struct clk_hw *mux_hw = composite->mux_hw; + + __clk_hw_set_clk(mux_hw, hw); + + return mux_ops->set_parent(mux_hw, index); +} + +static unsigned long clk_regmap_composite_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_regmap_composite *composite = to_clk_regmap_composite(hw); + const struct clk_ops *rate_ops = composite->rate_ops; + struct clk_hw *rate_hw = composite->rate_hw; + + __clk_hw_set_clk(rate_hw, hw); + + return rate_ops->recalc_rate(rate_hw, parent_rate); +} + +static int clk_regmap_composite_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_regmap_composite *composite = to_clk_regmap_composite(hw); + const struct clk_ops *rate_ops = composite->rate_ops; + const struct clk_ops *mux_ops = composite->mux_ops; + struct clk_hw *rate_hw = composite->rate_hw; + struct clk_hw *mux_hw = composite->mux_hw; + struct clk_hw *parent; + unsigned long parent_rate; + long tmp_rate, best_rate = 0; + unsigned long rate_diff; + unsigned long best_rate_diff = ULONG_MAX; + long rate; + int i; + + if (rate_hw && rate_ops && rate_ops->determine_rate) { + __clk_hw_set_clk(rate_hw, hw); + return rate_ops->determine_rate(rate_hw, req); + } else if (rate_hw && rate_ops && rate_ops->round_rate && + mux_hw && mux_ops && mux_ops->set_parent) { + req->best_parent_hw = NULL; + + if (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT) { + parent = clk_hw_get_parent(mux_hw); + req->best_parent_hw = parent; + req->best_parent_rate = clk_hw_get_rate(parent); + + rate = rate_ops->round_rate(rate_hw, req->rate, + &req->best_parent_rate); + if (rate < 0) + return rate; + + req->rate = rate; + return 0; + } + + for (i = 0; i < clk_hw_get_num_parents(mux_hw); i++) { + parent = clk_hw_get_parent_by_index(mux_hw, i); + if (!parent) + continue; + + parent_rate = clk_hw_get_rate(parent); + + tmp_rate = rate_ops->round_rate(rate_hw, req->rate, + &parent_rate); + if (tmp_rate < 0) + continue; + + rate_diff = abs(req->rate - tmp_rate); + + if (!rate_diff || !req->best_parent_hw || + best_rate_diff > rate_diff) { + req->best_parent_hw = parent; + req->best_parent_rate = parent_rate; + best_rate_diff = rate_diff; + best_rate = tmp_rate; + } + + if (!rate_diff) + return 0; + } + + req->rate = best_rate; + return 0; + } else if (mux_hw && mux_ops && mux_ops->determine_rate) { + __clk_hw_set_clk(mux_hw, hw); + return mux_ops->determine_rate(mux_hw, req); + } else { + return -EINVAL; + } + + return 0; +} + +static long clk_regmap_composite_round_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long *prate) +{ + struct clk_regmap_composite *composite = to_clk_regmap_composite(hw); + const struct clk_ops *rate_ops = composite->rate_ops; + struct clk_hw *rate_hw = composite->rate_hw; + + __clk_hw_set_clk(rate_hw, hw); + + return rate_ops->round_rate(rate_hw, rate, prate); +} + +static int clk_regmap_composite_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct clk_regmap_composite *composite = to_clk_regmap_composite(hw); + const struct clk_ops *rate_ops = composite->rate_ops; + struct clk_hw *rate_hw = composite->rate_hw; + + __clk_hw_set_clk(rate_hw, hw); + + return rate_ops->set_rate(rate_hw, rate, parent_rate); +} + +static int clk_regmap_composite_is_prepared(struct clk_hw *hw) +{ + struct clk_regmap_composite *composite = to_clk_regmap_composite(hw); + const struct clk_ops *gate_ops = composite->gate_ops; + struct clk_hw *gate_hw = composite->gate_hw; + + __clk_hw_set_clk(gate_hw, hw); + + return gate_ops->is_prepared(gate_hw); +} + +static int clk_regmap_composite_prepare(struct clk_hw *hw) +{ + struct clk_regmap_composite *composite = to_clk_regmap_composite(hw); + const struct clk_ops *gate_ops = composite->gate_ops; + struct clk_hw *gate_hw = composite->gate_hw; + + __clk_hw_set_clk(gate_hw, hw); + + return gate_ops->prepare(gate_hw); +} + +static void clk_regmap_composite_unprepare(struct clk_hw *hw) +{ + struct clk_regmap_composite *composite = to_clk_regmap_composite(hw); + const struct clk_ops *gate_ops = composite->gate_ops; + struct clk_hw *gate_hw = composite->gate_hw; + + __clk_hw_set_clk(gate_hw, hw); + + gate_ops->unprepare(gate_hw); +} + +struct clk * +devm_clk_regmap_register_composite(struct device *dev, const char *name, + const char *const *parent_names, + u8 num_parents, struct regmap *regmap, + u32 mux_reg, u8 mux_shift, u8 mux_width, + u32 div_reg, u8 div_shift, u8 div_width, + u32 gate_reg, u8 gate_shift, + unsigned long flags) +{ + struct clk_regmap_gate *gate = NULL; + struct clk_regmap_mux *mux = NULL; + struct clk_regmap_divider *div = NULL; + const struct clk_ops *mux_ops = NULL, *div_ops = NULL, *gate_ops = NULL; + struct clk_hw *mux_hw = NULL, *div_hw = NULL, *gate_hw = NULL; + struct clk *clk; + struct clk_init_data init; + struct clk_regmap_composite *composite; + struct clk_ops *clk_composite_ops; + + if (num_parents > 1) { + mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); + if (!mux) + return ERR_PTR(-ENOMEM); + + mux->dev = dev; + mux->regmap = regmap; + mux->reg = mux_reg; + mux->shift = mux_shift; + mux->mask = BIT(mux_width) - 1; + mux_ops = &clk_regmap_mux_ops; + mux_hw = &mux->hw; + } + + if (gate_reg > 0) { + gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL); + if (!gate) + return ERR_PTR(-ENOMEM); + + gate->dev = dev; + gate->regmap = regmap; + gate->reg = gate_reg; + gate->shift = gate_shift; + gate_ops = &clk_regmap_gate_ops; + gate_hw = &gate->hw; + } + + if (div_width > 0) { + div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL); + if (!div) + return ERR_PTR(-ENOMEM); + + div->dev = dev; + div->regmap = regmap; + div->reg = div_reg; + div->shift = div_shift; + div->width = div_width; + div_ops = &clk_regmap_divider_ops; + div_hw = &div->hw; + } + + composite = devm_kzalloc(dev, sizeof(*composite), GFP_KERNEL); + if (!composite) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.flags = flags; + init.parent_names = parent_names; + init.num_parents = num_parents; + + clk_composite_ops = &composite->ops; + + if (mux_hw && mux_ops) { + if (!mux_ops->get_parent) + return ERR_PTR(-EINVAL); + + composite->mux_hw = mux_hw; + composite->mux_ops = mux_ops; + clk_composite_ops->get_parent = + clk_regmap_composite_get_parent; + if (mux_ops->set_parent) + clk_composite_ops->set_parent = + clk_regmap_composite_set_parent; + if (mux_ops->determine_rate) + clk_composite_ops->determine_rate = + clk_regmap_composite_determine_rate; + } + + if (div_hw && div_ops) { + if (!div_ops->recalc_rate) + return ERR_PTR(-EINVAL); + + clk_composite_ops->recalc_rate = + clk_regmap_composite_recalc_rate; + + if (div_ops->determine_rate) + clk_composite_ops->determine_rate = + clk_regmap_composite_determine_rate; + else if (div_ops->round_rate) + clk_composite_ops->round_rate = + clk_regmap_composite_round_rate; + + /* .set_rate requires either .round_rate or .determine_rate */ + if (div_ops->set_rate) { + if (div_ops->determine_rate || div_ops->round_rate) + clk_composite_ops->set_rate = + clk_regmap_composite_set_rate; + else + WARN(1, "missing round_rate op\n"); + } + + composite->rate_hw = div_hw; + composite->rate_ops = div_ops; + } + + if (gate_hw && gate_ops) { + if (!gate_ops->is_prepared || !gate_ops->prepare || + !gate_ops->unprepare) + return ERR_PTR(-EINVAL); + + composite->gate_hw = gate_hw; + composite->gate_ops = gate_ops; + clk_composite_ops->is_prepared = + clk_regmap_composite_is_prepared; + clk_composite_ops->prepare = clk_regmap_composite_prepare; + clk_composite_ops->unprepare = clk_regmap_composite_unprepare; + } + + init.ops = clk_composite_ops; + composite->dev = dev; + composite->hw.init = &init; + + clk = devm_clk_register(dev, &composite->hw); + if (IS_ERR(clk)) + return clk; + + if (composite->mux_hw) + composite->mux_hw->clk = clk; + + if (composite->rate_hw) + composite->rate_hw->clk = clk; + + if (composite->gate_hw) + composite->gate_hw->clk = clk; + + return clk; +} +EXPORT_SYMBOL_GPL(devm_clk_regmap_register_composite); diff --git a/drivers/clk/rockchip/rk618/clk-regmap-divider.c b/drivers/clk/rockchip/rk618/clk-regmap-divider.c new file mode 100644 index 000000000000..ae38f6b1108f --- /dev/null +++ b/drivers/clk/rockchip/rk618/clk-regmap-divider.c @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017 Rockchip Electronics Co. Ltd. + * + * Base on code in drivers/clk/clk-divider.c. + * See clk-divider.c for further copyright information. + * + * 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 "clk-regmap.h" + +#define div_mask(width) ((1 << (width)) - 1) + +#define to_clk_regmap_divider(_hw) \ + container_of(_hw, struct clk_regmap_divider, hw) + +static unsigned long +clk_regmap_divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct clk_regmap_divider *divider = to_clk_regmap_divider(hw); + unsigned int val, div; + + regmap_read(divider->regmap, divider->reg, &val); + + div = val >> divider->shift; + div &= div_mask(divider->width); + + return divider_recalc_rate(hw, parent_rate, div, NULL, + CLK_DIVIDER_ROUND_CLOSEST); +} + +static long +clk_regmap_divider_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + struct clk_regmap_divider *divider = to_clk_regmap_divider(hw); + + return divider_round_rate(hw, rate, prate, NULL, divider->width, + CLK_DIVIDER_ROUND_CLOSEST); +} + +static int +clk_regmap_divider_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_regmap_divider *divider = to_clk_regmap_divider(hw); + u32 val, div; + + div = divider_get_val(rate, parent_rate, NULL, divider->width, + CLK_DIVIDER_ROUND_CLOSEST); + + dev_dbg(divider->dev, "%s: parent_rate=%ld, div=%d, rate=%ld\n", + clk_hw_get_name(hw), parent_rate, div, rate); + + val = div_mask(divider->width) << (divider->shift + 16); + val |= div << divider->shift; + + return regmap_write(divider->regmap, divider->reg, val); +} + +const struct clk_ops clk_regmap_divider_ops = { + .recalc_rate = clk_regmap_divider_recalc_rate, + .round_rate = clk_regmap_divider_round_rate, + .set_rate = clk_regmap_divider_set_rate, +}; +EXPORT_SYMBOL_GPL(clk_regmap_divider_ops); + +struct clk * +devm_clk_regmap_register_divider(struct device *dev, const char *name, + const char *parent_name, struct regmap *regmap, + u32 reg, u8 shift, u8 width, + unsigned long flags) +{ + struct clk_regmap_divider *divider; + struct clk_init_data init; + + divider = devm_kzalloc(dev, sizeof(*divider), GFP_KERNEL); + if (!divider) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &clk_regmap_divider_ops; + init.flags = flags; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + divider->dev = dev; + divider->regmap = regmap; + divider->reg = reg; + divider->shift = shift; + divider->width = width; + divider->hw.init = &init; + + return devm_clk_register(dev, ÷r->hw); +} +EXPORT_SYMBOL_GPL(devm_clk_regmap_register_divider); diff --git a/drivers/clk/rockchip/rk618/clk-regmap-gate.c b/drivers/clk/rockchip/rk618/clk-regmap-gate.c new file mode 100644 index 000000000000..6258a7e18691 --- /dev/null +++ b/drivers/clk/rockchip/rk618/clk-regmap-gate.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017 Rockchip Electronics Co. Ltd. + * + * Base on code in drivers/clk/clk-gate.c. + * See clk-gate.c for further copyright information. + * + * 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 "clk-regmap.h" + +#define to_clk_regmap_gate(_hw) container_of(_hw, struct clk_regmap_gate, hw) + +static int clk_regmap_gate_prepare(struct clk_hw *hw) +{ + struct clk_regmap_gate *gate = to_clk_regmap_gate(hw); + + return regmap_write(gate->regmap, gate->reg, + 0 | BIT(gate->shift + 16)); +} + +static void clk_regmap_gate_unprepare(struct clk_hw *hw) +{ + struct clk_regmap_gate *gate = to_clk_regmap_gate(hw); + + regmap_write(gate->regmap, gate->reg, + BIT(gate->shift) | BIT(gate->shift + 16)); +} + +static int clk_regmap_gate_is_prepared(struct clk_hw *hw) +{ + struct clk_regmap_gate *gate = to_clk_regmap_gate(hw); + u32 val; + + regmap_read(gate->regmap, gate->reg, &val); + + return !(val & BIT(gate->shift)); +} + +const struct clk_ops clk_regmap_gate_ops = { + .prepare = clk_regmap_gate_prepare, + .unprepare = clk_regmap_gate_unprepare, + .is_prepared = clk_regmap_gate_is_prepared, +}; +EXPORT_SYMBOL_GPL(clk_regmap_gate_ops); + +struct clk * +devm_clk_regmap_register_gate(struct device *dev, const char *name, + const char *parent_name, + struct regmap *regmap, u32 reg, u8 shift, + unsigned long flags) +{ + struct clk_regmap_gate *gate; + struct clk_init_data init; + + gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL); + if (!gate) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &clk_regmap_gate_ops; + init.flags = flags; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + gate->dev = dev; + gate->regmap = regmap; + gate->reg = reg; + gate->shift = shift; + gate->hw.init = &init; + + return devm_clk_register(dev, &gate->hw); +} +EXPORT_SYMBOL_GPL(devm_clk_regmap_register_gate); diff --git a/drivers/clk/rockchip/rk618/clk-regmap-mux.c b/drivers/clk/rockchip/rk618/clk-regmap-mux.c new file mode 100644 index 000000000000..760ac07fd4bb --- /dev/null +++ b/drivers/clk/rockchip/rk618/clk-regmap-mux.c @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017 Rockchip Electronics Co. Ltd. + * + * Base on code in drivers/clk/clk-mux.c. + * See clk-mux.c for further copyright information. + * + * 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 "clk-regmap.h" + +#define to_clk_regmap_mux(_hw) container_of(_hw, struct clk_regmap_mux, hw) + +static u8 clk_regmap_mux_get_parent(struct clk_hw *hw) +{ + struct clk_regmap_mux *mux = to_clk_regmap_mux(hw); + u8 index; + u32 val; + + regmap_read(mux->regmap, mux->reg, &val); + + index = val >> mux->shift; + index &= mux->mask; + + return index; +} + +static int clk_regmap_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_regmap_mux *mux = to_clk_regmap_mux(hw); + + return regmap_write(mux->regmap, mux->reg, (index << mux->shift) | + (mux->mask << (mux->shift + 16))); +} + +const struct clk_ops clk_regmap_mux_ops = { + .set_parent = clk_regmap_mux_set_parent, + .get_parent = clk_regmap_mux_get_parent, + .determine_rate = __clk_mux_determine_rate, +}; +EXPORT_SYMBOL_GPL(clk_regmap_mux_ops); + +struct clk * +devm_clk_regmap_register_mux(struct device *dev, const char *name, + const char * const *parent_names, u8 num_parents, + struct regmap *regmap, u32 reg, u8 shift, u8 width, + unsigned long flags) +{ + struct clk_regmap_mux *mux; + struct clk_init_data init; + + mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); + if (!mux) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &clk_regmap_mux_ops; + init.flags = flags; + init.parent_names = parent_names; + init.num_parents = num_parents; + + mux->dev = dev; + mux->regmap = regmap; + mux->reg = reg; + mux->shift = shift; + mux->mask = BIT(width) - 1; + mux->hw.init = &init; + + return devm_clk_register(dev, &mux->hw); +} +EXPORT_SYMBOL_GPL(devm_clk_regmap_register_mux); diff --git a/drivers/clk/rockchip/rk618/clk-regmap-pll.c b/drivers/clk/rockchip/rk618/clk-regmap-pll.c new file mode 100644 index 000000000000..df01f4a5fb31 --- /dev/null +++ b/drivers/clk/rockchip/rk618/clk-regmap-pll.c @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2017 Rockchip Electronics Co. Ltd. + * + * 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 "clk-regmap.h" + +#define PLLCON_OFFSET(x) (x * 4) + +#define PLL_BYPASS BIT(15) +#define PLL_POSTDIV1(x) HIWORD_UPDATE(x, 14, 12) +#define PLL_POSTDIV1_MASK GENMASK(14, 12) +#define PLL_POSTDIV1_SHIFT 12 +#define PLL_FBDIV(x) HIWORD_UPDATE(x, 11, 0) +#define PLL_FBDIV_MASK GENMASK(11, 0) +#define PLL_FBDIV_SHIFT 0 + +#define PLL_LOCK BIT(15) +#define PLL_POWER_DOWN HIWORD_UPDATE(1, 10, 10) +#define PLL_POWER_UP HIWORD_UPDATE(0, 10, 10) +#define PLL_DSMPD_MASK BIT(9) +#define PLL_DSMPD_SHIFT 9 +#define PLL_DSMPD(x) HIWORD_UPDATE(x, 9, 9) +#define PLL_POSTDIV2(x) HIWORD_UPDATE(x, 8, 6) +#define PLL_POSTDIV2_MASK GENMASK(8, 6) +#define PLL_POSTDIV2_SHIFT 6 +#define PLL_REFDIV(x) HIWORD_UPDATE(x, 5, 0) +#define PLL_REFDIV_MASK GENMASK(5, 0) +#define PLL_REFDIV_SHIFT 0 + +#define PLL_FOUT_4PHASE_CLK_POWER_DOWN BIT(27) +#define PLL_FOUT_VCO_CLK_POWER_DOWN BIT(26) +#define PLL_FOUT_POST_DIV_POWER_DOWN BIT(25) +#define PLL_DAC_POWER_DOWN BIT(24) +#define PLL_FRAC(x) UPDATE(x, 23, 0) +#define PLL_FRAC_MASK GENMASK(23, 0) +#define PLL_FRAC_SHIFT 0 + +#define MIN_FREF_RATE 10000000UL +#define MAX_FREF_RATE 800000000UL +#define MIN_FREFDIV_RATE 1000000UL +#define MAX_FREFDIV_RATE 40000000UL +#define MIN_FVCO_RATE 400000000UL +#define MAX_FVCO_RATE 1600000000UL +#define MIN_FOUTPOSTDIV_RATE 8000000UL +#define MAX_FOUTPOSTDIV_RATE 1600000000UL + +struct clk_regmap_pll { + struct clk_hw hw; + struct device *dev; + struct regmap *regmap; + unsigned int reg; +}; + +#define to_clk_regmap_pll(_hw) container_of(_hw, struct clk_regmap_pll, hw) + +static unsigned long +clk_regmap_pll_recalc_rate(struct clk_hw *hw, unsigned long prate) +{ + struct clk_regmap_pll *pll = to_clk_regmap_pll(hw); + unsigned int postdiv1, fbdiv, dsmpd, postdiv2, refdiv, frac; + unsigned int con0, con1, con2; + u64 foutvco, foutpostdiv; + + regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(0), &con0); + regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(1), &con1); + regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(2), &con2); + + postdiv1 = (con0 & PLL_POSTDIV1_MASK) >> PLL_POSTDIV1_SHIFT; + fbdiv = (con0 & PLL_FBDIV_MASK) >> PLL_FBDIV_SHIFT; + dsmpd = (con1 & PLL_DSMPD_MASK) >> PLL_DSMPD_SHIFT; + postdiv2 = (con1 & PLL_POSTDIV2_MASK) >> PLL_POSTDIV2_SHIFT; + refdiv = (con1 & PLL_REFDIV_MASK) >> PLL_REFDIV_SHIFT; + frac = (con2 & PLL_FRAC_MASK) >> PLL_FRAC_SHIFT; + + foutvco = prate * fbdiv; + do_div(foutvco, refdiv); + + if (!dsmpd) { + u64 frac_rate = prate * frac; + + do_div(frac_rate, refdiv); + foutvco += frac_rate >> 24; + } + + foutpostdiv = foutvco; + do_div(foutpostdiv, postdiv1); + do_div(foutpostdiv, postdiv2); + + return foutpostdiv; +} + +static unsigned long clk_pll_round_rate(unsigned long fin, + unsigned long fout, + u8 *refdiv, u16 *fbdiv, + u8 *postdiv1, u8 *postdiv2, + u32 *frac, u8 *dsmpd) +{ + u8 min_refdiv, max_refdiv, postdiv; + u8 _dsmpd = 1, _postdiv1 = 0, _postdiv2 = 0, _refdiv = 0; + u16 _fbdiv = 0; + u32 _frac = 0; + u64 foutvco, foutpostdiv; + + /* + * FREF : 10MHz ~ 800MHz + * FREFDIV : 1MHz ~ 40MHz + * FOUTVCO : 400MHz ~ 1.6GHz + * FOUTPOSTDIV : 8MHz ~ 1.6GHz + */ + if (fin < MIN_FREF_RATE || fin > MAX_FREF_RATE) + return -EINVAL; + + if (fout < MIN_FOUTPOSTDIV_RATE || fout > MAX_FOUTPOSTDIV_RATE) + return -EINVAL; + + min_refdiv = DIV_ROUND_UP(fin, MAX_FREFDIV_RATE); + max_refdiv = fin / MIN_FREFDIV_RATE; + if (max_refdiv > 64) + max_refdiv = 64; + + if (fout < MIN_FVCO_RATE) { + postdiv = DIV_ROUND_UP_ULL(MIN_FVCO_RATE, fout); + + for (_postdiv2 = 1; _postdiv2 < 8; _postdiv2++) { + if (postdiv % _postdiv2) + continue; + + _postdiv1 = postdiv / _postdiv2; + + if (_postdiv1 > 0 && _postdiv1 < 8) + break; + } + + fout *= _postdiv1 * _postdiv2; + } else { + _postdiv1 = 1; + _postdiv2 = 1; + } + + for (_refdiv = min_refdiv; _refdiv <= max_refdiv; _refdiv++) { + u64 tmp, frac_rate; + + if (fin % _refdiv) + continue; + + tmp = (u64)fout * _refdiv; + do_div(tmp, fin); + _fbdiv = tmp; + if (_fbdiv < 10 || _fbdiv > 1600) + continue; + + tmp = (u64)_fbdiv * fin; + do_div(tmp, _refdiv); + if (fout < MIN_FVCO_RATE || fout > MAX_FVCO_RATE) + continue; + + frac_rate = fout - tmp; + + if (frac_rate) { + tmp = (u64)frac_rate * _refdiv; + tmp <<= 24; + do_div(tmp, fin); + _frac = tmp; + _dsmpd = 0; + } + + break; + } + + /* + * If DSMPD = 1 (DSM is disabled, "integer mode") + * FOUTVCO = FREF / REFDIV * FBDIV + * FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2 + * + * If DSMPD = 0 (DSM is enabled, "fractional mode") + * FOUTVCO = FREF / REFDIV * (FBDIV + FRAC / 2^24) + * FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2 + */ + foutvco = fin * _fbdiv; + do_div(foutvco, _refdiv); + + if (!_dsmpd) { + u64 frac_rate = fin * _frac; + + do_div(frac_rate, _refdiv); + foutvco += frac_rate >> 24; + } + + foutpostdiv = foutvco; + do_div(foutpostdiv, _postdiv1); + do_div(foutpostdiv, _postdiv2); + + if (refdiv) + *refdiv = _refdiv; + if (fbdiv) + *fbdiv = _fbdiv; + if (postdiv1) + *postdiv1 = _postdiv1; + if (postdiv2) + *postdiv2 = _postdiv2; + if (frac) + *frac = _frac; + if (dsmpd) + *dsmpd = _dsmpd; + + return (unsigned long)foutpostdiv; +} + +static long +clk_regmap_pll_round_rate(struct clk_hw *hw, unsigned long drate, + unsigned long *prate) +{ + struct clk_regmap_pll *pll = to_clk_regmap_pll(hw); + long rate; + + rate = clk_pll_round_rate(*prate, drate, NULL, NULL, NULL, NULL, + NULL, NULL); + + dev_dbg(pll->dev, "%s: prate=%ld, drate=%ld, rate=%ld\n", + clk_hw_get_name(hw), *prate, drate, rate); + + return rate; +} + +static int +clk_regmap_pll_set_rate(struct clk_hw *hw, unsigned long drate, + unsigned long prate) +{ + struct clk_regmap_pll *pll = to_clk_regmap_pll(hw); + u8 refdiv, postdiv1, postdiv2, dsmpd; + u16 fbdiv; + u32 frac; + u32 v; + int ret; + + ret = clk_pll_round_rate(prate, drate, &refdiv, &fbdiv, &postdiv1, + &postdiv2, &frac, &dsmpd); + if (ret < 0) + return ret; + + /* When changing PLL setting, we must force PLL into power down mode. */ + regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1), PLL_POWER_DOWN); + + regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(0), + PLL_POSTDIV1(postdiv1) | PLL_FBDIV(fbdiv)); + regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1), + PLL_DSMPD(dsmpd) | PLL_POSTDIV2(postdiv2) | + PLL_REFDIV(refdiv)); + regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(2), PLL_FRAC(frac)); + + regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1), PLL_POWER_UP); + + ret = regmap_read_poll_timeout(pll->regmap, + pll->reg + PLLCON_OFFSET(1), + v, v & PLL_LOCK, 50, 50000); + if (ret) + dev_err(pll->dev, "PLL is not lock\n"); + + return 0; +} + +static int clk_regmap_pll_prepare(struct clk_hw *hw) +{ + struct clk_regmap_pll *pll = to_clk_regmap_pll(hw); + u32 v; + int ret; + + regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1), PLL_POWER_UP); + + ret = regmap_read_poll_timeout(pll->regmap, + pll->reg + PLLCON_OFFSET(1), + v, v & PLL_LOCK, 50, 50000); + if (ret) + dev_err(pll->dev, "PLL is not lock\n"); + + return 0; +} + +static void clk_regmap_pll_unprepare(struct clk_hw *hw) +{ + struct clk_regmap_pll *pll = to_clk_regmap_pll(hw); + + regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1), PLL_POWER_DOWN); +} + +static int clk_regmap_pll_is_prepared(struct clk_hw *hw) +{ + struct clk_regmap_pll *pll = to_clk_regmap_pll(hw); + unsigned int con1; + + regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(1), &con1); + + return !(con1 & PLL_POWER_DOWN); +} + +static const struct clk_ops clk_regmap_pll_ops = { + .recalc_rate = clk_regmap_pll_recalc_rate, + .round_rate = clk_regmap_pll_round_rate, + .set_rate = clk_regmap_pll_set_rate, + .prepare = clk_regmap_pll_prepare, + .unprepare = clk_regmap_pll_unprepare, + .is_prepared = clk_regmap_pll_is_prepared, +}; + +struct clk * +devm_clk_regmap_register_pll(struct device *dev, const char *name, + const char *parent_name, + struct regmap *regmap, u32 reg, + unsigned long flags) +{ + struct clk_regmap_pll *pll; + struct clk_init_data init; + + pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL); + if (!pll) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &clk_regmap_pll_ops; + init.flags = flags; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + pll->dev = dev; + pll->regmap = regmap; + pll->reg = reg; + pll->hw.init = &init; + + return devm_clk_register(dev, &pll->hw); +} +EXPORT_SYMBOL_GPL(devm_clk_regmap_register_pll); diff --git a/drivers/clk/rockchip/rk618/clk-regmap.h b/drivers/clk/rockchip/rk618/clk-regmap.h new file mode 100644 index 000000000000..9d3b1fbfbf53 --- /dev/null +++ b/drivers/clk/rockchip/rk618/clk-regmap.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2017 Rockchip Electronics Co. Ltd. + * + * 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. + */ + +#ifndef __CLK_REGMAP_H__ +#define __CLK_REGMAP_H__ + +#include +#include +#include +#include +#include +#include +#include + +#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l))) +#define HIWORD_UPDATE(v, h, l) (((v) << (l)) | (GENMASK((h), (l)) << 16)) + +struct clk_regmap_divider { + struct clk_hw hw; + struct device *dev; + struct regmap *regmap; + u32 reg; + u8 shift; + u8 width; +}; + +struct clk_regmap_gate { + struct clk_hw hw; + struct device *dev; + struct regmap *regmap; + u32 reg; + u8 shift; +}; + +struct clk_regmap_mux { + struct clk_hw hw; + struct device *dev; + struct regmap *regmap; + u32 reg; + u32 mask; + u8 shift; +}; + +extern const struct clk_ops clk_regmap_mux_ops; +extern const struct clk_ops clk_regmap_divider_ops; +extern const struct clk_ops clk_regmap_gate_ops; + +struct clk * +devm_clk_regmap_register_pll(struct device *dev, const char *name, + const char *parent_name, + struct regmap *regmap, u32 reg, + unsigned long flags); + +struct clk * +devm_clk_regmap_register_mux(struct device *dev, const char *name, + const char * const *parent_names, u8 num_parents, + struct regmap *regmap, u32 reg, u8 shift, u8 width, + unsigned long flags); + +struct clk * +devm_clk_regmap_register_divider(struct device *dev, const char *name, + const char *parent_name, struct regmap *regmap, + u32 reg, u8 shift, u8 width, + unsigned long flags); + +struct clk * +devm_clk_regmap_register_gate(struct device *dev, const char *name, + const char *parent_name, + struct regmap *regmap, u32 reg, u8 shift, + unsigned long flags); + +struct clk * +devm_clk_regmap_register_composite(struct device *dev, const char *name, + const char *const *parent_names, + u8 num_parents, struct regmap *regmap, + u32 mux_reg, u8 mux_shift, u8 mux_width, + u32 div_reg, u8 div_shift, u8 div_width, + u32 gate_reg, u8 gate_shift, + unsigned long flags); + +#endif diff --git a/drivers/clk/rockchip/rk618/clk-rk618.c b/drivers/clk/rockchip/rk618/clk-rk618.c new file mode 100644 index 000000000000..58edcf303c12 --- /dev/null +++ b/drivers/clk/rockchip/rk618/clk-rk618.c @@ -0,0 +1,532 @@ +/* + * Copyright (c) 2017 Rockchip Electronics Co. Ltd. + * + * 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 +#include +#include +#include +#include + +#include "clk-regmap.h" + +#define RK618_CRU_CLKSEL0 0x0058 +#define RK618_CRU_CLKSEL1 0x005c +#define RK618_CRU_CLKSEL2 0x0060 +#define RK618_CRU_CLKSEL3 0x0064 +#define RK618_CRU_PLL0_CON0 0x0068 +#define RK618_CRU_PLL0_CON1 0x006c +#define RK618_CRU_PLL0_CON2 0x0070 +#define RK618_CRU_PLL1_CON0 0x0074 +#define RK618_CRU_PLL1_CON1 0x0078 +#define RK618_CRU_PLL1_CON2 0x007c + +struct clk_pll_data { + unsigned int id; + const char *name; + const char *parent_name; + u32 reg; + unsigned long flags; +}; + +#define PLL(_id, _name, _parent_name, _reg, _flags) \ +{ \ + .id = _id, \ + .name = _name, \ + .parent_name = _parent_name, \ + .reg = _reg, \ + .flags = _flags, \ +} + +struct clk_mux_data { + unsigned int id; + const char *name; + const char *const *parent_names; + u8 num_parents; + u32 reg; + u8 shift; + u8 width; + unsigned long flags; +}; + +#define MUX(_id, _name, _parent_names, _reg, _shift, _width, _flags) \ +{ \ + .id = _id, \ + .name = _name, \ + .parent_names = _parent_names, \ + .num_parents = ARRAY_SIZE(_parent_names), \ + .reg = _reg, \ + .shift = _shift, \ + .width = _width, \ + .flags = _flags, \ +} + +struct clk_gate_data { + unsigned int id; + const char *name; + const char *parent_name; + u32 reg; + u8 shift; + unsigned long flags; +}; + +#define GATE(_id, _name, _parent_name, _reg, _shift, _flags) \ +{ \ + .id = _id, \ + .name = _name, \ + .parent_name = _parent_name, \ + .reg = _reg, \ + .shift = _shift, \ + .flags = _flags, \ +} + +struct clk_divider_data { + unsigned int id; + const char *name; + const char *parent_name; + u32 reg; + u8 shift; + u8 width; + unsigned long flags; +}; + +#define DIV(_id, _name, _parent_name, _reg, _shift, _width, _flags) \ +{ \ + .id = _id, \ + .name = _name, \ + .parent_name = _parent_name, \ + .reg = _reg, \ + .shift = _shift, \ + .width = _width, \ + .flags = _flags, \ +} + +struct clk_composite_data { + unsigned int id; + const char *name; + const char *const *parent_names; + u8 num_parents; + u32 mux_reg; + u8 mux_shift; + u8 mux_width; + u32 div_reg; + u8 div_shift; + u8 div_width; + u32 gate_reg; + u8 gate_shift; + unsigned long flags; +}; + +#define COMPOSITE(_id, _name, _parent_names, \ + _mux_reg, _mux_shift, _mux_width, \ + _div_reg, _div_shift, _div_width, \ + _gate_reg, _gate_shift, _flags) \ +{ \ + .id = _id, \ + .name = _name, \ + .parent_names = _parent_names, \ + .num_parents = ARRAY_SIZE(_parent_names), \ + .mux_reg = _mux_reg, \ + .mux_shift = _mux_shift, \ + .mux_width = _mux_width, \ + .div_reg = _div_reg, \ + .div_shift = _div_shift, \ + .div_width = _div_width, \ + .gate_reg = _gate_reg, \ + .gate_shift = _gate_shift, \ + .flags = _flags, \ +} + +#define COMPOSITE_NODIV(_id, _name, _parent_names, \ + _mux_reg, _mux_shift, _mux_width, \ + _gate_reg, _gate_shift, _flags) \ +{ \ + .id = _id, \ + .name = _name, \ + .parent_names = _parent_names, \ + .num_parents = ARRAY_SIZE(_parent_names), \ + .mux_reg = _mux_reg, \ + .mux_shift = _mux_shift, \ + .mux_width = _mux_width, \ + .gate_reg = _gate_reg, \ + .gate_shift = _gate_shift, \ + .flags = _flags, \ +} + +enum { + LCDC0_CLK = 1, + LCDC1_CLK, + VIF_PLLIN_CLK, + SCALER_PLLIN_CLK, + VIF_PLL_CLK, + SCALER_PLL_CLK, + VIF0_CLK, + VIF1_CLK, + SCALER_IN_CLK, + SCALER_CLK, + DITHER_CLK, + HDMI_CLK, + MIPI_CLK, + LVDS_CLK, + LVTTL_CLK, + RGB_CLK, + VIF0_PRE_CLK, + VIF1_PRE_CLK, + CODEC_CLK, + NR_CLKS, +}; + +struct rk618_cru { + struct device *dev; + struct rk618 *parent; + struct regmap *regmap; + + struct clk_onecell_data clk_data; +}; + +static char clkin_name[16] = "dummy"; +static char lcdc0_dclkp_name[16] = "dummy"; +static char lcdc1_dclkp_name[16] = "dummy"; + +#define PNAME(x) static const char *const x[] + +PNAME(mux_pll_in_p) = { "lcdc0_clk", "lcdc1_clk", clkin_name }; +PNAME(mux_pll_src_p) = { "vif_pll_clk", "scaler_pll_clk", }; +PNAME(mux_scaler_in_src_p) = { "vif0_clk", "vif1_clk" }; +PNAME(mux_hdmi_src_p) = { "vif1_clk", "scaler_clk", "vif0_clk" }; +PNAME(mux_dither_src_p) = { "vif0_clk", "scaler_clk" }; +PNAME(mux_vif0_src_p) = { "vif0_pre_clk", lcdc0_dclkp_name }; +PNAME(mux_vif1_src_p) = { "vif1_pre_clk", lcdc1_dclkp_name }; +PNAME(mux_codec_src_p) = { "vif_pll_clk", "scaler_pll_clk", clkin_name }; + +/* Two PLL, one for dual datarate input logic, the other for scaler */ +static const struct clk_pll_data rk618_clk_plls[] = { + PLL(VIF_PLL_CLK, "vif_pll_clk", "vif_pllin_clk", + RK618_CRU_PLL0_CON0, + 0), + PLL(SCALER_PLL_CLK, "scaler_pll_clk", "scaler_pllin_clk", + RK618_CRU_PLL1_CON0, + 0), +}; + +static const struct clk_mux_data rk618_clk_muxes[] = { + MUX(VIF_PLLIN_CLK, "vif_pllin_clk", mux_pll_in_p, + RK618_CRU_CLKSEL0, 6, 2, + 0), + MUX(SCALER_PLLIN_CLK, "scaler_pllin_clk", mux_pll_in_p, + RK618_CRU_CLKSEL0, 8, 2, + 0), + MUX(SCALER_IN_CLK, "scaler_in_clk", mux_scaler_in_src_p, + RK618_CRU_CLKSEL3, 15, 1, + 0), + MUX(DITHER_CLK, "dither_clk", mux_dither_src_p, + RK618_CRU_CLKSEL3, 14, 1, + 0), + MUX(VIF0_CLK, "vif0_clk", mux_vif0_src_p, + RK618_CRU_CLKSEL3, 1, 1, + CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + MUX(VIF1_CLK, "vif1_clk", mux_vif1_src_p, + RK618_CRU_CLKSEL3, 7, 1, + CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), +}; + +static const struct clk_divider_data rk618_clk_dividers[] = { + DIV(LCDC0_CLK, "lcdc0_clk", lcdc0_dclkp_name, + RK618_CRU_CLKSEL0, 0, 3, + 0), + DIV(LCDC1_CLK, "lcdc1_clk", lcdc1_dclkp_name, + RK618_CRU_CLKSEL0, 3, 3, + 0), +}; + +static const struct clk_gate_data rk618_clk_gates[] = { + GATE(MIPI_CLK, "mipi_clk", "dither_clk", + RK618_CRU_CLKSEL1, 10, + 0), + GATE(LVDS_CLK, "lvds_clk", "dither_clk", + RK618_CRU_CLKSEL1, 9, + 0), + GATE(LVTTL_CLK, "lvttl_clk", "dither_clk", + RK618_CRU_CLKSEL1, 12, + 0), + GATE(RGB_CLK, "rgb_clk", "dither_clk", + RK618_CRU_CLKSEL1, 11, + 0), +}; + +static const struct clk_composite_data rk618_clk_composites[] = { + COMPOSITE(SCALER_CLK, "scaler_clk", mux_pll_src_p, + RK618_CRU_CLKSEL1, 3, 1, + RK618_CRU_CLKSEL1, 5, 3, + RK618_CRU_CLKSEL1, 4, + CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + COMPOSITE_NODIV(HDMI_CLK, "hdmi_clk", mux_hdmi_src_p, + RK618_CRU_CLKSEL3, 12, 2, + RK618_CRU_CLKSEL1, 8, + 0), + COMPOSITE(VIF0_PRE_CLK, "vif0_pre_clk", mux_pll_src_p, + RK618_CRU_CLKSEL3, 0, 1, + RK618_CRU_CLKSEL3, 3, 3, + RK618_CRU_CLKSEL3, 2, + CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + COMPOSITE(VIF1_PRE_CLK, "vif1_pre_clk", mux_pll_src_p, + RK618_CRU_CLKSEL3, 6, 1, + RK618_CRU_CLKSEL3, 9, 3, + RK618_CRU_CLKSEL3, 8, + CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), + COMPOSITE_NODIV(CODEC_CLK, "codec_clk", mux_codec_src_p, + RK618_CRU_CLKSEL1, 0, 2, + RK618_CRU_CLKSEL1, 2, + 0), +}; + +static void rk618_clk_add_lookup(struct rk618_cru *cru, struct clk *clk, + unsigned int id) +{ + if (cru->clk_data.clks && id) + cru->clk_data.clks[id] = clk; +} + +static void rk618_clk_register_muxes(struct rk618_cru *cru) +{ + struct clk *clk; + int i; + + for (i = 0; i < ARRAY_SIZE(rk618_clk_muxes); i++) { + const struct clk_mux_data *data = &rk618_clk_muxes[i]; + + clk = devm_clk_regmap_register_mux(cru->dev, data->name, + data->parent_names, + data->num_parents, + cru->regmap, data->reg, + data->shift, data->width, + data->flags); + if (IS_ERR(clk)) { + dev_err(cru->dev, "failed to register clock %s\n", + data->name); + continue; + } + + rk618_clk_add_lookup(cru, clk, data->id); + } +} + +static void rk618_clk_register_dividers(struct rk618_cru *cru) +{ + struct clk *clk; + int i; + + for (i = 0; i < ARRAY_SIZE(rk618_clk_dividers); i++) { + const struct clk_divider_data *data = &rk618_clk_dividers[i]; + + clk = devm_clk_regmap_register_divider(cru->dev, data->name, + data->parent_name, + cru->regmap, data->reg, + data->shift, data->width, + data->flags); + if (IS_ERR(clk)) { + dev_err(cru->dev, "failed to register clock %s\n", + data->name); + continue; + } + + rk618_clk_add_lookup(cru, clk, data->id); + } +} + +static void rk618_clk_register_gates(struct rk618_cru *cru) +{ + struct clk *clk; + int i; + + for (i = 0; i < ARRAY_SIZE(rk618_clk_gates); i++) { + const struct clk_gate_data *data = &rk618_clk_gates[i]; + + clk = devm_clk_regmap_register_gate(cru->dev, data->name, + data->parent_name, + cru->regmap, + data->reg, data->shift, + data->flags); + if (IS_ERR(clk)) { + dev_err(cru->dev, "failed to register clock %s\n", + data->name); + continue; + } + + rk618_clk_add_lookup(cru, clk, data->id); + } +} + +static void rk618_clk_register_composites(struct rk618_cru *cru) +{ + struct clk *clk; + int i; + + for (i = 0; i < ARRAY_SIZE(rk618_clk_composites); i++) { + const struct clk_composite_data *data = + &rk618_clk_composites[i]; + + clk = devm_clk_regmap_register_composite(cru->dev, data->name, + data->parent_names, + data->num_parents, + cru->regmap, + data->mux_reg, + data->mux_shift, + data->mux_width, + data->div_reg, + data->div_shift, + data->div_width, + data->gate_reg, + data->gate_shift, + data->flags); + if (IS_ERR(clk)) { + dev_err(cru->dev, "failed to register clock %s\n", + data->name); + continue; + } + + rk618_clk_add_lookup(cru, clk, data->id); + } +} + +static void rk618_clk_register_plls(struct rk618_cru *cru) +{ + struct clk *clk; + int i; + + for (i = 0; i < ARRAY_SIZE(rk618_clk_plls); i++) { + const struct clk_pll_data *data = &rk618_clk_plls[i]; + + clk = devm_clk_regmap_register_pll(cru->dev, data->name, + data->parent_name, + cru->regmap, data->reg, + data->flags); + if (IS_ERR(clk)) { + dev_err(cru->dev, "failed to register clock %s\n", + data->name); + continue; + } + + rk618_clk_add_lookup(cru, clk, data->id); + } +} + +static int rk618_cru_probe(struct platform_device *pdev) +{ + struct rk618 *rk618 = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct rk618_cru *cru; + struct clk **clk_table; + const char *parent_name; + struct clk *clk; + int ret, i; + + if (!of_device_is_available(dev->of_node)) + return -ENODEV; + + cru = devm_kzalloc(dev, sizeof(*cru), GFP_KERNEL); + if (!cru) + return -ENOMEM; + + clk_table = devm_kcalloc(dev, NR_CLKS, sizeof(struct clk *), + GFP_KERNEL); + if (!clk_table) + return -ENOMEM; + + for (i = 0; i < NR_CLKS; i++) + clk_table[i] = ERR_PTR(-ENOENT); + + cru->dev = dev; + cru->parent = rk618; + cru->regmap = rk618->regmap; + cru->clk_data.clks = clk_table; + cru->clk_data.clk_num = NR_CLKS; + platform_set_drvdata(pdev, cru); + + clk = devm_clk_get(dev, "clkin"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(dev, "failed to get clkin: %d\n", ret); + return ret; + } + + strlcpy(clkin_name, __clk_get_name(clk), sizeof(clkin_name)); + + clk = devm_clk_get(dev, "lcdc0_dclkp"); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) != -ENOENT) { + ret = PTR_ERR(clk); + dev_err(dev, "failed to get lcdc0_dclkp: %d\n", ret); + return ret; + } + + clk = NULL; + } + + parent_name = __clk_get_name(clk); + if (parent_name) + strlcpy(lcdc0_dclkp_name, parent_name, + sizeof(lcdc0_dclkp_name)); + + clk = devm_clk_get(dev, "lcdc1_dclkp"); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) != -ENOENT) { + ret = PTR_ERR(clk); + dev_err(dev, "failed to get lcdc1_dclkp: %d\n", ret); + return ret; + } + + clk = NULL; + } + + parent_name = __clk_get_name(clk); + if (parent_name) + strlcpy(lcdc1_dclkp_name, parent_name, + sizeof(lcdc1_dclkp_name)); + + rk618_clk_register_plls(cru); + rk618_clk_register_muxes(cru); + rk618_clk_register_dividers(cru); + rk618_clk_register_gates(cru); + rk618_clk_register_composites(cru); + + return of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, + &cru->clk_data); +} + +static int rk618_cru_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + + return 0; +} + +static const struct of_device_id rk618_cru_of_match[] = { + { .compatible = "rockchip,rk618-cru", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rk618_cru_of_match); + +static struct platform_driver rk618_cru_driver = { + .driver = { + .name = "rk618-cru", + .of_match_table = of_match_ptr(rk618_cru_of_match), + }, + .probe = rk618_cru_probe, + .remove = rk618_cru_remove, +}; +module_platform_driver(rk618_cru_driver); + +MODULE_AUTHOR("Wyon Bi "); +MODULE_DESCRIPTION("Rockchip rk618 CRU driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/dt-bindings/clock/rk618-cru.h b/include/dt-bindings/clock/rk618-cru.h new file mode 100644 index 000000000000..72ae0aef1378 --- /dev/null +++ b/include/dt-bindings/clock/rk618-cru.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Rockchip Electronics Co. Ltd. + * + * 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. + */ + +#ifndef _DT_BINDINGS_CLK_RK618_CRU_H +#define _DT_BINDINGS_CLK_RK618_CRU_H + +#define LCDC0_CLK 1 +#define LCDC1_CLK 2 +#define VIF_PLLIN_CLK 3 +#define SCALER_PLLIN_CLK 4 +#define VIF_PLL_CLK 5 +#define SCALER_PLL_CLK 6 +#define VIF0_CLK 7 +#define VIF1_CLK 8 +#define SCALER_IN_CLK 9 +#define SCALER_CLK 10 +#define DITHER_CLK 11 +#define HDMI_CLK 12 +#define MIPI_CLK 13 +#define LVDS_CLK 14 +#define LVTTL_CLK 15 +#define RGB_CLK 16 +#define VIF0_PRE_CLK 17 +#define VIF1_PRE_CLK 18 +#define CODEC_CLK 19 + +#endif