cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

clk-peripheral.c (12412B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 *  Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
      4 */
      5
      6#include <linux/bitops.h>
      7#include <linux/clk-provider.h>
      8#include <linux/clkdev.h>
      9#include <linux/clk/at91_pmc.h>
     10#include <linux/of.h>
     11#include <linux/mfd/syscon.h>
     12#include <linux/regmap.h>
     13
     14#include "pmc.h"
     15
     16DEFINE_SPINLOCK(pmc_pcr_lock);
     17
     18#define PERIPHERAL_ID_MIN	2
     19#define PERIPHERAL_ID_MAX	31
     20#define PERIPHERAL_MASK(id)	(1 << ((id) & PERIPHERAL_ID_MAX))
     21
     22#define PERIPHERAL_MAX_SHIFT	3
     23
     24struct clk_peripheral {
     25	struct clk_hw hw;
     26	struct regmap *regmap;
     27	u32 id;
     28};
     29
     30#define to_clk_peripheral(hw) container_of(hw, struct clk_peripheral, hw)
     31
     32struct clk_sam9x5_peripheral {
     33	struct clk_hw hw;
     34	struct regmap *regmap;
     35	struct clk_range range;
     36	spinlock_t *lock;
     37	u32 id;
     38	u32 div;
     39	const struct clk_pcr_layout *layout;
     40	struct at91_clk_pms pms;
     41	bool auto_div;
     42	int chg_pid;
     43};
     44
     45#define to_clk_sam9x5_peripheral(hw) \
     46	container_of(hw, struct clk_sam9x5_peripheral, hw)
     47
     48static int clk_peripheral_enable(struct clk_hw *hw)
     49{
     50	struct clk_peripheral *periph = to_clk_peripheral(hw);
     51	int offset = AT91_PMC_PCER;
     52	u32 id = periph->id;
     53
     54	if (id < PERIPHERAL_ID_MIN)
     55		return 0;
     56	if (id > PERIPHERAL_ID_MAX)
     57		offset = AT91_PMC_PCER1;
     58	regmap_write(periph->regmap, offset, PERIPHERAL_MASK(id));
     59
     60	return 0;
     61}
     62
     63static void clk_peripheral_disable(struct clk_hw *hw)
     64{
     65	struct clk_peripheral *periph = to_clk_peripheral(hw);
     66	int offset = AT91_PMC_PCDR;
     67	u32 id = periph->id;
     68
     69	if (id < PERIPHERAL_ID_MIN)
     70		return;
     71	if (id > PERIPHERAL_ID_MAX)
     72		offset = AT91_PMC_PCDR1;
     73	regmap_write(periph->regmap, offset, PERIPHERAL_MASK(id));
     74}
     75
     76static int clk_peripheral_is_enabled(struct clk_hw *hw)
     77{
     78	struct clk_peripheral *periph = to_clk_peripheral(hw);
     79	int offset = AT91_PMC_PCSR;
     80	unsigned int status;
     81	u32 id = periph->id;
     82
     83	if (id < PERIPHERAL_ID_MIN)
     84		return 1;
     85	if (id > PERIPHERAL_ID_MAX)
     86		offset = AT91_PMC_PCSR1;
     87	regmap_read(periph->regmap, offset, &status);
     88
     89	return status & PERIPHERAL_MASK(id) ? 1 : 0;
     90}
     91
     92static const struct clk_ops peripheral_ops = {
     93	.enable = clk_peripheral_enable,
     94	.disable = clk_peripheral_disable,
     95	.is_enabled = clk_peripheral_is_enabled,
     96};
     97
     98struct clk_hw * __init
     99at91_clk_register_peripheral(struct regmap *regmap, const char *name,
    100			     const char *parent_name, u32 id)
    101{
    102	struct clk_peripheral *periph;
    103	struct clk_init_data init;
    104	struct clk_hw *hw;
    105	int ret;
    106
    107	if (!name || !parent_name || id > PERIPHERAL_ID_MAX)
    108		return ERR_PTR(-EINVAL);
    109
    110	periph = kzalloc(sizeof(*periph), GFP_KERNEL);
    111	if (!periph)
    112		return ERR_PTR(-ENOMEM);
    113
    114	init.name = name;
    115	init.ops = &peripheral_ops;
    116	init.parent_names = &parent_name;
    117	init.num_parents = 1;
    118	init.flags = 0;
    119
    120	periph->id = id;
    121	periph->hw.init = &init;
    122	periph->regmap = regmap;
    123
    124	hw = &periph->hw;
    125	ret = clk_hw_register(NULL, &periph->hw);
    126	if (ret) {
    127		kfree(periph);
    128		hw = ERR_PTR(ret);
    129	}
    130
    131	return hw;
    132}
    133
    134static void clk_sam9x5_peripheral_autodiv(struct clk_sam9x5_peripheral *periph)
    135{
    136	struct clk_hw *parent;
    137	unsigned long parent_rate;
    138	int shift = 0;
    139
    140	if (!periph->auto_div)
    141		return;
    142
    143	if (periph->range.max) {
    144		parent = clk_hw_get_parent_by_index(&periph->hw, 0);
    145		parent_rate = clk_hw_get_rate(parent);
    146		if (!parent_rate)
    147			return;
    148
    149		for (; shift < PERIPHERAL_MAX_SHIFT; shift++) {
    150			if (parent_rate >> shift <= periph->range.max)
    151				break;
    152		}
    153	}
    154
    155	periph->auto_div = false;
    156	periph->div = shift;
    157}
    158
    159static int clk_sam9x5_peripheral_set(struct clk_sam9x5_peripheral *periph,
    160				     unsigned int status)
    161{
    162	unsigned long flags;
    163	unsigned int enable = status ? AT91_PMC_PCR_EN : 0;
    164
    165	if (periph->id < PERIPHERAL_ID_MIN)
    166		return 0;
    167
    168	spin_lock_irqsave(periph->lock, flags);
    169	regmap_write(periph->regmap, periph->layout->offset,
    170		     (periph->id & periph->layout->pid_mask));
    171	regmap_update_bits(periph->regmap, periph->layout->offset,
    172			   periph->layout->div_mask | periph->layout->cmd |
    173			   enable,
    174			   field_prep(periph->layout->div_mask, periph->div) |
    175			   periph->layout->cmd | enable);
    176	spin_unlock_irqrestore(periph->lock, flags);
    177
    178	return 0;
    179}
    180
    181static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
    182{
    183	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
    184
    185	return clk_sam9x5_peripheral_set(periph, 1);
    186}
    187
    188static void clk_sam9x5_peripheral_disable(struct clk_hw *hw)
    189{
    190	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
    191	unsigned long flags;
    192
    193	if (periph->id < PERIPHERAL_ID_MIN)
    194		return;
    195
    196	spin_lock_irqsave(periph->lock, flags);
    197	regmap_write(periph->regmap, periph->layout->offset,
    198		     (periph->id & periph->layout->pid_mask));
    199	regmap_update_bits(periph->regmap, periph->layout->offset,
    200			   AT91_PMC_PCR_EN | periph->layout->cmd,
    201			   periph->layout->cmd);
    202	spin_unlock_irqrestore(periph->lock, flags);
    203}
    204
    205static int clk_sam9x5_peripheral_is_enabled(struct clk_hw *hw)
    206{
    207	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
    208	unsigned long flags;
    209	unsigned int status;
    210
    211	if (periph->id < PERIPHERAL_ID_MIN)
    212		return 1;
    213
    214	spin_lock_irqsave(periph->lock, flags);
    215	regmap_write(periph->regmap, periph->layout->offset,
    216		     (periph->id & periph->layout->pid_mask));
    217	regmap_read(periph->regmap, periph->layout->offset, &status);
    218	spin_unlock_irqrestore(periph->lock, flags);
    219
    220	return !!(status & AT91_PMC_PCR_EN);
    221}
    222
    223static unsigned long
    224clk_sam9x5_peripheral_recalc_rate(struct clk_hw *hw,
    225				  unsigned long parent_rate)
    226{
    227	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
    228	unsigned long flags;
    229	unsigned int status;
    230
    231	if (periph->id < PERIPHERAL_ID_MIN)
    232		return parent_rate;
    233
    234	spin_lock_irqsave(periph->lock, flags);
    235	regmap_write(periph->regmap, periph->layout->offset,
    236		     (periph->id & periph->layout->pid_mask));
    237	regmap_read(periph->regmap, periph->layout->offset, &status);
    238	spin_unlock_irqrestore(periph->lock, flags);
    239
    240	if (status & AT91_PMC_PCR_EN) {
    241		periph->div = field_get(periph->layout->div_mask, status);
    242		periph->auto_div = false;
    243	} else {
    244		clk_sam9x5_peripheral_autodiv(periph);
    245	}
    246
    247	return parent_rate >> periph->div;
    248}
    249
    250static void clk_sam9x5_peripheral_best_diff(struct clk_rate_request *req,
    251					    struct clk_hw *parent,
    252					    unsigned long parent_rate,
    253					    u32 shift, long *best_diff,
    254					    long *best_rate)
    255{
    256	unsigned long tmp_rate = parent_rate >> shift;
    257	unsigned long tmp_diff = abs(req->rate - tmp_rate);
    258
    259	if (*best_diff < 0 || *best_diff >= tmp_diff) {
    260		*best_rate = tmp_rate;
    261		*best_diff = tmp_diff;
    262		req->best_parent_rate = parent_rate;
    263		req->best_parent_hw = parent;
    264	}
    265}
    266
    267static int clk_sam9x5_peripheral_determine_rate(struct clk_hw *hw,
    268						struct clk_rate_request *req)
    269{
    270	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
    271	struct clk_hw *parent = clk_hw_get_parent(hw);
    272	struct clk_rate_request req_parent = *req;
    273	unsigned long parent_rate = clk_hw_get_rate(parent);
    274	unsigned long tmp_rate;
    275	long best_rate = LONG_MIN;
    276	long best_diff = LONG_MIN;
    277	u32 shift;
    278
    279	if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max)
    280		return parent_rate;
    281
    282	/* Fist step: check the available dividers. */
    283	for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
    284		tmp_rate = parent_rate >> shift;
    285
    286		if (periph->range.max && tmp_rate > periph->range.max)
    287			continue;
    288
    289		clk_sam9x5_peripheral_best_diff(req, parent, parent_rate,
    290						shift, &best_diff, &best_rate);
    291
    292		if (!best_diff || best_rate <= req->rate)
    293			break;
    294	}
    295
    296	if (periph->chg_pid < 0)
    297		goto end;
    298
    299	/* Step two: try to request rate from parent. */
    300	parent = clk_hw_get_parent_by_index(hw, periph->chg_pid);
    301	if (!parent)
    302		goto end;
    303
    304	for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
    305		req_parent.rate = req->rate << shift;
    306
    307		if (__clk_determine_rate(parent, &req_parent))
    308			continue;
    309
    310		clk_sam9x5_peripheral_best_diff(req, parent, req_parent.rate,
    311						shift, &best_diff, &best_rate);
    312
    313		if (!best_diff)
    314			break;
    315	}
    316end:
    317	if (best_rate < 0 ||
    318	    (periph->range.max && best_rate > periph->range.max))
    319		return -EINVAL;
    320
    321	pr_debug("PCK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
    322		 __func__, best_rate,
    323		 __clk_get_name((req->best_parent_hw)->clk),
    324		 req->best_parent_rate);
    325
    326	req->rate = best_rate;
    327
    328	return 0;
    329}
    330
    331static long clk_sam9x5_peripheral_round_rate(struct clk_hw *hw,
    332					     unsigned long rate,
    333					     unsigned long *parent_rate)
    334{
    335	int shift = 0;
    336	unsigned long best_rate;
    337	unsigned long best_diff;
    338	unsigned long cur_rate = *parent_rate;
    339	unsigned long cur_diff;
    340	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
    341
    342	if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max)
    343		return *parent_rate;
    344
    345	if (periph->range.max) {
    346		for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
    347			cur_rate = *parent_rate >> shift;
    348			if (cur_rate <= periph->range.max)
    349				break;
    350		}
    351	}
    352
    353	if (rate >= cur_rate)
    354		return cur_rate;
    355
    356	best_diff = cur_rate - rate;
    357	best_rate = cur_rate;
    358	for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
    359		cur_rate = *parent_rate >> shift;
    360		if (cur_rate < rate)
    361			cur_diff = rate - cur_rate;
    362		else
    363			cur_diff = cur_rate - rate;
    364
    365		if (cur_diff < best_diff) {
    366			best_diff = cur_diff;
    367			best_rate = cur_rate;
    368		}
    369
    370		if (!best_diff || cur_rate < rate)
    371			break;
    372	}
    373
    374	return best_rate;
    375}
    376
    377static int clk_sam9x5_peripheral_set_rate(struct clk_hw *hw,
    378					  unsigned long rate,
    379					  unsigned long parent_rate)
    380{
    381	int shift;
    382	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
    383	if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) {
    384		if (parent_rate == rate)
    385			return 0;
    386		else
    387			return -EINVAL;
    388	}
    389
    390	if (periph->range.max && rate > periph->range.max)
    391		return -EINVAL;
    392
    393	for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
    394		if (parent_rate >> shift == rate) {
    395			periph->auto_div = false;
    396			periph->div = shift;
    397			return 0;
    398		}
    399	}
    400
    401	return -EINVAL;
    402}
    403
    404static int clk_sam9x5_peripheral_save_context(struct clk_hw *hw)
    405{
    406	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
    407
    408	periph->pms.status = clk_sam9x5_peripheral_is_enabled(hw);
    409
    410	return 0;
    411}
    412
    413static void clk_sam9x5_peripheral_restore_context(struct clk_hw *hw)
    414{
    415	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
    416
    417	if (periph->pms.status)
    418		clk_sam9x5_peripheral_set(periph, periph->pms.status);
    419}
    420
    421static const struct clk_ops sam9x5_peripheral_ops = {
    422	.enable = clk_sam9x5_peripheral_enable,
    423	.disable = clk_sam9x5_peripheral_disable,
    424	.is_enabled = clk_sam9x5_peripheral_is_enabled,
    425	.recalc_rate = clk_sam9x5_peripheral_recalc_rate,
    426	.round_rate = clk_sam9x5_peripheral_round_rate,
    427	.set_rate = clk_sam9x5_peripheral_set_rate,
    428	.save_context = clk_sam9x5_peripheral_save_context,
    429	.restore_context = clk_sam9x5_peripheral_restore_context,
    430};
    431
    432static const struct clk_ops sam9x5_peripheral_chg_ops = {
    433	.enable = clk_sam9x5_peripheral_enable,
    434	.disable = clk_sam9x5_peripheral_disable,
    435	.is_enabled = clk_sam9x5_peripheral_is_enabled,
    436	.recalc_rate = clk_sam9x5_peripheral_recalc_rate,
    437	.determine_rate = clk_sam9x5_peripheral_determine_rate,
    438	.set_rate = clk_sam9x5_peripheral_set_rate,
    439	.save_context = clk_sam9x5_peripheral_save_context,
    440	.restore_context = clk_sam9x5_peripheral_restore_context,
    441};
    442
    443struct clk_hw * __init
    444at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock,
    445				    const struct clk_pcr_layout *layout,
    446				    const char *name, const char *parent_name,
    447				    u32 id, const struct clk_range *range,
    448				    int chg_pid)
    449{
    450	struct clk_sam9x5_peripheral *periph;
    451	struct clk_init_data init;
    452	struct clk_hw *hw;
    453	int ret;
    454
    455	if (!name || !parent_name)
    456		return ERR_PTR(-EINVAL);
    457
    458	periph = kzalloc(sizeof(*periph), GFP_KERNEL);
    459	if (!periph)
    460		return ERR_PTR(-ENOMEM);
    461
    462	init.name = name;
    463	init.parent_names = &parent_name;
    464	init.num_parents = 1;
    465	if (chg_pid < 0) {
    466		init.flags = 0;
    467		init.ops = &sam9x5_peripheral_ops;
    468	} else {
    469		init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
    470			     CLK_SET_RATE_PARENT;
    471		init.ops = &sam9x5_peripheral_chg_ops;
    472	}
    473
    474	periph->id = id;
    475	periph->hw.init = &init;
    476	periph->div = 0;
    477	periph->regmap = regmap;
    478	periph->lock = lock;
    479	if (layout->div_mask)
    480		periph->auto_div = true;
    481	periph->layout = layout;
    482	periph->range = *range;
    483	periph->chg_pid = chg_pid;
    484
    485	hw = &periph->hw;
    486	ret = clk_hw_register(NULL, &periph->hw);
    487	if (ret) {
    488		kfree(periph);
    489		hw = ERR_PTR(ret);
    490	} else {
    491		clk_sam9x5_peripheral_autodiv(periph);
    492	}
    493
    494	return hw;
    495}