Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Wolfram Sang | 1118 | 99.82% | 1 | 50.00% |
Geert Uytterhoeven | 2 | 0.18% | 1 | 50.00% |
Total | 1120 | 2 |
// SPDX-License-Identifier: GPL-2.0 /* * R-Car Gen3 Clock Pulse Generator Library * * Copyright (C) 2015-2018 Glider bvba * Copyright (C) 2019 Renesas Electronics Corp. * * Based on clk-rcar-gen3.c * * Copyright (C) 2015 Renesas Electronics Corp. */ #include <linux/clk.h> #include <linux/clk-provider.h> #include <linux/device.h> #include <linux/err.h> #include <linux/init.h> #include <linux/io.h> #include <linux/pm.h> #include <linux/slab.h> #include <linux/sys_soc.h> #include "rcar-cpg-lib.h" spinlock_t cpg_lock; void cpg_reg_modify(void __iomem *reg, u32 clear, u32 set) { unsigned long flags; u32 val; spin_lock_irqsave(&cpg_lock, flags); val = readl(reg); val &= ~clear; val |= set; writel(val, reg); spin_unlock_irqrestore(&cpg_lock, flags); }; static int cpg_simple_notifier_call(struct notifier_block *nb, unsigned long action, void *data) { struct cpg_simple_notifier *csn = container_of(nb, struct cpg_simple_notifier, nb); switch (action) { case PM_EVENT_SUSPEND: csn->saved = readl(csn->reg); return NOTIFY_OK; case PM_EVENT_RESUME: writel(csn->saved, csn->reg); return NOTIFY_OK; } return NOTIFY_DONE; } void cpg_simple_notifier_register(struct raw_notifier_head *notifiers, struct cpg_simple_notifier *csn) { csn->nb.notifier_call = cpg_simple_notifier_call; raw_notifier_chain_register(notifiers, &csn->nb); } /* * SDn Clock */ #define CPG_SD_STP_HCK BIT(9) #define CPG_SD_STP_CK BIT(8) #define CPG_SD_STP_MASK (CPG_SD_STP_HCK | CPG_SD_STP_CK) #define CPG_SD_FC_MASK (0x7 << 2 | 0x3 << 0) #define CPG_SD_DIV_TABLE_DATA(stp_hck, sd_srcfc, sd_fc, sd_div) \ { \ .val = ((stp_hck) ? CPG_SD_STP_HCK : 0) | \ ((sd_srcfc) << 2) | \ ((sd_fc) << 0), \ .div = (sd_div), \ } struct sd_div_table { u32 val; unsigned int div; }; struct sd_clock { struct clk_hw hw; const struct sd_div_table *div_table; struct cpg_simple_notifier csn; unsigned int div_num; unsigned int cur_div_idx; }; /* SDn divider * sd_srcfc sd_fc div * stp_hck (div) (div) = sd_srcfc x sd_fc *--------------------------------------------------------- * 0 0 (1) 1 (4) 4 : SDR104 / HS200 / HS400 (8 TAP) * 0 1 (2) 1 (4) 8 : SDR50 * 1 2 (4) 1 (4) 16 : HS / SDR25 * 1 3 (8) 1 (4) 32 : NS / SDR12 * 1 4 (16) 1 (4) 64 * 0 0 (1) 0 (2) 2 * 0 1 (2) 0 (2) 4 : SDR104 / HS200 / HS400 (4 TAP) * 1 2 (4) 0 (2) 8 * 1 3 (8) 0 (2) 16 * 1 4 (16) 0 (2) 32 * * NOTE: There is a quirk option to ignore the first row of the dividers * table when searching for suitable settings. This is because HS400 on * early ES versions of H3 and M3-W requires a specific setting to work. */ static const struct sd_div_table cpg_sd_div_table[] = { /* CPG_SD_DIV_TABLE_DATA(stp_hck, sd_srcfc, sd_fc, sd_div) */ CPG_SD_DIV_TABLE_DATA(0, 0, 1, 4), CPG_SD_DIV_TABLE_DATA(0, 1, 1, 8), CPG_SD_DIV_TABLE_DATA(1, 2, 1, 16), CPG_SD_DIV_TABLE_DATA(1, 3, 1, 32), CPG_SD_DIV_TABLE_DATA(1, 4, 1, 64), CPG_SD_DIV_TABLE_DATA(0, 0, 0, 2), CPG_SD_DIV_TABLE_DATA(0, 1, 0, 4), CPG_SD_DIV_TABLE_DATA(1, 2, 0, 8), CPG_SD_DIV_TABLE_DATA(1, 3, 0, 16), CPG_SD_DIV_TABLE_DATA(1, 4, 0, 32), }; #define to_sd_clock(_hw) container_of(_hw, struct sd_clock, hw) static int cpg_sd_clock_enable(struct clk_hw *hw) { struct sd_clock *clock = to_sd_clock(hw); cpg_reg_modify(clock->csn.reg, CPG_SD_STP_MASK, clock->div_table[clock->cur_div_idx].val & CPG_SD_STP_MASK); return 0; } static void cpg_sd_clock_disable(struct clk_hw *hw) { struct sd_clock *clock = to_sd_clock(hw); cpg_reg_modify(clock->csn.reg, 0, CPG_SD_STP_MASK); } static int cpg_sd_clock_is_enabled(struct clk_hw *hw) { struct sd_clock *clock = to_sd_clock(hw); return !(readl(clock->csn.reg) & CPG_SD_STP_MASK); } static unsigned long cpg_sd_clock_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct sd_clock *clock = to_sd_clock(hw); return DIV_ROUND_CLOSEST(parent_rate, clock->div_table[clock->cur_div_idx].div); } static int cpg_sd_clock_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { unsigned long best_rate = ULONG_MAX, diff_min = ULONG_MAX; struct sd_clock *clock = to_sd_clock(hw); unsigned long calc_rate, diff; unsigned int i; for (i = 0; i < clock->div_num; i++) { calc_rate = DIV_ROUND_CLOSEST(req->best_parent_rate, clock->div_table[i].div); if (calc_rate < req->min_rate || calc_rate > req->max_rate) continue; diff = calc_rate > req->rate ? calc_rate - req->rate : req->rate - calc_rate; if (diff < diff_min) { best_rate = calc_rate; diff_min = diff; } } if (best_rate == ULONG_MAX) return -EINVAL; req->rate = best_rate; return 0; } static int cpg_sd_clock_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct sd_clock *clock = to_sd_clock(hw); unsigned int i; for (i = 0; i < clock->div_num; i++) if (rate == DIV_ROUND_CLOSEST(parent_rate, clock->div_table[i].div)) break; if (i >= clock->div_num) return -EINVAL; clock->cur_div_idx = i; cpg_reg_modify(clock->csn.reg, CPG_SD_STP_MASK | CPG_SD_FC_MASK, clock->div_table[i].val & (CPG_SD_STP_MASK | CPG_SD_FC_MASK)); return 0; } static const struct clk_ops cpg_sd_clock_ops = { .enable = cpg_sd_clock_enable, .disable = cpg_sd_clock_disable, .is_enabled = cpg_sd_clock_is_enabled, .recalc_rate = cpg_sd_clock_recalc_rate, .determine_rate = cpg_sd_clock_determine_rate, .set_rate = cpg_sd_clock_set_rate, }; struct clk * __init cpg_sd_clk_register(const char *name, void __iomem *base, unsigned int offset, const char *parent_name, struct raw_notifier_head *notifiers, bool skip_first) { struct clk_init_data init = {}; struct sd_clock *clock; struct clk *clk; u32 val; clock = kzalloc(sizeof(*clock), GFP_KERNEL); if (!clock) return ERR_PTR(-ENOMEM); init.name = name; init.ops = &cpg_sd_clock_ops; init.flags = CLK_SET_RATE_PARENT; init.parent_names = &parent_name; init.num_parents = 1; clock->csn.reg = base + offset; clock->hw.init = &init; clock->div_table = cpg_sd_div_table; clock->div_num = ARRAY_SIZE(cpg_sd_div_table); if (skip_first) { clock->div_table++; clock->div_num--; } val = readl(clock->csn.reg) & ~CPG_SD_FC_MASK; val |= CPG_SD_STP_MASK | (clock->div_table[0].val & CPG_SD_FC_MASK); writel(val, clock->csn.reg); clk = clk_register(NULL, &clock->hw); if (IS_ERR(clk)) goto free_clock; cpg_simple_notifier_register(notifiers, &clock->csn); return clk; free_clock: kfree(clock); return clk; }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1