linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v5 00/15] clk: at91: updates for power management and dvfs
@ 2021-10-11 11:27 Claudiu Beznea
  2021-10-11 11:27 ` [PATCH v5 01/15] clk: at91: re-factor clocks suspend/resume Claudiu Beznea
                   ` (15 more replies)
  0 siblings, 16 replies; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

Hi,

This series addresses the clock power management for SAMA7G5 and also
updates the master clock driver and sam9x60-pll driver to accommodate
the requests at [1] and avoid overclocking of CPU and MCK0 domains while
changing the frequency via DVFS.

The power management part is implemented by adding
save_context()/restore_context() on each clock driver (patch 1/15). Since
the PM part is necessary only for backup mode (supported on SAMA5D2 and
SAMA7G5) the pmc.c has been adapted to call the
save_context()/restore_context() only on switches to/from backup mode
(patch 2/15).

Patch 3/15 adds the securam clock on SAMA7G5. This is necessary for
backup mode of SAMA7G5.

Patch 4/15 adapt SAMA7G5 MCK1..4 driver to use the defines at
include/linux/clk/at91_pmc.h introduced in commit ec03f18cc222
("clk: at91: add register definition for sama7g5's master clock").

Patch 5/15 improves a bit readabiblity in some places of master clock
driver.

Patch 6/15 enable the suspend/resume for clocks also for SAMA7G5.

Patches 7-10/15 adds fixes in master clock driver and sam9x60-pll driver.

Patches 11-12/15 address DVFS by adding notifiers for master clock driver
and sam9x60-pll driver to avoid overclocking for CPU domain and MCK0
domain.

Patch 13/15 removes the master clock prescaler from Linux clock tree
as it has been discovered a hardware bug on it and it may not lock
on some scenario although its output clock is stable.

Patch 14/15 decreases the low limit of MCK0 as it can go even to 32KHz.

Patch 15/15 uses clk_core_get_rate_recalc() in clk_rate_get().

Thank you,
Claudiu Beznea

[1] https://lore.kernel.org/lkml/20210105104426.4tmgc2l3vyicwedd@vireshk-i7/
[2] https://lore.kernel.org/lkml/163047507296.42057.10597374695758699868@swboyd.mtv.corp.google.com/ 

Changes in v5:
- dropped patches 16/17, 17/17 from previous version
- in patch 02/15:
	- use read_relaxed() to get the value at address pointed by 
	  at91_pmc_backup_suspend
	- removed of_find_device_by_node() from pmc_register_ops() since its
	  returned value was not used in the function (of_device_is_available()
	  is enough to know before mapping securam). of_find_device_by_node()
	  was the one which needed the changes of postcore_initcall() with
	  subsys_initcall() (as described in commit description from v4) thus
	  kept the postcore_initcall(); also updated the commit description
	  to reflect this.

Changes in v4:
- removed opp dependencies along with get_cpu_device() in patch 11/17 as
  those functionalities are not available at the initialization time for
  clocks instantiated with CLK_OF_DECLARE

Changes in v3:
- minor fixes in patch 1/7 (e.g. use regmap_read() + checks + update +
  regmap_write() sequence instead of regmap_read() + checks +
  regmap_update_bits()
- patch 4/17 has been updated after commit ec03f18cc222
  ("clk: at91: add register definition for sama7g5's master clock")
- patch 6-10/17, 14-17/17 are newly introduced
- notifier for sam9x60 div pll was introduced (patch 11/17) and
  notifier logic for master clock div has been changed (patch 12/17)
  to use safe divider on PRE_RATE_CHANGE events and update clock to highest
  possible rate on POST_RATE_CHANGE events
- master clock prescaler has been removed from Linux clock tree for
  SAMA7G5

Changes in v2:
- addressed code review comments
- collected tags

Claudiu Beznea (15):
  clk: at91: re-factor clocks suspend/resume
  clk: at91: pmc: execute suspend/resume only for backup mode
  clk: at91: sama7g5: add securam's peripheral clock
  clk: at91: clk-master: add register definition for sama7g5's master
    clock
  clk: at91: clk-master: improve readability by using local variables
  clk: at91: pmc: add sama7g5 to the list of available pmcs
  clk: at91: sam9x60-pll: use DIV_ROUND_CLOSEST_ULL
  clk: at91: clk-master: check if div or pres is zero
  clk: at91: clk-master: mask mckr against layout->mask
  clk: at91: clk-master: fix prescaler logic
  clk: at91: clk-sam9x60-pll: add notifier for div part of PLL
  clk: at91: clk-master: add notifier for divider
  clk: at91: sama7g5: remove prescaler part of master clock
  clk: at91: sama7g5: set low limit for mck0 at 32KHz
  clk: use clk_core_get_rate_recalc() in clk_rate_get()

 drivers/clk/at91/at91rm9200.c       |   2 +-
 drivers/clk/at91/at91sam9260.c      |   2 +-
 drivers/clk/at91/at91sam9g45.c      |   2 +-
 drivers/clk/at91/at91sam9n12.c      |   2 +-
 drivers/clk/at91/at91sam9rl.c       |   2 +-
 drivers/clk/at91/at91sam9x5.c       |   2 +-
 drivers/clk/at91/clk-generated.c    |  46 ++-
 drivers/clk/at91/clk-main.c         |  66 ++++
 drivers/clk/at91/clk-master.c       | 463 ++++++++++++++++++++++------
 drivers/clk/at91/clk-peripheral.c   |  40 ++-
 drivers/clk/at91/clk-pll.c          |  39 +++
 drivers/clk/at91/clk-programmable.c |  29 +-
 drivers/clk/at91/clk-sam9x60-pll.c  | 174 +++++++++--
 drivers/clk/at91/clk-system.c       |  20 ++
 drivers/clk/at91/clk-usb.c          |  27 ++
 drivers/clk/at91/clk-utmi.c         |  39 +++
 drivers/clk/at91/dt-compat.c        |   2 +-
 drivers/clk/at91/pmc.c              | 173 +++--------
 drivers/clk/at91/pmc.h              |  29 +-
 drivers/clk/at91/sam9x60.c          |   6 +-
 drivers/clk/at91/sama5d2.c          |   2 +-
 drivers/clk/at91/sama5d3.c          |   2 +-
 drivers/clk/at91/sama5d4.c          |   2 +-
 drivers/clk/at91/sama7g5.c          |  29 +-
 drivers/clk/clk.c                   |   2 +-
 25 files changed, 890 insertions(+), 312 deletions(-)

-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v5 01/15] clk: at91: re-factor clocks suspend/resume
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  7:46   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 02/15] clk: at91: pmc: execute suspend/resume only for backup mode Claudiu Beznea
                   ` (14 subsequent siblings)
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

SAMA5D2 and SAMA7G5 have a special power saving mode (backup mode) where
most of the SoC's components are powered off (including PMC). Resuming
from this mode is done with the help of bootloader. Peripherals are not
aware of the power saving mode thus most of them are disabling clocks in
proper suspend API and re-enable them in resume API without taking into
account the previously setup rate. Moreover some of the peripherals are
acting as wakeup sources and are not disabling the clocks in this
scenario, when suspending. Since backup mode cuts the power for
peripherals, in resume part these clocks needs to be re-configured.

The initial PMC suspend/resume code was designed only for SAMA5D2's PMC
(as it was the only one supporting backup mode). SAMA7G supports also
backup mode and its PMC is different (few new functionalities, different
registers offsets, different offsets in registers for each
functionalities). To address both SAMA5D2 and SAMA7G5 PMC add
.save_context()/.resume_context() support to each clocks driver and call
this from PMC driver.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---

Hi Stephen,

This patch applies good on top of patch at [1].

Thank you,
Claudiu Beznea

[1]
https://lore.kernel.org/lkml/20210913082633.110168-1-clement.leger@bootlin.com/

 drivers/clk/at91/clk-generated.c    |  46 +++++--
 drivers/clk/at91/clk-main.c         |  66 ++++++++++
 drivers/clk/at91/clk-master.c       | 194 ++++++++++++++++++++++++++--
 drivers/clk/at91/clk-peripheral.c   |  40 +++++-
 drivers/clk/at91/clk-pll.c          |  39 ++++++
 drivers/clk/at91/clk-programmable.c |  29 ++++-
 drivers/clk/at91/clk-sam9x60-pll.c  |  68 +++++++++-
 drivers/clk/at91/clk-system.c       |  20 +++
 drivers/clk/at91/clk-usb.c          |  27 ++++
 drivers/clk/at91/clk-utmi.c         |  39 ++++++
 drivers/clk/at91/pmc.c              | 147 +--------------------
 drivers/clk/at91/pmc.h              |  24 ++--
 12 files changed, 558 insertions(+), 181 deletions(-)

diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c
index b656d25a9767..23cc8297ec4c 100644
--- a/drivers/clk/at91/clk-generated.c
+++ b/drivers/clk/at91/clk-generated.c
@@ -27,6 +27,7 @@ struct clk_generated {
 	u32 id;
 	u32 gckdiv;
 	const struct clk_pcr_layout *layout;
+	struct at91_clk_pms pms;
 	u8 parent_id;
 	int chg_pid;
 };
@@ -34,25 +35,35 @@ struct clk_generated {
 #define to_clk_generated(hw) \
 	container_of(hw, struct clk_generated, hw)
 
-static int clk_generated_enable(struct clk_hw *hw)
+static int clk_generated_set(struct clk_generated *gck, int status)
 {
-	struct clk_generated *gck = to_clk_generated(hw);
 	unsigned long flags;
-
-	pr_debug("GCLK: %s, gckdiv = %d, parent id = %d\n",
-		 __func__, gck->gckdiv, gck->parent_id);
+	unsigned int enable = status ? AT91_PMC_PCR_GCKEN : 0;
 
 	spin_lock_irqsave(gck->lock, flags);
 	regmap_write(gck->regmap, gck->layout->offset,
 		     (gck->id & gck->layout->pid_mask));
 	regmap_update_bits(gck->regmap, gck->layout->offset,
 			   AT91_PMC_PCR_GCKDIV_MASK | gck->layout->gckcss_mask |
-			   gck->layout->cmd | AT91_PMC_PCR_GCKEN,
+			   gck->layout->cmd | enable,
 			   field_prep(gck->layout->gckcss_mask, gck->parent_id) |
 			   gck->layout->cmd |
 			   FIELD_PREP(AT91_PMC_PCR_GCKDIV_MASK, gck->gckdiv) |
-			   AT91_PMC_PCR_GCKEN);
+			   enable);
 	spin_unlock_irqrestore(gck->lock, flags);
+
+	return 0;
+}
+
+static int clk_generated_enable(struct clk_hw *hw)
+{
+	struct clk_generated *gck = to_clk_generated(hw);
+
+	pr_debug("GCLK: %s, gckdiv = %d, parent id = %d\n",
+		 __func__, gck->gckdiv, gck->parent_id);
+
+	clk_generated_set(gck, 1);
+
 	return 0;
 }
 
@@ -245,6 +256,23 @@ static int clk_generated_set_rate(struct clk_hw *hw,
 	return 0;
 }
 
+static int clk_generated_save_context(struct clk_hw *hw)
+{
+	struct clk_generated *gck = to_clk_generated(hw);
+
+	gck->pms.status = clk_generated_is_enabled(&gck->hw);
+
+	return 0;
+}
+
+static void clk_generated_restore_context(struct clk_hw *hw)
+{
+	struct clk_generated *gck = to_clk_generated(hw);
+
+	if (gck->pms.status)
+		clk_generated_set(gck, gck->pms.status);
+}
+
 static const struct clk_ops generated_ops = {
 	.enable = clk_generated_enable,
 	.disable = clk_generated_disable,
@@ -254,6 +282,8 @@ static const struct clk_ops generated_ops = {
 	.get_parent = clk_generated_get_parent,
 	.set_parent = clk_generated_set_parent,
 	.set_rate = clk_generated_set_rate,
+	.save_context = clk_generated_save_context,
+	.restore_context = clk_generated_restore_context,
 };
 
 /**
@@ -320,8 +350,6 @@ at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock,
 	if (ret) {
 		kfree(gck);
 		hw = ERR_PTR(ret);
-	} else {
-		pmc_register_id(id);
 	}
 
 	return hw;
diff --git a/drivers/clk/at91/clk-main.c b/drivers/clk/at91/clk-main.c
index cfae2f59df66..8601b27c1ae0 100644
--- a/drivers/clk/at91/clk-main.c
+++ b/drivers/clk/at91/clk-main.c
@@ -28,6 +28,7 @@
 struct clk_main_osc {
 	struct clk_hw hw;
 	struct regmap *regmap;
+	struct at91_clk_pms pms;
 };
 
 #define to_clk_main_osc(hw) container_of(hw, struct clk_main_osc, hw)
@@ -37,6 +38,7 @@ struct clk_main_rc_osc {
 	struct regmap *regmap;
 	unsigned long frequency;
 	unsigned long accuracy;
+	struct at91_clk_pms pms;
 };
 
 #define to_clk_main_rc_osc(hw) container_of(hw, struct clk_main_rc_osc, hw)
@@ -51,6 +53,7 @@ struct clk_rm9200_main {
 struct clk_sam9x5_main {
 	struct clk_hw hw;
 	struct regmap *regmap;
+	struct at91_clk_pms pms;
 	u8 parent;
 };
 
@@ -120,10 +123,29 @@ static int clk_main_osc_is_prepared(struct clk_hw *hw)
 	return (status & AT91_PMC_MOSCS) && clk_main_parent_select(tmp);
 }
 
+static int clk_main_osc_save_context(struct clk_hw *hw)
+{
+	struct clk_main_osc *osc = to_clk_main_osc(hw);
+
+	osc->pms.status = clk_main_osc_is_prepared(hw);
+
+	return 0;
+}
+
+static void clk_main_osc_restore_context(struct clk_hw *hw)
+{
+	struct clk_main_osc *osc = to_clk_main_osc(hw);
+
+	if (osc->pms.status)
+		clk_main_osc_prepare(hw);
+}
+
 static const struct clk_ops main_osc_ops = {
 	.prepare = clk_main_osc_prepare,
 	.unprepare = clk_main_osc_unprepare,
 	.is_prepared = clk_main_osc_is_prepared,
+	.save_context = clk_main_osc_save_context,
+	.restore_context = clk_main_osc_restore_context,
 };
 
 struct clk_hw * __init
@@ -240,12 +262,31 @@ static unsigned long clk_main_rc_osc_recalc_accuracy(struct clk_hw *hw,
 	return osc->accuracy;
 }
 
+static int clk_main_rc_osc_save_context(struct clk_hw *hw)
+{
+	struct clk_main_rc_osc *osc = to_clk_main_rc_osc(hw);
+
+	osc->pms.status = clk_main_rc_osc_is_prepared(hw);
+
+	return 0;
+}
+
+static void clk_main_rc_osc_restore_context(struct clk_hw *hw)
+{
+	struct clk_main_rc_osc *osc = to_clk_main_rc_osc(hw);
+
+	if (osc->pms.status)
+		clk_main_rc_osc_prepare(hw);
+}
+
 static const struct clk_ops main_rc_osc_ops = {
 	.prepare = clk_main_rc_osc_prepare,
 	.unprepare = clk_main_rc_osc_unprepare,
 	.is_prepared = clk_main_rc_osc_is_prepared,
 	.recalc_rate = clk_main_rc_osc_recalc_rate,
 	.recalc_accuracy = clk_main_rc_osc_recalc_accuracy,
+	.save_context = clk_main_rc_osc_save_context,
+	.restore_context = clk_main_rc_osc_restore_context,
 };
 
 struct clk_hw * __init
@@ -465,12 +506,37 @@ static u8 clk_sam9x5_main_get_parent(struct clk_hw *hw)
 	return clk_main_parent_select(status);
 }
 
+static int clk_sam9x5_main_save_context(struct clk_hw *hw)
+{
+	struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(hw);
+
+	clkmain->pms.status = clk_main_rc_osc_is_prepared(&clkmain->hw);
+	clkmain->pms.parent = clk_sam9x5_main_get_parent(&clkmain->hw);
+
+	return 0;
+}
+
+static void clk_sam9x5_main_restore_context(struct clk_hw *hw)
+{
+	struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(hw);
+	int ret;
+
+	ret = clk_sam9x5_main_set_parent(hw, clkmain->pms.parent);
+	if (ret)
+		return;
+
+	if (clkmain->pms.status)
+		clk_sam9x5_main_prepare(hw);
+}
+
 static const struct clk_ops sam9x5_main_ops = {
 	.prepare = clk_sam9x5_main_prepare,
 	.is_prepared = clk_sam9x5_main_is_prepared,
 	.recalc_rate = clk_sam9x5_main_recalc_rate,
 	.set_parent = clk_sam9x5_main_set_parent,
 	.get_parent = clk_sam9x5_main_get_parent,
+	.save_context = clk_sam9x5_main_save_context,
+	.restore_context = clk_sam9x5_main_restore_context,
 };
 
 struct clk_hw * __init
diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
index a80427980bf7..f75549fff023 100644
--- a/drivers/clk/at91/clk-master.c
+++ b/drivers/clk/at91/clk-master.c
@@ -37,6 +37,7 @@ struct clk_master {
 	spinlock_t *lock;
 	const struct clk_master_layout *layout;
 	const struct clk_master_characteristics *characteristics;
+	struct at91_clk_pms pms;
 	u32 *mux_table;
 	u32 mckr;
 	int chg_pid;
@@ -112,10 +113,52 @@ static unsigned long clk_master_div_recalc_rate(struct clk_hw *hw,
 	return rate;
 }
 
+static int clk_master_div_save_context(struct clk_hw *hw)
+{
+	struct clk_master *master = to_clk_master(hw);
+	struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+	unsigned long flags;
+	unsigned int mckr, div;
+
+	spin_lock_irqsave(master->lock, flags);
+	regmap_read(master->regmap, master->layout->offset, &mckr);
+	spin_unlock_irqrestore(master->lock, flags);
+
+	mckr &= master->layout->mask;
+	div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+	div = master->characteristics->divisors[div];
+
+	master->pms.parent_rate = clk_hw_get_rate(parent_hw);
+	master->pms.rate = DIV_ROUND_CLOSEST(master->pms.parent_rate, div);
+
+	return 0;
+}
+
+static void clk_master_div_restore_context(struct clk_hw *hw)
+{
+	struct clk_master *master = to_clk_master(hw);
+	unsigned long flags;
+	unsigned int mckr;
+	u8 div;
+
+	spin_lock_irqsave(master->lock, flags);
+	regmap_read(master->regmap, master->layout->offset, &mckr);
+	spin_unlock_irqrestore(master->lock, flags);
+
+	mckr &= master->layout->mask;
+	div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+	div = master->characteristics->divisors[div];
+
+	if (div != DIV_ROUND_CLOSEST(master->pms.parent_rate, master->pms.rate))
+		pr_warn("MCKR DIV not configured properly by firmware!\n");
+}
+
 static const struct clk_ops master_div_ops = {
 	.prepare = clk_master_prepare,
 	.is_prepared = clk_master_is_prepared,
 	.recalc_rate = clk_master_div_recalc_rate,
+	.save_context = clk_master_div_save_context,
+	.restore_context = clk_master_div_restore_context,
 };
 
 static int clk_master_div_set_rate(struct clk_hw *hw, unsigned long rate,
@@ -125,7 +168,9 @@ static int clk_master_div_set_rate(struct clk_hw *hw, unsigned long rate,
 	const struct clk_master_characteristics *characteristics =
 						master->characteristics;
 	unsigned long flags;
+	unsigned int mckr, tmp;
 	int div, i;
+	int ret;
 
 	div = DIV_ROUND_CLOSEST(parent_rate, rate);
 	if (div > ARRAY_SIZE(characteristics->divisors))
@@ -145,11 +190,24 @@ static int clk_master_div_set_rate(struct clk_hw *hw, unsigned long rate,
 		return -EINVAL;
 
 	spin_lock_irqsave(master->lock, flags);
-	regmap_update_bits(master->regmap, master->layout->offset,
-			   (MASTER_DIV_MASK << MASTER_DIV_SHIFT),
-			   (div << MASTER_DIV_SHIFT));
+	ret = regmap_read(master->regmap, master->layout->offset, &mckr);
+	if (ret)
+		goto unlock;
+
+	tmp = mckr & master->layout->mask;
+	tmp = (tmp >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+	if (tmp == div)
+		goto unlock;
+
+	mckr &= ~(MASTER_DIV_MASK << MASTER_DIV_SHIFT);
+	mckr |= (div << MASTER_DIV_SHIFT);
+	ret = regmap_write(master->regmap, master->layout->offset, mckr);
+	if (ret)
+		goto unlock;
+
 	while (!clk_master_ready(master))
 		cpu_relax();
+unlock:
 	spin_unlock_irqrestore(master->lock, flags);
 
 	return 0;
@@ -197,12 +255,25 @@ static int clk_master_div_determine_rate(struct clk_hw *hw,
 	return 0;
 }
 
+static void clk_master_div_restore_context_chg(struct clk_hw *hw)
+{
+	struct clk_master *master = to_clk_master(hw);
+	int ret;
+
+	ret = clk_master_div_set_rate(hw, master->pms.rate,
+				      master->pms.parent_rate);
+	if (ret)
+		pr_warn("Failed to restore MCK DIV clock\n");
+}
+
 static const struct clk_ops master_div_ops_chg = {
 	.prepare = clk_master_prepare,
 	.is_prepared = clk_master_is_prepared,
 	.recalc_rate = clk_master_div_recalc_rate,
 	.determine_rate = clk_master_div_determine_rate,
 	.set_rate = clk_master_div_set_rate,
+	.save_context = clk_master_div_save_context,
+	.restore_context = clk_master_div_restore_context_chg,
 };
 
 static void clk_sama7g5_master_best_diff(struct clk_rate_request *req,
@@ -272,7 +343,8 @@ static int clk_master_pres_set_rate(struct clk_hw *hw, unsigned long rate,
 {
 	struct clk_master *master = to_clk_master(hw);
 	unsigned long flags;
-	unsigned int pres;
+	unsigned int pres, mckr, tmp;
+	int ret;
 
 	pres = DIV_ROUND_CLOSEST(parent_rate, rate);
 	if (pres > MASTER_PRES_MAX)
@@ -284,15 +356,27 @@ static int clk_master_pres_set_rate(struct clk_hw *hw, unsigned long rate,
 		pres = ffs(pres) - 1;
 
 	spin_lock_irqsave(master->lock, flags);
-	regmap_update_bits(master->regmap, master->layout->offset,
-			   (MASTER_PRES_MASK << master->layout->pres_shift),
-			   (pres << master->layout->pres_shift));
+	ret = regmap_read(master->regmap, master->layout->offset, &mckr);
+	if (ret)
+		goto unlock;
+
+	mckr &= master->layout->mask;
+	tmp = (mckr >> master->layout->pres_shift) & MASTER_PRES_MASK;
+	if (pres == tmp)
+		goto unlock;
+
+	mckr &= ~(MASTER_PRES_MASK << master->layout->pres_shift);
+	mckr |= (pres << master->layout->pres_shift);
+	ret = regmap_write(master->regmap, master->layout->offset, mckr);
+	if (ret)
+		goto unlock;
 
 	while (!clk_master_ready(master))
 		cpu_relax();
+unlock:
 	spin_unlock_irqrestore(master->lock, flags);
 
-	return 0;
+	return ret;
 }
 
 static unsigned long clk_master_pres_recalc_rate(struct clk_hw *hw,
@@ -330,11 +414,68 @@ static u8 clk_master_pres_get_parent(struct clk_hw *hw)
 	return mckr & AT91_PMC_CSS;
 }
 
+static int clk_master_pres_save_context(struct clk_hw *hw)
+{
+	struct clk_master *master = to_clk_master(hw);
+	struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+	unsigned long flags;
+	unsigned int val, pres;
+
+	spin_lock_irqsave(master->lock, flags);
+	regmap_read(master->regmap, master->layout->offset, &val);
+	spin_unlock_irqrestore(master->lock, flags);
+
+	val &= master->layout->mask;
+	pres = (val >> master->layout->pres_shift) & MASTER_PRES_MASK;
+	if (pres == MASTER_PRES_MAX && master->characteristics->have_div3_pres)
+		pres = 3;
+	else
+		pres = (1 << pres);
+
+	master->pms.parent = val & AT91_PMC_CSS;
+	master->pms.parent_rate = clk_hw_get_rate(parent_hw);
+	master->pms.rate = DIV_ROUND_CLOSEST_ULL(master->pms.parent_rate, pres);
+
+	return 0;
+}
+
+static void clk_master_pres_restore_context(struct clk_hw *hw)
+{
+	struct clk_master *master = to_clk_master(hw);
+	unsigned long flags;
+	unsigned int val, pres;
+
+	spin_lock_irqsave(master->lock, flags);
+	regmap_read(master->regmap, master->layout->offset, &val);
+	spin_unlock_irqrestore(master->lock, flags);
+
+	val &= master->layout->mask;
+	pres = (val >> master->layout->pres_shift) & MASTER_PRES_MASK;
+	if (pres == MASTER_PRES_MAX && master->characteristics->have_div3_pres)
+		pres = 3;
+	else
+		pres = (1 << pres);
+
+	if (master->pms.rate !=
+	    DIV_ROUND_CLOSEST_ULL(master->pms.parent_rate, pres) ||
+	    (master->pms.parent != (val & AT91_PMC_CSS)))
+		pr_warn("MCKR PRES was not configured properly by firmware!\n");
+}
+
+static void clk_master_pres_restore_context_chg(struct clk_hw *hw)
+{
+	struct clk_master *master = to_clk_master(hw);
+
+	clk_master_pres_set_rate(hw, master->pms.rate, master->pms.parent_rate);
+}
+
 static const struct clk_ops master_pres_ops = {
 	.prepare = clk_master_prepare,
 	.is_prepared = clk_master_is_prepared,
 	.recalc_rate = clk_master_pres_recalc_rate,
 	.get_parent = clk_master_pres_get_parent,
+	.save_context = clk_master_pres_save_context,
+	.restore_context = clk_master_pres_restore_context,
 };
 
 static const struct clk_ops master_pres_ops_chg = {
@@ -344,6 +485,8 @@ static const struct clk_ops master_pres_ops_chg = {
 	.recalc_rate = clk_master_pres_recalc_rate,
 	.get_parent = clk_master_pres_get_parent,
 	.set_rate = clk_master_pres_set_rate,
+	.save_context = clk_master_pres_save_context,
+	.restore_context = clk_master_pres_restore_context_chg,
 };
 
 static struct clk_hw * __init
@@ -539,20 +682,21 @@ static int clk_sama7g5_master_set_parent(struct clk_hw *hw, u8 index)
 	return 0;
 }
 
-static int clk_sama7g5_master_enable(struct clk_hw *hw)
+static void clk_sama7g5_master_set(struct clk_master *master,
+				   unsigned int status)
 {
-	struct clk_master *master = to_clk_master(hw);
 	unsigned long flags;
 	unsigned int val, cparent;
+	unsigned int enable = status ? PMC_MCR_EN : 0;
 
 	spin_lock_irqsave(master->lock, flags);
 
 	regmap_write(master->regmap, PMC_MCR, PMC_MCR_ID(master->id));
 	regmap_read(master->regmap, PMC_MCR, &val);
 	regmap_update_bits(master->regmap, PMC_MCR,
-			   PMC_MCR_EN | PMC_MCR_CSS | PMC_MCR_DIV |
+			   enable | PMC_MCR_CSS | PMC_MCR_DIV |
 			   PMC_MCR_CMD | PMC_MCR_ID_MSK,
-			   PMC_MCR_EN | (master->parent << PMC_MCR_CSS_SHIFT) |
+			   enable | (master->parent << PMC_MCR_CSS_SHIFT) |
 			   (master->div << MASTER_DIV_SHIFT) |
 			   PMC_MCR_CMD | PMC_MCR_ID(master->id));
 
@@ -563,6 +707,13 @@ static int clk_sama7g5_master_enable(struct clk_hw *hw)
 		cpu_relax();
 
 	spin_unlock_irqrestore(master->lock, flags);
+}
+
+static int clk_sama7g5_master_enable(struct clk_hw *hw)
+{
+	struct clk_master *master = to_clk_master(hw);
+
+	clk_sama7g5_master_set(master, 1);
 
 	return 0;
 }
@@ -620,6 +771,23 @@ static int clk_sama7g5_master_set_rate(struct clk_hw *hw, unsigned long rate,
 	return 0;
 }
 
+static int clk_sama7g5_master_save_context(struct clk_hw *hw)
+{
+	struct clk_master *master = to_clk_master(hw);
+
+	master->pms.status = clk_sama7g5_master_is_enabled(hw);
+
+	return 0;
+}
+
+static void clk_sama7g5_master_restore_context(struct clk_hw *hw)
+{
+	struct clk_master *master = to_clk_master(hw);
+
+	if (master->pms.status)
+		clk_sama7g5_master_set(master, master->pms.status);
+}
+
 static const struct clk_ops sama7g5_master_ops = {
 	.enable = clk_sama7g5_master_enable,
 	.disable = clk_sama7g5_master_disable,
@@ -629,6 +797,8 @@ static const struct clk_ops sama7g5_master_ops = {
 	.set_rate = clk_sama7g5_master_set_rate,
 	.get_parent = clk_sama7g5_master_get_parent,
 	.set_parent = clk_sama7g5_master_set_parent,
+	.save_context = clk_sama7g5_master_save_context,
+	.restore_context = clk_sama7g5_master_restore_context,
 };
 
 struct clk_hw * __init
diff --git a/drivers/clk/at91/clk-peripheral.c b/drivers/clk/at91/clk-peripheral.c
index 7a27ba8e0577..e14fa5ac734c 100644
--- a/drivers/clk/at91/clk-peripheral.c
+++ b/drivers/clk/at91/clk-peripheral.c
@@ -37,6 +37,7 @@ struct clk_sam9x5_peripheral {
 	u32 id;
 	u32 div;
 	const struct clk_pcr_layout *layout;
+	struct at91_clk_pms pms;
 	bool auto_div;
 	int chg_pid;
 };
@@ -155,10 +156,11 @@ static void clk_sam9x5_peripheral_autodiv(struct clk_sam9x5_peripheral *periph)
 	periph->div = shift;
 }
 
-static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
+static int clk_sam9x5_peripheral_set(struct clk_sam9x5_peripheral *periph,
+				     unsigned int status)
 {
-	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 	unsigned long flags;
+	unsigned int enable = status ? AT91_PMC_PCR_EN : 0;
 
 	if (periph->id < PERIPHERAL_ID_MIN)
 		return 0;
@@ -168,15 +170,21 @@ static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
 		     (periph->id & periph->layout->pid_mask));
 	regmap_update_bits(periph->regmap, periph->layout->offset,
 			   periph->layout->div_mask | periph->layout->cmd |
-			   AT91_PMC_PCR_EN,
+			   enable,
 			   field_prep(periph->layout->div_mask, periph->div) |
-			   periph->layout->cmd |
-			   AT91_PMC_PCR_EN);
+			   periph->layout->cmd | enable);
 	spin_unlock_irqrestore(periph->lock, flags);
 
 	return 0;
 }
 
+static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
+{
+	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+
+	return clk_sam9x5_peripheral_set(periph, 1);
+}
+
 static void clk_sam9x5_peripheral_disable(struct clk_hw *hw)
 {
 	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
@@ -393,6 +401,23 @@ static int clk_sam9x5_peripheral_set_rate(struct clk_hw *hw,
 	return -EINVAL;
 }
 
+static int clk_sam9x5_peripheral_save_context(struct clk_hw *hw)
+{
+	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+
+	periph->pms.status = clk_sam9x5_peripheral_is_enabled(hw);
+
+	return 0;
+}
+
+static void clk_sam9x5_peripheral_restore_context(struct clk_hw *hw)
+{
+	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+
+	if (periph->pms.status)
+		clk_sam9x5_peripheral_set(periph, periph->pms.status);
+}
+
 static const struct clk_ops sam9x5_peripheral_ops = {
 	.enable = clk_sam9x5_peripheral_enable,
 	.disable = clk_sam9x5_peripheral_disable,
@@ -400,6 +425,8 @@ static const struct clk_ops sam9x5_peripheral_ops = {
 	.recalc_rate = clk_sam9x5_peripheral_recalc_rate,
 	.round_rate = clk_sam9x5_peripheral_round_rate,
 	.set_rate = clk_sam9x5_peripheral_set_rate,
+	.save_context = clk_sam9x5_peripheral_save_context,
+	.restore_context = clk_sam9x5_peripheral_restore_context,
 };
 
 static const struct clk_ops sam9x5_peripheral_chg_ops = {
@@ -409,6 +436,8 @@ static const struct clk_ops sam9x5_peripheral_chg_ops = {
 	.recalc_rate = clk_sam9x5_peripheral_recalc_rate,
 	.determine_rate = clk_sam9x5_peripheral_determine_rate,
 	.set_rate = clk_sam9x5_peripheral_set_rate,
+	.save_context = clk_sam9x5_peripheral_save_context,
+	.restore_context = clk_sam9x5_peripheral_restore_context,
 };
 
 struct clk_hw * __init
@@ -460,7 +489,6 @@ at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock,
 		hw = ERR_PTR(ret);
 	} else {
 		clk_sam9x5_peripheral_autodiv(periph);
-		pmc_register_id(id);
 	}
 
 	return hw;
diff --git a/drivers/clk/at91/clk-pll.c b/drivers/clk/at91/clk-pll.c
index 6ed986d3eee0..249d6a53cedf 100644
--- a/drivers/clk/at91/clk-pll.c
+++ b/drivers/clk/at91/clk-pll.c
@@ -40,6 +40,7 @@ struct clk_pll {
 	u16 mul;
 	const struct clk_pll_layout *layout;
 	const struct clk_pll_characteristics *characteristics;
+	struct at91_clk_pms pms;
 };
 
 static inline bool clk_pll_ready(struct regmap *regmap, int id)
@@ -260,6 +261,42 @@ static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
 	return 0;
 }
 
+static int clk_pll_save_context(struct clk_hw *hw)
+{
+	struct clk_pll *pll = to_clk_pll(hw);
+	struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+
+	pll->pms.parent_rate = clk_hw_get_rate(parent_hw);
+	pll->pms.rate = clk_pll_recalc_rate(&pll->hw, pll->pms.parent_rate);
+	pll->pms.status = clk_pll_ready(pll->regmap, PLL_REG(pll->id));
+
+	return 0;
+}
+
+static void clk_pll_restore_context(struct clk_hw *hw)
+{
+	struct clk_pll *pll = to_clk_pll(hw);
+	unsigned long calc_rate;
+	unsigned int pllr, pllr_out, pllr_count;
+	u8 out = 0;
+
+	if (pll->characteristics->out)
+		out = pll->characteristics->out[pll->range];
+
+	regmap_read(pll->regmap, PLL_REG(pll->id), &pllr);
+
+	calc_rate = (pll->pms.parent_rate / PLL_DIV(pllr)) *
+		     (PLL_MUL(pllr, pll->layout) + 1);
+	pllr_count = (pllr >> PLL_COUNT_SHIFT) & PLL_MAX_COUNT;
+	pllr_out = (pllr >> PLL_OUT_SHIFT) & out;
+
+	if (pll->pms.rate != calc_rate ||
+	    pll->pms.status != clk_pll_ready(pll->regmap, PLL_REG(pll->id)) ||
+	    pllr_count != PLL_MAX_COUNT ||
+	    (out && pllr_out != out))
+		pr_warn("PLLAR was not configured properly by firmware\n");
+}
+
 static const struct clk_ops pll_ops = {
 	.prepare = clk_pll_prepare,
 	.unprepare = clk_pll_unprepare,
@@ -267,6 +304,8 @@ static const struct clk_ops pll_ops = {
 	.recalc_rate = clk_pll_recalc_rate,
 	.round_rate = clk_pll_round_rate,
 	.set_rate = clk_pll_set_rate,
+	.save_context = clk_pll_save_context,
+	.restore_context = clk_pll_restore_context,
 };
 
 struct clk_hw * __init
diff --git a/drivers/clk/at91/clk-programmable.c b/drivers/clk/at91/clk-programmable.c
index fcf8f6a1c2c6..6c4b259d31d3 100644
--- a/drivers/clk/at91/clk-programmable.c
+++ b/drivers/clk/at91/clk-programmable.c
@@ -24,6 +24,7 @@ struct clk_programmable {
 	u32 *mux_table;
 	u8 id;
 	const struct clk_programmable_layout *layout;
+	struct at91_clk_pms pms;
 };
 
 #define to_clk_programmable(hw) container_of(hw, struct clk_programmable, hw)
@@ -177,12 +178,38 @@ static int clk_programmable_set_rate(struct clk_hw *hw, unsigned long rate,
 	return 0;
 }
 
+static int clk_programmable_save_context(struct clk_hw *hw)
+{
+	struct clk_programmable *prog = to_clk_programmable(hw);
+	struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+
+	prog->pms.parent = clk_programmable_get_parent(hw);
+	prog->pms.parent_rate = clk_hw_get_rate(parent_hw);
+	prog->pms.rate = clk_programmable_recalc_rate(hw, prog->pms.parent_rate);
+
+	return 0;
+}
+
+static void clk_programmable_restore_context(struct clk_hw *hw)
+{
+	struct clk_programmable *prog = to_clk_programmable(hw);
+	int ret;
+
+	ret = clk_programmable_set_parent(hw, prog->pms.parent);
+	if (ret)
+		return;
+
+	clk_programmable_set_rate(hw, prog->pms.rate, prog->pms.parent_rate);
+}
+
 static const struct clk_ops programmable_ops = {
 	.recalc_rate = clk_programmable_recalc_rate,
 	.determine_rate = clk_programmable_determine_rate,
 	.get_parent = clk_programmable_get_parent,
 	.set_parent = clk_programmable_set_parent,
 	.set_rate = clk_programmable_set_rate,
+	.save_context = clk_programmable_save_context,
+	.restore_context = clk_programmable_restore_context,
 };
 
 struct clk_hw * __init
@@ -221,8 +248,6 @@ at91_clk_register_programmable(struct regmap *regmap,
 	if (ret) {
 		kfree(prog);
 		hw = ERR_PTR(ret);
-	} else {
-		pmc_register_pck(id);
 	}
 
 	return hw;
diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c
index 34e3ab13741a..7020d3bf6e13 100644
--- a/drivers/clk/at91/clk-sam9x60-pll.c
+++ b/drivers/clk/at91/clk-sam9x60-pll.c
@@ -38,12 +38,14 @@ struct sam9x60_pll_core {
 
 struct sam9x60_frac {
 	struct sam9x60_pll_core core;
+	struct at91_clk_pms pms;
 	u32 frac;
 	u16 mul;
 };
 
 struct sam9x60_div {
 	struct sam9x60_pll_core core;
+	struct at91_clk_pms pms;
 	u8 div;
 };
 
@@ -75,9 +77,8 @@ static unsigned long sam9x60_frac_pll_recalc_rate(struct clk_hw *hw,
 		((u64)parent_rate * frac->frac >> 22));
 }
 
-static int sam9x60_frac_pll_prepare(struct clk_hw *hw)
+static int sam9x60_frac_pll_set(struct sam9x60_pll_core *core)
 {
-	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
 	struct sam9x60_frac *frac = to_sam9x60_frac(core);
 	struct regmap *regmap = core->regmap;
 	unsigned int val, cfrac, cmul;
@@ -141,6 +142,13 @@ static int sam9x60_frac_pll_prepare(struct clk_hw *hw)
 	return 0;
 }
 
+static int sam9x60_frac_pll_prepare(struct clk_hw *hw)
+{
+	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+
+	return sam9x60_frac_pll_set(core);
+}
+
 static void sam9x60_frac_pll_unprepare(struct clk_hw *hw)
 {
 	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
@@ -280,6 +288,25 @@ static int sam9x60_frac_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate,
 	return ret;
 }
 
+static int sam9x60_frac_pll_save_context(struct clk_hw *hw)
+{
+	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+	struct sam9x60_frac *frac = to_sam9x60_frac(core);
+
+	frac->pms.status = sam9x60_pll_ready(core->regmap, core->id);
+
+	return 0;
+}
+
+static void sam9x60_frac_pll_restore_context(struct clk_hw *hw)
+{
+	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+	struct sam9x60_frac *frac = to_sam9x60_frac(core);
+
+	if (frac->pms.status)
+		sam9x60_frac_pll_set(core);
+}
+
 static const struct clk_ops sam9x60_frac_pll_ops = {
 	.prepare = sam9x60_frac_pll_prepare,
 	.unprepare = sam9x60_frac_pll_unprepare,
@@ -287,6 +314,8 @@ static const struct clk_ops sam9x60_frac_pll_ops = {
 	.recalc_rate = sam9x60_frac_pll_recalc_rate,
 	.round_rate = sam9x60_frac_pll_round_rate,
 	.set_rate = sam9x60_frac_pll_set_rate,
+	.save_context = sam9x60_frac_pll_save_context,
+	.restore_context = sam9x60_frac_pll_restore_context,
 };
 
 static const struct clk_ops sam9x60_frac_pll_ops_chg = {
@@ -296,11 +325,12 @@ static const struct clk_ops sam9x60_frac_pll_ops_chg = {
 	.recalc_rate = sam9x60_frac_pll_recalc_rate,
 	.round_rate = sam9x60_frac_pll_round_rate,
 	.set_rate = sam9x60_frac_pll_set_rate_chg,
+	.save_context = sam9x60_frac_pll_save_context,
+	.restore_context = sam9x60_frac_pll_restore_context,
 };
 
-static int sam9x60_div_pll_prepare(struct clk_hw *hw)
+static int sam9x60_div_pll_set(struct sam9x60_pll_core *core)
 {
-	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
 	struct sam9x60_div *div = to_sam9x60_div(core);
 	struct regmap *regmap = core->regmap;
 	unsigned long flags;
@@ -334,6 +364,13 @@ static int sam9x60_div_pll_prepare(struct clk_hw *hw)
 	return 0;
 }
 
+static int sam9x60_div_pll_prepare(struct clk_hw *hw)
+{
+	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+
+	return sam9x60_div_pll_set(core);
+}
+
 static void sam9x60_div_pll_unprepare(struct clk_hw *hw)
 {
 	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
@@ -482,6 +519,25 @@ static int sam9x60_div_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate,
 	return 0;
 }
 
+static int sam9x60_div_pll_save_context(struct clk_hw *hw)
+{
+	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+	struct sam9x60_div *div = to_sam9x60_div(core);
+
+	div->pms.status = sam9x60_div_pll_is_prepared(hw);
+
+	return 0;
+}
+
+static void sam9x60_div_pll_restore_context(struct clk_hw *hw)
+{
+	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+	struct sam9x60_div *div = to_sam9x60_div(core);
+
+	if (div->pms.status)
+		sam9x60_div_pll_set(core);
+}
+
 static const struct clk_ops sam9x60_div_pll_ops = {
 	.prepare = sam9x60_div_pll_prepare,
 	.unprepare = sam9x60_div_pll_unprepare,
@@ -489,6 +545,8 @@ static const struct clk_ops sam9x60_div_pll_ops = {
 	.recalc_rate = sam9x60_div_pll_recalc_rate,
 	.round_rate = sam9x60_div_pll_round_rate,
 	.set_rate = sam9x60_div_pll_set_rate,
+	.save_context = sam9x60_div_pll_save_context,
+	.restore_context = sam9x60_div_pll_restore_context,
 };
 
 static const struct clk_ops sam9x60_div_pll_ops_chg = {
@@ -498,6 +556,8 @@ static const struct clk_ops sam9x60_div_pll_ops_chg = {
 	.recalc_rate = sam9x60_div_pll_recalc_rate,
 	.round_rate = sam9x60_div_pll_round_rate,
 	.set_rate = sam9x60_div_pll_set_rate_chg,
+	.save_context = sam9x60_div_pll_save_context,
+	.restore_context = sam9x60_div_pll_restore_context,
 };
 
 struct clk_hw * __init
diff --git a/drivers/clk/at91/clk-system.c b/drivers/clk/at91/clk-system.c
index f83ec0de86c3..80720fd1a9cf 100644
--- a/drivers/clk/at91/clk-system.c
+++ b/drivers/clk/at91/clk-system.c
@@ -20,6 +20,7 @@
 struct clk_system {
 	struct clk_hw hw;
 	struct regmap *regmap;
+	struct at91_clk_pms pms;
 	u8 id;
 };
 
@@ -77,10 +78,29 @@ static int clk_system_is_prepared(struct clk_hw *hw)
 	return !!(status & (1 << sys->id));
 }
 
+static int clk_system_save_context(struct clk_hw *hw)
+{
+	struct clk_system *sys = to_clk_system(hw);
+
+	sys->pms.status = clk_system_is_prepared(hw);
+
+	return 0;
+}
+
+static void clk_system_restore_context(struct clk_hw *hw)
+{
+	struct clk_system *sys = to_clk_system(hw);
+
+	if (sys->pms.status)
+		clk_system_prepare(&sys->hw);
+}
+
 static const struct clk_ops system_ops = {
 	.prepare = clk_system_prepare,
 	.unprepare = clk_system_unprepare,
 	.is_prepared = clk_system_is_prepared,
+	.save_context = clk_system_save_context,
+	.restore_context = clk_system_restore_context,
 };
 
 struct clk_hw * __init
diff --git a/drivers/clk/at91/clk-usb.c b/drivers/clk/at91/clk-usb.c
index 31d5c45e30d7..b0696a928aa9 100644
--- a/drivers/clk/at91/clk-usb.c
+++ b/drivers/clk/at91/clk-usb.c
@@ -24,6 +24,7 @@
 struct at91sam9x5_clk_usb {
 	struct clk_hw hw;
 	struct regmap *regmap;
+	struct at91_clk_pms pms;
 	u32 usbs_mask;
 	u8 num_parents;
 };
@@ -148,12 +149,38 @@ static int at91sam9x5_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
 	return 0;
 }
 
+static int at91sam9x5_usb_save_context(struct clk_hw *hw)
+{
+	struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+	struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+
+	usb->pms.parent = at91sam9x5_clk_usb_get_parent(hw);
+	usb->pms.parent_rate = clk_hw_get_rate(parent_hw);
+	usb->pms.rate = at91sam9x5_clk_usb_recalc_rate(hw, usb->pms.parent_rate);
+
+	return 0;
+}
+
+static void at91sam9x5_usb_restore_context(struct clk_hw *hw)
+{
+	struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+	int ret;
+
+	ret = at91sam9x5_clk_usb_set_parent(hw, usb->pms.parent);
+	if (ret)
+		return;
+
+	at91sam9x5_clk_usb_set_rate(hw, usb->pms.rate, usb->pms.parent_rate);
+}
+
 static const struct clk_ops at91sam9x5_usb_ops = {
 	.recalc_rate = at91sam9x5_clk_usb_recalc_rate,
 	.determine_rate = at91sam9x5_clk_usb_determine_rate,
 	.get_parent = at91sam9x5_clk_usb_get_parent,
 	.set_parent = at91sam9x5_clk_usb_set_parent,
 	.set_rate = at91sam9x5_clk_usb_set_rate,
+	.save_context = at91sam9x5_usb_save_context,
+	.restore_context = at91sam9x5_usb_restore_context,
 };
 
 static int at91sam9n12_clk_usb_enable(struct clk_hw *hw)
diff --git a/drivers/clk/at91/clk-utmi.c b/drivers/clk/at91/clk-utmi.c
index df9f3fc3b6a6..a22c10d9a1b9 100644
--- a/drivers/clk/at91/clk-utmi.c
+++ b/drivers/clk/at91/clk-utmi.c
@@ -23,6 +23,7 @@ struct clk_utmi {
 	struct clk_hw hw;
 	struct regmap *regmap_pmc;
 	struct regmap *regmap_sfr;
+	struct at91_clk_pms pms;
 };
 
 #define to_clk_utmi(hw) container_of(hw, struct clk_utmi, hw)
@@ -113,11 +114,30 @@ static unsigned long clk_utmi_recalc_rate(struct clk_hw *hw,
 	return UTMI_RATE;
 }
 
+static int clk_utmi_save_context(struct clk_hw *hw)
+{
+	struct clk_utmi *utmi = to_clk_utmi(hw);
+
+	utmi->pms.status = clk_utmi_is_prepared(hw);
+
+	return 0;
+}
+
+static void clk_utmi_restore_context(struct clk_hw *hw)
+{
+	struct clk_utmi *utmi = to_clk_utmi(hw);
+
+	if (utmi->pms.status)
+		clk_utmi_prepare(hw);
+}
+
 static const struct clk_ops utmi_ops = {
 	.prepare = clk_utmi_prepare,
 	.unprepare = clk_utmi_unprepare,
 	.is_prepared = clk_utmi_is_prepared,
 	.recalc_rate = clk_utmi_recalc_rate,
+	.save_context = clk_utmi_save_context,
+	.restore_context = clk_utmi_restore_context,
 };
 
 static struct clk_hw * __init
@@ -232,10 +252,29 @@ static int clk_utmi_sama7g5_is_prepared(struct clk_hw *hw)
 	return 0;
 }
 
+static int clk_utmi_sama7g5_save_context(struct clk_hw *hw)
+{
+	struct clk_utmi *utmi = to_clk_utmi(hw);
+
+	utmi->pms.status = clk_utmi_sama7g5_is_prepared(hw);
+
+	return 0;
+}
+
+static void clk_utmi_sama7g5_restore_context(struct clk_hw *hw)
+{
+	struct clk_utmi *utmi = to_clk_utmi(hw);
+
+	if (utmi->pms.status)
+		clk_utmi_sama7g5_prepare(hw);
+}
+
 static const struct clk_ops sama7g5_utmi_ops = {
 	.prepare = clk_utmi_sama7g5_prepare,
 	.is_prepared = clk_utmi_sama7g5_is_prepared,
 	.recalc_rate = clk_utmi_recalc_rate,
+	.save_context = clk_utmi_sama7g5_save_context,
+	.restore_context = clk_utmi_sama7g5_restore_context,
 };
 
 struct clk_hw * __init
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index b40035b011d0..b2806946a77a 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -3,6 +3,7 @@
  *  Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
  */
 
+#include <linux/clk.h>
 #include <linux/clk-provider.h>
 #include <linux/clkdev.h>
 #include <linux/clk/at91_pmc.h>
@@ -14,8 +15,6 @@
 
 #include <asm/proc-fns.h>
 
-#include <dt-bindings/clock/at91.h>
-
 #include "pmc.h"
 
 #define PMC_MAX_IDS 128
@@ -111,147 +110,19 @@ struct pmc_data *pmc_data_allocate(unsigned int ncore, unsigned int nsystem,
 }
 
 #ifdef CONFIG_PM
-static struct regmap *pmcreg;
-
-static u8 registered_ids[PMC_MAX_IDS];
-static u8 registered_pcks[PMC_MAX_PCKS];
-
-static struct
-{
-	u32 scsr;
-	u32 pcsr0;
-	u32 uckr;
-	u32 mor;
-	u32 mcfr;
-	u32 pllar;
-	u32 mckr;
-	u32 usb;
-	u32 imr;
-	u32 pcsr1;
-	u32 pcr[PMC_MAX_IDS];
-	u32 audio_pll0;
-	u32 audio_pll1;
-	u32 pckr[PMC_MAX_PCKS];
-} pmc_cache;
-
-/*
- * As Peripheral ID 0 is invalid on AT91 chips, the identifier is stored
- * without alteration in the table, and 0 is for unused clocks.
- */
-void pmc_register_id(u8 id)
-{
-	int i;
-
-	for (i = 0; i < PMC_MAX_IDS; i++) {
-		if (registered_ids[i] == 0) {
-			registered_ids[i] = id;
-			break;
-		}
-		if (registered_ids[i] == id)
-			break;
-	}
-}
-
-/*
- * As Programmable Clock 0 is valid on AT91 chips, there is an offset
- * of 1 between the stored value and the real clock ID.
- */
-void pmc_register_pck(u8 pck)
-{
-	int i;
-
-	for (i = 0; i < PMC_MAX_PCKS; i++) {
-		if (registered_pcks[i] == 0) {
-			registered_pcks[i] = pck + 1;
-			break;
-		}
-		if (registered_pcks[i] == (pck + 1))
-			break;
-	}
-}
-
-static int pmc_suspend(void)
+static int at91_pmc_suspend(void)
 {
-	int i;
-	u8 num;
-
-	regmap_read(pmcreg, AT91_PMC_SCSR, &pmc_cache.scsr);
-	regmap_read(pmcreg, AT91_PMC_PCSR, &pmc_cache.pcsr0);
-	regmap_read(pmcreg, AT91_CKGR_UCKR, &pmc_cache.uckr);
-	regmap_read(pmcreg, AT91_CKGR_MOR, &pmc_cache.mor);
-	regmap_read(pmcreg, AT91_CKGR_MCFR, &pmc_cache.mcfr);
-	regmap_read(pmcreg, AT91_CKGR_PLLAR, &pmc_cache.pllar);
-	regmap_read(pmcreg, AT91_PMC_MCKR, &pmc_cache.mckr);
-	regmap_read(pmcreg, AT91_PMC_USB, &pmc_cache.usb);
-	regmap_read(pmcreg, AT91_PMC_IMR, &pmc_cache.imr);
-	regmap_read(pmcreg, AT91_PMC_PCSR1, &pmc_cache.pcsr1);
-
-	for (i = 0; registered_ids[i]; i++) {
-		regmap_write(pmcreg, AT91_PMC_PCR,
-			     (registered_ids[i] & AT91_PMC_PCR_PID_MASK));
-		regmap_read(pmcreg, AT91_PMC_PCR,
-			    &pmc_cache.pcr[registered_ids[i]]);
-	}
-	for (i = 0; registered_pcks[i]; i++) {
-		num = registered_pcks[i] - 1;
-		regmap_read(pmcreg, AT91_PMC_PCKR(num), &pmc_cache.pckr[num]);
-	}
-
-	return 0;
+	return clk_save_context();
 }
 
-static bool pmc_ready(unsigned int mask)
+static void at91_pmc_resume(void)
 {
-	unsigned int status;
-
-	regmap_read(pmcreg, AT91_PMC_SR, &status);
-
-	return ((status & mask) == mask) ? 1 : 0;
-}
-
-static void pmc_resume(void)
-{
-	int i;
-	u8 num;
-	u32 tmp;
-	u32 mask = AT91_PMC_MCKRDY | AT91_PMC_LOCKA;
-
-	regmap_read(pmcreg, AT91_PMC_MCKR, &tmp);
-	if (pmc_cache.mckr != tmp)
-		pr_warn("MCKR was not configured properly by the firmware\n");
-	regmap_read(pmcreg, AT91_CKGR_PLLAR, &tmp);
-	if (pmc_cache.pllar != tmp)
-		pr_warn("PLLAR was not configured properly by the firmware\n");
-
-	regmap_write(pmcreg, AT91_PMC_SCER, pmc_cache.scsr);
-	regmap_write(pmcreg, AT91_PMC_PCER, pmc_cache.pcsr0);
-	regmap_write(pmcreg, AT91_CKGR_UCKR, pmc_cache.uckr);
-	regmap_write(pmcreg, AT91_CKGR_MOR, pmc_cache.mor);
-	regmap_write(pmcreg, AT91_CKGR_MCFR, pmc_cache.mcfr);
-	regmap_write(pmcreg, AT91_PMC_USB, pmc_cache.usb);
-	regmap_write(pmcreg, AT91_PMC_IMR, pmc_cache.imr);
-	regmap_write(pmcreg, AT91_PMC_PCER1, pmc_cache.pcsr1);
-
-	for (i = 0; registered_ids[i]; i++) {
-		regmap_write(pmcreg, AT91_PMC_PCR,
-			     pmc_cache.pcr[registered_ids[i]] |
-			     AT91_PMC_PCR_CMD);
-	}
-	for (i = 0; registered_pcks[i]; i++) {
-		num = registered_pcks[i] - 1;
-		regmap_write(pmcreg, AT91_PMC_PCKR(num), pmc_cache.pckr[num]);
-	}
-
-	if (pmc_cache.uckr & AT91_PMC_UPLLEN)
-		mask |= AT91_PMC_LOCKU;
-
-	while (!pmc_ready(mask))
-		cpu_relax();
+	clk_restore_context();
 }
 
 static struct syscore_ops pmc_syscore_ops = {
-	.suspend = pmc_suspend,
-	.resume = pmc_resume,
+	.suspend = at91_pmc_suspend,
+	.resume = at91_pmc_resume,
 };
 
 static const struct of_device_id sama5d2_pmc_dt_ids[] = {
@@ -271,11 +142,7 @@ static int __init pmc_register_ops(void)
 		of_node_put(np);
 		return -ENODEV;
 	}
-
-	pmcreg = device_node_to_regmap(np);
 	of_node_put(np);
-	if (IS_ERR(pmcreg))
-		return PTR_ERR(pmcreg);
 
 	register_syscore_ops(&pmc_syscore_ops);
 
diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index a49076c804a9..45df094498ce 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -13,6 +13,8 @@
 #include <linux/regmap.h>
 #include <linux/spinlock.h>
 
+#include <dt-bindings/clock/at91.h>
+
 extern spinlock_t pmc_pcr_lock;
 
 struct pmc_data {
@@ -98,6 +100,20 @@ struct clk_pcr_layout {
 	u32 pid_mask;
 };
 
+/**
+ * struct at91_clk_pms - Power management state for AT91 clock
+ * @rate: clock rate
+ * @parent_rate: clock parent rate
+ * @status: clock status (enabled or disabled)
+ * @parent: clock parent index
+ */
+struct at91_clk_pms {
+	unsigned long rate;
+	unsigned long parent_rate;
+	unsigned int status;
+	unsigned int parent;
+};
+
 #define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1))
 #define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask))
 
@@ -248,12 +264,4 @@ struct clk_hw * __init
 at91_clk_sama7g5_register_utmi(struct regmap *regmap, const char *name,
 			       const char *parent_name);
 
-#ifdef CONFIG_PM
-void pmc_register_id(u8 id);
-void pmc_register_pck(u8 pck);
-#else
-static inline void pmc_register_id(u8 id) {}
-static inline void pmc_register_pck(u8 pck) {}
-#endif
-
 #endif /* __PMC_H_ */
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 02/15] clk: at91: pmc: execute suspend/resume only for backup mode
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
  2021-10-11 11:27 ` [PATCH v5 01/15] clk: at91: re-factor clocks suspend/resume Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  7:51   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 03/15] clk: at91: sama7g5: add securam's peripheral clock Claudiu Beznea
                   ` (13 subsequent siblings)
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

Before going to backup mode architecture specific PM code sets the first
word in securam (file arch/arm/mach-at91/pm.c, function at91_pm_begin()).
Thus take this into account when suspending/resuming clocks. This will
avoid executing unnecessary instructions when suspending to non backup
modes.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/pmc.c | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index b2806946a77a..517973062719 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -8,6 +8,7 @@
 #include <linux/clkdev.h>
 #include <linux/clk/at91_pmc.h>
 #include <linux/of.h>
+#include <linux/of_address.h>
 #include <linux/mfd/syscon.h>
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
@@ -110,13 +111,35 @@ struct pmc_data *pmc_data_allocate(unsigned int ncore, unsigned int nsystem,
 }
 
 #ifdef CONFIG_PM
+
+/* Address in SECURAM that say if we suspend to backup mode. */
+static void __iomem *at91_pmc_backup_suspend;
+
 static int at91_pmc_suspend(void)
 {
+	unsigned int backup;
+
+	if (!at91_pmc_backup_suspend)
+		return 0;
+
+	backup = readl_relaxed(at91_pmc_backup_suspend);
+	if (!backup)
+		return 0;
+
 	return clk_save_context();
 }
 
 static void at91_pmc_resume(void)
 {
+	unsigned int backup;
+
+	if (!at91_pmc_backup_suspend)
+		return;
+
+	backup = readl_relaxed(at91_pmc_backup_suspend);
+	if (!backup)
+		return;
+
 	clk_restore_context();
 }
 
@@ -144,6 +167,22 @@ static int __init pmc_register_ops(void)
 	}
 	of_node_put(np);
 
+	np = of_find_compatible_node(NULL, NULL, "atmel,sama5d2-securam");
+	if (!np)
+		return -ENODEV;
+
+	if (!of_device_is_available(np)) {
+		of_node_put(np);
+		return -ENODEV;
+	}
+	of_node_put(np);
+
+	at91_pmc_backup_suspend = of_iomap(np, 0);
+	if (!at91_pmc_backup_suspend) {
+		pr_warn("%s(): unable to map securam\n", __func__);
+		return -ENOMEM;
+	}
+
 	register_syscore_ops(&pmc_syscore_ops);
 
 	return 0;
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 03/15] clk: at91: sama7g5: add securam's peripheral clock
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
  2021-10-11 11:27 ` [PATCH v5 01/15] clk: at91: re-factor clocks suspend/resume Claudiu Beznea
  2021-10-11 11:27 ` [PATCH v5 02/15] clk: at91: pmc: execute suspend/resume only for backup mode Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-11 11:27 ` [PATCH v5 04/15] clk: at91: clk-master: add register definition for sama7g5's master clock Claudiu Beznea
                   ` (12 subsequent siblings)
  15 siblings, 0 replies; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

Add SECURAM's peripheral clock.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>
---
 drivers/clk/at91/sama7g5.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c
index cf8c079aa086..970135e19a75 100644
--- a/drivers/clk/at91/sama7g5.c
+++ b/drivers/clk/at91/sama7g5.c
@@ -377,6 +377,7 @@ static const struct {
 	u8 id;
 } sama7g5_periphck[] = {
 	{ .n = "pioA_clk",	.p = "mck0", .id = 11, },
+	{ .n = "securam_clk",	.p = "mck0", .id = 18, },
 	{ .n = "sfr_clk",	.p = "mck1", .id = 19, },
 	{ .n = "hsmc_clk",	.p = "mck1", .id = 21, },
 	{ .n = "xdmac0_clk",	.p = "mck1", .id = 22, },
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 04/15] clk: at91: clk-master: add register definition for sama7g5's master clock
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (2 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 03/15] clk: at91: sama7g5: add securam's peripheral clock Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  7:52   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 05/15] clk: at91: clk-master: improve readability by using local variables Claudiu Beznea
                   ` (11 subsequent siblings)
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

SAMA7G5 has 4 master clocks (MCK1..4) which are controlled though the
register at offset 0x30 (relative to PMC). In the last/first phase of
suspend/resume procedure (which is architecture specific) the parent
of master clocks are changed (via assembly code) for more power saving
(see file arch/arm/mach-at91/pm_suspend.S, macros at91_mckx_ps_enable
and at91_mckx_ps_restore). Thus the macros corresponding to register
at offset 0x30 need to be shared b/w clk-master.c and pm_suspend.S.
commit ec03f18cc222 ("clk: at91: add register definition for sama7g5's
master clock") introduced the proper macros but didn't adapted the
clk-master.c as well. Thus, this commit adapt the clk-master.c to use
the macros introduced in commit ec03f18cc222 ("clk: at91: add register
definition for sama7g5's master clock").

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/clk-master.c | 50 ++++++++++++++++-------------------
 1 file changed, 23 insertions(+), 27 deletions(-)

diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
index f75549fff023..88f7af1bfff6 100644
--- a/drivers/clk/at91/clk-master.c
+++ b/drivers/clk/at91/clk-master.c
@@ -17,15 +17,7 @@
 #define MASTER_DIV_SHIFT	8
 #define MASTER_DIV_MASK		0x7
 
-#define PMC_MCR			0x30
-#define PMC_MCR_ID_MSK		GENMASK(3, 0)
-#define PMC_MCR_CMD		BIT(7)
-#define PMC_MCR_DIV		GENMASK(10, 8)
-#define PMC_MCR_CSS		GENMASK(20, 16)
 #define PMC_MCR_CSS_SHIFT	(16)
-#define PMC_MCR_EN		BIT(28)
-
-#define PMC_MCR_ID(x)		((x) & PMC_MCR_ID_MSK)
 
 #define MASTER_MAX_ID		4
 
@@ -687,20 +679,22 @@ static void clk_sama7g5_master_set(struct clk_master *master,
 {
 	unsigned long flags;
 	unsigned int val, cparent;
-	unsigned int enable = status ? PMC_MCR_EN : 0;
+	unsigned int enable = status ? AT91_PMC_MCR_V2_EN : 0;
 
 	spin_lock_irqsave(master->lock, flags);
 
-	regmap_write(master->regmap, PMC_MCR, PMC_MCR_ID(master->id));
-	regmap_read(master->regmap, PMC_MCR, &val);
-	regmap_update_bits(master->regmap, PMC_MCR,
-			   enable | PMC_MCR_CSS | PMC_MCR_DIV |
-			   PMC_MCR_CMD | PMC_MCR_ID_MSK,
+	regmap_write(master->regmap, AT91_PMC_MCR_V2,
+		     AT91_PMC_MCR_V2_ID(master->id));
+	regmap_read(master->regmap, AT91_PMC_MCR_V2, &val);
+	regmap_update_bits(master->regmap, AT91_PMC_MCR_V2,
+			   enable | AT91_PMC_MCR_V2_CSS | AT91_PMC_MCR_V2_DIV |
+			   AT91_PMC_MCR_V2_CMD | AT91_PMC_MCR_V2_ID_MSK,
 			   enable | (master->parent << PMC_MCR_CSS_SHIFT) |
 			   (master->div << MASTER_DIV_SHIFT) |
-			   PMC_MCR_CMD | PMC_MCR_ID(master->id));
+			   AT91_PMC_MCR_V2_CMD |
+			   AT91_PMC_MCR_V2_ID(master->id));
 
-	cparent = (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT;
+	cparent = (val & AT91_PMC_MCR_V2_CSS) >> PMC_MCR_CSS_SHIFT;
 
 	/* Wait here only if parent is being changed. */
 	while ((cparent != master->parent) && !clk_master_ready(master))
@@ -725,10 +719,12 @@ static void clk_sama7g5_master_disable(struct clk_hw *hw)
 
 	spin_lock_irqsave(master->lock, flags);
 
-	regmap_write(master->regmap, PMC_MCR, master->id);
-	regmap_update_bits(master->regmap, PMC_MCR,
-			   PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK,
-			   PMC_MCR_CMD | PMC_MCR_ID(master->id));
+	regmap_write(master->regmap, AT91_PMC_MCR_V2, master->id);
+	regmap_update_bits(master->regmap, AT91_PMC_MCR_V2,
+			   AT91_PMC_MCR_V2_EN | AT91_PMC_MCR_V2_CMD |
+			   AT91_PMC_MCR_V2_ID_MSK,
+			   AT91_PMC_MCR_V2_CMD |
+			   AT91_PMC_MCR_V2_ID(master->id));
 
 	spin_unlock_irqrestore(master->lock, flags);
 }
@@ -741,12 +737,12 @@ static int clk_sama7g5_master_is_enabled(struct clk_hw *hw)
 
 	spin_lock_irqsave(master->lock, flags);
 
-	regmap_write(master->regmap, PMC_MCR, master->id);
-	regmap_read(master->regmap, PMC_MCR, &val);
+	regmap_write(master->regmap, AT91_PMC_MCR_V2, master->id);
+	regmap_read(master->regmap, AT91_PMC_MCR_V2, &val);
 
 	spin_unlock_irqrestore(master->lock, flags);
 
-	return !!(val & PMC_MCR_EN);
+	return !!(val & AT91_PMC_MCR_V2_EN);
 }
 
 static int clk_sama7g5_master_set_rate(struct clk_hw *hw, unsigned long rate,
@@ -842,10 +838,10 @@ at91_clk_sama7g5_register_master(struct regmap *regmap,
 	master->mux_table = mux_table;
 
 	spin_lock_irqsave(master->lock, flags);
-	regmap_write(master->regmap, PMC_MCR, master->id);
-	regmap_read(master->regmap, PMC_MCR, &val);
-	master->parent = (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT;
-	master->div = (val & PMC_MCR_DIV) >> MASTER_DIV_SHIFT;
+	regmap_write(master->regmap, AT91_PMC_MCR_V2, master->id);
+	regmap_read(master->regmap, AT91_PMC_MCR_V2, &val);
+	master->parent = (val & AT91_PMC_MCR_V2_CSS) >> PMC_MCR_CSS_SHIFT;
+	master->div = (val & AT91_PMC_MCR_V2_DIV) >> MASTER_DIV_SHIFT;
 	spin_unlock_irqrestore(master->lock, flags);
 
 	hw = &master->hw;
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 05/15] clk: at91: clk-master: improve readability by using local variables
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (3 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 04/15] clk: at91: clk-master: add register definition for sama7g5's master clock Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  7:53   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 06/15] clk: at91: pmc: add sama7g5 to the list of available pmcs Claudiu Beznea
                   ` (10 subsequent siblings)
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

Improve readability in clk_sama7g5_master_set() by using local
variables.

Suggested-by: Nicolas Ferre <nicolas.ferre@microchip.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/clk-master.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
index 88f7af1bfff6..9a2c8e64cacf 100644
--- a/drivers/clk/at91/clk-master.c
+++ b/drivers/clk/at91/clk-master.c
@@ -680,6 +680,8 @@ static void clk_sama7g5_master_set(struct clk_master *master,
 	unsigned long flags;
 	unsigned int val, cparent;
 	unsigned int enable = status ? AT91_PMC_MCR_V2_EN : 0;
+	unsigned int parent = master->parent << PMC_MCR_CSS_SHIFT;
+	unsigned int div = master->div << MASTER_DIV_SHIFT;
 
 	spin_lock_irqsave(master->lock, flags);
 
@@ -689,9 +691,7 @@ static void clk_sama7g5_master_set(struct clk_master *master,
 	regmap_update_bits(master->regmap, AT91_PMC_MCR_V2,
 			   enable | AT91_PMC_MCR_V2_CSS | AT91_PMC_MCR_V2_DIV |
 			   AT91_PMC_MCR_V2_CMD | AT91_PMC_MCR_V2_ID_MSK,
-			   enable | (master->parent << PMC_MCR_CSS_SHIFT) |
-			   (master->div << MASTER_DIV_SHIFT) |
-			   AT91_PMC_MCR_V2_CMD |
+			   enable | parent | div | AT91_PMC_MCR_V2_CMD |
 			   AT91_PMC_MCR_V2_ID(master->id));
 
 	cparent = (val & AT91_PMC_MCR_V2_CSS) >> PMC_MCR_CSS_SHIFT;
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 06/15] clk: at91: pmc: add sama7g5 to the list of available pmcs
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (4 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 05/15] clk: at91: clk-master: improve readability by using local variables Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  7:54   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 07/15] clk: at91: sam9x60-pll: use DIV_ROUND_CLOSEST_ULL Claudiu Beznea
                   ` (9 subsequent siblings)
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

Add SAMA7G5 to the list of available PMCs such that the suspend/resume
code for clocks to be used on backup mode.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/pmc.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index 517973062719..5aa9c1f1c886 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -148,8 +148,9 @@ static struct syscore_ops pmc_syscore_ops = {
 	.resume = at91_pmc_resume,
 };
 
-static const struct of_device_id sama5d2_pmc_dt_ids[] = {
+static const struct of_device_id pmc_dt_ids[] = {
 	{ .compatible = "atmel,sama5d2-pmc" },
+	{ .compatible = "microchip,sama7g5-pmc", },
 	{ /* sentinel */ }
 };
 
@@ -157,7 +158,7 @@ static int __init pmc_register_ops(void)
 {
 	struct device_node *np;
 
-	np = of_find_matching_node(NULL, sama5d2_pmc_dt_ids);
+	np = of_find_matching_node(NULL, pmc_dt_ids);
 	if (!np)
 		return -ENODEV;
 
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 07/15] clk: at91: sam9x60-pll: use DIV_ROUND_CLOSEST_ULL
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (5 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 06/15] clk: at91: pmc: add sama7g5 to the list of available pmcs Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  7:54   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 08/15] clk: at91: clk-master: check if div or pres is zero Claudiu Beznea
                   ` (8 subsequent siblings)
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

Use DIV_ROUND_CLOSEST_ULL() to avoid any inconsistency b/w the rate
computed in sam9x60_frac_pll_recalc_rate() and the one computed in
sam9x60_frac_pll_compute_mul_frac().

Fixes: 43b1bb4a9b3e1 ("clk: at91: clk-sam9x60-pll: re-factor to support plls with multiple outputs")
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/clk-sam9x60-pll.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c
index 7020d3bf6e13..a73d7c96ce1d 100644
--- a/drivers/clk/at91/clk-sam9x60-pll.c
+++ b/drivers/clk/at91/clk-sam9x60-pll.c
@@ -73,8 +73,8 @@ static unsigned long sam9x60_frac_pll_recalc_rate(struct clk_hw *hw,
 	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
 	struct sam9x60_frac *frac = to_sam9x60_frac(core);
 
-	return (parent_rate * (frac->mul + 1) +
-		((u64)parent_rate * frac->frac >> 22));
+	return parent_rate * (frac->mul + 1) +
+		DIV_ROUND_CLOSEST_ULL((u64)parent_rate * frac->frac, (1 << 22));
 }
 
 static int sam9x60_frac_pll_set(struct sam9x60_pll_core *core)
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 08/15] clk: at91: clk-master: check if div or pres is zero
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (6 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 07/15] clk: at91: sam9x60-pll: use DIV_ROUND_CLOSEST_ULL Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  7:55   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 09/15] clk: at91: clk-master: mask mckr against layout->mask Claudiu Beznea
                   ` (7 subsequent siblings)
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

Check if div or pres is zero before using it as argument for ffs().
In case div is zero ffs() will return 0 and thus substracting from
zero will lead to invalid values to be setup in registers.

Fixes: 7a110b9107ed8 ("clk: at91: clk-master: re-factor master clock")
Fixes: 75c88143f3b87 ("clk: at91: clk-master: add master clock support for SAMA7G5")
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/clk-master.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
index 9a2c8e64cacf..2093e13b5068 100644
--- a/drivers/clk/at91/clk-master.c
+++ b/drivers/clk/at91/clk-master.c
@@ -344,7 +344,7 @@ static int clk_master_pres_set_rate(struct clk_hw *hw, unsigned long rate,
 
 	else if (pres == 3)
 		pres = MASTER_PRES_MAX;
-	else
+	else if (pres)
 		pres = ffs(pres) - 1;
 
 	spin_lock_irqsave(master->lock, flags);
@@ -757,7 +757,7 @@ static int clk_sama7g5_master_set_rate(struct clk_hw *hw, unsigned long rate,
 
 	if (div == 3)
 		div = MASTER_PRES_MAX;
-	else
+	else if (div)
 		div = ffs(div) - 1;
 
 	spin_lock_irqsave(master->lock, flags);
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 09/15] clk: at91: clk-master: mask mckr against layout->mask
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (7 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 08/15] clk: at91: clk-master: check if div or pres is zero Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  7:56   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 10/15] clk: at91: clk-master: fix prescaler logic Claudiu Beznea
                   ` (6 subsequent siblings)
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

Mask values read/written from/to MCKR against layout->mask as this
mask may be different b/w PMC versions.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/clk-master.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
index 2093e13b5068..6da9ae34313a 100644
--- a/drivers/clk/at91/clk-master.c
+++ b/drivers/clk/at91/clk-master.c
@@ -186,8 +186,8 @@ static int clk_master_div_set_rate(struct clk_hw *hw, unsigned long rate,
 	if (ret)
 		goto unlock;
 
-	tmp = mckr & master->layout->mask;
-	tmp = (tmp >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+	mckr &= master->layout->mask;
+	tmp = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
 	if (tmp == div)
 		goto unlock;
 
@@ -384,6 +384,7 @@ static unsigned long clk_master_pres_recalc_rate(struct clk_hw *hw,
 	regmap_read(master->regmap, master->layout->offset, &val);
 	spin_unlock_irqrestore(master->lock, flags);
 
+	val &= master->layout->mask;
 	pres = (val >> master->layout->pres_shift) & MASTER_PRES_MASK;
 	if (pres == 3 && characteristics->have_div3_pres)
 		pres = 3;
@@ -403,6 +404,8 @@ static u8 clk_master_pres_get_parent(struct clk_hw *hw)
 	regmap_read(master->regmap, master->layout->offset, &mckr);
 	spin_unlock_irqrestore(master->lock, flags);
 
+	mckr &= master->layout->mask;
+
 	return mckr & AT91_PMC_CSS;
 }
 
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 10/15] clk: at91: clk-master: fix prescaler logic
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (8 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 09/15] clk: at91: clk-master: mask mckr against layout->mask Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  7:58   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 11/15] clk: at91: clk-sam9x60-pll: add notifier for div part of PLL Claudiu Beznea
                   ` (5 subsequent siblings)
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

When prescaler value read from register is MASTER_PRES_MAX it means
that the input clock will be divided by 3. Fix the code to reflect
this.

Fixes: 7a110b9107ed8 ("clk: at91: clk-master: re-factor master clock")
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/clk-master.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
index 6da9ae34313a..e67bcd03a827 100644
--- a/drivers/clk/at91/clk-master.c
+++ b/drivers/clk/at91/clk-master.c
@@ -386,7 +386,7 @@ static unsigned long clk_master_pres_recalc_rate(struct clk_hw *hw,
 
 	val &= master->layout->mask;
 	pres = (val >> master->layout->pres_shift) & MASTER_PRES_MASK;
-	if (pres == 3 && characteristics->have_div3_pres)
+	if (pres == MASTER_PRES_MAX && characteristics->have_div3_pres)
 		pres = 3;
 	else
 		pres = (1 << pres);
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 11/15] clk: at91: clk-sam9x60-pll: add notifier for div part of PLL
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (9 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 10/15] clk: at91: clk-master: fix prescaler logic Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  8:01   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 12/15] clk: at91: clk-master: add notifier for divider Claudiu Beznea
                   ` (4 subsequent siblings)
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

SAM9X60's PLL which is also part of SAMA7G5 is composed of 2 parts:
one fractional part and one divider. On SAMA7G5 the CPU PLL could be
changed at run-time to implement DVFS. The hardware clock tree on
SAMA7G5 for CPU PLL is as follows:

                       +---- div1 ----------------> cpuck
                       |
FRAC PLL ---> DIV PLL -+-> prescaler ---> div0 ---> mck0

The div1 block is not implemented in Linux; on prescaler block it has
been discovered a bug on some scenarios and will be removed from Linux
in next commits. Thus, the final clock tree that will be used in Linux
will be as follows:

                       +-----------> cpuck
                       |
FRAC PLL ---> DIV PLL -+-> div0 ---> mck0

It has been proposed in [1] to not introduce a new CPUFreq driver but
to overload the proper clock drivers with proper operation such that
cpufreq-dt to be used. To accomplish this DIV PLL and div0 implement
clock notifiers which applies safe dividers before FRAC PLL is changed.
The current commit treats only the DIV PLL by adding a notifier that
sets a safe divider on PRE_RATE_CHANGE events. The safe divider is
provided by initialization clock code (sama7g5.c). The div0 is treated
in next commits (to keep the changes as clean as possible).

[1] https://lore.kernel.org/lkml/20210105104426.4tmgc2l3vyicwedd@vireshk-i7/

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/clk-sam9x60-pll.c | 102 ++++++++++++++++++++++-------
 drivers/clk/at91/pmc.h             |   3 +-
 drivers/clk/at91/sam9x60.c         |   6 +-
 drivers/clk/at91/sama7g5.c         |  13 +++-
 4 files changed, 95 insertions(+), 29 deletions(-)

diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c
index a73d7c96ce1d..d757003004cb 100644
--- a/drivers/clk/at91/clk-sam9x60-pll.c
+++ b/drivers/clk/at91/clk-sam9x60-pll.c
@@ -5,6 +5,7 @@
  */
 
 #include <linux/bitfield.h>
+#include <linux/clk.h>
 #include <linux/clk-provider.h>
 #include <linux/clkdev.h>
 #include <linux/clk/at91_pmc.h>
@@ -47,12 +48,15 @@ struct sam9x60_div {
 	struct sam9x60_pll_core core;
 	struct at91_clk_pms pms;
 	u8 div;
+	u8 safe_div;
 };
 
 #define to_sam9x60_pll_core(hw)	container_of(hw, struct sam9x60_pll_core, hw)
 #define to_sam9x60_frac(core)	container_of(core, struct sam9x60_frac, core)
 #define to_sam9x60_div(core)	container_of(core, struct sam9x60_div, core)
 
+static struct sam9x60_div *notifier_div;
+
 static inline bool sam9x60_pll_ready(struct regmap *regmap, int id)
 {
 	unsigned int status;
@@ -329,6 +333,26 @@ static const struct clk_ops sam9x60_frac_pll_ops_chg = {
 	.restore_context = sam9x60_frac_pll_restore_context,
 };
 
+/* This function should be called with spinlock acquired. */
+static void sam9x60_div_pll_set_div(struct sam9x60_pll_core *core, u32 div,
+				    bool enable)
+{
+	struct regmap *regmap = core->regmap;
+	u32 ena_msk = enable ? core->layout->endiv_mask : 0;
+	u32 ena_val = enable ? (1 << core->layout->endiv_shift) : 0;
+
+	regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
+			   core->layout->div_mask | ena_msk,
+			   (div << core->layout->div_shift) | ena_val);
+
+	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+			   AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+			   AT91_PMC_PLL_UPDT_UPDATE | core->id);
+
+	while (!sam9x60_pll_ready(regmap, core->id))
+		cpu_relax();
+}
+
 static int sam9x60_div_pll_set(struct sam9x60_pll_core *core)
 {
 	struct sam9x60_div *div = to_sam9x60_div(core);
@@ -346,17 +370,7 @@ static int sam9x60_div_pll_set(struct sam9x60_pll_core *core)
 	if (!!(val & core->layout->endiv_mask) && cdiv == div->div)
 		goto unlock;
 
-	regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
-			   core->layout->div_mask | core->layout->endiv_mask,
-			   (div->div << core->layout->div_shift) |
-			   (1 << core->layout->endiv_shift));
-
-	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
-			   AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
-			   AT91_PMC_PLL_UPDT_UPDATE | core->id);
-
-	while (!sam9x60_pll_ready(regmap, core->id))
-		cpu_relax();
+	sam9x60_div_pll_set_div(core, div->div, 1);
 
 unlock:
 	spin_unlock_irqrestore(core->lock, flags);
@@ -502,16 +516,7 @@ static int sam9x60_div_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate,
 	if (cdiv == div->div)
 		goto unlock;
 
-	regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
-			   core->layout->div_mask,
-			   (div->div << core->layout->div_shift));
-
-	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
-			   AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
-			   AT91_PMC_PLL_UPDT_UPDATE | core->id);
-
-	while (!sam9x60_pll_ready(regmap, core->id))
-		cpu_relax();
+	sam9x60_div_pll_set_div(core, div->div, 0);
 
 unlock:
 	spin_unlock_irqrestore(core->lock, irqflags);
@@ -538,6 +543,48 @@ static void sam9x60_div_pll_restore_context(struct clk_hw *hw)
 		sam9x60_div_pll_set(core);
 }
 
+static int sam9x60_div_pll_notifier_fn(struct notifier_block *notifier,
+				       unsigned long code, void *data)
+{
+	struct sam9x60_div *div = notifier_div;
+	struct sam9x60_pll_core core = div->core;
+	struct regmap *regmap = core.regmap;
+	unsigned long irqflags;
+	u32 val, cdiv;
+	int ret = NOTIFY_DONE;
+
+	if (code != PRE_RATE_CHANGE)
+		return ret;
+
+	/*
+	 * We switch to safe divider to avoid overclocking of other domains
+	 * feed by us while the frac PLL (our parent) is changed.
+	 */
+	div->div = div->safe_div;
+
+	spin_lock_irqsave(core.lock, irqflags);
+	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+			   core.id);
+	regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
+	cdiv = (val & core.layout->div_mask) >> core.layout->div_shift;
+
+	/* Stop if nothing changed. */
+	if (cdiv == div->safe_div)
+		goto unlock;
+
+	sam9x60_div_pll_set_div(&core, div->div, 0);
+	ret = NOTIFY_OK;
+
+unlock:
+	spin_unlock_irqrestore(core.lock, irqflags);
+
+	return ret;
+}
+
+static struct notifier_block sam9x60_div_pll_notifier = {
+	.notifier_call = sam9x60_div_pll_notifier_fn,
+};
+
 static const struct clk_ops sam9x60_div_pll_ops = {
 	.prepare = sam9x60_div_pll_prepare,
 	.unprepare = sam9x60_div_pll_unprepare,
@@ -647,7 +694,8 @@ struct clk_hw * __init
 sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
 			     const char *name, const char *parent_name, u8 id,
 			     const struct clk_pll_characteristics *characteristics,
-			     const struct clk_pll_layout *layout, u32 flags)
+			     const struct clk_pll_layout *layout, u32 flags,
+			     u32 safe_div)
 {
 	struct sam9x60_div *div;
 	struct clk_hw *hw;
@@ -656,9 +704,13 @@ sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
 	unsigned int val;
 	int ret;
 
-	if (id > PLL_MAX_ID || !lock)
+	/* We only support one changeable PLL. */
+	if (id > PLL_MAX_ID || !lock || (safe_div && notifier_div))
 		return ERR_PTR(-EINVAL);
 
+	if (safe_div >= PLL_DIV_MAX)
+		safe_div = PLL_DIV_MAX - 1;
+
 	div = kzalloc(sizeof(*div), GFP_KERNEL);
 	if (!div)
 		return ERR_PTR(-ENOMEM);
@@ -678,6 +730,7 @@ sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
 	div->core.layout = layout;
 	div->core.regmap = regmap;
 	div->core.lock = lock;
+	div->safe_div = safe_div;
 
 	spin_lock_irqsave(div->core.lock, irqflags);
 
@@ -693,6 +746,9 @@ sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
 	if (ret) {
 		kfree(div);
 		hw = ERR_PTR(ret);
+	} else if (div->safe_div) {
+		notifier_div = div;
+		clk_notifier_register(hw->clk, &sam9x60_div_pll_notifier);
 	}
 
 	return hw;
diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index 45df094498ce..207ecccef29f 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -214,7 +214,8 @@ struct clk_hw * __init
 sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
 			     const char *name, const char *parent_name, u8 id,
 			     const struct clk_pll_characteristics *characteristics,
-			     const struct clk_pll_layout *layout, u32 flags);
+			     const struct clk_pll_layout *layout, u32 flags,
+			     u32 safe_div);
 
 struct clk_hw * __init
 sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock,
diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c
index 5f6fa89571b7..5c264185f261 100644
--- a/drivers/clk/at91/sam9x60.c
+++ b/drivers/clk/at91/sam9x60.c
@@ -242,7 +242,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
 					    * This feeds CPU. It should not
 					    * be disabled.
 					    */
-					  CLK_IS_CRITICAL | CLK_SET_RATE_GATE);
+					  CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0);
 	if (IS_ERR(hw))
 		goto err_free;
 
@@ -260,7 +260,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
 					  &pll_div_layout,
 					  CLK_SET_RATE_GATE |
 					  CLK_SET_PARENT_GATE |
-					  CLK_SET_RATE_PARENT);
+					  CLK_SET_RATE_PARENT, 0);
 	if (IS_ERR(hw))
 		goto err_free;
 
@@ -279,7 +279,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
 	hw = at91_clk_register_master_div(regmap, "masterck_div",
 					  "masterck_pres", &sam9x60_master_layout,
 					  &mck_characteristics, &mck_lock,
-					  CLK_SET_RATE_GATE);
+					  CLK_SET_RATE_GATE, 0);
 	if (IS_ERR(hw))
 		goto err_free;
 
diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c
index 970135e19a75..ae52c10af040 100644
--- a/drivers/clk/at91/sama7g5.c
+++ b/drivers/clk/at91/sama7g5.c
@@ -127,6 +127,8 @@ static const struct clk_pll_characteristics pll_characteristics = {
  * @t:		clock type
  * @f:		clock flags
  * @eid:	export index in sama7g5->chws[] array
+ * @safe_div:	intermediate divider need to be set on PRE_RATE_CHANGE
+ *		notification
  */
 static const struct {
 	const char *n;
@@ -136,6 +138,7 @@ static const struct {
 	unsigned long f;
 	u8 t;
 	u8 eid;
+	u8 safe_div;
 } sama7g5_plls[][PLL_ID_MAX] = {
 	[PLL_ID_CPU] = {
 		{ .n = "cpupll_fracck",
@@ -156,7 +159,12 @@ static const struct {
 		  .t = PLL_TYPE_DIV,
 		   /* This feeds CPU. It should not be disabled. */
 		  .f = CLK_IS_CRITICAL | CLK_SET_RATE_PARENT,
-		  .eid = PMC_CPUPLL, },
+		  .eid = PMC_CPUPLL,
+		  /*
+		   * Safe div=15 should be safe even for switching b/w 1GHz and
+		   * 90MHz (frac pll might go up to 1.2GHz).
+		   */
+		  .safe_div = 15, },
 	},
 
 	[PLL_ID_SYS] = {
@@ -967,7 +975,8 @@ static void __init sama7g5_pmc_setup(struct device_node *np)
 					sama7g5_plls[i][j].p, i,
 					sama7g5_plls[i][j].c,
 					sama7g5_plls[i][j].l,
-					sama7g5_plls[i][j].f);
+					sama7g5_plls[i][j].f,
+					sama7g5_plls[i][j].safe_div);
 				break;
 
 			default:
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 12/15] clk: at91: clk-master: add notifier for divider
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (10 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 11/15] clk: at91: clk-sam9x60-pll: add notifier for div part of PLL Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  8:06   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 13/15] clk: at91: sama7g5: remove prescaler part of master clock Claudiu Beznea
                   ` (3 subsequent siblings)
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

SAMA7G5 supports DVFS by changing cpuck. On SAMA7G5 mck0 shares the same
parent with cpuck as seen in the following clock tree:

                       +----------> cpuck
                       |
FRAC PLL ---> DIV PLL -+-> DIV ---> mck0

mck0 could go b/w 32KHz and 200MHz on SAMA7G5. To avoid mck0 overclocking
while changing FRAC PLL or DIV PLL the commit implements a notifier for
mck0 which applies a safe divider to register (maximum value of the divider
which is 5) on PRE_RATE_CHANGE events (such that changes on PLL to not
overclock mck0) and sets the maximum allowed rate on POST_RATE_CHANGE
events.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/at91rm9200.c  |   2 +-
 drivers/clk/at91/at91sam9260.c |   2 +-
 drivers/clk/at91/at91sam9g45.c |   2 +-
 drivers/clk/at91/at91sam9n12.c |   2 +-
 drivers/clk/at91/at91sam9rl.c  |   2 +-
 drivers/clk/at91/at91sam9x5.c  |   2 +-
 drivers/clk/at91/clk-master.c  | 244 +++++++++++++++++++++++----------
 drivers/clk/at91/dt-compat.c   |   2 +-
 drivers/clk/at91/pmc.h         |   2 +-
 drivers/clk/at91/sama5d2.c     |   2 +-
 drivers/clk/at91/sama5d3.c     |   2 +-
 drivers/clk/at91/sama5d4.c     |   2 +-
 drivers/clk/at91/sama7g5.c     |   2 +-
 13 files changed, 186 insertions(+), 82 deletions(-)

diff --git a/drivers/clk/at91/at91rm9200.c b/drivers/clk/at91/at91rm9200.c
index 428a6f4b9ebc..fff4fdda974f 100644
--- a/drivers/clk/at91/at91rm9200.c
+++ b/drivers/clk/at91/at91rm9200.c
@@ -152,7 +152,7 @@ static void __init at91rm9200_pmc_setup(struct device_node *np)
 					  "masterck_pres",
 					  &at91rm9200_master_layout,
 					  &rm9200_mck_characteristics,
-					  &rm9200_mck_lock, CLK_SET_RATE_GATE);
+					  &rm9200_mck_lock, CLK_SET_RATE_GATE, 0);
 	if (IS_ERR(hw))
 		goto err_free;
 
diff --git a/drivers/clk/at91/at91sam9260.c b/drivers/clk/at91/at91sam9260.c
index b29843bea278..79802f864ee5 100644
--- a/drivers/clk/at91/at91sam9260.c
+++ b/drivers/clk/at91/at91sam9260.c
@@ -429,7 +429,7 @@ static void __init at91sam926x_pmc_setup(struct device_node *np,
 					  &at91rm9200_master_layout,
 					  data->mck_characteristics,
 					  &at91sam9260_mck_lock,
-					  CLK_SET_RATE_GATE);
+					  CLK_SET_RATE_GATE, 0);
 	if (IS_ERR(hw))
 		goto err_free;
 
diff --git a/drivers/clk/at91/at91sam9g45.c b/drivers/clk/at91/at91sam9g45.c
index 15da0dfe3ef2..7ed984f8058c 100644
--- a/drivers/clk/at91/at91sam9g45.c
+++ b/drivers/clk/at91/at91sam9g45.c
@@ -164,7 +164,7 @@ static void __init at91sam9g45_pmc_setup(struct device_node *np)
 					  &at91rm9200_master_layout,
 					  &mck_characteristics,
 					  &at91sam9g45_mck_lock,
-					  CLK_SET_RATE_GATE);
+					  CLK_SET_RATE_GATE, 0);
 	if (IS_ERR(hw))
 		goto err_free;
 
diff --git a/drivers/clk/at91/at91sam9n12.c b/drivers/clk/at91/at91sam9n12.c
index 7fe435f4b46b..63cc58944b00 100644
--- a/drivers/clk/at91/at91sam9n12.c
+++ b/drivers/clk/at91/at91sam9n12.c
@@ -191,7 +191,7 @@ static void __init at91sam9n12_pmc_setup(struct device_node *np)
 					  &at91sam9x5_master_layout,
 					  &mck_characteristics,
 					  &at91sam9n12_mck_lock,
-					  CLK_SET_RATE_GATE);
+					  CLK_SET_RATE_GATE, 0);
 	if (IS_ERR(hw))
 		goto err_free;
 
diff --git a/drivers/clk/at91/at91sam9rl.c b/drivers/clk/at91/at91sam9rl.c
index ecbabf5162bd..4d4faf6c61d8 100644
--- a/drivers/clk/at91/at91sam9rl.c
+++ b/drivers/clk/at91/at91sam9rl.c
@@ -132,7 +132,7 @@ static void __init at91sam9rl_pmc_setup(struct device_node *np)
 					  "masterck_pres",
 					  &at91rm9200_master_layout,
 					  &sam9rl_mck_characteristics,
-					  &sam9rl_mck_lock, CLK_SET_RATE_GATE);
+					  &sam9rl_mck_lock, CLK_SET_RATE_GATE, 0);
 	if (IS_ERR(hw))
 		goto err_free;
 
diff --git a/drivers/clk/at91/at91sam9x5.c b/drivers/clk/at91/at91sam9x5.c
index 5cce48c64ea2..bd8007b4f3e0 100644
--- a/drivers/clk/at91/at91sam9x5.c
+++ b/drivers/clk/at91/at91sam9x5.c
@@ -210,7 +210,7 @@ static void __init at91sam9x5_pmc_setup(struct device_node *np,
 					  "masterck_pres",
 					  &at91sam9x5_master_layout,
 					  &mck_characteristics, &mck_lock,
-					  CLK_SET_RATE_GATE);
+					  CLK_SET_RATE_GATE, 0);
 	if (IS_ERR(hw))
 		goto err_free;
 
diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
index e67bcd03a827..b2d0a7f4f7f9 100644
--- a/drivers/clk/at91/clk-master.c
+++ b/drivers/clk/at91/clk-master.c
@@ -5,6 +5,7 @@
 
 #include <linux/clk-provider.h>
 #include <linux/clkdev.h>
+#include <linux/clk.h>
 #include <linux/clk/at91_pmc.h>
 #include <linux/of.h>
 #include <linux/mfd/syscon.h>
@@ -36,8 +37,12 @@ struct clk_master {
 	u8 id;
 	u8 parent;
 	u8 div;
+	u32 safe_div;
 };
 
+/* MCK div reference to be used by notifier. */
+static struct clk_master *master_div;
+
 static inline bool clk_master_ready(struct clk_master *master)
 {
 	unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY;
@@ -153,107 +158,81 @@ static const struct clk_ops master_div_ops = {
 	.restore_context = clk_master_div_restore_context,
 };
 
-static int clk_master_div_set_rate(struct clk_hw *hw, unsigned long rate,
-				   unsigned long parent_rate)
+/* This function must be called with lock acquired. */
+static int clk_master_div_set(struct clk_master *master,
+			      unsigned long parent_rate, int div)
 {
-	struct clk_master *master = to_clk_master(hw);
 	const struct clk_master_characteristics *characteristics =
 						master->characteristics;
-	unsigned long flags;
-	unsigned int mckr, tmp;
-	int div, i;
+	unsigned long rate = parent_rate;
+	unsigned int max_div = 0, div_index = 0, max_div_index = 0;
+	unsigned int i, mckr, tmp;
 	int ret;
 
-	div = DIV_ROUND_CLOSEST(parent_rate, rate);
-	if (div > ARRAY_SIZE(characteristics->divisors))
-		return -EINVAL;
-
 	for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) {
 		if (!characteristics->divisors[i])
 			break;
 
-		if (div == characteristics->divisors[i]) {
-			div = i;
-			break;
+		if (div == characteristics->divisors[i])
+			div_index = i;
+
+		if (max_div < characteristics->divisors[i]) {
+			max_div = characteristics->divisors[i];
+			max_div_index = i;
 		}
 	}
 
-	if (i == ARRAY_SIZE(characteristics->divisors))
-		return -EINVAL;
+	if (div > max_div)
+		div_index = max_div_index;
 
-	spin_lock_irqsave(master->lock, flags);
 	ret = regmap_read(master->regmap, master->layout->offset, &mckr);
 	if (ret)
-		goto unlock;
+		return ret;
 
 	mckr &= master->layout->mask;
 	tmp = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
-	if (tmp == div)
-		goto unlock;
+	if (tmp == div_index)
+		return 0;
+
+	rate /= characteristics->divisors[div_index];
+	if (rate < characteristics->output.min)
+		pr_warn("master clk div is underclocked");
+	else if (rate > characteristics->output.max)
+		pr_warn("master clk div is overclocked");
 
 	mckr &= ~(MASTER_DIV_MASK << MASTER_DIV_SHIFT);
-	mckr |= (div << MASTER_DIV_SHIFT);
+	mckr |= (div_index << MASTER_DIV_SHIFT);
 	ret = regmap_write(master->regmap, master->layout->offset, mckr);
 	if (ret)
-		goto unlock;
+		return ret;
 
 	while (!clk_master_ready(master))
 		cpu_relax();
-unlock:
-	spin_unlock_irqrestore(master->lock, flags);
+
+	master->div = characteristics->divisors[div_index];
 
 	return 0;
 }
 
-static int clk_master_div_determine_rate(struct clk_hw *hw,
-					 struct clk_rate_request *req)
+static unsigned long clk_master_div_recalc_rate_chg(struct clk_hw *hw,
+						    unsigned long parent_rate)
 {
 	struct clk_master *master = to_clk_master(hw);
-	const struct clk_master_characteristics *characteristics =
-						master->characteristics;
-	struct clk_hw *parent;
-	unsigned long parent_rate, tmp_rate, best_rate = 0;
-	int i, best_diff = INT_MIN, tmp_diff;
-
-	parent = clk_hw_get_parent(hw);
-	if (!parent)
-		return -EINVAL;
-
-	parent_rate = clk_hw_get_rate(parent);
-	if (!parent_rate)
-		return -EINVAL;
-
-	for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) {
-		if (!characteristics->divisors[i])
-			break;
 
-		tmp_rate = DIV_ROUND_CLOSEST_ULL(parent_rate,
-						 characteristics->divisors[i]);
-		tmp_diff = abs(tmp_rate - req->rate);
-
-		if (!best_rate || best_diff > tmp_diff) {
-			best_diff = tmp_diff;
-			best_rate = tmp_rate;
-		}
-
-		if (!best_diff)
-			break;
-	}
-
-	req->best_parent_rate = best_rate;
-	req->best_parent_hw = parent;
-	req->rate = best_rate;
-
-	return 0;
+	return DIV_ROUND_CLOSEST_ULL(parent_rate, master->div);
 }
 
 static void clk_master_div_restore_context_chg(struct clk_hw *hw)
 {
 	struct clk_master *master = to_clk_master(hw);
+	unsigned long flags;
 	int ret;
 
-	ret = clk_master_div_set_rate(hw, master->pms.rate,
-				      master->pms.parent_rate);
+	spin_lock_irqsave(master->lock, flags);
+	ret = clk_master_div_set(master, master->pms.parent_rate,
+				 DIV_ROUND_CLOSEST(master->pms.parent_rate,
+						   master->pms.rate));
+	spin_unlock_irqrestore(master->lock, flags);
 	if (ret)
 		pr_warn("Failed to restore MCK DIV clock\n");
 }
@@ -261,13 +240,116 @@ static void clk_master_div_restore_context_chg(struct clk_hw *hw)
 static const struct clk_ops master_div_ops_chg = {
 	.prepare = clk_master_prepare,
 	.is_prepared = clk_master_is_prepared,
-	.recalc_rate = clk_master_div_recalc_rate,
-	.determine_rate = clk_master_div_determine_rate,
-	.set_rate = clk_master_div_set_rate,
+	.recalc_rate = clk_master_div_recalc_rate_chg,
 	.save_context = clk_master_div_save_context,
 	.restore_context = clk_master_div_restore_context_chg,
 };
 
+static int clk_master_div_notifier_fn(struct notifier_block *notifier,
+				      unsigned long code, void *data)
+{
+	const struct clk_master_characteristics *characteristics =
+						master_div->characteristics;
+	struct clk_notifier_data *cnd = data;
+	unsigned long flags, new_parent_rate, new_rate;
+	unsigned int mckr, div, new_div = 0;
+	int ret, i;
+	long tmp_diff;
+	long best_diff = -1;
+
+	spin_lock_irqsave(master_div->lock, flags);
+	switch (code) {
+	case PRE_RATE_CHANGE:
+		/*
+		 * We want to avoid any overclocking of MCK DIV domain. To do
+		 * this we set a safe divider (the underclocking is not of
+		 * interest as we can go as low as 32KHz). The relation
+		 * b/w this clock and its parents are as follows:
+		 *
+		 * FRAC PLL -> DIV PLL -> MCK DIV
+		 *
+		 * With the proper safe divider we should be good even with FRAC
+		 * PLL at its maximum value.
+		 */
+		ret = regmap_read(master_div->regmap, master_div->layout->offset,
+				  &mckr);
+		if (ret) {
+			ret = NOTIFY_STOP_MASK;
+			goto unlock;
+		}
+
+		mckr &= master_div->layout->mask;
+		div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+
+		/* Switch to safe divider. */
+		clk_master_div_set(master_div,
+				   cnd->old_rate * characteristics->divisors[div],
+				   master_div->safe_div);
+		break;
+
+	case POST_RATE_CHANGE:
+		/*
+		 * At this point we want to restore MCK DIV domain to its maximum
+		 * allowed rate.
+		 */
+		ret = regmap_read(master_div->regmap, master_div->layout->offset,
+				  &mckr);
+		if (ret) {
+			ret = NOTIFY_STOP_MASK;
+			goto unlock;
+		}
+
+		mckr &= master_div->layout->mask;
+		div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+		new_parent_rate = cnd->new_rate * characteristics->divisors[div];
+
+		for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) {
+			if (!characteristics->divisors[i])
+				break;
+
+			new_rate = DIV_ROUND_CLOSEST_ULL(new_parent_rate,
+							 characteristics->divisors[i]);
+
+			tmp_diff = characteristics->output.max - new_rate;
+			if (tmp_diff < 0)
+				continue;
+
+			if (best_diff < 0 || best_diff > tmp_diff) {
+				new_div = characteristics->divisors[i];
+				best_diff = tmp_diff;
+			}
+
+			if (!tmp_diff)
+				break;
+		}
+
+		if (!new_div) {
+			ret = NOTIFY_STOP_MASK;
+			goto unlock;
+		}
+
+		/* Update the div to preserve MCK DIV clock rate. */
+		clk_master_div_set(master_div, new_parent_rate,
+				   new_div);
+
+		ret = NOTIFY_OK;
+		break;
+
+	default:
+		ret = NOTIFY_DONE;
+		break;
+	}
+
+unlock:
+	spin_unlock_irqrestore(master_div->lock, flags);
+
+	return ret;
+}
+
+static struct notifier_block clk_master_div_notifier = {
+	.notifier_call = clk_master_div_notifier_fn,
+};
+
 static void clk_sama7g5_master_best_diff(struct clk_rate_request *req,
 					 struct clk_hw *parent,
 					 unsigned long parent_rate,
@@ -496,6 +578,8 @@ at91_clk_register_master_internal(struct regmap *regmap,
 	struct clk_master *master;
 	struct clk_init_data init;
 	struct clk_hw *hw;
+	unsigned int mckr;
+	unsigned long irqflags;
 	int ret;
 
 	if (!name || !num_parents || !parent_names || !lock)
@@ -518,6 +602,16 @@ at91_clk_register_master_internal(struct regmap *regmap,
 	master->chg_pid = chg_pid;
 	master->lock = lock;
 
+	if (ops == &master_div_ops_chg) {
+		spin_lock_irqsave(master->lock, irqflags);
+		regmap_read(master->regmap, master->layout->offset, &mckr);
+		spin_unlock_irqrestore(master->lock, irqflags);
+
+		mckr &= layout->mask;
+		mckr = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+		master->div = characteristics->divisors[mckr];
+	}
+
 	hw = &master->hw;
 	ret = clk_hw_register(NULL, &master->hw);
 	if (ret) {
@@ -554,19 +648,29 @@ at91_clk_register_master_div(struct regmap *regmap,
 		const char *name, const char *parent_name,
 		const struct clk_master_layout *layout,
 		const struct clk_master_characteristics *characteristics,
-		spinlock_t *lock, u32 flags)
+		spinlock_t *lock, u32 flags, u32 safe_div)
 {
 	const struct clk_ops *ops;
+	struct clk_hw *hw;
 
 	if (flags & CLK_SET_RATE_GATE)
 		ops = &master_div_ops;
 	else
 		ops = &master_div_ops_chg;
 
-	return at91_clk_register_master_internal(regmap, name, 1,
-						 &parent_name, layout,
-						 characteristics, ops,
-						 lock, flags, -EINVAL);
+	hw = at91_clk_register_master_internal(regmap, name, 1,
+					       &parent_name, layout,
+					       characteristics, ops,
+					       lock, flags, -EINVAL);
+
+	if (!IS_ERR(hw) && safe_div) {
+		master_div = to_clk_master(hw);
+		master_div->safe_div = safe_div;
+		clk_notifier_register(hw->clk,
+				      &clk_master_div_notifier);
+	}
+
+	return hw;
 }
 
 static unsigned long
diff --git a/drivers/clk/at91/dt-compat.c b/drivers/clk/at91/dt-compat.c
index a97b99c2dc12..ca2dbb65b9df 100644
--- a/drivers/clk/at91/dt-compat.c
+++ b/drivers/clk/at91/dt-compat.c
@@ -399,7 +399,7 @@ of_at91_clk_master_setup(struct device_node *np,
 
 	hw = at91_clk_register_master_div(regmap, name, "masterck_pres",
 					  layout, characteristics,
-					  &mck_lock, CLK_SET_RATE_GATE);
+					  &mck_lock, CLK_SET_RATE_GATE, 0);
 	if (IS_ERR(hw))
 		goto out_free_characteristics;
 
diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index 207ecccef29f..3a1bf6194c28 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -182,7 +182,7 @@ at91_clk_register_master_div(struct regmap *regmap, const char *name,
 			     const char *parent_names,
 			     const struct clk_master_layout *layout,
 			     const struct clk_master_characteristics *characteristics,
-			     spinlock_t *lock, u32 flags);
+			     spinlock_t *lock, u32 flags, u32 safe_div);
 
 struct clk_hw * __init
 at91_clk_sama7g5_register_master(struct regmap *regmap,
diff --git a/drivers/clk/at91/sama5d2.c b/drivers/clk/at91/sama5d2.c
index 3d1f78176c3e..d027294a0089 100644
--- a/drivers/clk/at91/sama5d2.c
+++ b/drivers/clk/at91/sama5d2.c
@@ -249,7 +249,7 @@ static void __init sama5d2_pmc_setup(struct device_node *np)
 					  "masterck_pres",
 					  &at91sam9x5_master_layout,
 					  &mck_characteristics, &mck_lock,
-					  CLK_SET_RATE_GATE);
+					  CLK_SET_RATE_GATE, 0);
 	if (IS_ERR(hw))
 		goto err_free;
 
diff --git a/drivers/clk/at91/sama5d3.c b/drivers/clk/at91/sama5d3.c
index d376257807d2..339d0f382ff0 100644
--- a/drivers/clk/at91/sama5d3.c
+++ b/drivers/clk/at91/sama5d3.c
@@ -184,7 +184,7 @@ static void __init sama5d3_pmc_setup(struct device_node *np)
 					  "masterck_pres",
 					  &at91sam9x5_master_layout,
 					  &mck_characteristics, &mck_lock,
-					  CLK_SET_RATE_GATE);
+					  CLK_SET_RATE_GATE, 0);
 	if (IS_ERR(hw))
 		goto err_free;
 
diff --git a/drivers/clk/at91/sama5d4.c b/drivers/clk/at91/sama5d4.c
index 5cbaac68da44..4af75b1e39e9 100644
--- a/drivers/clk/at91/sama5d4.c
+++ b/drivers/clk/at91/sama5d4.c
@@ -199,7 +199,7 @@ static void __init sama5d4_pmc_setup(struct device_node *np)
 					  "masterck_pres",
 					  &at91sam9x5_master_layout,
 					  &mck_characteristics, &mck_lock,
-					  CLK_SET_RATE_GATE);
+					  CLK_SET_RATE_GATE, 0);
 	if (IS_ERR(hw))
 		goto err_free;
 
diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c
index ae52c10af040..c66bde6f7b47 100644
--- a/drivers/clk/at91/sama7g5.c
+++ b/drivers/clk/at91/sama7g5.c
@@ -1003,7 +1003,7 @@ static void __init sama7g5_pmc_setup(struct device_node *np)
 
 	hw = at91_clk_register_master_div(regmap, "mck0", "cpuck",
 					  &mck0_layout, &mck0_characteristics,
-					  &pmc_mck0_lock, 0);
+					  &pmc_mck0_lock, CLK_GET_RATE_NOCACHE, 5);
 	if (IS_ERR(hw))
 		goto err_free;
 
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 13/15] clk: at91: sama7g5: remove prescaler part of master clock
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (11 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 12/15] clk: at91: clk-master: add notifier for divider Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  8:07   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 14/15] clk: at91: sama7g5: set low limit for mck0 at 32KHz Claudiu Beznea
                   ` (2 subsequent siblings)
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

On SAMA7G5 the prescaler part of master clock has been implemented as a
changeable one. Everytime the prescaler is changed the PMC_SR.MCKRDY bit
must be polled. Value 1 for PMC_SR.MCKRDY means the prescaler update is
done. Driver polls for this bit until it becomes 1. On SAMA7G5 it has
been discovered that in some conditions the PMC_SR.MCKRDY is not rising
but the rate it provides it's stable. The workaround is to add a timeout
when polling for PMC_SR.MCKRDY. At the moment, for SAMA7G5, the prescaler
will be removed from Linux clock tree as all the frequencies for CPU could
be obtained from PLL and also there will be less overhead when changing
frequency via DVFS.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/sama7g5.c | 11 +----------
 1 file changed, 1 insertion(+), 10 deletions(-)

diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c
index c66bde6f7b47..fd9d17eabf54 100644
--- a/drivers/clk/at91/sama7g5.c
+++ b/drivers/clk/at91/sama7g5.c
@@ -992,16 +992,7 @@ static void __init sama7g5_pmc_setup(struct device_node *np)
 	}
 
 	parent_names[0] = "cpupll_divpmcck";
-	hw = at91_clk_register_master_pres(regmap, "cpuck", 1, parent_names,
-					   &mck0_layout, &mck0_characteristics,
-					   &pmc_mck0_lock,
-					   CLK_SET_RATE_PARENT, 0);
-	if (IS_ERR(hw))
-		goto err_free;
-
-	sama7g5_pmc->chws[PMC_CPU] = hw;
-
-	hw = at91_clk_register_master_div(regmap, "mck0", "cpuck",
+	hw = at91_clk_register_master_div(regmap, "mck0", "cpupll_divpmcck",
 					  &mck0_layout, &mck0_characteristics,
 					  &pmc_mck0_lock, CLK_GET_RATE_NOCACHE, 5);
 	if (IS_ERR(hw))
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 14/15] clk: at91: sama7g5: set low limit for mck0 at 32KHz
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (12 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 13/15] clk: at91: sama7g5: remove prescaler part of master clock Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  8:07   ` Nicolas Ferre
  2021-10-11 11:27 ` [PATCH v5 15/15] clk: use clk_core_get_rate_recalc() in clk_rate_get() Claudiu Beznea
  2021-10-15  8:33 ` [PATCH v5 00/15] clk: at91: updates for power management and dvfs Nicolas Ferre
  15 siblings, 1 reply; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

MCK0 could go as low as 32KHz. Set this limit.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/sama7g5.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c
index fd9d17eabf54..369dfafabbca 100644
--- a/drivers/clk/at91/sama7g5.c
+++ b/drivers/clk/at91/sama7g5.c
@@ -850,7 +850,7 @@ static const struct {
 
 /* MCK0 characteristics. */
 static const struct clk_master_characteristics mck0_characteristics = {
-	.output = { .min = 50000000, .max = 200000000 },
+	.output = { .min = 32768, .max = 200000000 },
 	.divisors = { 1, 2, 4, 3, 5 },
 	.have_div3_pres = 1,
 };
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 15/15] clk: use clk_core_get_rate_recalc() in clk_rate_get()
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (13 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 14/15] clk: at91: sama7g5: set low limit for mck0 at 32KHz Claudiu Beznea
@ 2021-10-11 11:27 ` Claudiu Beznea
  2021-10-15  8:08   ` Nicolas Ferre
  2021-10-27  1:26   ` Stephen Boyd
  2021-10-15  8:33 ` [PATCH v5 00/15] clk: at91: updates for power management and dvfs Nicolas Ferre
  15 siblings, 2 replies; 34+ messages in thread
From: Claudiu Beznea @ 2021-10-11 11:27 UTC (permalink / raw)
  To: mturquette, sboyd, nicolas.ferre, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

In case clock flags contains CLK_GET_RATE_NOCACHE the clk_rate_get()
will return the cached rate. Thus, use clk_core_get_rate_recalc() which
takes proper action when clock flags contains CLK_GET_RATE_NOCACHE.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/clk.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 65508eb89ec9..64838754cdef 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -3108,7 +3108,7 @@ static int clk_rate_get(void *data, u64 *val)
 {
 	struct clk_core *core = data;
 
-	*val = core->rate;
+	*val = clk_core_get_rate_recalc(core);
 	return 0;
 }
 
-- 
2.25.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 01/15] clk: at91: re-factor clocks suspend/resume
  2021-10-11 11:27 ` [PATCH v5 01/15] clk: at91: re-factor clocks suspend/resume Claudiu Beznea
@ 2021-10-15  7:46   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  7:46 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> SAMA5D2 and SAMA7G5 have a special power saving mode (backup mode) where
> most of the SoC's components are powered off (including PMC). Resuming
> from this mode is done with the help of bootloader. Peripherals are not
> aware of the power saving mode thus most of them are disabling clocks in
> proper suspend API and re-enable them in resume API without taking into
> account the previously setup rate. Moreover some of the peripherals are
> acting as wakeup sources and are not disabling the clocks in this
> scenario, when suspending. Since backup mode cuts the power for
> peripherals, in resume part these clocks needs to be re-configured.
> 
> The initial PMC suspend/resume code was designed only for SAMA5D2's PMC
> (as it was the only one supporting backup mode). SAMA7G supports also
> backup mode and its PMC is different (few new functionalities, different
> registers offsets, different offsets in registers for each
> functionalities). To address both SAMA5D2 and SAMA7G5 PMC add
> .save_context()/.resume_context() support to each clocks driver and call
> this from PMC driver.
> 
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

> ---
> 
> Hi Stephen,
> 
> This patch applies good on top of patch at [1].
> 
> Thank you,
> Claudiu Beznea
> 
> [1]
> https://lore.kernel.org/lkml/20210913082633.110168-1-clement.leger@bootlin.com/
> 
>   drivers/clk/at91/clk-generated.c    |  46 +++++--
>   drivers/clk/at91/clk-main.c         |  66 ++++++++++
>   drivers/clk/at91/clk-master.c       | 194 ++++++++++++++++++++++++++--
>   drivers/clk/at91/clk-peripheral.c   |  40 +++++-
>   drivers/clk/at91/clk-pll.c          |  39 ++++++
>   drivers/clk/at91/clk-programmable.c |  29 ++++-
>   drivers/clk/at91/clk-sam9x60-pll.c  |  68 +++++++++-
>   drivers/clk/at91/clk-system.c       |  20 +++
>   drivers/clk/at91/clk-usb.c          |  27 ++++
>   drivers/clk/at91/clk-utmi.c         |  39 ++++++
>   drivers/clk/at91/pmc.c              | 147 +--------------------
>   drivers/clk/at91/pmc.h              |  24 ++--
>   12 files changed, 558 insertions(+), 181 deletions(-)
> 
> diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c
> index b656d25a9767..23cc8297ec4c 100644
> --- a/drivers/clk/at91/clk-generated.c
> +++ b/drivers/clk/at91/clk-generated.c
> @@ -27,6 +27,7 @@ struct clk_generated {
>   	u32 id;
>   	u32 gckdiv;
>   	const struct clk_pcr_layout *layout;
> +	struct at91_clk_pms pms;
>   	u8 parent_id;
>   	int chg_pid;
>   };
> @@ -34,25 +35,35 @@ struct clk_generated {
>   #define to_clk_generated(hw) \
>   	container_of(hw, struct clk_generated, hw)
>   
> -static int clk_generated_enable(struct clk_hw *hw)
> +static int clk_generated_set(struct clk_generated *gck, int status)
>   {
> -	struct clk_generated *gck = to_clk_generated(hw);
>   	unsigned long flags;
> -
> -	pr_debug("GCLK: %s, gckdiv = %d, parent id = %d\n",
> -		 __func__, gck->gckdiv, gck->parent_id);
> +	unsigned int enable = status ? AT91_PMC_PCR_GCKEN : 0;
>   
>   	spin_lock_irqsave(gck->lock, flags);
>   	regmap_write(gck->regmap, gck->layout->offset,
>   		     (gck->id & gck->layout->pid_mask));
>   	regmap_update_bits(gck->regmap, gck->layout->offset,
>   			   AT91_PMC_PCR_GCKDIV_MASK | gck->layout->gckcss_mask |
> -			   gck->layout->cmd | AT91_PMC_PCR_GCKEN,
> +			   gck->layout->cmd | enable,
>   			   field_prep(gck->layout->gckcss_mask, gck->parent_id) |
>   			   gck->layout->cmd |
>   			   FIELD_PREP(AT91_PMC_PCR_GCKDIV_MASK, gck->gckdiv) |
> -			   AT91_PMC_PCR_GCKEN);
> +			   enable);
>   	spin_unlock_irqrestore(gck->lock, flags);
> +
> +	return 0;
> +}
> +
> +static int clk_generated_enable(struct clk_hw *hw)
> +{
> +	struct clk_generated *gck = to_clk_generated(hw);
> +
> +	pr_debug("GCLK: %s, gckdiv = %d, parent id = %d\n",
> +		 __func__, gck->gckdiv, gck->parent_id);
> +
> +	clk_generated_set(gck, 1);
> +
>   	return 0;
>   }
>   
> @@ -245,6 +256,23 @@ static int clk_generated_set_rate(struct clk_hw *hw,
>   	return 0;
>   }
>   
> +static int clk_generated_save_context(struct clk_hw *hw)
> +{
> +	struct clk_generated *gck = to_clk_generated(hw);
> +
> +	gck->pms.status = clk_generated_is_enabled(&gck->hw);
> +
> +	return 0;
> +}
> +
> +static void clk_generated_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_generated *gck = to_clk_generated(hw);
> +
> +	if (gck->pms.status)
> +		clk_generated_set(gck, gck->pms.status);
> +}
> +
>   static const struct clk_ops generated_ops = {
>   	.enable = clk_generated_enable,
>   	.disable = clk_generated_disable,
> @@ -254,6 +282,8 @@ static const struct clk_ops generated_ops = {
>   	.get_parent = clk_generated_get_parent,
>   	.set_parent = clk_generated_set_parent,
>   	.set_rate = clk_generated_set_rate,
> +	.save_context = clk_generated_save_context,
> +	.restore_context = clk_generated_restore_context,
>   };
>   
>   /**
> @@ -320,8 +350,6 @@ at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock,
>   	if (ret) {
>   		kfree(gck);
>   		hw = ERR_PTR(ret);
> -	} else {
> -		pmc_register_id(id);
>   	}
>   
>   	return hw;
> diff --git a/drivers/clk/at91/clk-main.c b/drivers/clk/at91/clk-main.c
> index cfae2f59df66..8601b27c1ae0 100644
> --- a/drivers/clk/at91/clk-main.c
> +++ b/drivers/clk/at91/clk-main.c
> @@ -28,6 +28,7 @@
>   struct clk_main_osc {
>   	struct clk_hw hw;
>   	struct regmap *regmap;
> +	struct at91_clk_pms pms;
>   };
>   
>   #define to_clk_main_osc(hw) container_of(hw, struct clk_main_osc, hw)
> @@ -37,6 +38,7 @@ struct clk_main_rc_osc {
>   	struct regmap *regmap;
>   	unsigned long frequency;
>   	unsigned long accuracy;
> +	struct at91_clk_pms pms;
>   };
>   
>   #define to_clk_main_rc_osc(hw) container_of(hw, struct clk_main_rc_osc, hw)
> @@ -51,6 +53,7 @@ struct clk_rm9200_main {
>   struct clk_sam9x5_main {
>   	struct clk_hw hw;
>   	struct regmap *regmap;
> +	struct at91_clk_pms pms;
>   	u8 parent;
>   };
>   
> @@ -120,10 +123,29 @@ static int clk_main_osc_is_prepared(struct clk_hw *hw)
>   	return (status & AT91_PMC_MOSCS) && clk_main_parent_select(tmp);
>   }
>   
> +static int clk_main_osc_save_context(struct clk_hw *hw)
> +{
> +	struct clk_main_osc *osc = to_clk_main_osc(hw);
> +
> +	osc->pms.status = clk_main_osc_is_prepared(hw);
> +
> +	return 0;
> +}
> +
> +static void clk_main_osc_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_main_osc *osc = to_clk_main_osc(hw);
> +
> +	if (osc->pms.status)
> +		clk_main_osc_prepare(hw);
> +}
> +
>   static const struct clk_ops main_osc_ops = {
>   	.prepare = clk_main_osc_prepare,
>   	.unprepare = clk_main_osc_unprepare,
>   	.is_prepared = clk_main_osc_is_prepared,
> +	.save_context = clk_main_osc_save_context,
> +	.restore_context = clk_main_osc_restore_context,
>   };
>   
>   struct clk_hw * __init
> @@ -240,12 +262,31 @@ static unsigned long clk_main_rc_osc_recalc_accuracy(struct clk_hw *hw,
>   	return osc->accuracy;
>   }
>   
> +static int clk_main_rc_osc_save_context(struct clk_hw *hw)
> +{
> +	struct clk_main_rc_osc *osc = to_clk_main_rc_osc(hw);
> +
> +	osc->pms.status = clk_main_rc_osc_is_prepared(hw);
> +
> +	return 0;
> +}
> +
> +static void clk_main_rc_osc_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_main_rc_osc *osc = to_clk_main_rc_osc(hw);
> +
> +	if (osc->pms.status)
> +		clk_main_rc_osc_prepare(hw);
> +}
> +
>   static const struct clk_ops main_rc_osc_ops = {
>   	.prepare = clk_main_rc_osc_prepare,
>   	.unprepare = clk_main_rc_osc_unprepare,
>   	.is_prepared = clk_main_rc_osc_is_prepared,
>   	.recalc_rate = clk_main_rc_osc_recalc_rate,
>   	.recalc_accuracy = clk_main_rc_osc_recalc_accuracy,
> +	.save_context = clk_main_rc_osc_save_context,
> +	.restore_context = clk_main_rc_osc_restore_context,
>   };
>   
>   struct clk_hw * __init
> @@ -465,12 +506,37 @@ static u8 clk_sam9x5_main_get_parent(struct clk_hw *hw)
>   	return clk_main_parent_select(status);
>   }
>   
> +static int clk_sam9x5_main_save_context(struct clk_hw *hw)
> +{
> +	struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(hw);
> +
> +	clkmain->pms.status = clk_main_rc_osc_is_prepared(&clkmain->hw);
> +	clkmain->pms.parent = clk_sam9x5_main_get_parent(&clkmain->hw);
> +
> +	return 0;
> +}
> +
> +static void clk_sam9x5_main_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(hw);
> +	int ret;
> +
> +	ret = clk_sam9x5_main_set_parent(hw, clkmain->pms.parent);
> +	if (ret)
> +		return;
> +
> +	if (clkmain->pms.status)
> +		clk_sam9x5_main_prepare(hw);
> +}
> +
>   static const struct clk_ops sam9x5_main_ops = {
>   	.prepare = clk_sam9x5_main_prepare,
>   	.is_prepared = clk_sam9x5_main_is_prepared,
>   	.recalc_rate = clk_sam9x5_main_recalc_rate,
>   	.set_parent = clk_sam9x5_main_set_parent,
>   	.get_parent = clk_sam9x5_main_get_parent,
> +	.save_context = clk_sam9x5_main_save_context,
> +	.restore_context = clk_sam9x5_main_restore_context,
>   };
>   
>   struct clk_hw * __init
> diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
> index a80427980bf7..f75549fff023 100644
> --- a/drivers/clk/at91/clk-master.c
> +++ b/drivers/clk/at91/clk-master.c
> @@ -37,6 +37,7 @@ struct clk_master {
>   	spinlock_t *lock;
>   	const struct clk_master_layout *layout;
>   	const struct clk_master_characteristics *characteristics;
> +	struct at91_clk_pms pms;
>   	u32 *mux_table;
>   	u32 mckr;
>   	int chg_pid;
> @@ -112,10 +113,52 @@ static unsigned long clk_master_div_recalc_rate(struct clk_hw *hw,
>   	return rate;
>   }
>   
> +static int clk_master_div_save_context(struct clk_hw *hw)
> +{
> +	struct clk_master *master = to_clk_master(hw);
> +	struct clk_hw *parent_hw = clk_hw_get_parent(hw);
> +	unsigned long flags;
> +	unsigned int mckr, div;
> +
> +	spin_lock_irqsave(master->lock, flags);
> +	regmap_read(master->regmap, master->layout->offset, &mckr);
> +	spin_unlock_irqrestore(master->lock, flags);
> +
> +	mckr &= master->layout->mask;
> +	div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
> +	div = master->characteristics->divisors[div];
> +
> +	master->pms.parent_rate = clk_hw_get_rate(parent_hw);
> +	master->pms.rate = DIV_ROUND_CLOSEST(master->pms.parent_rate, div);
> +
> +	return 0;
> +}
> +
> +static void clk_master_div_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_master *master = to_clk_master(hw);
> +	unsigned long flags;
> +	unsigned int mckr;
> +	u8 div;
> +
> +	spin_lock_irqsave(master->lock, flags);
> +	regmap_read(master->regmap, master->layout->offset, &mckr);
> +	spin_unlock_irqrestore(master->lock, flags);
> +
> +	mckr &= master->layout->mask;
> +	div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
> +	div = master->characteristics->divisors[div];
> +
> +	if (div != DIV_ROUND_CLOSEST(master->pms.parent_rate, master->pms.rate))
> +		pr_warn("MCKR DIV not configured properly by firmware!\n");
> +}
> +
>   static const struct clk_ops master_div_ops = {
>   	.prepare = clk_master_prepare,
>   	.is_prepared = clk_master_is_prepared,
>   	.recalc_rate = clk_master_div_recalc_rate,
> +	.save_context = clk_master_div_save_context,
> +	.restore_context = clk_master_div_restore_context,
>   };
>   
>   static int clk_master_div_set_rate(struct clk_hw *hw, unsigned long rate,
> @@ -125,7 +168,9 @@ static int clk_master_div_set_rate(struct clk_hw *hw, unsigned long rate,
>   	const struct clk_master_characteristics *characteristics =
>   						master->characteristics;
>   	unsigned long flags;
> +	unsigned int mckr, tmp;
>   	int div, i;
> +	int ret;
>   
>   	div = DIV_ROUND_CLOSEST(parent_rate, rate);
>   	if (div > ARRAY_SIZE(characteristics->divisors))
> @@ -145,11 +190,24 @@ static int clk_master_div_set_rate(struct clk_hw *hw, unsigned long rate,
>   		return -EINVAL;
>   
>   	spin_lock_irqsave(master->lock, flags);
> -	regmap_update_bits(master->regmap, master->layout->offset,
> -			   (MASTER_DIV_MASK << MASTER_DIV_SHIFT),
> -			   (div << MASTER_DIV_SHIFT));
> +	ret = regmap_read(master->regmap, master->layout->offset, &mckr);
> +	if (ret)
> +		goto unlock;
> +
> +	tmp = mckr & master->layout->mask;
> +	tmp = (tmp >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
> +	if (tmp == div)
> +		goto unlock;
> +
> +	mckr &= ~(MASTER_DIV_MASK << MASTER_DIV_SHIFT);
> +	mckr |= (div << MASTER_DIV_SHIFT);
> +	ret = regmap_write(master->regmap, master->layout->offset, mckr);
> +	if (ret)
> +		goto unlock;
> +
>   	while (!clk_master_ready(master))
>   		cpu_relax();
> +unlock:
>   	spin_unlock_irqrestore(master->lock, flags);
>   
>   	return 0;
> @@ -197,12 +255,25 @@ static int clk_master_div_determine_rate(struct clk_hw *hw,
>   	return 0;
>   }
>   
> +static void clk_master_div_restore_context_chg(struct clk_hw *hw)
> +{
> +	struct clk_master *master = to_clk_master(hw);
> +	int ret;
> +
> +	ret = clk_master_div_set_rate(hw, master->pms.rate,
> +				      master->pms.parent_rate);
> +	if (ret)
> +		pr_warn("Failed to restore MCK DIV clock\n");
> +}
> +
>   static const struct clk_ops master_div_ops_chg = {
>   	.prepare = clk_master_prepare,
>   	.is_prepared = clk_master_is_prepared,
>   	.recalc_rate = clk_master_div_recalc_rate,
>   	.determine_rate = clk_master_div_determine_rate,
>   	.set_rate = clk_master_div_set_rate,
> +	.save_context = clk_master_div_save_context,
> +	.restore_context = clk_master_div_restore_context_chg,
>   };
>   
>   static void clk_sama7g5_master_best_diff(struct clk_rate_request *req,
> @@ -272,7 +343,8 @@ static int clk_master_pres_set_rate(struct clk_hw *hw, unsigned long rate,
>   {
>   	struct clk_master *master = to_clk_master(hw);
>   	unsigned long flags;
> -	unsigned int pres;
> +	unsigned int pres, mckr, tmp;
> +	int ret;
>   
>   	pres = DIV_ROUND_CLOSEST(parent_rate, rate);
>   	if (pres > MASTER_PRES_MAX)
> @@ -284,15 +356,27 @@ static int clk_master_pres_set_rate(struct clk_hw *hw, unsigned long rate,
>   		pres = ffs(pres) - 1;
>   
>   	spin_lock_irqsave(master->lock, flags);
> -	regmap_update_bits(master->regmap, master->layout->offset,
> -			   (MASTER_PRES_MASK << master->layout->pres_shift),
> -			   (pres << master->layout->pres_shift));
> +	ret = regmap_read(master->regmap, master->layout->offset, &mckr);
> +	if (ret)
> +		goto unlock;
> +
> +	mckr &= master->layout->mask;
> +	tmp = (mckr >> master->layout->pres_shift) & MASTER_PRES_MASK;
> +	if (pres == tmp)
> +		goto unlock;
> +
> +	mckr &= ~(MASTER_PRES_MASK << master->layout->pres_shift);
> +	mckr |= (pres << master->layout->pres_shift);
> +	ret = regmap_write(master->regmap, master->layout->offset, mckr);
> +	if (ret)
> +		goto unlock;
>   
>   	while (!clk_master_ready(master))
>   		cpu_relax();
> +unlock:
>   	spin_unlock_irqrestore(master->lock, flags);
>   
> -	return 0;
> +	return ret;
>   }
>   
>   static unsigned long clk_master_pres_recalc_rate(struct clk_hw *hw,
> @@ -330,11 +414,68 @@ static u8 clk_master_pres_get_parent(struct clk_hw *hw)
>   	return mckr & AT91_PMC_CSS;
>   }
>   
> +static int clk_master_pres_save_context(struct clk_hw *hw)
> +{
> +	struct clk_master *master = to_clk_master(hw);
> +	struct clk_hw *parent_hw = clk_hw_get_parent(hw);
> +	unsigned long flags;
> +	unsigned int val, pres;
> +
> +	spin_lock_irqsave(master->lock, flags);
> +	regmap_read(master->regmap, master->layout->offset, &val);
> +	spin_unlock_irqrestore(master->lock, flags);
> +
> +	val &= master->layout->mask;
> +	pres = (val >> master->layout->pres_shift) & MASTER_PRES_MASK;
> +	if (pres == MASTER_PRES_MAX && master->characteristics->have_div3_pres)
> +		pres = 3;
> +	else
> +		pres = (1 << pres);
> +
> +	master->pms.parent = val & AT91_PMC_CSS;
> +	master->pms.parent_rate = clk_hw_get_rate(parent_hw);
> +	master->pms.rate = DIV_ROUND_CLOSEST_ULL(master->pms.parent_rate, pres);
> +
> +	return 0;
> +}
> +
> +static void clk_master_pres_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_master *master = to_clk_master(hw);
> +	unsigned long flags;
> +	unsigned int val, pres;
> +
> +	spin_lock_irqsave(master->lock, flags);
> +	regmap_read(master->regmap, master->layout->offset, &val);
> +	spin_unlock_irqrestore(master->lock, flags);
> +
> +	val &= master->layout->mask;
> +	pres = (val >> master->layout->pres_shift) & MASTER_PRES_MASK;
> +	if (pres == MASTER_PRES_MAX && master->characteristics->have_div3_pres)
> +		pres = 3;
> +	else
> +		pres = (1 << pres);
> +
> +	if (master->pms.rate !=
> +	    DIV_ROUND_CLOSEST_ULL(master->pms.parent_rate, pres) ||
> +	    (master->pms.parent != (val & AT91_PMC_CSS)))
> +		pr_warn("MCKR PRES was not configured properly by firmware!\n");
> +}
> +
> +static void clk_master_pres_restore_context_chg(struct clk_hw *hw)
> +{
> +	struct clk_master *master = to_clk_master(hw);
> +
> +	clk_master_pres_set_rate(hw, master->pms.rate, master->pms.parent_rate);
> +}
> +
>   static const struct clk_ops master_pres_ops = {
>   	.prepare = clk_master_prepare,
>   	.is_prepared = clk_master_is_prepared,
>   	.recalc_rate = clk_master_pres_recalc_rate,
>   	.get_parent = clk_master_pres_get_parent,
> +	.save_context = clk_master_pres_save_context,
> +	.restore_context = clk_master_pres_restore_context,
>   };
>   
>   static const struct clk_ops master_pres_ops_chg = {
> @@ -344,6 +485,8 @@ static const struct clk_ops master_pres_ops_chg = {
>   	.recalc_rate = clk_master_pres_recalc_rate,
>   	.get_parent = clk_master_pres_get_parent,
>   	.set_rate = clk_master_pres_set_rate,
> +	.save_context = clk_master_pres_save_context,
> +	.restore_context = clk_master_pres_restore_context_chg,
>   };
>   
>   static struct clk_hw * __init
> @@ -539,20 +682,21 @@ static int clk_sama7g5_master_set_parent(struct clk_hw *hw, u8 index)
>   	return 0;
>   }
>   
> -static int clk_sama7g5_master_enable(struct clk_hw *hw)
> +static void clk_sama7g5_master_set(struct clk_master *master,
> +				   unsigned int status)
>   {
> -	struct clk_master *master = to_clk_master(hw);
>   	unsigned long flags;
>   	unsigned int val, cparent;
> +	unsigned int enable = status ? PMC_MCR_EN : 0;
>   
>   	spin_lock_irqsave(master->lock, flags);
>   
>   	regmap_write(master->regmap, PMC_MCR, PMC_MCR_ID(master->id));
>   	regmap_read(master->regmap, PMC_MCR, &val);
>   	regmap_update_bits(master->regmap, PMC_MCR,
> -			   PMC_MCR_EN | PMC_MCR_CSS | PMC_MCR_DIV |
> +			   enable | PMC_MCR_CSS | PMC_MCR_DIV |
>   			   PMC_MCR_CMD | PMC_MCR_ID_MSK,
> -			   PMC_MCR_EN | (master->parent << PMC_MCR_CSS_SHIFT) |
> +			   enable | (master->parent << PMC_MCR_CSS_SHIFT) |
>   			   (master->div << MASTER_DIV_SHIFT) |
>   			   PMC_MCR_CMD | PMC_MCR_ID(master->id));
>   
> @@ -563,6 +707,13 @@ static int clk_sama7g5_master_enable(struct clk_hw *hw)
>   		cpu_relax();
>   
>   	spin_unlock_irqrestore(master->lock, flags);
> +}
> +
> +static int clk_sama7g5_master_enable(struct clk_hw *hw)
> +{
> +	struct clk_master *master = to_clk_master(hw);
> +
> +	clk_sama7g5_master_set(master, 1);
>   
>   	return 0;
>   }
> @@ -620,6 +771,23 @@ static int clk_sama7g5_master_set_rate(struct clk_hw *hw, unsigned long rate,
>   	return 0;
>   }
>   
> +static int clk_sama7g5_master_save_context(struct clk_hw *hw)
> +{
> +	struct clk_master *master = to_clk_master(hw);
> +
> +	master->pms.status = clk_sama7g5_master_is_enabled(hw);
> +
> +	return 0;
> +}
> +
> +static void clk_sama7g5_master_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_master *master = to_clk_master(hw);
> +
> +	if (master->pms.status)
> +		clk_sama7g5_master_set(master, master->pms.status);
> +}
> +
>   static const struct clk_ops sama7g5_master_ops = {
>   	.enable = clk_sama7g5_master_enable,
>   	.disable = clk_sama7g5_master_disable,
> @@ -629,6 +797,8 @@ static const struct clk_ops sama7g5_master_ops = {
>   	.set_rate = clk_sama7g5_master_set_rate,
>   	.get_parent = clk_sama7g5_master_get_parent,
>   	.set_parent = clk_sama7g5_master_set_parent,
> +	.save_context = clk_sama7g5_master_save_context,
> +	.restore_context = clk_sama7g5_master_restore_context,
>   };
>   
>   struct clk_hw * __init
> diff --git a/drivers/clk/at91/clk-peripheral.c b/drivers/clk/at91/clk-peripheral.c
> index 7a27ba8e0577..e14fa5ac734c 100644
> --- a/drivers/clk/at91/clk-peripheral.c
> +++ b/drivers/clk/at91/clk-peripheral.c
> @@ -37,6 +37,7 @@ struct clk_sam9x5_peripheral {
>   	u32 id;
>   	u32 div;
>   	const struct clk_pcr_layout *layout;
> +	struct at91_clk_pms pms;
>   	bool auto_div;
>   	int chg_pid;
>   };
> @@ -155,10 +156,11 @@ static void clk_sam9x5_peripheral_autodiv(struct clk_sam9x5_peripheral *periph)
>   	periph->div = shift;
>   }
>   
> -static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
> +static int clk_sam9x5_peripheral_set(struct clk_sam9x5_peripheral *periph,
> +				     unsigned int status)
>   {
> -	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
>   	unsigned long flags;
> +	unsigned int enable = status ? AT91_PMC_PCR_EN : 0;
>   
>   	if (periph->id < PERIPHERAL_ID_MIN)
>   		return 0;
> @@ -168,15 +170,21 @@ static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
>   		     (periph->id & periph->layout->pid_mask));
>   	regmap_update_bits(periph->regmap, periph->layout->offset,
>   			   periph->layout->div_mask | periph->layout->cmd |
> -			   AT91_PMC_PCR_EN,
> +			   enable,
>   			   field_prep(periph->layout->div_mask, periph->div) |
> -			   periph->layout->cmd |
> -			   AT91_PMC_PCR_EN);
> +			   periph->layout->cmd | enable);
>   	spin_unlock_irqrestore(periph->lock, flags);
>   
>   	return 0;
>   }
>   
> +static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
> +{
> +	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
> +
> +	return clk_sam9x5_peripheral_set(periph, 1);
> +}
> +
>   static void clk_sam9x5_peripheral_disable(struct clk_hw *hw)
>   {
>   	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
> @@ -393,6 +401,23 @@ static int clk_sam9x5_peripheral_set_rate(struct clk_hw *hw,
>   	return -EINVAL;
>   }
>   
> +static int clk_sam9x5_peripheral_save_context(struct clk_hw *hw)
> +{
> +	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
> +
> +	periph->pms.status = clk_sam9x5_peripheral_is_enabled(hw);
> +
> +	return 0;
> +}
> +
> +static void clk_sam9x5_peripheral_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
> +
> +	if (periph->pms.status)
> +		clk_sam9x5_peripheral_set(periph, periph->pms.status);
> +}
> +
>   static const struct clk_ops sam9x5_peripheral_ops = {
>   	.enable = clk_sam9x5_peripheral_enable,
>   	.disable = clk_sam9x5_peripheral_disable,
> @@ -400,6 +425,8 @@ static const struct clk_ops sam9x5_peripheral_ops = {
>   	.recalc_rate = clk_sam9x5_peripheral_recalc_rate,
>   	.round_rate = clk_sam9x5_peripheral_round_rate,
>   	.set_rate = clk_sam9x5_peripheral_set_rate,
> +	.save_context = clk_sam9x5_peripheral_save_context,
> +	.restore_context = clk_sam9x5_peripheral_restore_context,
>   };
>   
>   static const struct clk_ops sam9x5_peripheral_chg_ops = {
> @@ -409,6 +436,8 @@ static const struct clk_ops sam9x5_peripheral_chg_ops = {
>   	.recalc_rate = clk_sam9x5_peripheral_recalc_rate,
>   	.determine_rate = clk_sam9x5_peripheral_determine_rate,
>   	.set_rate = clk_sam9x5_peripheral_set_rate,
> +	.save_context = clk_sam9x5_peripheral_save_context,
> +	.restore_context = clk_sam9x5_peripheral_restore_context,
>   };
>   
>   struct clk_hw * __init
> @@ -460,7 +489,6 @@ at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock,
>   		hw = ERR_PTR(ret);
>   	} else {
>   		clk_sam9x5_peripheral_autodiv(periph);
> -		pmc_register_id(id);
>   	}
>   
>   	return hw;
> diff --git a/drivers/clk/at91/clk-pll.c b/drivers/clk/at91/clk-pll.c
> index 6ed986d3eee0..249d6a53cedf 100644
> --- a/drivers/clk/at91/clk-pll.c
> +++ b/drivers/clk/at91/clk-pll.c
> @@ -40,6 +40,7 @@ struct clk_pll {
>   	u16 mul;
>   	const struct clk_pll_layout *layout;
>   	const struct clk_pll_characteristics *characteristics;
> +	struct at91_clk_pms pms;
>   };
>   
>   static inline bool clk_pll_ready(struct regmap *regmap, int id)
> @@ -260,6 +261,42 @@ static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
>   	return 0;
>   }
>   
> +static int clk_pll_save_context(struct clk_hw *hw)
> +{
> +	struct clk_pll *pll = to_clk_pll(hw);
> +	struct clk_hw *parent_hw = clk_hw_get_parent(hw);
> +
> +	pll->pms.parent_rate = clk_hw_get_rate(parent_hw);
> +	pll->pms.rate = clk_pll_recalc_rate(&pll->hw, pll->pms.parent_rate);
> +	pll->pms.status = clk_pll_ready(pll->regmap, PLL_REG(pll->id));
> +
> +	return 0;
> +}
> +
> +static void clk_pll_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_pll *pll = to_clk_pll(hw);
> +	unsigned long calc_rate;
> +	unsigned int pllr, pllr_out, pllr_count;
> +	u8 out = 0;
> +
> +	if (pll->characteristics->out)
> +		out = pll->characteristics->out[pll->range];
> +
> +	regmap_read(pll->regmap, PLL_REG(pll->id), &pllr);
> +
> +	calc_rate = (pll->pms.parent_rate / PLL_DIV(pllr)) *
> +		     (PLL_MUL(pllr, pll->layout) + 1);
> +	pllr_count = (pllr >> PLL_COUNT_SHIFT) & PLL_MAX_COUNT;
> +	pllr_out = (pllr >> PLL_OUT_SHIFT) & out;
> +
> +	if (pll->pms.rate != calc_rate ||
> +	    pll->pms.status != clk_pll_ready(pll->regmap, PLL_REG(pll->id)) ||
> +	    pllr_count != PLL_MAX_COUNT ||
> +	    (out && pllr_out != out))
> +		pr_warn("PLLAR was not configured properly by firmware\n");
> +}
> +
>   static const struct clk_ops pll_ops = {
>   	.prepare = clk_pll_prepare,
>   	.unprepare = clk_pll_unprepare,
> @@ -267,6 +304,8 @@ static const struct clk_ops pll_ops = {
>   	.recalc_rate = clk_pll_recalc_rate,
>   	.round_rate = clk_pll_round_rate,
>   	.set_rate = clk_pll_set_rate,
> +	.save_context = clk_pll_save_context,
> +	.restore_context = clk_pll_restore_context,
>   };
>   
>   struct clk_hw * __init
> diff --git a/drivers/clk/at91/clk-programmable.c b/drivers/clk/at91/clk-programmable.c
> index fcf8f6a1c2c6..6c4b259d31d3 100644
> --- a/drivers/clk/at91/clk-programmable.c
> +++ b/drivers/clk/at91/clk-programmable.c
> @@ -24,6 +24,7 @@ struct clk_programmable {
>   	u32 *mux_table;
>   	u8 id;
>   	const struct clk_programmable_layout *layout;
> +	struct at91_clk_pms pms;
>   };
>   
>   #define to_clk_programmable(hw) container_of(hw, struct clk_programmable, hw)
> @@ -177,12 +178,38 @@ static int clk_programmable_set_rate(struct clk_hw *hw, unsigned long rate,
>   	return 0;
>   }
>   
> +static int clk_programmable_save_context(struct clk_hw *hw)
> +{
> +	struct clk_programmable *prog = to_clk_programmable(hw);
> +	struct clk_hw *parent_hw = clk_hw_get_parent(hw);
> +
> +	prog->pms.parent = clk_programmable_get_parent(hw);
> +	prog->pms.parent_rate = clk_hw_get_rate(parent_hw);
> +	prog->pms.rate = clk_programmable_recalc_rate(hw, prog->pms.parent_rate);
> +
> +	return 0;
> +}
> +
> +static void clk_programmable_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_programmable *prog = to_clk_programmable(hw);
> +	int ret;
> +
> +	ret = clk_programmable_set_parent(hw, prog->pms.parent);
> +	if (ret)
> +		return;
> +
> +	clk_programmable_set_rate(hw, prog->pms.rate, prog->pms.parent_rate);
> +}
> +
>   static const struct clk_ops programmable_ops = {
>   	.recalc_rate = clk_programmable_recalc_rate,
>   	.determine_rate = clk_programmable_determine_rate,
>   	.get_parent = clk_programmable_get_parent,
>   	.set_parent = clk_programmable_set_parent,
>   	.set_rate = clk_programmable_set_rate,
> +	.save_context = clk_programmable_save_context,
> +	.restore_context = clk_programmable_restore_context,
>   };
>   
>   struct clk_hw * __init
> @@ -221,8 +248,6 @@ at91_clk_register_programmable(struct regmap *regmap,
>   	if (ret) {
>   		kfree(prog);
>   		hw = ERR_PTR(ret);
> -	} else {
> -		pmc_register_pck(id);
>   	}
>   
>   	return hw;
> diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c
> index 34e3ab13741a..7020d3bf6e13 100644
> --- a/drivers/clk/at91/clk-sam9x60-pll.c
> +++ b/drivers/clk/at91/clk-sam9x60-pll.c
> @@ -38,12 +38,14 @@ struct sam9x60_pll_core {
>   
>   struct sam9x60_frac {
>   	struct sam9x60_pll_core core;
> +	struct at91_clk_pms pms;
>   	u32 frac;
>   	u16 mul;
>   };
>   
>   struct sam9x60_div {
>   	struct sam9x60_pll_core core;
> +	struct at91_clk_pms pms;
>   	u8 div;
>   };
>   
> @@ -75,9 +77,8 @@ static unsigned long sam9x60_frac_pll_recalc_rate(struct clk_hw *hw,
>   		((u64)parent_rate * frac->frac >> 22));
>   }
>   
> -static int sam9x60_frac_pll_prepare(struct clk_hw *hw)
> +static int sam9x60_frac_pll_set(struct sam9x60_pll_core *core)
>   {
> -	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
>   	struct sam9x60_frac *frac = to_sam9x60_frac(core);
>   	struct regmap *regmap = core->regmap;
>   	unsigned int val, cfrac, cmul;
> @@ -141,6 +142,13 @@ static int sam9x60_frac_pll_prepare(struct clk_hw *hw)
>   	return 0;
>   }
>   
> +static int sam9x60_frac_pll_prepare(struct clk_hw *hw)
> +{
> +	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
> +
> +	return sam9x60_frac_pll_set(core);
> +}
> +
>   static void sam9x60_frac_pll_unprepare(struct clk_hw *hw)
>   {
>   	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
> @@ -280,6 +288,25 @@ static int sam9x60_frac_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate,
>   	return ret;
>   }
>   
> +static int sam9x60_frac_pll_save_context(struct clk_hw *hw)
> +{
> +	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
> +	struct sam9x60_frac *frac = to_sam9x60_frac(core);
> +
> +	frac->pms.status = sam9x60_pll_ready(core->regmap, core->id);
> +
> +	return 0;
> +}
> +
> +static void sam9x60_frac_pll_restore_context(struct clk_hw *hw)
> +{
> +	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
> +	struct sam9x60_frac *frac = to_sam9x60_frac(core);
> +
> +	if (frac->pms.status)
> +		sam9x60_frac_pll_set(core);
> +}
> +
>   static const struct clk_ops sam9x60_frac_pll_ops = {
>   	.prepare = sam9x60_frac_pll_prepare,
>   	.unprepare = sam9x60_frac_pll_unprepare,
> @@ -287,6 +314,8 @@ static const struct clk_ops sam9x60_frac_pll_ops = {
>   	.recalc_rate = sam9x60_frac_pll_recalc_rate,
>   	.round_rate = sam9x60_frac_pll_round_rate,
>   	.set_rate = sam9x60_frac_pll_set_rate,
> +	.save_context = sam9x60_frac_pll_save_context,
> +	.restore_context = sam9x60_frac_pll_restore_context,
>   };
>   
>   static const struct clk_ops sam9x60_frac_pll_ops_chg = {
> @@ -296,11 +325,12 @@ static const struct clk_ops sam9x60_frac_pll_ops_chg = {
>   	.recalc_rate = sam9x60_frac_pll_recalc_rate,
>   	.round_rate = sam9x60_frac_pll_round_rate,
>   	.set_rate = sam9x60_frac_pll_set_rate_chg,
> +	.save_context = sam9x60_frac_pll_save_context,
> +	.restore_context = sam9x60_frac_pll_restore_context,
>   };
>   
> -static int sam9x60_div_pll_prepare(struct clk_hw *hw)
> +static int sam9x60_div_pll_set(struct sam9x60_pll_core *core)
>   {
> -	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
>   	struct sam9x60_div *div = to_sam9x60_div(core);
>   	struct regmap *regmap = core->regmap;
>   	unsigned long flags;
> @@ -334,6 +364,13 @@ static int sam9x60_div_pll_prepare(struct clk_hw *hw)
>   	return 0;
>   }
>   
> +static int sam9x60_div_pll_prepare(struct clk_hw *hw)
> +{
> +	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
> +
> +	return sam9x60_div_pll_set(core);
> +}
> +
>   static void sam9x60_div_pll_unprepare(struct clk_hw *hw)
>   {
>   	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
> @@ -482,6 +519,25 @@ static int sam9x60_div_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate,
>   	return 0;
>   }
>   
> +static int sam9x60_div_pll_save_context(struct clk_hw *hw)
> +{
> +	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
> +	struct sam9x60_div *div = to_sam9x60_div(core);
> +
> +	div->pms.status = sam9x60_div_pll_is_prepared(hw);
> +
> +	return 0;
> +}
> +
> +static void sam9x60_div_pll_restore_context(struct clk_hw *hw)
> +{
> +	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
> +	struct sam9x60_div *div = to_sam9x60_div(core);
> +
> +	if (div->pms.status)
> +		sam9x60_div_pll_set(core);
> +}
> +
>   static const struct clk_ops sam9x60_div_pll_ops = {
>   	.prepare = sam9x60_div_pll_prepare,
>   	.unprepare = sam9x60_div_pll_unprepare,
> @@ -489,6 +545,8 @@ static const struct clk_ops sam9x60_div_pll_ops = {
>   	.recalc_rate = sam9x60_div_pll_recalc_rate,
>   	.round_rate = sam9x60_div_pll_round_rate,
>   	.set_rate = sam9x60_div_pll_set_rate,
> +	.save_context = sam9x60_div_pll_save_context,
> +	.restore_context = sam9x60_div_pll_restore_context,
>   };
>   
>   static const struct clk_ops sam9x60_div_pll_ops_chg = {
> @@ -498,6 +556,8 @@ static const struct clk_ops sam9x60_div_pll_ops_chg = {
>   	.recalc_rate = sam9x60_div_pll_recalc_rate,
>   	.round_rate = sam9x60_div_pll_round_rate,
>   	.set_rate = sam9x60_div_pll_set_rate_chg,
> +	.save_context = sam9x60_div_pll_save_context,
> +	.restore_context = sam9x60_div_pll_restore_context,
>   };
>   
>   struct clk_hw * __init
> diff --git a/drivers/clk/at91/clk-system.c b/drivers/clk/at91/clk-system.c
> index f83ec0de86c3..80720fd1a9cf 100644
> --- a/drivers/clk/at91/clk-system.c
> +++ b/drivers/clk/at91/clk-system.c
> @@ -20,6 +20,7 @@
>   struct clk_system {
>   	struct clk_hw hw;
>   	struct regmap *regmap;
> +	struct at91_clk_pms pms;
>   	u8 id;
>   };
>   
> @@ -77,10 +78,29 @@ static int clk_system_is_prepared(struct clk_hw *hw)
>   	return !!(status & (1 << sys->id));
>   }
>   
> +static int clk_system_save_context(struct clk_hw *hw)
> +{
> +	struct clk_system *sys = to_clk_system(hw);
> +
> +	sys->pms.status = clk_system_is_prepared(hw);
> +
> +	return 0;
> +}
> +
> +static void clk_system_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_system *sys = to_clk_system(hw);
> +
> +	if (sys->pms.status)
> +		clk_system_prepare(&sys->hw);
> +}
> +
>   static const struct clk_ops system_ops = {
>   	.prepare = clk_system_prepare,
>   	.unprepare = clk_system_unprepare,
>   	.is_prepared = clk_system_is_prepared,
> +	.save_context = clk_system_save_context,
> +	.restore_context = clk_system_restore_context,
>   };
>   
>   struct clk_hw * __init
> diff --git a/drivers/clk/at91/clk-usb.c b/drivers/clk/at91/clk-usb.c
> index 31d5c45e30d7..b0696a928aa9 100644
> --- a/drivers/clk/at91/clk-usb.c
> +++ b/drivers/clk/at91/clk-usb.c
> @@ -24,6 +24,7 @@
>   struct at91sam9x5_clk_usb {
>   	struct clk_hw hw;
>   	struct regmap *regmap;
> +	struct at91_clk_pms pms;
>   	u32 usbs_mask;
>   	u8 num_parents;
>   };
> @@ -148,12 +149,38 @@ static int at91sam9x5_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
>   	return 0;
>   }
>   
> +static int at91sam9x5_usb_save_context(struct clk_hw *hw)
> +{
> +	struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> +	struct clk_hw *parent_hw = clk_hw_get_parent(hw);
> +
> +	usb->pms.parent = at91sam9x5_clk_usb_get_parent(hw);
> +	usb->pms.parent_rate = clk_hw_get_rate(parent_hw);
> +	usb->pms.rate = at91sam9x5_clk_usb_recalc_rate(hw, usb->pms.parent_rate);
> +
> +	return 0;
> +}
> +
> +static void at91sam9x5_usb_restore_context(struct clk_hw *hw)
> +{
> +	struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
> +	int ret;
> +
> +	ret = at91sam9x5_clk_usb_set_parent(hw, usb->pms.parent);
> +	if (ret)
> +		return;
> +
> +	at91sam9x5_clk_usb_set_rate(hw, usb->pms.rate, usb->pms.parent_rate);
> +}
> +
>   static const struct clk_ops at91sam9x5_usb_ops = {
>   	.recalc_rate = at91sam9x5_clk_usb_recalc_rate,
>   	.determine_rate = at91sam9x5_clk_usb_determine_rate,
>   	.get_parent = at91sam9x5_clk_usb_get_parent,
>   	.set_parent = at91sam9x5_clk_usb_set_parent,
>   	.set_rate = at91sam9x5_clk_usb_set_rate,
> +	.save_context = at91sam9x5_usb_save_context,
> +	.restore_context = at91sam9x5_usb_restore_context,
>   };
>   
>   static int at91sam9n12_clk_usb_enable(struct clk_hw *hw)
> diff --git a/drivers/clk/at91/clk-utmi.c b/drivers/clk/at91/clk-utmi.c
> index df9f3fc3b6a6..a22c10d9a1b9 100644
> --- a/drivers/clk/at91/clk-utmi.c
> +++ b/drivers/clk/at91/clk-utmi.c
> @@ -23,6 +23,7 @@ struct clk_utmi {
>   	struct clk_hw hw;
>   	struct regmap *regmap_pmc;
>   	struct regmap *regmap_sfr;
> +	struct at91_clk_pms pms;
>   };
>   
>   #define to_clk_utmi(hw) container_of(hw, struct clk_utmi, hw)
> @@ -113,11 +114,30 @@ static unsigned long clk_utmi_recalc_rate(struct clk_hw *hw,
>   	return UTMI_RATE;
>   }
>   
> +static int clk_utmi_save_context(struct clk_hw *hw)
> +{
> +	struct clk_utmi *utmi = to_clk_utmi(hw);
> +
> +	utmi->pms.status = clk_utmi_is_prepared(hw);
> +
> +	return 0;
> +}
> +
> +static void clk_utmi_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_utmi *utmi = to_clk_utmi(hw);
> +
> +	if (utmi->pms.status)
> +		clk_utmi_prepare(hw);
> +}
> +
>   static const struct clk_ops utmi_ops = {
>   	.prepare = clk_utmi_prepare,
>   	.unprepare = clk_utmi_unprepare,
>   	.is_prepared = clk_utmi_is_prepared,
>   	.recalc_rate = clk_utmi_recalc_rate,
> +	.save_context = clk_utmi_save_context,
> +	.restore_context = clk_utmi_restore_context,
>   };
>   
>   static struct clk_hw * __init
> @@ -232,10 +252,29 @@ static int clk_utmi_sama7g5_is_prepared(struct clk_hw *hw)
>   	return 0;
>   }
>   
> +static int clk_utmi_sama7g5_save_context(struct clk_hw *hw)
> +{
> +	struct clk_utmi *utmi = to_clk_utmi(hw);
> +
> +	utmi->pms.status = clk_utmi_sama7g5_is_prepared(hw);
> +
> +	return 0;
> +}
> +
> +static void clk_utmi_sama7g5_restore_context(struct clk_hw *hw)
> +{
> +	struct clk_utmi *utmi = to_clk_utmi(hw);
> +
> +	if (utmi->pms.status)
> +		clk_utmi_sama7g5_prepare(hw);
> +}
> +
>   static const struct clk_ops sama7g5_utmi_ops = {
>   	.prepare = clk_utmi_sama7g5_prepare,
>   	.is_prepared = clk_utmi_sama7g5_is_prepared,
>   	.recalc_rate = clk_utmi_recalc_rate,
> +	.save_context = clk_utmi_sama7g5_save_context,
> +	.restore_context = clk_utmi_sama7g5_restore_context,
>   };
>   
>   struct clk_hw * __init
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> index b40035b011d0..b2806946a77a 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -3,6 +3,7 @@
>    *  Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
>    */
>   
> +#include <linux/clk.h>
>   #include <linux/clk-provider.h>
>   #include <linux/clkdev.h>
>   #include <linux/clk/at91_pmc.h>
> @@ -14,8 +15,6 @@
>   
>   #include <asm/proc-fns.h>
>   
> -#include <dt-bindings/clock/at91.h>
> -
>   #include "pmc.h"
>   
>   #define PMC_MAX_IDS 128
> @@ -111,147 +110,19 @@ struct pmc_data *pmc_data_allocate(unsigned int ncore, unsigned int nsystem,
>   }
>   
>   #ifdef CONFIG_PM
> -static struct regmap *pmcreg;
> -
> -static u8 registered_ids[PMC_MAX_IDS];
> -static u8 registered_pcks[PMC_MAX_PCKS];
> -
> -static struct
> -{
> -	u32 scsr;
> -	u32 pcsr0;
> -	u32 uckr;
> -	u32 mor;
> -	u32 mcfr;
> -	u32 pllar;
> -	u32 mckr;
> -	u32 usb;
> -	u32 imr;
> -	u32 pcsr1;
> -	u32 pcr[PMC_MAX_IDS];
> -	u32 audio_pll0;
> -	u32 audio_pll1;
> -	u32 pckr[PMC_MAX_PCKS];
> -} pmc_cache;
> -
> -/*
> - * As Peripheral ID 0 is invalid on AT91 chips, the identifier is stored
> - * without alteration in the table, and 0 is for unused clocks.
> - */
> -void pmc_register_id(u8 id)
> -{
> -	int i;
> -
> -	for (i = 0; i < PMC_MAX_IDS; i++) {
> -		if (registered_ids[i] == 0) {
> -			registered_ids[i] = id;
> -			break;
> -		}
> -		if (registered_ids[i] == id)
> -			break;
> -	}
> -}
> -
> -/*
> - * As Programmable Clock 0 is valid on AT91 chips, there is an offset
> - * of 1 between the stored value and the real clock ID.
> - */
> -void pmc_register_pck(u8 pck)
> -{
> -	int i;
> -
> -	for (i = 0; i < PMC_MAX_PCKS; i++) {
> -		if (registered_pcks[i] == 0) {
> -			registered_pcks[i] = pck + 1;
> -			break;
> -		}
> -		if (registered_pcks[i] == (pck + 1))
> -			break;
> -	}
> -}
> -
> -static int pmc_suspend(void)
> +static int at91_pmc_suspend(void)
>   {
> -	int i;
> -	u8 num;
> -
> -	regmap_read(pmcreg, AT91_PMC_SCSR, &pmc_cache.scsr);
> -	regmap_read(pmcreg, AT91_PMC_PCSR, &pmc_cache.pcsr0);
> -	regmap_read(pmcreg, AT91_CKGR_UCKR, &pmc_cache.uckr);
> -	regmap_read(pmcreg, AT91_CKGR_MOR, &pmc_cache.mor);
> -	regmap_read(pmcreg, AT91_CKGR_MCFR, &pmc_cache.mcfr);
> -	regmap_read(pmcreg, AT91_CKGR_PLLAR, &pmc_cache.pllar);
> -	regmap_read(pmcreg, AT91_PMC_MCKR, &pmc_cache.mckr);
> -	regmap_read(pmcreg, AT91_PMC_USB, &pmc_cache.usb);
> -	regmap_read(pmcreg, AT91_PMC_IMR, &pmc_cache.imr);
> -	regmap_read(pmcreg, AT91_PMC_PCSR1, &pmc_cache.pcsr1);
> -
> -	for (i = 0; registered_ids[i]; i++) {
> -		regmap_write(pmcreg, AT91_PMC_PCR,
> -			     (registered_ids[i] & AT91_PMC_PCR_PID_MASK));
> -		regmap_read(pmcreg, AT91_PMC_PCR,
> -			    &pmc_cache.pcr[registered_ids[i]]);
> -	}
> -	for (i = 0; registered_pcks[i]; i++) {
> -		num = registered_pcks[i] - 1;
> -		regmap_read(pmcreg, AT91_PMC_PCKR(num), &pmc_cache.pckr[num]);
> -	}
> -
> -	return 0;
> +	return clk_save_context();
>   }
>   
> -static bool pmc_ready(unsigned int mask)
> +static void at91_pmc_resume(void)
>   {
> -	unsigned int status;
> -
> -	regmap_read(pmcreg, AT91_PMC_SR, &status);
> -
> -	return ((status & mask) == mask) ? 1 : 0;
> -}
> -
> -static void pmc_resume(void)
> -{
> -	int i;
> -	u8 num;
> -	u32 tmp;
> -	u32 mask = AT91_PMC_MCKRDY | AT91_PMC_LOCKA;
> -
> -	regmap_read(pmcreg, AT91_PMC_MCKR, &tmp);
> -	if (pmc_cache.mckr != tmp)
> -		pr_warn("MCKR was not configured properly by the firmware\n");
> -	regmap_read(pmcreg, AT91_CKGR_PLLAR, &tmp);
> -	if (pmc_cache.pllar != tmp)
> -		pr_warn("PLLAR was not configured properly by the firmware\n");
> -
> -	regmap_write(pmcreg, AT91_PMC_SCER, pmc_cache.scsr);
> -	regmap_write(pmcreg, AT91_PMC_PCER, pmc_cache.pcsr0);
> -	regmap_write(pmcreg, AT91_CKGR_UCKR, pmc_cache.uckr);
> -	regmap_write(pmcreg, AT91_CKGR_MOR, pmc_cache.mor);
> -	regmap_write(pmcreg, AT91_CKGR_MCFR, pmc_cache.mcfr);
> -	regmap_write(pmcreg, AT91_PMC_USB, pmc_cache.usb);
> -	regmap_write(pmcreg, AT91_PMC_IMR, pmc_cache.imr);
> -	regmap_write(pmcreg, AT91_PMC_PCER1, pmc_cache.pcsr1);
> -
> -	for (i = 0; registered_ids[i]; i++) {
> -		regmap_write(pmcreg, AT91_PMC_PCR,
> -			     pmc_cache.pcr[registered_ids[i]] |
> -			     AT91_PMC_PCR_CMD);
> -	}
> -	for (i = 0; registered_pcks[i]; i++) {
> -		num = registered_pcks[i] - 1;
> -		regmap_write(pmcreg, AT91_PMC_PCKR(num), pmc_cache.pckr[num]);
> -	}
> -
> -	if (pmc_cache.uckr & AT91_PMC_UPLLEN)
> -		mask |= AT91_PMC_LOCKU;
> -
> -	while (!pmc_ready(mask))
> -		cpu_relax();
> +	clk_restore_context();
>   }
>   
>   static struct syscore_ops pmc_syscore_ops = {
> -	.suspend = pmc_suspend,
> -	.resume = pmc_resume,
> +	.suspend = at91_pmc_suspend,
> +	.resume = at91_pmc_resume,
>   };
>   
>   static const struct of_device_id sama5d2_pmc_dt_ids[] = {
> @@ -271,11 +142,7 @@ static int __init pmc_register_ops(void)
>   		of_node_put(np);
>   		return -ENODEV;
>   	}
> -
> -	pmcreg = device_node_to_regmap(np);
>   	of_node_put(np);
> -	if (IS_ERR(pmcreg))
> -		return PTR_ERR(pmcreg);
>   
>   	register_syscore_ops(&pmc_syscore_ops);
>   
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index a49076c804a9..45df094498ce 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -13,6 +13,8 @@
>   #include <linux/regmap.h>
>   #include <linux/spinlock.h>
>   
> +#include <dt-bindings/clock/at91.h>
> +
>   extern spinlock_t pmc_pcr_lock;
>   
>   struct pmc_data {
> @@ -98,6 +100,20 @@ struct clk_pcr_layout {
>   	u32 pid_mask;
>   };
>   
> +/**
> + * struct at91_clk_pms - Power management state for AT91 clock
> + * @rate: clock rate
> + * @parent_rate: clock parent rate
> + * @status: clock status (enabled or disabled)
> + * @parent: clock parent index
> + */
> +struct at91_clk_pms {
> +	unsigned long rate;
> +	unsigned long parent_rate;
> +	unsigned int status;
> +	unsigned int parent;
> +};
> +
>   #define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1))
>   #define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask))
>   
> @@ -248,12 +264,4 @@ struct clk_hw * __init
>   at91_clk_sama7g5_register_utmi(struct regmap *regmap, const char *name,
>   			       const char *parent_name);
>   
> -#ifdef CONFIG_PM
> -void pmc_register_id(u8 id);
> -void pmc_register_pck(u8 pck);
> -#else
> -static inline void pmc_register_id(u8 id) {}
> -static inline void pmc_register_pck(u8 pck) {}
> -#endif
> -
>   #endif /* __PMC_H_ */
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 02/15] clk: at91: pmc: execute suspend/resume only for backup mode
  2021-10-11 11:27 ` [PATCH v5 02/15] clk: at91: pmc: execute suspend/resume only for backup mode Claudiu Beznea
@ 2021-10-15  7:51   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  7:51 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> Before going to backup mode architecture specific PM code sets the first
> word in securam (file arch/arm/mach-at91/pm.c, function at91_pm_begin()).
> Thus take this into account when suspending/resuming clocks. This will
> avoid executing unnecessary instructions when suspending to non backup
> modes.
> 
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

Ok to do it like this:
Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>


> ---
>   drivers/clk/at91/pmc.c | 39 +++++++++++++++++++++++++++++++++++++++
>   1 file changed, 39 insertions(+)
> 
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> index b2806946a77a..517973062719 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -8,6 +8,7 @@
>   #include <linux/clkdev.h>
>   #include <linux/clk/at91_pmc.h>
>   #include <linux/of.h>
> +#include <linux/of_address.h>
>   #include <linux/mfd/syscon.h>
>   #include <linux/platform_device.h>
>   #include <linux/regmap.h>
> @@ -110,13 +111,35 @@ struct pmc_data *pmc_data_allocate(unsigned int ncore, unsigned int nsystem,
>   }
>   
>   #ifdef CONFIG_PM
> +
> +/* Address in SECURAM that say if we suspend to backup mode. */
> +static void __iomem *at91_pmc_backup_suspend;
> +
>   static int at91_pmc_suspend(void)
>   {
> +	unsigned int backup;
> +
> +	if (!at91_pmc_backup_suspend)
> +		return 0;
> +
> +	backup = readl_relaxed(at91_pmc_backup_suspend);
> +	if (!backup)
> +		return 0;
> +
>   	return clk_save_context();
>   }
>   
>   static void at91_pmc_resume(void)
>   {
> +	unsigned int backup;
> +
> +	if (!at91_pmc_backup_suspend)
> +		return;
> +
> +	backup = readl_relaxed(at91_pmc_backup_suspend);
> +	if (!backup)
> +		return;
> +
>   	clk_restore_context();
>   }
>   
> @@ -144,6 +167,22 @@ static int __init pmc_register_ops(void)
>   	}
>   	of_node_put(np);
>   
> +	np = of_find_compatible_node(NULL, NULL, "atmel,sama5d2-securam");
> +	if (!np)
> +		return -ENODEV;
> +
> +	if (!of_device_is_available(np)) {
> +		of_node_put(np);
> +		return -ENODEV;
> +	}
> +	of_node_put(np);
> +
> +	at91_pmc_backup_suspend = of_iomap(np, 0);
> +	if (!at91_pmc_backup_suspend) {
> +		pr_warn("%s(): unable to map securam\n", __func__);
> +		return -ENOMEM;
> +	}
> +
>   	register_syscore_ops(&pmc_syscore_ops);
>   
>   	return 0;
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 04/15] clk: at91: clk-master: add register definition for sama7g5's master clock
  2021-10-11 11:27 ` [PATCH v5 04/15] clk: at91: clk-master: add register definition for sama7g5's master clock Claudiu Beznea
@ 2021-10-15  7:52   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  7:52 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> SAMA7G5 has 4 master clocks (MCK1..4) which are controlled though the
> register at offset 0x30 (relative to PMC). In the last/first phase of
> suspend/resume procedure (which is architecture specific) the parent
> of master clocks are changed (via assembly code) for more power saving
> (see file arch/arm/mach-at91/pm_suspend.S, macros at91_mckx_ps_enable
> and at91_mckx_ps_restore). Thus the macros corresponding to register
> at offset 0x30 need to be shared b/w clk-master.c and pm_suspend.S.
> commit ec03f18cc222 ("clk: at91: add register definition for sama7g5's
> master clock") introduced the proper macros but didn't adapted the
> clk-master.c as well. Thus, this commit adapt the clk-master.c to use
> the macros introduced in commit ec03f18cc222 ("clk: at91: add register
> definition for sama7g5's master clock").
> 
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

> ---
>   drivers/clk/at91/clk-master.c | 50 ++++++++++++++++-------------------
>   1 file changed, 23 insertions(+), 27 deletions(-)
> 
> diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
> index f75549fff023..88f7af1bfff6 100644
> --- a/drivers/clk/at91/clk-master.c
> +++ b/drivers/clk/at91/clk-master.c
> @@ -17,15 +17,7 @@
>   #define MASTER_DIV_SHIFT	8
>   #define MASTER_DIV_MASK		0x7
>   
> -#define PMC_MCR			0x30
> -#define PMC_MCR_ID_MSK		GENMASK(3, 0)
> -#define PMC_MCR_CMD		BIT(7)
> -#define PMC_MCR_DIV		GENMASK(10, 8)
> -#define PMC_MCR_CSS		GENMASK(20, 16)
>   #define PMC_MCR_CSS_SHIFT	(16)
> -#define PMC_MCR_EN		BIT(28)
> -
> -#define PMC_MCR_ID(x)		((x) & PMC_MCR_ID_MSK)
>   
>   #define MASTER_MAX_ID		4
>   
> @@ -687,20 +679,22 @@ static void clk_sama7g5_master_set(struct clk_master *master,
>   {
>   	unsigned long flags;
>   	unsigned int val, cparent;
> -	unsigned int enable = status ? PMC_MCR_EN : 0;
> +	unsigned int enable = status ? AT91_PMC_MCR_V2_EN : 0;
>   
>   	spin_lock_irqsave(master->lock, flags);
>   
> -	regmap_write(master->regmap, PMC_MCR, PMC_MCR_ID(master->id));
> -	regmap_read(master->regmap, PMC_MCR, &val);
> -	regmap_update_bits(master->regmap, PMC_MCR,
> -			   enable | PMC_MCR_CSS | PMC_MCR_DIV |
> -			   PMC_MCR_CMD | PMC_MCR_ID_MSK,
> +	regmap_write(master->regmap, AT91_PMC_MCR_V2,
> +		     AT91_PMC_MCR_V2_ID(master->id));
> +	regmap_read(master->regmap, AT91_PMC_MCR_V2, &val);
> +	regmap_update_bits(master->regmap, AT91_PMC_MCR_V2,
> +			   enable | AT91_PMC_MCR_V2_CSS | AT91_PMC_MCR_V2_DIV |
> +			   AT91_PMC_MCR_V2_CMD | AT91_PMC_MCR_V2_ID_MSK,
>   			   enable | (master->parent << PMC_MCR_CSS_SHIFT) |
>   			   (master->div << MASTER_DIV_SHIFT) |
> -			   PMC_MCR_CMD | PMC_MCR_ID(master->id));
> +			   AT91_PMC_MCR_V2_CMD |
> +			   AT91_PMC_MCR_V2_ID(master->id));
>   
> -	cparent = (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT;
> +	cparent = (val & AT91_PMC_MCR_V2_CSS) >> PMC_MCR_CSS_SHIFT;
>   
>   	/* Wait here only if parent is being changed. */
>   	while ((cparent != master->parent) && !clk_master_ready(master))
> @@ -725,10 +719,12 @@ static void clk_sama7g5_master_disable(struct clk_hw *hw)
>   
>   	spin_lock_irqsave(master->lock, flags);
>   
> -	regmap_write(master->regmap, PMC_MCR, master->id);
> -	regmap_update_bits(master->regmap, PMC_MCR,
> -			   PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK,
> -			   PMC_MCR_CMD | PMC_MCR_ID(master->id));
> +	regmap_write(master->regmap, AT91_PMC_MCR_V2, master->id);
> +	regmap_update_bits(master->regmap, AT91_PMC_MCR_V2,
> +			   AT91_PMC_MCR_V2_EN | AT91_PMC_MCR_V2_CMD |
> +			   AT91_PMC_MCR_V2_ID_MSK,
> +			   AT91_PMC_MCR_V2_CMD |
> +			   AT91_PMC_MCR_V2_ID(master->id));
>   
>   	spin_unlock_irqrestore(master->lock, flags);
>   }
> @@ -741,12 +737,12 @@ static int clk_sama7g5_master_is_enabled(struct clk_hw *hw)
>   
>   	spin_lock_irqsave(master->lock, flags);
>   
> -	regmap_write(master->regmap, PMC_MCR, master->id);
> -	regmap_read(master->regmap, PMC_MCR, &val);
> +	regmap_write(master->regmap, AT91_PMC_MCR_V2, master->id);
> +	regmap_read(master->regmap, AT91_PMC_MCR_V2, &val);
>   
>   	spin_unlock_irqrestore(master->lock, flags);
>   
> -	return !!(val & PMC_MCR_EN);
> +	return !!(val & AT91_PMC_MCR_V2_EN);
>   }
>   
>   static int clk_sama7g5_master_set_rate(struct clk_hw *hw, unsigned long rate,
> @@ -842,10 +838,10 @@ at91_clk_sama7g5_register_master(struct regmap *regmap,
>   	master->mux_table = mux_table;
>   
>   	spin_lock_irqsave(master->lock, flags);
> -	regmap_write(master->regmap, PMC_MCR, master->id);
> -	regmap_read(master->regmap, PMC_MCR, &val);
> -	master->parent = (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT;
> -	master->div = (val & PMC_MCR_DIV) >> MASTER_DIV_SHIFT;
> +	regmap_write(master->regmap, AT91_PMC_MCR_V2, master->id);
> +	regmap_read(master->regmap, AT91_PMC_MCR_V2, &val);
> +	master->parent = (val & AT91_PMC_MCR_V2_CSS) >> PMC_MCR_CSS_SHIFT;
> +	master->div = (val & AT91_PMC_MCR_V2_DIV) >> MASTER_DIV_SHIFT;
>   	spin_unlock_irqrestore(master->lock, flags);
>   
>   	hw = &master->hw;
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 05/15] clk: at91: clk-master: improve readability by using local variables
  2021-10-11 11:27 ` [PATCH v5 05/15] clk: at91: clk-master: improve readability by using local variables Claudiu Beznea
@ 2021-10-15  7:53   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  7:53 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> Improve readability in clk_sama7g5_master_set() by using local
> variables.
> 
> Suggested-by: Nicolas Ferre <nicolas.ferre@microchip.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

So, obviously ;-) :
Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

Thanks Claudiu!

> ---
>   drivers/clk/at91/clk-master.c | 6 +++---
>   1 file changed, 3 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
> index 88f7af1bfff6..9a2c8e64cacf 100644
> --- a/drivers/clk/at91/clk-master.c
> +++ b/drivers/clk/at91/clk-master.c
> @@ -680,6 +680,8 @@ static void clk_sama7g5_master_set(struct clk_master *master,
>   	unsigned long flags;
>   	unsigned int val, cparent;
>   	unsigned int enable = status ? AT91_PMC_MCR_V2_EN : 0;
> +	unsigned int parent = master->parent << PMC_MCR_CSS_SHIFT;
> +	unsigned int div = master->div << MASTER_DIV_SHIFT;
>   
>   	spin_lock_irqsave(master->lock, flags);
>   
> @@ -689,9 +691,7 @@ static void clk_sama7g5_master_set(struct clk_master *master,
>   	regmap_update_bits(master->regmap, AT91_PMC_MCR_V2,
>   			   enable | AT91_PMC_MCR_V2_CSS | AT91_PMC_MCR_V2_DIV |
>   			   AT91_PMC_MCR_V2_CMD | AT91_PMC_MCR_V2_ID_MSK,
> -			   enable | (master->parent << PMC_MCR_CSS_SHIFT) |
> -			   (master->div << MASTER_DIV_SHIFT) |
> -			   AT91_PMC_MCR_V2_CMD |
> +			   enable | parent | div | AT91_PMC_MCR_V2_CMD |
>   			   AT91_PMC_MCR_V2_ID(master->id));
>   
>   	cparent = (val & AT91_PMC_MCR_V2_CSS) >> PMC_MCR_CSS_SHIFT;
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 06/15] clk: at91: pmc: add sama7g5 to the list of available pmcs
  2021-10-11 11:27 ` [PATCH v5 06/15] clk: at91: pmc: add sama7g5 to the list of available pmcs Claudiu Beznea
@ 2021-10-15  7:54   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  7:54 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> Add SAMA7G5 to the list of available PMCs such that the suspend/resume
> code for clocks to be used on backup mode.
> 
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

> ---
>   drivers/clk/at91/pmc.c | 5 +++--
>   1 file changed, 3 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> index 517973062719..5aa9c1f1c886 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -148,8 +148,9 @@ static struct syscore_ops pmc_syscore_ops = {
>   	.resume = at91_pmc_resume,
>   };
>   
> -static const struct of_device_id sama5d2_pmc_dt_ids[] = {
> +static const struct of_device_id pmc_dt_ids[] = {
>   	{ .compatible = "atmel,sama5d2-pmc" },
> +	{ .compatible = "microchip,sama7g5-pmc", },
>   	{ /* sentinel */ }
>   };
>   
> @@ -157,7 +158,7 @@ static int __init pmc_register_ops(void)
>   {
>   	struct device_node *np;
>   
> -	np = of_find_matching_node(NULL, sama5d2_pmc_dt_ids);
> +	np = of_find_matching_node(NULL, pmc_dt_ids);
>   	if (!np)
>   		return -ENODEV;
>   
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 07/15] clk: at91: sam9x60-pll: use DIV_ROUND_CLOSEST_ULL
  2021-10-11 11:27 ` [PATCH v5 07/15] clk: at91: sam9x60-pll: use DIV_ROUND_CLOSEST_ULL Claudiu Beznea
@ 2021-10-15  7:54   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  7:54 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> Use DIV_ROUND_CLOSEST_ULL() to avoid any inconsistency b/w the rate
> computed in sam9x60_frac_pll_recalc_rate() and the one computed in
> sam9x60_frac_pll_compute_mul_frac().
> 
> Fixes: 43b1bb4a9b3e1 ("clk: at91: clk-sam9x60-pll: re-factor to support plls with multiple outputs")
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

> ---
>   drivers/clk/at91/clk-sam9x60-pll.c | 4 ++--
>   1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c
> index 7020d3bf6e13..a73d7c96ce1d 100644
> --- a/drivers/clk/at91/clk-sam9x60-pll.c
> +++ b/drivers/clk/at91/clk-sam9x60-pll.c
> @@ -73,8 +73,8 @@ static unsigned long sam9x60_frac_pll_recalc_rate(struct clk_hw *hw,
>   	struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
>   	struct sam9x60_frac *frac = to_sam9x60_frac(core);
>   
> -	return (parent_rate * (frac->mul + 1) +
> -		((u64)parent_rate * frac->frac >> 22));
> +	return parent_rate * (frac->mul + 1) +
> +		DIV_ROUND_CLOSEST_ULL((u64)parent_rate * frac->frac, (1 << 22));
>   }
>   
>   static int sam9x60_frac_pll_set(struct sam9x60_pll_core *core)
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 08/15] clk: at91: clk-master: check if div or pres is zero
  2021-10-11 11:27 ` [PATCH v5 08/15] clk: at91: clk-master: check if div or pres is zero Claudiu Beznea
@ 2021-10-15  7:55   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  7:55 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> Check if div or pres is zero before using it as argument for ffs().
> In case div is zero ffs() will return 0 and thus substracting from
> zero will lead to invalid values to be setup in registers.
> 
> Fixes: 7a110b9107ed8 ("clk: at91: clk-master: re-factor master clock")
> Fixes: 75c88143f3b87 ("clk: at91: clk-master: add master clock support for SAMA7G5")
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

> ---
>   drivers/clk/at91/clk-master.c | 4 ++--
>   1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
> index 9a2c8e64cacf..2093e13b5068 100644
> --- a/drivers/clk/at91/clk-master.c
> +++ b/drivers/clk/at91/clk-master.c
> @@ -344,7 +344,7 @@ static int clk_master_pres_set_rate(struct clk_hw *hw, unsigned long rate,
>   
>   	else if (pres == 3)
>   		pres = MASTER_PRES_MAX;
> -	else
> +	else if (pres)
>   		pres = ffs(pres) - 1;
>   
>   	spin_lock_irqsave(master->lock, flags);
> @@ -757,7 +757,7 @@ static int clk_sama7g5_master_set_rate(struct clk_hw *hw, unsigned long rate,
>   
>   	if (div == 3)
>   		div = MASTER_PRES_MAX;
> -	else
> +	else if (div)
>   		div = ffs(div) - 1;
>   
>   	spin_lock_irqsave(master->lock, flags);
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 09/15] clk: at91: clk-master: mask mckr against layout->mask
  2021-10-11 11:27 ` [PATCH v5 09/15] clk: at91: clk-master: mask mckr against layout->mask Claudiu Beznea
@ 2021-10-15  7:56   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  7:56 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> Mask values read/written from/to MCKR against layout->mask as this
> mask may be different b/w PMC versions.
> 
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

> ---
>   drivers/clk/at91/clk-master.c | 7 +++++--
>   1 file changed, 5 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
> index 2093e13b5068..6da9ae34313a 100644
> --- a/drivers/clk/at91/clk-master.c
> +++ b/drivers/clk/at91/clk-master.c
> @@ -186,8 +186,8 @@ static int clk_master_div_set_rate(struct clk_hw *hw, unsigned long rate,
>   	if (ret)
>   		goto unlock;
>   
> -	tmp = mckr & master->layout->mask;
> -	tmp = (tmp >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
> +	mckr &= master->layout->mask;
> +	tmp = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
>   	if (tmp == div)
>   		goto unlock;
>   
> @@ -384,6 +384,7 @@ static unsigned long clk_master_pres_recalc_rate(struct clk_hw *hw,
>   	regmap_read(master->regmap, master->layout->offset, &val);
>   	spin_unlock_irqrestore(master->lock, flags);
>   
> +	val &= master->layout->mask;
>   	pres = (val >> master->layout->pres_shift) & MASTER_PRES_MASK;
>   	if (pres == 3 && characteristics->have_div3_pres)
>   		pres = 3;
> @@ -403,6 +404,8 @@ static u8 clk_master_pres_get_parent(struct clk_hw *hw)
>   	regmap_read(master->regmap, master->layout->offset, &mckr);
>   	spin_unlock_irqrestore(master->lock, flags);
>   
> +	mckr &= master->layout->mask;
> +
>   	return mckr & AT91_PMC_CSS;
>   }
>   
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 10/15] clk: at91: clk-master: fix prescaler logic
  2021-10-11 11:27 ` [PATCH v5 10/15] clk: at91: clk-master: fix prescaler logic Claudiu Beznea
@ 2021-10-15  7:58   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  7:58 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> When prescaler value read from register is MASTER_PRES_MAX it means
> that the input clock will be divided by 3. Fix the code to reflect
> this.
> 
> Fixes: 7a110b9107ed8 ("clk: at91: clk-master: re-factor master clock")
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

> ---
>   drivers/clk/at91/clk-master.c | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
> index 6da9ae34313a..e67bcd03a827 100644
> --- a/drivers/clk/at91/clk-master.c
> +++ b/drivers/clk/at91/clk-master.c
> @@ -386,7 +386,7 @@ static unsigned long clk_master_pres_recalc_rate(struct clk_hw *hw,
>   
>   	val &= master->layout->mask;
>   	pres = (val >> master->layout->pres_shift) & MASTER_PRES_MASK;
> -	if (pres == 3 && characteristics->have_div3_pres)
> +	if (pres == MASTER_PRES_MAX && characteristics->have_div3_pres)
>   		pres = 3;
>   	else
>   		pres = (1 << pres);
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 11/15] clk: at91: clk-sam9x60-pll: add notifier for div part of PLL
  2021-10-11 11:27 ` [PATCH v5 11/15] clk: at91: clk-sam9x60-pll: add notifier for div part of PLL Claudiu Beznea
@ 2021-10-15  8:01   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  8:01 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> SAM9X60's PLL which is also part of SAMA7G5 is composed of 2 parts:
> one fractional part and one divider. On SAMA7G5 the CPU PLL could be
> changed at run-time to implement DVFS. The hardware clock tree on
> SAMA7G5 for CPU PLL is as follows:
> 
>                         +---- div1 ----------------> cpuck
>                         |
> FRAC PLL ---> DIV PLL -+-> prescaler ---> div0 ---> mck0
> 
> The div1 block is not implemented in Linux; on prescaler block it has
> been discovered a bug on some scenarios and will be removed from Linux
> in next commits. Thus, the final clock tree that will be used in Linux
> will be as follows:
> 
>                         +-----------> cpuck
>                         |
> FRAC PLL ---> DIV PLL -+-> div0 ---> mck0
> 
> It has been proposed in [1] to not introduce a new CPUFreq driver but
> to overload the proper clock drivers with proper operation such that
> cpufreq-dt to be used. To accomplish this DIV PLL and div0 implement
> clock notifiers which applies safe dividers before FRAC PLL is changed.
> The current commit treats only the DIV PLL by adding a notifier that
> sets a safe divider on PRE_RATE_CHANGE events. The safe divider is
> provided by initialization clock code (sama7g5.c). The div0 is treated
> in next commits (to keep the changes as clean as possible).
> 
> [1] https://lore.kernel.org/lkml/20210105104426.4tmgc2l3vyicwedd@vireshk-i7/
> 
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

Yes, it's needed:
Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

> ---
>   drivers/clk/at91/clk-sam9x60-pll.c | 102 ++++++++++++++++++++++-------
>   drivers/clk/at91/pmc.h             |   3 +-
>   drivers/clk/at91/sam9x60.c         |   6 +-
>   drivers/clk/at91/sama7g5.c         |  13 +++-
>   4 files changed, 95 insertions(+), 29 deletions(-)
> 
> diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c
> index a73d7c96ce1d..d757003004cb 100644
> --- a/drivers/clk/at91/clk-sam9x60-pll.c
> +++ b/drivers/clk/at91/clk-sam9x60-pll.c
> @@ -5,6 +5,7 @@
>    */
>   
>   #include <linux/bitfield.h>
> +#include <linux/clk.h>
>   #include <linux/clk-provider.h>
>   #include <linux/clkdev.h>
>   #include <linux/clk/at91_pmc.h>
> @@ -47,12 +48,15 @@ struct sam9x60_div {
>   	struct sam9x60_pll_core core;
>   	struct at91_clk_pms pms;
>   	u8 div;
> +	u8 safe_div;
>   };
>   
>   #define to_sam9x60_pll_core(hw)	container_of(hw, struct sam9x60_pll_core, hw)
>   #define to_sam9x60_frac(core)	container_of(core, struct sam9x60_frac, core)
>   #define to_sam9x60_div(core)	container_of(core, struct sam9x60_div, core)
>   
> +static struct sam9x60_div *notifier_div;
> +
>   static inline bool sam9x60_pll_ready(struct regmap *regmap, int id)
>   {
>   	unsigned int status;
> @@ -329,6 +333,26 @@ static const struct clk_ops sam9x60_frac_pll_ops_chg = {
>   	.restore_context = sam9x60_frac_pll_restore_context,
>   };
>   
> +/* This function should be called with spinlock acquired. */
> +static void sam9x60_div_pll_set_div(struct sam9x60_pll_core *core, u32 div,
> +				    bool enable)
> +{
> +	struct regmap *regmap = core->regmap;
> +	u32 ena_msk = enable ? core->layout->endiv_mask : 0;
> +	u32 ena_val = enable ? (1 << core->layout->endiv_shift) : 0;
> +
> +	regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
> +			   core->layout->div_mask | ena_msk,
> +			   (div << core->layout->div_shift) | ena_val);
> +
> +	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
> +			   AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
> +			   AT91_PMC_PLL_UPDT_UPDATE | core->id);
> +
> +	while (!sam9x60_pll_ready(regmap, core->id))
> +		cpu_relax();
> +}
> +
>   static int sam9x60_div_pll_set(struct sam9x60_pll_core *core)
>   {
>   	struct sam9x60_div *div = to_sam9x60_div(core);
> @@ -346,17 +370,7 @@ static int sam9x60_div_pll_set(struct sam9x60_pll_core *core)
>   	if (!!(val & core->layout->endiv_mask) && cdiv == div->div)
>   		goto unlock;
>   
> -	regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
> -			   core->layout->div_mask | core->layout->endiv_mask,
> -			   (div->div << core->layout->div_shift) |
> -			   (1 << core->layout->endiv_shift));
> -
> -	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
> -			   AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
> -			   AT91_PMC_PLL_UPDT_UPDATE | core->id);
> -
> -	while (!sam9x60_pll_ready(regmap, core->id))
> -		cpu_relax();
> +	sam9x60_div_pll_set_div(core, div->div, 1);
>   
>   unlock:
>   	spin_unlock_irqrestore(core->lock, flags);
> @@ -502,16 +516,7 @@ static int sam9x60_div_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate,
>   	if (cdiv == div->div)
>   		goto unlock;
>   
> -	regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
> -			   core->layout->div_mask,
> -			   (div->div << core->layout->div_shift));
> -
> -	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
> -			   AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
> -			   AT91_PMC_PLL_UPDT_UPDATE | core->id);
> -
> -	while (!sam9x60_pll_ready(regmap, core->id))
> -		cpu_relax();
> +	sam9x60_div_pll_set_div(core, div->div, 0);
>   
>   unlock:
>   	spin_unlock_irqrestore(core->lock, irqflags);
> @@ -538,6 +543,48 @@ static void sam9x60_div_pll_restore_context(struct clk_hw *hw)
>   		sam9x60_div_pll_set(core);
>   }
>   
> +static int sam9x60_div_pll_notifier_fn(struct notifier_block *notifier,
> +				       unsigned long code, void *data)
> +{
> +	struct sam9x60_div *div = notifier_div;
> +	struct sam9x60_pll_core core = div->core;
> +	struct regmap *regmap = core.regmap;
> +	unsigned long irqflags;
> +	u32 val, cdiv;
> +	int ret = NOTIFY_DONE;
> +
> +	if (code != PRE_RATE_CHANGE)
> +		return ret;
> +
> +	/*
> +	 * We switch to safe divider to avoid overclocking of other domains
> +	 * feed by us while the frac PLL (our parent) is changed.
> +	 */
> +	div->div = div->safe_div;
> +
> +	spin_lock_irqsave(core.lock, irqflags);
> +	regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
> +			   core.id);
> +	regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
> +	cdiv = (val & core.layout->div_mask) >> core.layout->div_shift;
> +
> +	/* Stop if nothing changed. */
> +	if (cdiv == div->safe_div)
> +		goto unlock;
> +
> +	sam9x60_div_pll_set_div(&core, div->div, 0);
> +	ret = NOTIFY_OK;
> +
> +unlock:
> +	spin_unlock_irqrestore(core.lock, irqflags);
> +
> +	return ret;
> +}
> +
> +static struct notifier_block sam9x60_div_pll_notifier = {
> +	.notifier_call = sam9x60_div_pll_notifier_fn,
> +};
> +
>   static const struct clk_ops sam9x60_div_pll_ops = {
>   	.prepare = sam9x60_div_pll_prepare,
>   	.unprepare = sam9x60_div_pll_unprepare,
> @@ -647,7 +694,8 @@ struct clk_hw * __init
>   sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
>   			     const char *name, const char *parent_name, u8 id,
>   			     const struct clk_pll_characteristics *characteristics,
> -			     const struct clk_pll_layout *layout, u32 flags)
> +			     const struct clk_pll_layout *layout, u32 flags,
> +			     u32 safe_div)
>   {
>   	struct sam9x60_div *div;
>   	struct clk_hw *hw;
> @@ -656,9 +704,13 @@ sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
>   	unsigned int val;
>   	int ret;
>   
> -	if (id > PLL_MAX_ID || !lock)
> +	/* We only support one changeable PLL. */
> +	if (id > PLL_MAX_ID || !lock || (safe_div && notifier_div))
>   		return ERR_PTR(-EINVAL);
>   
> +	if (safe_div >= PLL_DIV_MAX)
> +		safe_div = PLL_DIV_MAX - 1;
> +
>   	div = kzalloc(sizeof(*div), GFP_KERNEL);
>   	if (!div)
>   		return ERR_PTR(-ENOMEM);
> @@ -678,6 +730,7 @@ sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
>   	div->core.layout = layout;
>   	div->core.regmap = regmap;
>   	div->core.lock = lock;
> +	div->safe_div = safe_div;
>   
>   	spin_lock_irqsave(div->core.lock, irqflags);
>   
> @@ -693,6 +746,9 @@ sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
>   	if (ret) {
>   		kfree(div);
>   		hw = ERR_PTR(ret);
> +	} else if (div->safe_div) {
> +		notifier_div = div;
> +		clk_notifier_register(hw->clk, &sam9x60_div_pll_notifier);
>   	}
>   
>   	return hw;
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index 45df094498ce..207ecccef29f 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -214,7 +214,8 @@ struct clk_hw * __init
>   sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
>   			     const char *name, const char *parent_name, u8 id,
>   			     const struct clk_pll_characteristics *characteristics,
> -			     const struct clk_pll_layout *layout, u32 flags);
> +			     const struct clk_pll_layout *layout, u32 flags,
> +			     u32 safe_div);
>   
>   struct clk_hw * __init
>   sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock,
> diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c
> index 5f6fa89571b7..5c264185f261 100644
> --- a/drivers/clk/at91/sam9x60.c
> +++ b/drivers/clk/at91/sam9x60.c
> @@ -242,7 +242,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
>   					    * This feeds CPU. It should not
>   					    * be disabled.
>   					    */
> -					  CLK_IS_CRITICAL | CLK_SET_RATE_GATE);
> +					  CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> @@ -260,7 +260,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
>   					  &pll_div_layout,
>   					  CLK_SET_RATE_GATE |
>   					  CLK_SET_PARENT_GATE |
> -					  CLK_SET_RATE_PARENT);
> +					  CLK_SET_RATE_PARENT, 0);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> @@ -279,7 +279,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np)
>   	hw = at91_clk_register_master_div(regmap, "masterck_div",
>   					  "masterck_pres", &sam9x60_master_layout,
>   					  &mck_characteristics, &mck_lock,
> -					  CLK_SET_RATE_GATE);
> +					  CLK_SET_RATE_GATE, 0);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c
> index 970135e19a75..ae52c10af040 100644
> --- a/drivers/clk/at91/sama7g5.c
> +++ b/drivers/clk/at91/sama7g5.c
> @@ -127,6 +127,8 @@ static const struct clk_pll_characteristics pll_characteristics = {
>    * @t:		clock type
>    * @f:		clock flags
>    * @eid:	export index in sama7g5->chws[] array
> + * @safe_div:	intermediate divider need to be set on PRE_RATE_CHANGE
> + *		notification
>    */
>   static const struct {
>   	const char *n;
> @@ -136,6 +138,7 @@ static const struct {
>   	unsigned long f;
>   	u8 t;
>   	u8 eid;
> +	u8 safe_div;
>   } sama7g5_plls[][PLL_ID_MAX] = {
>   	[PLL_ID_CPU] = {
>   		{ .n = "cpupll_fracck",
> @@ -156,7 +159,12 @@ static const struct {
>   		  .t = PLL_TYPE_DIV,
>   		   /* This feeds CPU. It should not be disabled. */
>   		  .f = CLK_IS_CRITICAL | CLK_SET_RATE_PARENT,
> -		  .eid = PMC_CPUPLL, },
> +		  .eid = PMC_CPUPLL,
> +		  /*
> +		   * Safe div=15 should be safe even for switching b/w 1GHz and
> +		   * 90MHz (frac pll might go up to 1.2GHz).
> +		   */
> +		  .safe_div = 15, },
>   	},
>   
>   	[PLL_ID_SYS] = {
> @@ -967,7 +975,8 @@ static void __init sama7g5_pmc_setup(struct device_node *np)
>   					sama7g5_plls[i][j].p, i,
>   					sama7g5_plls[i][j].c,
>   					sama7g5_plls[i][j].l,
> -					sama7g5_plls[i][j].f);
> +					sama7g5_plls[i][j].f,
> +					sama7g5_plls[i][j].safe_div);
>   				break;
>   
>   			default:
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 12/15] clk: at91: clk-master: add notifier for divider
  2021-10-11 11:27 ` [PATCH v5 12/15] clk: at91: clk-master: add notifier for divider Claudiu Beznea
@ 2021-10-15  8:06   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  8:06 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> SAMA7G5 supports DVFS by changing cpuck. On SAMA7G5 mck0 shares the same
> parent with cpuck as seen in the following clock tree:
> 
>                         +----------> cpuck
>                         |
> FRAC PLL ---> DIV PLL -+-> DIV ---> mck0
> 
> mck0 could go b/w 32KHz and 200MHz on SAMA7G5. To avoid mck0 overclocking
> while changing FRAC PLL or DIV PLL the commit implements a notifier for
> mck0 which applies a safe divider to register (maximum value of the divider
> which is 5) on PRE_RATE_CHANGE events (such that changes on PLL to not
> overclock mck0) and sets the maximum allowed rate on POST_RATE_CHANGE
> events.
> 
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

looks good to me:
Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

> ---
>   drivers/clk/at91/at91rm9200.c  |   2 +-
>   drivers/clk/at91/at91sam9260.c |   2 +-
>   drivers/clk/at91/at91sam9g45.c |   2 +-
>   drivers/clk/at91/at91sam9n12.c |   2 +-
>   drivers/clk/at91/at91sam9rl.c  |   2 +-
>   drivers/clk/at91/at91sam9x5.c  |   2 +-
>   drivers/clk/at91/clk-master.c  | 244 +++++++++++++++++++++++----------
>   drivers/clk/at91/dt-compat.c   |   2 +-
>   drivers/clk/at91/pmc.h         |   2 +-
>   drivers/clk/at91/sama5d2.c     |   2 +-
>   drivers/clk/at91/sama5d3.c     |   2 +-
>   drivers/clk/at91/sama5d4.c     |   2 +-
>   drivers/clk/at91/sama7g5.c     |   2 +-
>   13 files changed, 186 insertions(+), 82 deletions(-)
> 
> diff --git a/drivers/clk/at91/at91rm9200.c b/drivers/clk/at91/at91rm9200.c
> index 428a6f4b9ebc..fff4fdda974f 100644
> --- a/drivers/clk/at91/at91rm9200.c
> +++ b/drivers/clk/at91/at91rm9200.c
> @@ -152,7 +152,7 @@ static void __init at91rm9200_pmc_setup(struct device_node *np)
>   					  "masterck_pres",
>   					  &at91rm9200_master_layout,
>   					  &rm9200_mck_characteristics,
> -					  &rm9200_mck_lock, CLK_SET_RATE_GATE);
> +					  &rm9200_mck_lock, CLK_SET_RATE_GATE, 0);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> diff --git a/drivers/clk/at91/at91sam9260.c b/drivers/clk/at91/at91sam9260.c
> index b29843bea278..79802f864ee5 100644
> --- a/drivers/clk/at91/at91sam9260.c
> +++ b/drivers/clk/at91/at91sam9260.c
> @@ -429,7 +429,7 @@ static void __init at91sam926x_pmc_setup(struct device_node *np,
>   					  &at91rm9200_master_layout,
>   					  data->mck_characteristics,
>   					  &at91sam9260_mck_lock,
> -					  CLK_SET_RATE_GATE);
> +					  CLK_SET_RATE_GATE, 0);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> diff --git a/drivers/clk/at91/at91sam9g45.c b/drivers/clk/at91/at91sam9g45.c
> index 15da0dfe3ef2..7ed984f8058c 100644
> --- a/drivers/clk/at91/at91sam9g45.c
> +++ b/drivers/clk/at91/at91sam9g45.c
> @@ -164,7 +164,7 @@ static void __init at91sam9g45_pmc_setup(struct device_node *np)
>   					  &at91rm9200_master_layout,
>   					  &mck_characteristics,
>   					  &at91sam9g45_mck_lock,
> -					  CLK_SET_RATE_GATE);
> +					  CLK_SET_RATE_GATE, 0);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> diff --git a/drivers/clk/at91/at91sam9n12.c b/drivers/clk/at91/at91sam9n12.c
> index 7fe435f4b46b..63cc58944b00 100644
> --- a/drivers/clk/at91/at91sam9n12.c
> +++ b/drivers/clk/at91/at91sam9n12.c
> @@ -191,7 +191,7 @@ static void __init at91sam9n12_pmc_setup(struct device_node *np)
>   					  &at91sam9x5_master_layout,
>   					  &mck_characteristics,
>   					  &at91sam9n12_mck_lock,
> -					  CLK_SET_RATE_GATE);
> +					  CLK_SET_RATE_GATE, 0);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> diff --git a/drivers/clk/at91/at91sam9rl.c b/drivers/clk/at91/at91sam9rl.c
> index ecbabf5162bd..4d4faf6c61d8 100644
> --- a/drivers/clk/at91/at91sam9rl.c
> +++ b/drivers/clk/at91/at91sam9rl.c
> @@ -132,7 +132,7 @@ static void __init at91sam9rl_pmc_setup(struct device_node *np)
>   					  "masterck_pres",
>   					  &at91rm9200_master_layout,
>   					  &sam9rl_mck_characteristics,
> -					  &sam9rl_mck_lock, CLK_SET_RATE_GATE);
> +					  &sam9rl_mck_lock, CLK_SET_RATE_GATE, 0);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> diff --git a/drivers/clk/at91/at91sam9x5.c b/drivers/clk/at91/at91sam9x5.c
> index 5cce48c64ea2..bd8007b4f3e0 100644
> --- a/drivers/clk/at91/at91sam9x5.c
> +++ b/drivers/clk/at91/at91sam9x5.c
> @@ -210,7 +210,7 @@ static void __init at91sam9x5_pmc_setup(struct device_node *np,
>   					  "masterck_pres",
>   					  &at91sam9x5_master_layout,
>   					  &mck_characteristics, &mck_lock,
> -					  CLK_SET_RATE_GATE);
> +					  CLK_SET_RATE_GATE, 0);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
> index e67bcd03a827..b2d0a7f4f7f9 100644
> --- a/drivers/clk/at91/clk-master.c
> +++ b/drivers/clk/at91/clk-master.c
> @@ -5,6 +5,7 @@
>   
>   #include <linux/clk-provider.h>
>   #include <linux/clkdev.h>
> +#include <linux/clk.h>
>   #include <linux/clk/at91_pmc.h>
>   #include <linux/of.h>
>   #include <linux/mfd/syscon.h>
> @@ -36,8 +37,12 @@ struct clk_master {
>   	u8 id;
>   	u8 parent;
>   	u8 div;
> +	u32 safe_div;
>   };
>   
> +/* MCK div reference to be used by notifier. */
> +static struct clk_master *master_div;
> +
>   static inline bool clk_master_ready(struct clk_master *master)
>   {
>   	unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY;
> @@ -153,107 +158,81 @@ static const struct clk_ops master_div_ops = {
>   	.restore_context = clk_master_div_restore_context,
>   };
>   
> -static int clk_master_div_set_rate(struct clk_hw *hw, unsigned long rate,
> -				   unsigned long parent_rate)
> +/* This function must be called with lock acquired. */
> +static int clk_master_div_set(struct clk_master *master,
> +			      unsigned long parent_rate, int div)
>   {
> -	struct clk_master *master = to_clk_master(hw);
>   	const struct clk_master_characteristics *characteristics =
>   						master->characteristics;
> -	unsigned long flags;
> -	unsigned int mckr, tmp;
> -	int div, i;
> +	unsigned long rate = parent_rate;
> +	unsigned int max_div = 0, div_index = 0, max_div_index = 0;
> +	unsigned int i, mckr, tmp;
>   	int ret;
>   
> -	div = DIV_ROUND_CLOSEST(parent_rate, rate);
> -	if (div > ARRAY_SIZE(characteristics->divisors))
> -		return -EINVAL;
> -
>   	for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) {
>   		if (!characteristics->divisors[i])
>   			break;
>   
> -		if (div == characteristics->divisors[i]) {
> -			div = i;
> -			break;
> +		if (div == characteristics->divisors[i])
> +			div_index = i;
> +
> +		if (max_div < characteristics->divisors[i]) {
> +			max_div = characteristics->divisors[i];
> +			max_div_index = i;
>   		}
>   	}
>   
> -	if (i == ARRAY_SIZE(characteristics->divisors))
> -		return -EINVAL;
> +	if (div > max_div)
> +		div_index = max_div_index;
>   
> -	spin_lock_irqsave(master->lock, flags);
>   	ret = regmap_read(master->regmap, master->layout->offset, &mckr);
>   	if (ret)
> -		goto unlock;
> +		return ret;
>   
>   	mckr &= master->layout->mask;
>   	tmp = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
> -	if (tmp == div)
> -		goto unlock;
> +	if (tmp == div_index)
> +		return 0;
> +
> +	rate /= characteristics->divisors[div_index];
> +	if (rate < characteristics->output.min)
> +		pr_warn("master clk div is underclocked");
> +	else if (rate > characteristics->output.max)
> +		pr_warn("master clk div is overclocked");
>   
>   	mckr &= ~(MASTER_DIV_MASK << MASTER_DIV_SHIFT);
> -	mckr |= (div << MASTER_DIV_SHIFT);
> +	mckr |= (div_index << MASTER_DIV_SHIFT);
>   	ret = regmap_write(master->regmap, master->layout->offset, mckr);
>   	if (ret)
> -		goto unlock;
> +		return ret;
>   
>   	while (!clk_master_ready(master))
>   		cpu_relax();
> -unlock:
> -	spin_unlock_irqrestore(master->lock, flags);
> +
> +	master->div = characteristics->divisors[div_index];
>   
>   	return 0;
>   }
>   
> -static int clk_master_div_determine_rate(struct clk_hw *hw,
> -					 struct clk_rate_request *req)
> +static unsigned long clk_master_div_recalc_rate_chg(struct clk_hw *hw,
> +						    unsigned long parent_rate)
>   {
>   	struct clk_master *master = to_clk_master(hw);
> -	const struct clk_master_characteristics *characteristics =
> -						master->characteristics;
> -	struct clk_hw *parent;
> -	unsigned long parent_rate, tmp_rate, best_rate = 0;
> -	int i, best_diff = INT_MIN, tmp_diff;
> -
> -	parent = clk_hw_get_parent(hw);
> -	if (!parent)
> -		return -EINVAL;
> -
> -	parent_rate = clk_hw_get_rate(parent);
> -	if (!parent_rate)
> -		return -EINVAL;
> -
> -	for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) {
> -		if (!characteristics->divisors[i])
> -			break;
>   
> -		tmp_rate = DIV_ROUND_CLOSEST_ULL(parent_rate,
> -						 characteristics->divisors[i]);
> -		tmp_diff = abs(tmp_rate - req->rate);
> -
> -		if (!best_rate || best_diff > tmp_diff) {
> -			best_diff = tmp_diff;
> -			best_rate = tmp_rate;
> -		}
> -
> -		if (!best_diff)
> -			break;
> -	}
> -
> -	req->best_parent_rate = best_rate;
> -	req->best_parent_hw = parent;
> -	req->rate = best_rate;
> -
> -	return 0;
> +	return DIV_ROUND_CLOSEST_ULL(parent_rate, master->div);
>   }
>   
>   static void clk_master_div_restore_context_chg(struct clk_hw *hw)
>   {
>   	struct clk_master *master = to_clk_master(hw);
> +	unsigned long flags;
>   	int ret;
>   
> -	ret = clk_master_div_set_rate(hw, master->pms.rate,
> -				      master->pms.parent_rate);
> +	spin_lock_irqsave(master->lock, flags);
> +	ret = clk_master_div_set(master, master->pms.parent_rate,
> +				 DIV_ROUND_CLOSEST(master->pms.parent_rate,
> +						   master->pms.rate));
> +	spin_unlock_irqrestore(master->lock, flags);
>   	if (ret)
>   		pr_warn("Failed to restore MCK DIV clock\n");
>   }
> @@ -261,13 +240,116 @@ static void clk_master_div_restore_context_chg(struct clk_hw *hw)
>   static const struct clk_ops master_div_ops_chg = {
>   	.prepare = clk_master_prepare,
>   	.is_prepared = clk_master_is_prepared,
> -	.recalc_rate = clk_master_div_recalc_rate,
> -	.determine_rate = clk_master_div_determine_rate,
> -	.set_rate = clk_master_div_set_rate,
> +	.recalc_rate = clk_master_div_recalc_rate_chg,
>   	.save_context = clk_master_div_save_context,
>   	.restore_context = clk_master_div_restore_context_chg,
>   };
>   
> +static int clk_master_div_notifier_fn(struct notifier_block *notifier,
> +				      unsigned long code, void *data)
> +{
> +	const struct clk_master_characteristics *characteristics =
> +						master_div->characteristics;
> +	struct clk_notifier_data *cnd = data;
> +	unsigned long flags, new_parent_rate, new_rate;
> +	unsigned int mckr, div, new_div = 0;
> +	int ret, i;
> +	long tmp_diff;
> +	long best_diff = -1;
> +
> +	spin_lock_irqsave(master_div->lock, flags);
> +	switch (code) {
> +	case PRE_RATE_CHANGE:
> +		/*
> +		 * We want to avoid any overclocking of MCK DIV domain. To do
> +		 * this we set a safe divider (the underclocking is not of
> +		 * interest as we can go as low as 32KHz). The relation
> +		 * b/w this clock and its parents are as follows:
> +		 *
> +		 * FRAC PLL -> DIV PLL -> MCK DIV
> +		 *
> +		 * With the proper safe divider we should be good even with FRAC
> +		 * PLL at its maximum value.
> +		 */
> +		ret = regmap_read(master_div->regmap, master_div->layout->offset,
> +				  &mckr);
> +		if (ret) {
> +			ret = NOTIFY_STOP_MASK;
> +			goto unlock;
> +		}
> +
> +		mckr &= master_div->layout->mask;
> +		div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
> +
> +		/* Switch to safe divider. */
> +		clk_master_div_set(master_div,
> +				   cnd->old_rate * characteristics->divisors[div],
> +				   master_div->safe_div);
> +		break;
> +
> +	case POST_RATE_CHANGE:
> +		/*
> +		 * At this point we want to restore MCK DIV domain to its maximum
> +		 * allowed rate.
> +		 */
> +		ret = regmap_read(master_div->regmap, master_div->layout->offset,
> +				  &mckr);
> +		if (ret) {
> +			ret = NOTIFY_STOP_MASK;
> +			goto unlock;
> +		}
> +
> +		mckr &= master_div->layout->mask;
> +		div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
> +		new_parent_rate = cnd->new_rate * characteristics->divisors[div];
> +
> +		for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) {
> +			if (!characteristics->divisors[i])
> +				break;
> +
> +			new_rate = DIV_ROUND_CLOSEST_ULL(new_parent_rate,
> +							 characteristics->divisors[i]);
> +
> +			tmp_diff = characteristics->output.max - new_rate;
> +			if (tmp_diff < 0)
> +				continue;
> +
> +			if (best_diff < 0 || best_diff > tmp_diff) {
> +				new_div = characteristics->divisors[i];
> +				best_diff = tmp_diff;
> +			}
> +
> +			if (!tmp_diff)
> +				break;
> +		}
> +
> +		if (!new_div) {
> +			ret = NOTIFY_STOP_MASK;
> +			goto unlock;
> +		}
> +
> +		/* Update the div to preserve MCK DIV clock rate. */
> +		clk_master_div_set(master_div, new_parent_rate,
> +				   new_div);
> +
> +		ret = NOTIFY_OK;
> +		break;
> +
> +	default:
> +		ret = NOTIFY_DONE;
> +		break;
> +	}
> +
> +unlock:
> +	spin_unlock_irqrestore(master_div->lock, flags);
> +
> +	return ret;
> +}
> +
> +static struct notifier_block clk_master_div_notifier = {
> +	.notifier_call = clk_master_div_notifier_fn,
> +};
> +
>   static void clk_sama7g5_master_best_diff(struct clk_rate_request *req,
>   					 struct clk_hw *parent,
>   					 unsigned long parent_rate,
> @@ -496,6 +578,8 @@ at91_clk_register_master_internal(struct regmap *regmap,
>   	struct clk_master *master;
>   	struct clk_init_data init;
>   	struct clk_hw *hw;
> +	unsigned int mckr;
> +	unsigned long irqflags;
>   	int ret;
>   
>   	if (!name || !num_parents || !parent_names || !lock)
> @@ -518,6 +602,16 @@ at91_clk_register_master_internal(struct regmap *regmap,
>   	master->chg_pid = chg_pid;
>   	master->lock = lock;
>   
> +	if (ops == &master_div_ops_chg) {
> +		spin_lock_irqsave(master->lock, irqflags);
> +		regmap_read(master->regmap, master->layout->offset, &mckr);
> +		spin_unlock_irqrestore(master->lock, irqflags);
> +
> +		mckr &= layout->mask;
> +		mckr = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
> +		master->div = characteristics->divisors[mckr];
> +	}
> +
>   	hw = &master->hw;
>   	ret = clk_hw_register(NULL, &master->hw);
>   	if (ret) {
> @@ -554,19 +648,29 @@ at91_clk_register_master_div(struct regmap *regmap,
>   		const char *name, const char *parent_name,
>   		const struct clk_master_layout *layout,
>   		const struct clk_master_characteristics *characteristics,
> -		spinlock_t *lock, u32 flags)
> +		spinlock_t *lock, u32 flags, u32 safe_div)
>   {
>   	const struct clk_ops *ops;
> +	struct clk_hw *hw;
>   
>   	if (flags & CLK_SET_RATE_GATE)
>   		ops = &master_div_ops;
>   	else
>   		ops = &master_div_ops_chg;
>   
> -	return at91_clk_register_master_internal(regmap, name, 1,
> -						 &parent_name, layout,
> -						 characteristics, ops,
> -						 lock, flags, -EINVAL);
> +	hw = at91_clk_register_master_internal(regmap, name, 1,
> +					       &parent_name, layout,
> +					       characteristics, ops,
> +					       lock, flags, -EINVAL);
> +
> +	if (!IS_ERR(hw) && safe_div) {
> +		master_div = to_clk_master(hw);
> +		master_div->safe_div = safe_div;
> +		clk_notifier_register(hw->clk,
> +				      &clk_master_div_notifier);
> +	}
> +
> +	return hw;
>   }
>   
>   static unsigned long
> diff --git a/drivers/clk/at91/dt-compat.c b/drivers/clk/at91/dt-compat.c
> index a97b99c2dc12..ca2dbb65b9df 100644
> --- a/drivers/clk/at91/dt-compat.c
> +++ b/drivers/clk/at91/dt-compat.c
> @@ -399,7 +399,7 @@ of_at91_clk_master_setup(struct device_node *np,
>   
>   	hw = at91_clk_register_master_div(regmap, name, "masterck_pres",
>   					  layout, characteristics,
> -					  &mck_lock, CLK_SET_RATE_GATE);
> +					  &mck_lock, CLK_SET_RATE_GATE, 0);
>   	if (IS_ERR(hw))
>   		goto out_free_characteristics;
>   
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index 207ecccef29f..3a1bf6194c28 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -182,7 +182,7 @@ at91_clk_register_master_div(struct regmap *regmap, const char *name,
>   			     const char *parent_names,
>   			     const struct clk_master_layout *layout,
>   			     const struct clk_master_characteristics *characteristics,
> -			     spinlock_t *lock, u32 flags);
> +			     spinlock_t *lock, u32 flags, u32 safe_div);
>   
>   struct clk_hw * __init
>   at91_clk_sama7g5_register_master(struct regmap *regmap,
> diff --git a/drivers/clk/at91/sama5d2.c b/drivers/clk/at91/sama5d2.c
> index 3d1f78176c3e..d027294a0089 100644
> --- a/drivers/clk/at91/sama5d2.c
> +++ b/drivers/clk/at91/sama5d2.c
> @@ -249,7 +249,7 @@ static void __init sama5d2_pmc_setup(struct device_node *np)
>   					  "masterck_pres",
>   					  &at91sam9x5_master_layout,
>   					  &mck_characteristics, &mck_lock,
> -					  CLK_SET_RATE_GATE);
> +					  CLK_SET_RATE_GATE, 0);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> diff --git a/drivers/clk/at91/sama5d3.c b/drivers/clk/at91/sama5d3.c
> index d376257807d2..339d0f382ff0 100644
> --- a/drivers/clk/at91/sama5d3.c
> +++ b/drivers/clk/at91/sama5d3.c
> @@ -184,7 +184,7 @@ static void __init sama5d3_pmc_setup(struct device_node *np)
>   					  "masterck_pres",
>   					  &at91sam9x5_master_layout,
>   					  &mck_characteristics, &mck_lock,
> -					  CLK_SET_RATE_GATE);
> +					  CLK_SET_RATE_GATE, 0);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> diff --git a/drivers/clk/at91/sama5d4.c b/drivers/clk/at91/sama5d4.c
> index 5cbaac68da44..4af75b1e39e9 100644
> --- a/drivers/clk/at91/sama5d4.c
> +++ b/drivers/clk/at91/sama5d4.c
> @@ -199,7 +199,7 @@ static void __init sama5d4_pmc_setup(struct device_node *np)
>   					  "masterck_pres",
>   					  &at91sam9x5_master_layout,
>   					  &mck_characteristics, &mck_lock,
> -					  CLK_SET_RATE_GATE);
> +					  CLK_SET_RATE_GATE, 0);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c
> index ae52c10af040..c66bde6f7b47 100644
> --- a/drivers/clk/at91/sama7g5.c
> +++ b/drivers/clk/at91/sama7g5.c
> @@ -1003,7 +1003,7 @@ static void __init sama7g5_pmc_setup(struct device_node *np)
>   
>   	hw = at91_clk_register_master_div(regmap, "mck0", "cpuck",
>   					  &mck0_layout, &mck0_characteristics,
> -					  &pmc_mck0_lock, 0);
> +					  &pmc_mck0_lock, CLK_GET_RATE_NOCACHE, 5);
>   	if (IS_ERR(hw))
>   		goto err_free;
>   
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 13/15] clk: at91: sama7g5: remove prescaler part of master clock
  2021-10-11 11:27 ` [PATCH v5 13/15] clk: at91: sama7g5: remove prescaler part of master clock Claudiu Beznea
@ 2021-10-15  8:07   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  8:07 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> On SAMA7G5 the prescaler part of master clock has been implemented as a
> changeable one. Everytime the prescaler is changed the PMC_SR.MCKRDY bit
> must be polled. Value 1 for PMC_SR.MCKRDY means the prescaler update is
> done. Driver polls for this bit until it becomes 1. On SAMA7G5 it has
> been discovered that in some conditions the PMC_SR.MCKRDY is not rising
> but the rate it provides it's stable. The workaround is to add a timeout
> when polling for PMC_SR.MCKRDY. At the moment, for SAMA7G5, the prescaler
> will be removed from Linux clock tree as all the frequencies for CPU could
> be obtained from PLL and also there will be less overhead when changing
> frequency via DVFS.
> 
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

Indeed:
Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

> ---
>   drivers/clk/at91/sama7g5.c | 11 +----------
>   1 file changed, 1 insertion(+), 10 deletions(-)
> 
> diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c
> index c66bde6f7b47..fd9d17eabf54 100644
> --- a/drivers/clk/at91/sama7g5.c
> +++ b/drivers/clk/at91/sama7g5.c
> @@ -992,16 +992,7 @@ static void __init sama7g5_pmc_setup(struct device_node *np)
>   	}
>   
>   	parent_names[0] = "cpupll_divpmcck";
> -	hw = at91_clk_register_master_pres(regmap, "cpuck", 1, parent_names,
> -					   &mck0_layout, &mck0_characteristics,
> -					   &pmc_mck0_lock,
> -					   CLK_SET_RATE_PARENT, 0);
> -	if (IS_ERR(hw))
> -		goto err_free;
> -
> -	sama7g5_pmc->chws[PMC_CPU] = hw;
> -
> -	hw = at91_clk_register_master_div(regmap, "mck0", "cpuck",
> +	hw = at91_clk_register_master_div(regmap, "mck0", "cpupll_divpmcck",
>   					  &mck0_layout, &mck0_characteristics,
>   					  &pmc_mck0_lock, CLK_GET_RATE_NOCACHE, 5);
>   	if (IS_ERR(hw))
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 14/15] clk: at91: sama7g5: set low limit for mck0 at 32KHz
  2021-10-11 11:27 ` [PATCH v5 14/15] clk: at91: sama7g5: set low limit for mck0 at 32KHz Claudiu Beznea
@ 2021-10-15  8:07   ` Nicolas Ferre
  0 siblings, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  8:07 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> MCK0 could go as low as 32KHz. Set this limit.
> 
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

> ---
>   drivers/clk/at91/sama7g5.c | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c
> index fd9d17eabf54..369dfafabbca 100644
> --- a/drivers/clk/at91/sama7g5.c
> +++ b/drivers/clk/at91/sama7g5.c
> @@ -850,7 +850,7 @@ static const struct {
>   
>   /* MCK0 characteristics. */
>   static const struct clk_master_characteristics mck0_characteristics = {
> -	.output = { .min = 50000000, .max = 200000000 },
> +	.output = { .min = 32768, .max = 200000000 },
>   	.divisors = { 1, 2, 4, 3, 5 },
>   	.have_div3_pres = 1,
>   };
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 15/15] clk: use clk_core_get_rate_recalc() in clk_rate_get()
  2021-10-11 11:27 ` [PATCH v5 15/15] clk: use clk_core_get_rate_recalc() in clk_rate_get() Claudiu Beznea
@ 2021-10-15  8:08   ` Nicolas Ferre
  2021-10-27  1:26   ` Stephen Boyd
  1 sibling, 0 replies; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  8:08 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd, alexandre.belloni, ludovic.desroches
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> In case clock flags contains CLK_GET_RATE_NOCACHE the clk_rate_get()
> will return the cached rate. Thus, use clk_core_get_rate_recalc() which
> takes proper action when clock flags contains CLK_GET_RATE_NOCACHE.
> 
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>

Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>

> ---
>   drivers/clk/clk.c | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> index 65508eb89ec9..64838754cdef 100644
> --- a/drivers/clk/clk.c
> +++ b/drivers/clk/clk.c
> @@ -3108,7 +3108,7 @@ static int clk_rate_get(void *data, u64 *val)
>   {
>   	struct clk_core *core = data;
>   
> -	*val = core->rate;
> +	*val = clk_core_get_rate_recalc(core);
>   	return 0;
>   }
>   
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 00/15] clk: at91: updates for power management and dvfs
  2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
                   ` (14 preceding siblings ...)
  2021-10-11 11:27 ` [PATCH v5 15/15] clk: use clk_core_get_rate_recalc() in clk_rate_get() Claudiu Beznea
@ 2021-10-15  8:33 ` Nicolas Ferre
  2021-10-27  1:35   ` Stephen Boyd
  15 siblings, 1 reply; 34+ messages in thread
From: Nicolas Ferre @ 2021-10-15  8:33 UTC (permalink / raw)
  To: Claudiu Beznea, mturquette, sboyd
  Cc: linux-clk, linux-arm-kernel, linux-kernel, ludovic.desroches,
	alexandre.belloni

Stephen,

On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> Hi,
> 
> This series addresses the clock power management for SAMA7G5 and also
> updates the master clock driver and sam9x60-pll driver to accommodate
> the requests at [1] and avoid overclocking of CPU and MCK0 domains while
> changing the frequency via DVFS.

I've just added my Ack tags to the whole series. I think that it brings 
so much important additions to our new SoC as well as fixing some (non 
urgent) bugs.

In order to streamline the process of including this series to upstream, 
do you want me to send you a pull-request? against latest clk-at91 
branch in your tree?

Best regards,
   Nicolas

> The power management part is implemented by adding
> save_context()/restore_context() on each clock driver (patch 1/15). Since
> the PM part is necessary only for backup mode (supported on SAMA5D2 and
> SAMA7G5) the pmc.c has been adapted to call the
> save_context()/restore_context() only on switches to/from backup mode
> (patch 2/15).
> 
> Patch 3/15 adds the securam clock on SAMA7G5. This is necessary for
> backup mode of SAMA7G5.
> 
> Patch 4/15 adapt SAMA7G5 MCK1..4 driver to use the defines at
> include/linux/clk/at91_pmc.h introduced in commit ec03f18cc222
> ("clk: at91: add register definition for sama7g5's master clock").
> 
> Patch 5/15 improves a bit readability in some places of master clock
> driver.
> 
> Patch 6/15 enable the suspend/resume for clocks also for SAMA7G5.
> 
> Patches 7-10/15 adds fixes in master clock driver and sam9x60-pll driver.
> 
> Patches 11-12/15 address DVFS by adding notifiers for master clock driver
> and sam9x60-pll driver to avoid overclocking for CPU domain and MCK0
> domain.
> 
> Patch 13/15 removes the master clock prescaler from Linux clock tree
> as it has been discovered a hardware bug on it and it may not lock
> on some scenario although its output clock is stable.
> 
> Patch 14/15 decreases the low limit of MCK0 as it can go even to 32KHz.
> 
> Patch 15/15 uses clk_core_get_rate_recalc() in clk_rate_get().
> 
> Thank you,
> Claudiu Beznea
> 
> [1] https://lore.kernel.org/lkml/20210105104426.4tmgc2l3vyicwedd@vireshk-i7/
> [2] https://lore.kernel.org/lkml/163047507296.42057.10597374695758699868@swboyd.mtv.corp.google.com/
> 
> Changes in v5:
> - dropped patches 16/17, 17/17 from previous version
> - in patch 02/15:
> 	- use read_relaxed() to get the value at address pointed by
> 	  at91_pmc_backup_suspend
> 	- removed of_find_device_by_node() from pmc_register_ops() since its
> 	  returned value was not used in the function (of_device_is_available()
> 	  is enough to know before mapping securam). of_find_device_by_node()
> 	  was the one which needed the changes of postcore_initcall() with
> 	  subsys_initcall() (as described in commit description from v4) thus
> 	  kept the postcore_initcall(); also updated the commit description
> 	  to reflect this.
> 
> Changes in v4:
> - removed opp dependencies along with get_cpu_device() in patch 11/17 as
>    those functionalities are not available at the initialization time for
>    clocks instantiated with CLK_OF_DECLARE
> 
> Changes in v3:
> - minor fixes in patch 1/7 (e.g. use regmap_read() + checks + update +
>    regmap_write() sequence instead of regmap_read() + checks +
>    regmap_update_bits()
> - patch 4/17 has been updated after commit ec03f18cc222
>    ("clk: at91: add register definition for sama7g5's master clock")
> - patch 6-10/17, 14-17/17 are newly introduced
> - notifier for sam9x60 div pll was introduced (patch 11/17) and
>    notifier logic for master clock div has been changed (patch 12/17)
>    to use safe divider on PRE_RATE_CHANGE events and update clock to highest
>    possible rate on POST_RATE_CHANGE events
> - master clock prescaler has been removed from Linux clock tree for
>    SAMA7G5
> 
> Changes in v2:
> - addressed code review comments
> - collected tags
> 
> Claudiu Beznea (15):
>    clk: at91: re-factor clocks suspend/resume
>    clk: at91: pmc: execute suspend/resume only for backup mode
>    clk: at91: sama7g5: add securam's peripheral clock
>    clk: at91: clk-master: add register definition for sama7g5's master
>      clock
>    clk: at91: clk-master: improve readability by using local variables
>    clk: at91: pmc: add sama7g5 to the list of available pmcs
>    clk: at91: sam9x60-pll: use DIV_ROUND_CLOSEST_ULL
>    clk: at91: clk-master: check if div or pres is zero
>    clk: at91: clk-master: mask mckr against layout->mask
>    clk: at91: clk-master: fix prescaler logic
>    clk: at91: clk-sam9x60-pll: add notifier for div part of PLL
>    clk: at91: clk-master: add notifier for divider
>    clk: at91: sama7g5: remove prescaler part of master clock
>    clk: at91: sama7g5: set low limit for mck0 at 32KHz
>    clk: use clk_core_get_rate_recalc() in clk_rate_get()
> 
>   drivers/clk/at91/at91rm9200.c       |   2 +-
>   drivers/clk/at91/at91sam9260.c      |   2 +-
>   drivers/clk/at91/at91sam9g45.c      |   2 +-
>   drivers/clk/at91/at91sam9n12.c      |   2 +-
>   drivers/clk/at91/at91sam9rl.c       |   2 +-
>   drivers/clk/at91/at91sam9x5.c       |   2 +-
>   drivers/clk/at91/clk-generated.c    |  46 ++-
>   drivers/clk/at91/clk-main.c         |  66 ++++
>   drivers/clk/at91/clk-master.c       | 463 ++++++++++++++++++++++------
>   drivers/clk/at91/clk-peripheral.c   |  40 ++-
>   drivers/clk/at91/clk-pll.c          |  39 +++
>   drivers/clk/at91/clk-programmable.c |  29 +-
>   drivers/clk/at91/clk-sam9x60-pll.c  | 174 +++++++++--
>   drivers/clk/at91/clk-system.c       |  20 ++
>   drivers/clk/at91/clk-usb.c          |  27 ++
>   drivers/clk/at91/clk-utmi.c         |  39 +++
>   drivers/clk/at91/dt-compat.c        |   2 +-
>   drivers/clk/at91/pmc.c              | 173 +++--------
>   drivers/clk/at91/pmc.h              |  29 +-
>   drivers/clk/at91/sam9x60.c          |   6 +-
>   drivers/clk/at91/sama5d2.c          |   2 +-
>   drivers/clk/at91/sama5d3.c          |   2 +-
>   drivers/clk/at91/sama5d4.c          |   2 +-
>   drivers/clk/at91/sama7g5.c          |  29 +-
>   drivers/clk/clk.c                   |   2 +-
>   25 files changed, 890 insertions(+), 312 deletions(-)
> 


-- 
Nicolas Ferre

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 15/15] clk: use clk_core_get_rate_recalc() in clk_rate_get()
  2021-10-11 11:27 ` [PATCH v5 15/15] clk: use clk_core_get_rate_recalc() in clk_rate_get() Claudiu Beznea
  2021-10-15  8:08   ` Nicolas Ferre
@ 2021-10-27  1:26   ` Stephen Boyd
  2021-10-27  7:01     ` Claudiu.Beznea
  1 sibling, 1 reply; 34+ messages in thread
From: Stephen Boyd @ 2021-10-27  1:26 UTC (permalink / raw)
  To: Claudiu Beznea, alexandre.belloni, ludovic.desroches, mturquette,
	nicolas.ferre
  Cc: linux-clk, linux-arm-kernel, linux-kernel, Claudiu Beznea

Quoting Claudiu Beznea (2021-10-11 04:27:19)
> In case clock flags contains CLK_GET_RATE_NOCACHE the clk_rate_get()
> will return the cached rate. Thus, use clk_core_get_rate_recalc() which
> takes proper action when clock flags contains CLK_GET_RATE_NOCACHE.
> 
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
> ---
>  drivers/clk/clk.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> index 65508eb89ec9..64838754cdef 100644
> --- a/drivers/clk/clk.c
> +++ b/drivers/clk/clk.c
> @@ -3108,7 +3108,7 @@ static int clk_rate_get(void *data, u64 *val)
>  {
>         struct clk_core *core = data;
>  
> -       *val = core->rate;
> +       *val = clk_core_get_rate_recalc(core);

We need to grab the prepare lock now. I'll fix it when applying.

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 00/15] clk: at91: updates for power management and dvfs
  2021-10-15  8:33 ` [PATCH v5 00/15] clk: at91: updates for power management and dvfs Nicolas Ferre
@ 2021-10-27  1:35   ` Stephen Boyd
  0 siblings, 0 replies; 34+ messages in thread
From: Stephen Boyd @ 2021-10-27  1:35 UTC (permalink / raw)
  To: Claudiu Beznea, Nicolas Ferre, mturquette
  Cc: linux-clk, linux-arm-kernel, linux-kernel, ludovic.desroches,
	alexandre.belloni

Quoting Nicolas Ferre (2021-10-15 01:33:38)
> Stephen,
> 
> On 11/10/2021 at 13:27, Claudiu Beznea wrote:
> > Hi,
> > 
> > This series addresses the clock power management for SAMA7G5 and also
> > updates the master clock driver and sam9x60-pll driver to accommodate
> > the requests at [1] and avoid overclocking of CPU and MCK0 domains while
> > changing the frequency via DVFS.
> 
> I've just added my Ack tags to the whole series. I think that it brings 
> so much important additions to our new SoC as well as fixing some (non 
> urgent) bugs.
> 
> In order to streamline the process of including this series to upstream, 
> do you want me to send you a pull-request? against latest clk-at91 
> branch in your tree?
> 

That's fine. Just let me know if you want to do that in the future. For
now I picked them all up and pushed it out.

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 15/15] clk: use clk_core_get_rate_recalc() in clk_rate_get()
  2021-10-27  1:26   ` Stephen Boyd
@ 2021-10-27  7:01     ` Claudiu.Beznea
  0 siblings, 0 replies; 34+ messages in thread
From: Claudiu.Beznea @ 2021-10-27  7:01 UTC (permalink / raw)
  To: sboyd, alexandre.belloni, Ludovic.Desroches, mturquette, Nicolas.Ferre
  Cc: linux-clk, linux-arm-kernel, linux-kernel

On 27.10.2021 04:26, Stephen Boyd wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
> Quoting Claudiu Beznea (2021-10-11 04:27:19)
>> In case clock flags contains CLK_GET_RATE_NOCACHE the clk_rate_get()
>> will return the cached rate. Thus, use clk_core_get_rate_recalc() which
>> takes proper action when clock flags contains CLK_GET_RATE_NOCACHE.
>>
>> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
>> ---
>>  drivers/clk/clk.c | 2 +-
>>  1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
>> index 65508eb89ec9..64838754cdef 100644
>> --- a/drivers/clk/clk.c
>> +++ b/drivers/clk/clk.c
>> @@ -3108,7 +3108,7 @@ static int clk_rate_get(void *data, u64 *val)
>>  {
>>         struct clk_core *core = data;
>>
>> -       *val = core->rate;
>> +       *val = clk_core_get_rate_recalc(core);
> 
> We need to grab the prepare lock now. I'll fix it when applying.

Thank you!

> 

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

end of thread, other threads:[~2021-10-27  7:03 UTC | newest]

Thread overview: 34+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-10-11 11:27 [PATCH v5 00/15] clk: at91: updates for power management and dvfs Claudiu Beznea
2021-10-11 11:27 ` [PATCH v5 01/15] clk: at91: re-factor clocks suspend/resume Claudiu Beznea
2021-10-15  7:46   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 02/15] clk: at91: pmc: execute suspend/resume only for backup mode Claudiu Beznea
2021-10-15  7:51   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 03/15] clk: at91: sama7g5: add securam's peripheral clock Claudiu Beznea
2021-10-11 11:27 ` [PATCH v5 04/15] clk: at91: clk-master: add register definition for sama7g5's master clock Claudiu Beznea
2021-10-15  7:52   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 05/15] clk: at91: clk-master: improve readability by using local variables Claudiu Beznea
2021-10-15  7:53   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 06/15] clk: at91: pmc: add sama7g5 to the list of available pmcs Claudiu Beznea
2021-10-15  7:54   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 07/15] clk: at91: sam9x60-pll: use DIV_ROUND_CLOSEST_ULL Claudiu Beznea
2021-10-15  7:54   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 08/15] clk: at91: clk-master: check if div or pres is zero Claudiu Beznea
2021-10-15  7:55   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 09/15] clk: at91: clk-master: mask mckr against layout->mask Claudiu Beznea
2021-10-15  7:56   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 10/15] clk: at91: clk-master: fix prescaler logic Claudiu Beznea
2021-10-15  7:58   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 11/15] clk: at91: clk-sam9x60-pll: add notifier for div part of PLL Claudiu Beznea
2021-10-15  8:01   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 12/15] clk: at91: clk-master: add notifier for divider Claudiu Beznea
2021-10-15  8:06   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 13/15] clk: at91: sama7g5: remove prescaler part of master clock Claudiu Beznea
2021-10-15  8:07   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 14/15] clk: at91: sama7g5: set low limit for mck0 at 32KHz Claudiu Beznea
2021-10-15  8:07   ` Nicolas Ferre
2021-10-11 11:27 ` [PATCH v5 15/15] clk: use clk_core_get_rate_recalc() in clk_rate_get() Claudiu Beznea
2021-10-15  8:08   ` Nicolas Ferre
2021-10-27  1:26   ` Stephen Boyd
2021-10-27  7:01     ` Claudiu.Beznea
2021-10-15  8:33 ` [PATCH v5 00/15] clk: at91: updates for power management and dvfs Nicolas Ferre
2021-10-27  1:35   ` Stephen Boyd

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).