Re: [PATCH v2 02/11] soc: tegra: Add Tegra PMC clock registrations into PMC driver

From: Sowjanya Komatineni
Date: Mon Dec 02 2019 - 19:07:41 EST



On 12/2/19 3:14 PM, Sowjanya Komatineni wrote:

On 12/2/19 3:10 PM, Sowjanya Komatineni wrote:

On 12/2/19 2:58 PM, Sowjanya Komatineni wrote:

On 12/2/19 1:50 PM, Dmitry Osipenko wrote:
02.12.2019 23:09, Sowjanya Komatineni ÐÐÑÐÑ:
On 11/28/19 5:25 AM, Dmitry Osipenko wrote:
28.11.2019 01:57, Sowjanya Komatineni ÐÐÑÐÑ:
On 11/27/19 7:14 AM, Dmitry Osipenko wrote:
27.11.2019 07:59, Sowjanya Komatineni ÐÐÑÐÑ:
Tegra210 and prior Tegra PMC has clk_out_1, clk_out_2, clk_out_3 with
mux and gate for each of these clocks.

Currently these PMC clocks are registered by Tegra clock driver using
clk_register_mux and clk_register_gate by passing PMC base address
and register offsets and PMC programming for these clocks happens
through direct PMC access by the clock driver.

With this, when PMC is in secure mode any direct PMC access from the
non-secure world does not go through and these clocks will not be
functional.

This patch adds these clocks registration with PMC as a clock provider
for these clocks. clk_ops callback implementations for these clocks
uses tegra_pmc_readl and tegra_pmc_writel which supports PMC
programming
in secure mode and non-secure mode.

Signed-off-by: Sowjanya Komatineni <skomatineni@xxxxxxxxxx>
---
ÂÂÂ drivers/soc/tegra/pmc.c | 330
++++++++++++++++++++++++++++++++++++++++++++++++
ÂÂÂ 1 file changed, 330 insertions(+)

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index ea0e11a09c12..a353f6d0a832 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -13,6 +13,9 @@
ÂÂÂ Â #include <linux/arm-smccc.h>
ÂÂÂ #include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/clk-conf.h>
ÂÂÂ #include <linux/clk/tegra.h>
ÂÂÂ #include <linux/debugfs.h>
ÂÂÂ #include <linux/delay.h>
@@ -48,6 +51,7 @@
ÂÂÂ #include <dt-bindings/pinctrl/pinctrl-tegra-io-pad.h>
ÂÂÂ #include <dt-bindings/gpio/tegra186-gpio.h>
ÂÂÂ #include <dt-bindings/gpio/tegra194-gpio.h>
+#include <dt-bindings/soc/tegra-pmc.h>
ÂÂÂ Â #define PMC_CNTRLÂÂÂÂÂÂÂÂÂÂÂ 0x0
 #define PMC_CNTRL_INTR_POLARITY BIT(17) /* inverts INTR
polarity */
@@ -100,6 +104,7 @@
ÂÂÂ #define PMC_WAKE2_STATUSÂÂÂÂÂÂÂ 0x168
ÂÂÂ #define PMC_SW_WAKE2_STATUSÂÂÂÂÂÂÂ 0x16c
ÂÂÂ +#define PMC_CLK_OUT_CNTRLÂÂÂÂÂÂÂ 0x1a8
ÂÂÂ #define PMC_SENSOR_CTRLÂÂÂÂÂÂÂÂÂÂÂ 0x1b0
 #define PMC_SENSOR_CTRL_SCRATCH_WRITE BIT(2)
 #define PMC_SENSOR_CTRL_ENABLE_RST BIT(1)
@@ -155,6 +160,91 @@
 #define TEGRA_SMC_PMC_READ 0xaa
 #define TEGRA_SMC_PMC_WRITE 0xbb
ÂÂÂ +struct pmc_clk_mux {
+ÂÂÂ struct clk_hwÂÂÂ hw;
+ÂÂÂ unsigned longÂÂÂ offs;
+ÂÂÂ u32ÂÂÂÂÂÂÂ mask;
+ÂÂÂ u32ÂÂÂÂÂÂÂ shift;
+};
+
+#define to_pmc_clk_mux(_hw) container_of(_hw, struct pmc_clk_mux, hw)
+
+struct pmc_clk_gate {
+ÂÂÂ struct clk_hwÂÂÂ hw;
+ÂÂÂ unsigned longÂÂÂ offs;
+ÂÂÂ u32ÂÂÂÂÂÂÂ shift;
+};
+
+#define to_pmc_clk_gate(_hw) container_of(_hw, struct
pmc_clk_gate, hw)
+
+struct pmc_clk_init_data {
+ÂÂÂ char *mux_name;
+ÂÂÂ char *gate_name;
+ÂÂÂ const char **parents;
+ÂÂÂ int num_parents;
+ÂÂÂ int mux_id;
+ÂÂÂ int gate_id;
+ÂÂÂ char *dev_name;
+ÂÂÂ u8 mux_shift;
+ÂÂÂ u8 gate_shift;
+ÂÂÂ u8 init_parent_index;
+ÂÂÂ int init_state;
+};
+
+static const char *clk_out1_parents[] = { "clk_m", "clk_m_div2",
+ÂÂÂ "clk_m_div4", "extern1",
+};
+
+static const char *clk_out2_parents[] = { "clk_m", "clk_m_div2",
+ÂÂÂ "clk_m_div4", "extern2",
+};
+
+static const char *clk_out3_parents[] = { "clk_m", "clk_m_div2",
+ÂÂÂ "clk_m_div4", "extern3",
+};
+
+static struct pmc_clk_init_data tegra_pmc_clks_data[] = {
+ÂÂÂ {
+ÂÂÂÂÂÂÂ .mux_name = "clk_out_1_mux",
+ÂÂÂÂÂÂÂ .gate_name = "clk_out_1",
+ÂÂÂÂÂÂÂ .parents = clk_out1_parents,
+ÂÂÂÂÂÂÂ .num_parents = ARRAY_SIZE(clk_out1_parents),
+ÂÂÂÂÂÂÂ .mux_id = TEGRA_PMC_CLK_OUT_1_MUX,
+ÂÂÂÂÂÂÂ .gate_id = TEGRA_PMC_CLK_OUT_1,
+ÂÂÂÂÂÂÂ .dev_name = "extern1",
+ÂÂÂÂÂÂÂ .mux_shift = 6,
+ÂÂÂÂÂÂÂ .gate_shift = 2,
+ÂÂÂÂÂÂÂ .init_parent_index = 3,
+ÂÂÂÂÂÂÂ .init_state = 1,
+ÂÂÂ },
+ÂÂÂ {
+ÂÂÂÂÂÂÂ .mux_name = "clk_out_2_mux",
+ÂÂÂÂÂÂÂ .gate_name = "clk_out_2",
+ÂÂÂÂÂÂÂ .parents = clk_out2_parents,
+ÂÂÂÂÂÂÂ .num_parents = ARRAY_SIZE(clk_out2_parents),
+ÂÂÂÂÂÂÂ .mux_id = TEGRA_PMC_CLK_OUT_2_MUX,
+ÂÂÂÂÂÂÂ .gate_id = TEGRA_PMC_CLK_OUT_2,
+ÂÂÂÂÂÂÂ .dev_name = "extern2",
+ÂÂÂÂÂÂÂ .mux_shift = 14,
+ÂÂÂÂÂÂÂ .gate_shift = 10,
+ÂÂÂÂÂÂÂ .init_parent_index = 0,
+ÂÂÂÂÂÂÂ .init_state = 0,
+ÂÂÂ },
+ÂÂÂ {
+ÂÂÂÂÂÂÂ .mux_name = "clk_out_3_mux",
+ÂÂÂÂÂÂÂ .gate_name = "clk_out_3",
+ÂÂÂÂÂÂÂ .parents = clk_out3_parents,
+ÂÂÂÂÂÂÂ .num_parents = ARRAY_SIZE(clk_out3_parents),
+ÂÂÂÂÂÂÂ .mux_id = TEGRA_PMC_CLK_OUT_3_MUX,
+ÂÂÂÂÂÂÂ .gate_id = TEGRA_PMC_CLK_OUT_3,
+ÂÂÂÂÂÂÂ .dev_name = "extern3",
+ÂÂÂÂÂÂÂ .mux_shift = 22,
+ÂÂÂÂÂÂÂ .gate_shift = 18,
+ÂÂÂÂÂÂÂ .init_parent_index = 0,
+ÂÂÂÂÂÂÂ .init_state = 0,
+ÂÂÂ },
+};
+
ÂÂÂ struct tegra_powergate {
ÂÂÂÂÂÂÂ struct generic_pm_domain genpd;
ÂÂÂÂÂÂÂ struct tegra_pmc *pmc;
@@ -254,6 +344,9 @@ struct tegra_pmc_soc {
ÂÂÂÂÂÂÂÂ */
ÂÂÂÂÂÂÂ const struct tegra_wake_event *wake_events;
ÂÂÂÂÂÂÂ unsigned int num_wake_events;
+
+ÂÂÂ struct pmc_clk_init_data *pmc_clks_data;
+ÂÂÂ unsigned int num_pmc_clks;
ÂÂÂ };
ÂÂÂ Â static const char * const tegra186_reset_sources[] = {
@@ -2163,6 +2256,228 @@ static int tegra_pmc_clk_notify_cb(struct
notifier_block *nb,
ÂÂÂÂÂÂÂ return NOTIFY_OK;
ÂÂÂ }
ÂÂÂ +static void pmc_clk_fence_udelay(u32 offset)
+{
+ÂÂÂ tegra_pmc_readl(pmc, offset);
+ÂÂÂ /* pmc clk propagation delay 2 us */
+ÂÂÂ udelay(2);
+}
+
+static u8 pmc_clk_mux_get_parent(struct clk_hw *hw)
+{
+ÂÂÂ struct pmc_clk_mux *mux = to_pmc_clk_mux(hw);
+ÂÂÂ int num_parents = clk_hw_get_num_parents(hw);
+ÂÂÂ u32 val;
+
+ÂÂÂ val = tegra_pmc_readl(pmc, mux->offs) >> mux->shift;
+ÂÂÂ val &= mux->mask;
+
+ÂÂÂ if (val >= num_parents)
+ÂÂÂÂÂÂÂ return -EINVAL;
+
+ÂÂÂ return val;
+}
+
+static int pmc_clk_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ÂÂÂ struct pmc_clk_mux *mux = to_pmc_clk_mux(hw);
+ÂÂÂ u32 val;
+
+ÂÂÂ val = tegra_pmc_readl(pmc, mux->offs);
+ÂÂÂ val &= ~(mux->mask << mux->shift);
+ÂÂÂ val |= index << mux->shift;
+ÂÂÂ tegra_pmc_writel(pmc, val, mux->offs);
+ÂÂÂ pmc_clk_fence_udelay(mux->offs);
+
+ÂÂÂ return 0;
+}
+
+static const struct clk_ops pmc_clk_mux_ops = {
+ÂÂÂ .get_parent = pmc_clk_mux_get_parent,
+ÂÂÂ .set_parent = pmc_clk_mux_set_parent,
+ÂÂÂ .determine_rate = __clk_mux_determine_rate,
+};
+
+static struct clk *
+tegra_pmc_clk_mux_register(const char *name, const char * const
*parent_names,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂ int num_parents, unsigned long flags,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long offset, u32 shift, u32 mask)
+{
+ÂÂÂ struct clk_init_data init;
+ÂÂÂ struct pmc_clk_mux *mux;
+
+ÂÂÂ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ÂÂÂ if (!mux)
+ÂÂÂÂÂÂÂ return ERR_PTR(-ENOMEM);
+
+ÂÂÂ init.name = name;
+ÂÂÂ init.ops = &pmc_clk_mux_ops;
+ÂÂÂ init.parent_names = parent_names;
+ÂÂÂ init.num_parents = num_parents;
+ÂÂÂ init.flags = flags;
+
+ÂÂÂ mux->hw.init = &init;
+ÂÂÂ mux->offs = offset;
+ÂÂÂ mux->mask = mask;
+ÂÂÂ mux->shift = shift;
+
+ÂÂÂ return clk_register(NULL, &mux->hw);
+}
+
+static int pmc_clk_is_enabled(struct clk_hw *hw)
+{
+ÂÂÂ struct pmc_clk_gate *gate = to_pmc_clk_gate(hw);
+
+ÂÂÂ return tegra_pmc_readl(pmc, gate->offs) & BIT(gate->shift) ? 1
: 0;
+}
+
+static void pmc_clk_set_state(struct clk_hw *hw, int state)
+{
+ÂÂÂ struct pmc_clk_gate *gate = to_pmc_clk_gate(hw);
+ÂÂÂ u32 val;
+
+ÂÂÂ val = tegra_pmc_readl(pmc, gate->offs);
+ÂÂÂ val = state ? (val | BIT(gate->shift)) : (val &
~BIT(gate->shift));
+ÂÂÂ tegra_pmc_writel(pmc, val, gate->offs);
+ÂÂÂ pmc_clk_fence_udelay(gate->offs);
+}
+
+static int pmc_clk_enable(struct clk_hw *hw)
+{
+ÂÂÂ pmc_clk_set_state(hw, 1);
+
+ÂÂÂ return 0;
+}
+
+static void pmc_clk_disable(struct clk_hw *hw)
+{
+ÂÂÂ pmc_clk_set_state(hw, 0);
+}
+
+static const struct clk_ops pmc_clk_gate_ops = {
+ÂÂÂ .is_enabled = pmc_clk_is_enabled,
+ÂÂÂ .enable = pmc_clk_enable,
+ÂÂÂ .disable = pmc_clk_disable,
+};
+
+static struct clk *
+tegra_pmc_clk_gate_register(const char *name, const char
*parent_name,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long flags, unsigned long offset,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ u32 shift)
+{
+ÂÂÂ struct clk_init_data init;
+ÂÂÂ struct pmc_clk_gate *gate;
+
+ÂÂÂ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ÂÂÂ if (!gate)
+ÂÂÂÂÂÂÂ return ERR_PTR(-ENOMEM);
+
+ÂÂÂ init.name = name;
+ÂÂÂ init.ops = &pmc_clk_gate_ops;
+ÂÂÂ init.parent_names = &parent_name;
+ÂÂÂ init.num_parents = 1;
+ÂÂÂ init.flags = flags;
+
+ÂÂÂ gate->hw.init = &init;
+ÂÂÂ gate->offs = offset;
+ÂÂÂ gate->shift = shift;
+
+ÂÂÂ return clk_register(NULL, &gate->hw);
+}
+
+static void tegra_pmc_clock_register(struct tegra_pmc *pmc,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_node *np)
+{
+ÂÂÂ struct clk *clkmux, *clk, *parent;
+ÂÂÂ struct clk_onecell_data *clk_data;
+ÂÂÂ unsigned int num_clks;
+ÂÂÂ int i, ret;
+
+ÂÂÂ /* each pmc clock output has a mux and a gate */
+ÂÂÂ num_clks = pmc->soc->num_pmc_clks * 2;
+
+ÂÂÂ if (!num_clks)
+ÂÂÂÂÂÂÂ return;
+
+ÂÂÂ clk_data = kmalloc(sizeof(*clk_data), GFP_KERNEL);
+ÂÂÂ if (!clk_data)
+ÂÂÂÂÂÂÂ return;
+
+ÂÂÂ clk_data->clks = kcalloc(TEGRA_PMC_CLK_MAX,
sizeof(*clk_data->clks),
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ GFP_KERNEL);
+ÂÂÂ if (!clk_data->clks)
+ÂÂÂÂÂÂÂ goto free_clkdata;
+
+ÂÂÂ clk_data->clk_num = num_clks;
+
+ÂÂÂ for (i = 0; i < pmc->soc->num_pmc_clks; i++) {
+ÂÂÂÂÂÂÂ struct pmc_clk_init_data *data;
+
+ÂÂÂÂÂÂÂ data = pmc->soc->pmc_clks_data + i;
+
+ÂÂÂÂÂÂÂ clkmux = tegra_pmc_clk_mux_register(data->mux_name,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ data->parents,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ data->num_parents,
+ CLK_SET_RATE_NO_REPARENT |
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ CLK_SET_RATE_PARENT,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ PMC_CLK_OUT_CNTRL,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ data->mux_shift, 3);
+ÂÂÂÂÂÂÂ if (IS_ERR(clkmux))
+ÂÂÂÂÂÂÂÂÂÂÂ goto free_clks;
+
+ÂÂÂÂÂÂÂ clk_data->clks[data->mux_id] = clkmux;
+
+ÂÂÂÂÂÂÂ clk = tegra_pmc_clk_gate_register(data->gate_name,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ data->mux_name,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ CLK_SET_RATE_PARENT,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ PMC_CLK_OUT_CNTRL,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ data->gate_shift);
+ÂÂÂÂÂÂÂ if (IS_ERR(clk))
+ÂÂÂÂÂÂÂÂÂÂÂ goto free_clks;
+
+ÂÂÂÂÂÂÂ clk_data->clks[data->gate_id] = clk;
+
+ÂÂÂÂÂÂÂ ret = clk_set_parent(clk, clkmux);
+ÂÂÂÂÂÂÂ if (ret < 0) {
+ÂÂÂÂÂÂÂÂÂÂÂ pr_err("failed to set parent of %s to %s\n",
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ __func__, __clk_get_name(clk),
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ __clk_get_name(clkmux));
+ÂÂÂÂÂÂÂ }
+
+ÂÂÂÂÂÂÂ clk_register_clkdev(clk, data->dev_name, data->gate_name);
+
+ÂÂÂÂÂÂÂ /* configure initial clock parent and state */
+ÂÂÂÂÂÂÂ parent = clk_get_sys(data->gate_name,
+ data->parents[data->init_parent_index]);
Couldn't the default parent be defined using "assigned clock" in a
device-tree? Please see "Assigned clock parents and rates" in the doc.

https://www.kernel.org/doc/Documentation/devicetree/bindings/clock/clock-bindings.txt

Then you could simply use of_clk_set_defaults(pmc->dev->of_node, true).

Yes, of_clk_add_provider() does of_clk_set_defaults which sets based on assigned parents and clock rates.

This need device tree to specify assigned clock parent properties. Will update device tree and remove init parent from the driver.

assigned-clock properties should be set in consumer node of these clocks and currently these clocks are not used yet.

So will just remove init parent from driver and when these clocks are used device tree can be updated in corresponding consumer node with these properties.

How about default ON/OFF init state for the clocks? I see assigned-clock properties for parent and rate only.

But based on existing clock-tegra-pmc driver, I see clk_out_1 is default enabled with extern1 parent for T30 thru T210 platforms.

Peter/Thierry, What was the reason we enable clk_out_1 right from the clock registration?

clk_out_1 is for audio and its not required to be enabled during the boot and audio driver can enable/disable it.

same with blink 32khz which is used for WIFI. WIFI driver can enable/disable during power up/down sequence and technically as per design we dont need to have it always on right from the boot.

So can remove out clocks init states from driver once thierry also agree on this.


+ÂÂÂÂÂÂÂ if (!IS_ERR(parent)) {
+ÂÂÂÂÂÂÂÂÂÂÂ ret = clk_set_parent(clkmux, parent);
+ÂÂÂÂÂÂÂÂÂÂÂ if (ret < 0) {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ pr_err("failed to set parent of %s to %s\n",
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ __func__, __clk_get_name(clkmux),
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ __clk_get_name(parent));
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ WARN_ON(1);
+ÂÂÂÂÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂ }
+
+ÂÂÂÂÂÂÂ if (data->init_state) {
+ÂÂÂÂÂÂÂÂÂÂÂ if (clk_prepare_enable(clk)) {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ pr_err("failed to enable %s\n", __func__,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ __clk_get_name(clk));
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ WARN_ON(1);
Alternatively you could write it like this:

ÂÂÂÂÂerr = clk_prepare_enable(clk);

ÂÂÂÂÂWARN_ON(err, "failed to enable %s: %d\n",
ÂÂÂÂÂÂÂÂ __clk_get_name(clk), err);

Should be a bit better to move the WARN_ON to the end of errors
handling
in order to catch all possible errors:

@@ -2510,6 +2510,7 @@ static void tegra_pmc_clock_register(struct
tegra_pmc *pmc,
ÂÂÂÂÂÂÂÂÂÂ return;

ÂÂÂ free_clks:
+ÂÂÂÂÂÂ WARN_ON(1);
ÂÂÂÂÂÂÂÂÂÂ kfree(clk_data->clks);
ÂÂÂ free_clkdata:
ÂÂÂÂÂÂÂÂÂÂ kfree(clk_data);
Reason I had WARN_ON right during clk_set_parent failure is to have the
loop continue for subsequence pmc clocks registration instead of
terminating all pmc clocks registration.
Ah, okay. Nevertheless this WARN_ON in the end shouldn't be the least
(IMO).
Hi Dmitry, Just want to be clear on the above comment. Are you
suggesting to add additional WARN_ON at the end?
Yes, it was my suggestion.

Thought WARN_ON right during corresponding clock failure with warn
message showing clock names will be clear and also other clocks still
should be registered.

To add additional WARN_ON at the end need to track status of each clock
and use that to as warn condition.
You could add a warning/error message to every point of failure.

Primarily, it is important not to miss a error. Secondarily, it is
important to make diagnostic message meaningful.

Realistically, I doubt that this chunk of code will ever fail once it is
known to work well. So it will be nice to have a more detailed
diagnostics (just in a case), but it shouldn't be a must.

OK, Will add additional WARN message "failed registering PMC clocks" at the end.