cregit-Linux how code gets into the kernel

Release 4.11 drivers/clk/renesas/renesas-cpg-mssr.c

/*
 * Renesas Clock Pulse Generator / Module Standby and Software Reset
 *
 * Copyright (C) 2015 Glider bvba
 *
 * Based on clk-mstp.c, clk-rcar-gen2.c, and clk-rcar-gen3.c
 *
 * Copyright (C) 2013 Ideas On Board SPRL
 * Copyright (C) 2015 Renesas Electronics Corp.
 *
 * 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; version 2 of the License.
 */

#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clk/renesas.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_clock.h>
#include <linux/pm_domain.h>
#include <linux/reset-controller.h>
#include <linux/slab.h>

#include <dt-bindings/clock/renesas-cpg-mssr.h>

#include "renesas-cpg-mssr.h"
#include "clk-div6.h"

#ifdef DEBUG

#define WARN_DEBUG(x)	WARN_ON(x)
#else

#define WARN_DEBUG(x)	do { } while (0)
#endif


/*
 * Module Standby and Software Reset register offets.
 *
 * If the registers exist, these are valid for SH-Mobile, R-Mobile,
 * R-Car Gen2, R-Car Gen3, and RZ/G1.
 * These are NOT valid for R-Car Gen1 and RZ/A1!
 */

/*
 * Module Stop Status Register offsets
 */


static const u16 mstpsr[] = {
	0x030, 0x038, 0x040, 0x048, 0x04C, 0x03C, 0x1C0, 0x1C4,
	0x9A0, 0x9A4, 0x9A8, 0x9AC,
};


#define	MSTPSR(i)	mstpsr[i]


/*
 * System Module Stop Control Register offsets
 */


static const u16 smstpcr[] = {
	0x130, 0x134, 0x138, 0x13C, 0x140, 0x144, 0x148, 0x14C,
	0x990, 0x994, 0x998, 0x99C,
};


#define	SMSTPCR(i)	smstpcr[i]


/*
 * Software Reset Register offsets
 */


static const u16 srcr[] = {
	0x0A0, 0x0A8, 0x0B0, 0x0B8, 0x0BC, 0x0C4, 0x1C8, 0x1CC,
	0x920, 0x924, 0x928, 0x92C,
};


#define	SRCR(i)		srcr[i]


/* Realtime Module Stop Control Register offsets */

#define RMSTPCR(i)	(smstpcr[i] - 0x20)

/* Modem Module Stop Control Register offsets (r8a73a4) */

#define MMSTPCR(i)	(smstpcr[i] + 0x20)

/* Software Reset Clearing Register offsets */

#define	SRSTCLR(i)	(0x940 + (i) * 4)


/**
 * Clock Pulse Generator / Module Standby and Software Reset Private Data
 *
 * @rcdev: Optional reset controller entity
 * @dev: CPG/MSSR device
 * @base: CPG/MSSR register block base address
 * @rmw_lock: protects RMW register accesses
 * @clks: Array containing all Core and Module Clocks
 * @num_core_clks: Number of Core Clocks in clks[]
 * @num_mod_clks: Number of Module Clocks in clks[]
 * @last_dt_core_clk: ID of the last Core Clock exported to DT
 */

struct cpg_mssr_priv {
#ifdef CONFIG_RESET_CONTROLLER
	
struct reset_controller_dev rcdev;
#endif
	
struct device *dev;
	
void __iomem *base;
	
spinlock_t rmw_lock;

	
struct clk **clks;
	
unsigned int num_core_clks;
	
unsigned int num_mod_clks;
	
unsigned int last_dt_core_clk;
};


/**
 * struct mstp_clock - MSTP gating clock
 * @hw: handle between common and hardware-specific interfaces
 * @index: MSTP clock number
 * @priv: CPG/MSSR private data
 */

struct mstp_clock {
	
struct clk_hw hw;
	
u32 index;
	
struct cpg_mssr_priv *priv;
};


#define to_mstp_clock(_hw) container_of(_hw, struct mstp_clock, hw)


static int cpg_mstp_clock_endisable(struct clk_hw *hw, bool enable) { struct mstp_clock *clock = to_mstp_clock(hw); struct cpg_mssr_priv *priv = clock->priv; unsigned int reg = clock->index / 32; unsigned int bit = clock->index % 32; struct device *dev = priv->dev; u32 bitmask = BIT(bit); unsigned long flags; unsigned int i; u32 value; dev_dbg(dev, "MSTP %u%02u/%pC %s\n", reg, bit, hw->clk, enable ? "ON" : "OFF"); spin_lock_irqsave(&priv->rmw_lock, flags); value = readl(priv->base + SMSTPCR(reg)); if (enable) value &= ~bitmask; else value |= bitmask; writel(value, priv->base + SMSTPCR(reg)); spin_unlock_irqrestore(&priv->rmw_lock, flags); if (!enable) return 0; for (i = 1000; i > 0; --i) { if (!(readl(priv->base + MSTPSR(reg)) & bitmask)) break; cpu_relax(); } if (!i) { dev_err(dev, "Failed to enable SMSTP %p[%d]\n", priv->base + SMSTPCR(reg), bit); return -ETIMEDOUT; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven242100.00%3100.00%
Total242100.00%3100.00%


static int cpg_mstp_clock_enable(struct clk_hw *hw) { return cpg_mstp_clock_endisable(hw, true); }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven19100.00%1100.00%
Total19100.00%1100.00%


static void cpg_mstp_clock_disable(struct clk_hw *hw) { cpg_mstp_clock_endisable(hw, false); }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven18100.00%1100.00%
Total18100.00%1100.00%


static int cpg_mstp_clock_is_enabled(struct clk_hw *hw) { struct mstp_clock *clock = to_mstp_clock(hw); struct cpg_mssr_priv *priv = clock->priv; u32 value; value = readl(priv->base + MSTPSR(clock->index / 32)); return !(value & BIT(clock->index % 32)); }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven66100.00%2100.00%
Total66100.00%2100.00%

static const struct clk_ops cpg_mstp_clock_ops = { .enable = cpg_mstp_clock_enable, .disable = cpg_mstp_clock_disable, .is_enabled = cpg_mstp_clock_is_enabled, };
static struct clk *cpg_mssr_clk_src_twocell_get(struct of_phandle_args *clkspec, void *data) { unsigned int clkidx = clkspec->args[1]; struct cpg_mssr_priv *priv = data; struct device *dev = priv->dev; unsigned int idx; const char *type; struct clk *clk; switch (clkspec->args[0]) { case CPG_CORE: type = "core"; if (clkidx > priv->last_dt_core_clk) { dev_err(dev, "Invalid %s clock index %u\n", type, clkidx); return ERR_PTR(-EINVAL); } clk = priv->clks[clkidx]; break; case CPG_MOD: type = "module"; idx = MOD_CLK_PACK(clkidx); if (clkidx % 100 > 31 || idx >= priv->num_mod_clks) { dev_err(dev, "Invalid %s clock index %u\n", type, clkidx); return ERR_PTR(-EINVAL); } clk = priv->clks[priv->num_core_clks + idx]; break; default: dev_err(dev, "Invalid CPG clock type %u\n", clkspec->args[0]); return ERR_PTR(-EINVAL); } if (IS_ERR(clk)) dev_err(dev, "Cannot get %s clock %u: %ld", type, clkidx, PTR_ERR(clk)); else dev_dbg(dev, "clock (%u, %u) is %pC at %pCr Hz\n", clkspec->args[0], clkspec->args[1], clk, clk); return clk; }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven250100.00%1100.00%
Total250100.00%1100.00%


static void __init cpg_mssr_register_core_clk(const struct cpg_core_clk *core, const struct cpg_mssr_info *info, struct cpg_mssr_priv *priv) { struct clk *clk = NULL, *parent; struct device *dev = priv->dev; unsigned int id = core->id, div = core->div; const char *parent_name; WARN_DEBUG(id >= priv->num_core_clks); WARN_DEBUG(PTR_ERR(priv->clks[id]) != -ENOENT); switch (core->type) { case CLK_TYPE_IN: clk = of_clk_get_by_name(priv->dev->of_node, core->name); break; case CLK_TYPE_FF: case CLK_TYPE_DIV6P1: case CLK_TYPE_DIV6_RO: WARN_DEBUG(core->parent >= priv->num_core_clks); parent = priv->clks[core->parent]; if (IS_ERR(parent)) { clk = parent; goto fail; } parent_name = __clk_get_name(parent); if (core->type == CLK_TYPE_DIV6_RO) /* Multiply with the DIV6 register value */ div *= (readl(priv->base + core->offset) & 0x3f) + 1; if (core->type == CLK_TYPE_DIV6P1) { clk = cpg_div6_register(core->name, 1, &parent_name, priv->base + core->offset); } else { clk = clk_register_fixed_factor(NULL, core->name, parent_name, 0, core->mult, div); } break; default: if (info->cpg_clk_register) clk = info->cpg_clk_register(dev, core, info, priv->clks, priv->base); else dev_err(dev, "%s has unsupported core clock type %u\n", core->name, core->type); break; } if (IS_ERR_OR_NULL(clk)) goto fail; dev_dbg(dev, "Core clock %pC at %pCr Hz\n", clk, clk); priv->clks[id] = clk; return; fail: dev_err(dev, "Failed to register %s clock %s: %ld\n", "core", core->name, PTR_ERR(clk)); }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven29584.53%266.67%
Wolfram Sang5415.47%133.33%
Total349100.00%3100.00%


static void __init cpg_mssr_register_mod_clk(const struct mssr_mod_clk *mod, const struct cpg_mssr_info *info, struct cpg_mssr_priv *priv) { struct mstp_clock *clock = NULL; struct device *dev = priv->dev; unsigned int id = mod->id; struct clk_init_data init; struct clk *parent, *clk; const char *parent_name; unsigned int i; WARN_DEBUG(id < priv->num_core_clks); WARN_DEBUG(id >= priv->num_core_clks + priv->num_mod_clks); WARN_DEBUG(mod->parent >= priv->num_core_clks + priv->num_mod_clks); WARN_DEBUG(PTR_ERR(priv->clks[id]) != -ENOENT); parent = priv->clks[mod->parent]; if (IS_ERR(parent)) { clk = parent; goto fail; } clock = kzalloc(sizeof(*clock), GFP_KERNEL); if (!clock) { clk = ERR_PTR(-ENOMEM); goto fail; } init.name = mod->name; init.ops = &cpg_mstp_clock_ops; init.flags = CLK_IS_BASIC | CLK_SET_RATE_PARENT; for (i = 0; i < info->num_crit_mod_clks; i++) if (id == info->crit_mod_clks[i]) { dev_dbg(dev, "MSTP %s setting CLK_IS_CRITICAL\n", mod->name); init.flags |= CLK_IS_CRITICAL; break; } parent_name = __clk_get_name(parent); init.parent_names = &parent_name; init.num_parents = 1; clock->index = id - priv->num_core_clks; clock->priv = priv; clock->hw.init = &init; clk = clk_register(NULL, &clock->hw); if (IS_ERR(clk)) goto fail; dev_dbg(dev, "Module clock %pC at %pCr Hz\n", clk, clk); priv->clks[id] = clk; return; fail: dev_err(dev, "Failed to register %s clock %s: %ld\n", "module", mod->name, PTR_ERR(clk)); kfree(clock); }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven362100.00%3100.00%
Total362100.00%3100.00%

struct cpg_mssr_clk_domain { struct generic_pm_domain genpd; struct device_node *np; unsigned int num_core_pm_clks; unsigned int core_pm_clks[0]; }; static struct cpg_mssr_clk_domain *cpg_mssr_clk_domain;
static bool cpg_mssr_is_pm_clk(const struct of_phandle_args *clkspec, struct cpg_mssr_clk_domain *pd) { unsigned int i; if (clkspec->np != pd->np || clkspec->args_count != 2) return false; switch (clkspec->args[0]) { case CPG_CORE: for (i = 0; i < pd->num_core_pm_clks; i++) if (clkspec->args[1] == pd->core_pm_clks[i]) return true; return false; case CPG_MOD: return true; default: return false; } }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven101100.00%1100.00%
Total101100.00%1100.00%


int cpg_mssr_attach_dev(struct generic_pm_domain *unused, struct device *dev) { struct cpg_mssr_clk_domain *pd = cpg_mssr_clk_domain; struct device_node *np = dev->of_node; struct of_phandle_args clkspec; struct clk *clk; int i = 0; int error; if (!pd) { dev_dbg(dev, "CPG/MSSR clock domain not yet available\n"); return -EPROBE_DEFER; } while (!of_parse_phandle_with_args(np, "clocks", "#clock-cells", i, &clkspec)) { if (cpg_mssr_is_pm_clk(&clkspec, pd)) goto found; of_node_put(clkspec.np); i++; } return 0; found: clk = of_clk_get_from_provider(&clkspec); of_node_put(clkspec.np); if (IS_ERR(clk)) return PTR_ERR(clk); error = pm_clk_create(dev); if (error) { dev_err(dev, "pm_clk_create failed %d\n", error); goto fail_put; } error = pm_clk_add_clk(dev, clk); if (error) { dev_err(dev, "pm_clk_add_clk %pC failed %d\n", clk, error); goto fail_destroy; } return 0; fail_destroy: pm_clk_destroy(dev); fail_put: clk_put(clk); return error; }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven215100.00%2100.00%
Total215100.00%2100.00%


void cpg_mssr_detach_dev(struct generic_pm_domain *unused, struct device *dev) { if (!list_empty(&dev->power.subsys_data->clock_list)) pm_clk_destroy(dev); }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven35100.00%2100.00%
Total35100.00%2100.00%


static int __init cpg_mssr_add_clk_domain(struct device *dev, const unsigned int *core_pm_clks, unsigned int num_core_pm_clks) { struct device_node *np = dev->of_node; struct generic_pm_domain *genpd; struct cpg_mssr_clk_domain *pd; size_t pm_size = num_core_pm_clks * sizeof(core_pm_clks[0]); pd = devm_kzalloc(dev, sizeof(*pd) + pm_size, GFP_KERNEL); if (!pd) return -ENOMEM; pd->np = np; pd->num_core_pm_clks = num_core_pm_clks; memcpy(pd->core_pm_clks, core_pm_clks, pm_size); genpd = &pd->genpd; genpd->name = np->name; genpd->flags = GENPD_FLAG_PM_CLK; genpd->attach_dev = cpg_mssr_attach_dev; genpd->detach_dev = cpg_mssr_detach_dev; pm_genpd_init(genpd, &pm_domain_always_on_gov, false); cpg_mssr_clk_domain = pd; of_genpd_add_provider_simple(np, genpd); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven160100.00%4100.00%
Total160100.00%4100.00%

#ifdef CONFIG_RESET_CONTROLLER #define rcdev_to_priv(x) container_of(x, struct cpg_mssr_priv, rcdev)
static int cpg_mssr_reset(struct reset_controller_dev *rcdev, unsigned long id) { struct cpg_mssr_priv *priv = rcdev_to_priv(rcdev); unsigned int reg = id / 32; unsigned int bit = id % 32; u32 bitmask = BIT(bit); unsigned long flags; u32 value; dev_dbg(priv->dev, "reset %u%02u\n", reg, bit); /* Reset module */ spin_lock_irqsave(&priv->rmw_lock, flags); value = readl(priv->base + SRCR(reg)); value |= bitmask; writel(value, priv->base + SRCR(reg)); spin_unlock_irqrestore(&priv->rmw_lock, flags); /* Wait for at least one cycle of the RCLK clock (@ ca. 32 kHz) */ udelay(35); /* Release module from reset state */ writel(bitmask, priv->base + SRSTCLR(reg)); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven146100.00%2100.00%
Total146100.00%2100.00%


static int cpg_mssr_assert(struct reset_controller_dev *rcdev, unsigned long id) { struct cpg_mssr_priv *priv = rcdev_to_priv(rcdev); unsigned int reg = id / 32; unsigned int bit = id % 32; u32 bitmask = BIT(bit); unsigned long flags; u32 value; dev_dbg(priv->dev, "assert %u%02u\n", reg, bit); spin_lock_irqsave(&priv->rmw_lock, flags); value = readl(priv->base + SRCR(reg)); value |= bitmask; writel(value, priv->base + SRCR(reg)); spin_unlock_irqrestore(&priv->rmw_lock, flags); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven124100.00%1100.00%
Total124100.00%1100.00%


static int cpg_mssr_deassert(struct reset_controller_dev *rcdev, unsigned long id) { struct cpg_mssr_priv *priv = rcdev_to_priv(rcdev); unsigned int reg = id / 32; unsigned int bit = id % 32; u32 bitmask = BIT(bit); dev_dbg(priv->dev, "deassert %u%02u\n", reg, bit); writel(bitmask, priv->base + SRSTCLR(reg)); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven79100.00%1100.00%
Total79100.00%1100.00%


static int cpg_mssr_status(struct reset_controller_dev *rcdev, unsigned long id) { struct cpg_mssr_priv *priv = rcdev_to_priv(rcdev); unsigned int reg = id / 32; unsigned int bit = id % 32; u32 bitmask = BIT(bit); return !!(readl(priv->base + SRCR(reg)) & bitmask); }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven68100.00%1100.00%
Total68100.00%1100.00%

static const struct reset_control_ops cpg_mssr_reset_ops = { .reset = cpg_mssr_reset, .assert = cpg_mssr_assert, .deassert = cpg_mssr_deassert, .status = cpg_mssr_status, };
static int cpg_mssr_reset_xlate(struct reset_controller_dev *rcdev, const struct of_phandle_args *reset_spec) { struct cpg_mssr_priv *priv = rcdev_to_priv(rcdev); unsigned int unpacked = reset_spec->args[0]; unsigned int idx = MOD_CLK_PACK(unpacked); if (unpacked % 100 > 31 || idx >= rcdev->nr_resets) { dev_err(priv->dev, "Invalid reset index %u\n", unpacked); return -EINVAL; } return idx; }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven81100.00%1100.00%
Total81100.00%1100.00%


static int cpg_mssr_reset_controller_register(struct cpg_mssr_priv *priv) { priv->rcdev.ops = &cpg_mssr_reset_ops; priv->rcdev.of_node = priv->dev->of_node; priv->rcdev.of_reset_n_cells = 1; priv->rcdev.of_xlate = cpg_mssr_reset_xlate; priv->rcdev.nr_resets = priv->num_mod_clks; return devm_reset_controller_register(priv->dev, &priv->rcdev); }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven71100.00%1100.00%
Total71100.00%1100.00%

#else /* !CONFIG_RESET_CONTROLLER */
static inline int cpg_mssr_reset_controller_register(struct cpg_mssr_priv *priv) { return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven15100.00%1100.00%
Total15100.00%1100.00%

#endif /* !CONFIG_RESET_CONTROLLER */ static const struct of_device_id cpg_mssr_match[] = { #ifdef CONFIG_ARCH_R8A7743 { .compatible = "renesas,r8a7743-cpg-mssr", .data = &r8a7743_cpg_mssr_info, }, #endif #ifdef CONFIG_ARCH_R8A7745 { .compatible = "renesas,r8a7745-cpg-mssr", .data = &r8a7745_cpg_mssr_info, }, #endif #ifdef CONFIG_ARCH_R8A7795 { .compatible = "renesas,r8a7795-cpg-mssr", .data = &r8a7795_cpg_mssr_info, }, #endif #ifdef CONFIG_ARCH_R8A7796 { .compatible = "renesas,r8a7796-cpg-mssr", .data = &r8a7796_cpg_mssr_info, }, #endif { /* sentinel */ } };
static void cpg_mssr_del_clk_provider(void *data) { of_clk_del_provider(data); }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven15100.00%1100.00%
Total15100.00%1100.00%


static int __init cpg_mssr_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; const struct cpg_mssr_info *info; struct cpg_mssr_priv *priv; unsigned int nclks, i; struct resource *res; struct clk **clks; int error; info = of_match_node(cpg_mssr_match, np)->data; if (info->init) { error = info->init(dev); if (error) return error; } priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dev = dev; spin_lock_init(&priv->rmw_lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->base = devm_ioremap_resource(dev, res); if (IS_ERR(priv->base)) return PTR_ERR(priv->base); nclks = info->num_total_core_clks + info->num_hw_mod_clks; clks = devm_kmalloc_array(dev, nclks, sizeof(*clks), GFP_KERNEL); if (!clks) return -ENOMEM; priv->clks = clks; priv->num_core_clks = info->num_total_core_clks; priv->num_mod_clks = info->num_hw_mod_clks; priv->last_dt_core_clk = info->last_dt_core_clk; for (i = 0; i < nclks; i++) clks[i] = ERR_PTR(-ENOENT); for (i = 0; i < info->num_core_clks; i++) cpg_mssr_register_core_clk(&info->core_clks[i], info, priv); for (i = 0; i < info->num_mod_clks; i++) cpg_mssr_register_mod_clk(&info->mod_clks[i], info, priv); error = of_clk_add_provider(np, cpg_mssr_clk_src_twocell_get, priv); if (error) return error; error = devm_add_action_or_reset(dev, cpg_mssr_del_clk_provider, np); if (error) return error; error = cpg_mssr_add_clk_domain(dev, info->core_pm_clks, info->num_core_pm_clks); if (error) return error; error = cpg_mssr_reset_controller_register(priv); if (error) return error; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven38997.49%375.00%
Sudip Mukherjee102.51%125.00%
Total399100.00%4100.00%

static struct platform_driver cpg_mssr_driver = { .driver = { .name = "renesas-cpg-mssr", .of_match_table = cpg_mssr_match, }, };
static int __init cpg_mssr_init(void) { return platform_driver_probe(&cpg_mssr_driver, cpg_mssr_probe); }

Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven18100.00%1100.00%
Total18100.00%1100.00%

subsys_initcall(cpg_mssr_init); MODULE_DESCRIPTION("Renesas CPG/MSSR Driver"); MODULE_LICENSE("GPL v2");

Overall Contributors

PersonTokensPropCommitsCommitProp
Geert Uytterhoeven328198.09%1285.71%
Wolfram Sang541.61%17.14%
Sudip Mukherjee100.30%17.14%
Total3345100.00%14100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.