1
0
Fork 0

Merge branches 'clk-stm32f4', 'clk-tegra', 'clk-at91', 'clk-sifive-fu540' and 'clk-spdx' into clk-next

- Support for STM32F769
 - Rework AT91 sckc DT bindings
 - Fix slow RC oscillator issue on sama5d3
 - AT91 sam9x60 PMC support
 - SiFive FU540 PRCI and PLL support

* clk-stm32f4:
  clk: stm32mp1: Add ddrperfm clock
  clk: stm32: Introduce clocks of STM32F769 board

* clk-tegra:
  clk: tegra: divider: Mark Memory Controller clock as read-only
  clk: tegra: emc: Replace BUG() with WARN_ONCE()
  clk: tegra: emc: Fix EMC max-rate clamping
  clk: tegra: emc: Support multiple RAM codes
  clk: tegra: emc: Don't enable EMC clock manually
  clk: tegra124: Remove lock-enable bit from PLLM
  clk: tegra: Fix PLLM programming on Tegra124+ when PMC overrides divider
  clk: tegra: Don't enable already enabled PLLs

* clk-at91:
  clk: at91: Mark struct clk_range as const
  clk: at91: add sam9x60 pmc driver
  dt-bindings: clk: at91: add bindings for SAM9X60 pmc
  clk: at91: add sam9x60 PLL driver
  clk: at91: master: Add sam9x60 support
  clk: at91: usb: Add sam9x60 support
  clk: at91: allow configuring generated PCR layout
  clk: at91: allow configuring peripheral PCR layout
  clk: at91: sckc: handle different RC startup time
  clk: at91: modernize sckc binding
  dt-bindings: clock: at91: new sckc bindings

* clk-sifive-fu540:
  clk: sifive: add a driver for the SiFive FU540 PRCI IP block
  clk: analogbits: add Wide-Range PLL library
  dt-bindings: clk: add documentation for the SiFive PRCI driver

* clk-spdx:
  clk: sunxi-ng: Use the correct style for SPDX License Identifier
  clk: sprd: Use the correct style for SPDX License Identifier
  clk: renesas: Use the correct style for SPDX License Identifier
  clk: qcom: Use the correct style for SPDX License Identifier
  clk: davinci: Use the correct style for SPDX License Identifier
  clk: actions: Use the correct style for SPDX License Identifier
hifive-unleashed-5.2
Stephen Boyd 2019-05-07 11:45:29 -07:00
59 changed files with 2432 additions and 267 deletions

View File

@ -8,35 +8,30 @@ Slow Clock controller:
Required properties:
- compatible : shall be one of the following:
"atmel,at91sam9x5-sckc" or
"atmel,at91sam9x5-sckc",
"atmel,sama5d3-sckc" or
"atmel,sama5d4-sckc":
at91 SCKC (Slow Clock Controller)
This node contains the slow clock definitions.
"atmel,at91sam9x5-clk-slow-osc":
at91 slow oscillator
"atmel,at91sam9x5-clk-slow-rc-osc":
at91 internal slow RC oscillator
- reg : defines the IO memory reserved for the SCKC.
- #size-cells : shall be 0 (reg is used to encode clk id).
- #address-cells : shall be 1 (reg is used to encode clk id).
- #clock-cells : shall be 0.
- clocks : shall be the input parent clock phandle for the clock.
Optional properties:
- atmel,osc-bypass : boolean property. Set this when a clock signal is directly
provided on XIN.
For example:
sckc: sckc@fffffe50 {
compatible = "atmel,sama5d3-pmc";
reg = <0xfffffe50 0x4>
#size-cells = <0>;
#address-cells = <1>;
/* put at91 slow clocks here */
sckc@fffffe50 {
compatible = "atmel,at91sam9x5-sckc";
reg = <0xfffffe50 0x4>;
clocks = <&slow_xtal>;
#clock-cells = <0>;
};
Power Management Controller (PMC):
Required properties:
- compatible : shall be "atmel,<chip>-pmc", "syscon":
- compatible : shall be "atmel,<chip>-pmc", "syscon" or
"microchip,sam9x60-pmc"
<chip> can be: at91rm9200, at91sam9260, at91sam9261,
at91sam9263, at91sam9g45, at91sam9n12, at91sam9rl, at91sam9g15,
at91sam9g25, at91sam9g35, at91sam9x25, at91sam9x35, at91sam9x5,

View File

@ -0,0 +1,46 @@
SiFive FU540 PRCI bindings
On the FU540 family of SoCs, most system-wide clock and reset integration
is via the PRCI IP block.
Required properties:
- compatible: Should be "sifive,<chip>-prci". Only one value is
supported: "sifive,fu540-c000-prci"
- reg: Should describe the PRCI's register target physical address region
- clocks: Should point to the hfclk device tree node and the rtcclk
device tree node. The RTC clock here is not a time-of-day clock,
but is instead a high-stability clock source for system timers
and cycle counters.
- #clock-cells: Should be <1>
The clock consumer should specify the desired clock via the clock ID
macros defined in include/dt-bindings/clock/sifive-fu540-prci.h.
These macros begin with PRCI_CLK_.
The hfclk and rtcclk nodes are required, and represent physical
crystals or resonators located on the PCB. These nodes should be present
underneath /, rather than /soc.
Examples:
/* under /, in PCB-specific DT data */
hfclk: hfclk {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <33333333>;
clock-output-names = "hfclk";
};
rtcclk: rtcclk {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <1000000>;
clock-output-names = "rtcclk";
};
/* under /soc, in SoC-specific DT data */
prci: clock-controller@10000000 {
compatible = "sifive,fu540-c000-prci";
reg = <0x0 0x10000000 0x0 0x1000>;
clocks = <&hfclk>, <&rtcclk>;
#clock-cells = <1>;
};

View File

@ -11,6 +11,8 @@ Required properties:
"st,stm32f42xx-rcc"
"st,stm32f469-rcc"
"st,stm32f746-rcc"
"st,stm32f769-rcc"
- reg: should be register base and length as documented in the
datasheet
- #reset-cells: 1, see below
@ -102,6 +104,10 @@ The secondary index is bound with the following magic numbers:
28 CLK_I2C3
29 CLK_I2C4
30 CLK_LPTIMER (LPTimer1 clock)
31 CLK_PLL_SRC
32 CLK_DFSDM1
33 CLK_ADFSDM1
34 CLK_F769_DSI
)
Example:

View File

@ -960,6 +960,12 @@ F: drivers/iio/adc/ltc2497*
X: drivers/iio/*/adjd*
F: drivers/staging/iio/*/ad*
ANALOGBITS PLL LIBRARIES
M: Paul Walmsley <paul.walmsley@sifive.com>
S: Supported
F: drivers/clk/analogbits/*
F: include/linux/clk/analogbits*
ANDES ARCHITECTURE
M: Greentime Hu <green.hu@gmail.com>
M: Vincent Chen <deanbo422@gmail.com>

View File

@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
config CLKDEV_LOOKUP
bool
@ -304,6 +305,7 @@ config COMMON_CLK_FIXED_MMIO
Support for Memory Mapped IO Fixed clocks
source "drivers/clk/actions/Kconfig"
source "drivers/clk/analogbits/Kconfig"
source "drivers/clk/bcm/Kconfig"
source "drivers/clk/hisilicon/Kconfig"
source "drivers/clk/imgtec/Kconfig"
@ -316,6 +318,7 @@ source "drivers/clk/mvebu/Kconfig"
source "drivers/clk/qcom/Kconfig"
source "drivers/clk/renesas/Kconfig"
source "drivers/clk/samsung/Kconfig"
source "drivers/clk/sifive/Kconfig"
source "drivers/clk/sprd/Kconfig"
source "drivers/clk/sunxi/Kconfig"
source "drivers/clk/sunxi-ng/Kconfig"

View File

@ -66,6 +66,7 @@ obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o
# please keep this section sorted lexicographically by directory path name
obj-y += actions/
obj-y += analogbits/
obj-$(CONFIG_COMMON_CLK_AT91) += at91/
obj-$(CONFIG_ARCH_ARTPEC) += axis/
obj-$(CONFIG_ARC_PLAT_AXS10X) += axs10x/
@ -95,6 +96,7 @@ obj-$(CONFIG_COMMON_CLK_QCOM) += qcom/
obj-y += renesas/
obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
obj-$(CONFIG_COMMON_CLK_SAMSUNG) += samsung/
obj-$(CONFIG_CLK_SIFIVE) += sifive/
obj-$(CONFIG_ARCH_SIRF) += sirf/
obj-$(CONFIG_ARCH_SOCFPGA) += socfpga/
obj-$(CONFIG_PLAT_SPEAR) += spear/

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0+
/* SPDX-License-Identifier: GPL-2.0+ */
//
// OWL common clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0+
/* SPDX-License-Identifier: GPL-2.0+ */
//
// OWL composite clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0+
/* SPDX-License-Identifier: GPL-2.0+ */
//
// OWL divider clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0+
/* SPDX-License-Identifier: GPL-2.0+ */
//
// OWL factor clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0+
/* SPDX-License-Identifier: GPL-2.0+ */
//
// OWL fixed factor clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0+
/* SPDX-License-Identifier: GPL-2.0+ */
//
// OWL gate clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0+
/* SPDX-License-Identifier: GPL-2.0+ */
//
// OWL mux clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0+
/* SPDX-License-Identifier: GPL-2.0+ */
//
// OWL pll clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* SPDX-License-Identifier: GPL-2.0-or-later */
//
// Actions Semi Owl SoCs Reset Management Unit driver
//

View File

@ -0,0 +1,2 @@
config CLK_ANALOGBITS_WRPLL_CLN28HPC
bool

View File

@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CLK_ANALOGBITS_WRPLL_CLN28HPC) += wrpll-cln28hpc.o

View File

@ -0,0 +1,364 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2018-2019 SiFive, Inc.
* Wesley Terpstra
* Paul Walmsley
*
* This library supports configuration parsing and reprogramming of
* the CLN28HPC variant of the Analog Bits Wide Range PLL. The
* intention is for this library to be reusable for any device that
* integrates this PLL; thus the register structure and programming
* details are expected to be provided by a separate IP block driver.
*
* The bulk of this code is primarily useful for clock configurations
* that must operate at arbitrary rates, as opposed to clock configurations
* that are restricted by software or manufacturer guidance to a small,
* pre-determined set of performance points.
*
* References:
* - Analog Bits "Wide Range PLL Datasheet", version 2015.10.01
* - SiFive FU540-C000 Manual v1p0, Chapter 7 "Clocking and Reset"
* https://static.dev.sifive.com/FU540-C000-v1.0.pdf
*/
#include <linux/bug.h>
#include <linux/err.h>
#include <linux/log2.h>
#include <linux/math64.h>
#include <linux/clk/analogbits-wrpll-cln28hpc.h>
/* MIN_INPUT_FREQ: minimum input clock frequency, in Hz (Fref_min) */
#define MIN_INPUT_FREQ 7000000
/* MAX_INPUT_FREQ: maximum input clock frequency, in Hz (Fref_max) */
#define MAX_INPUT_FREQ 600000000
/* MIN_POST_DIVIDE_REF_FREQ: minimum post-divider reference frequency, in Hz */
#define MIN_POST_DIVR_FREQ 7000000
/* MAX_POST_DIVIDE_REF_FREQ: maximum post-divider reference frequency, in Hz */
#define MAX_POST_DIVR_FREQ 200000000
/* MIN_VCO_FREQ: minimum VCO frequency, in Hz (Fvco_min) */
#define MIN_VCO_FREQ 2400000000UL
/* MAX_VCO_FREQ: maximum VCO frequency, in Hz (Fvco_max) */
#define MAX_VCO_FREQ 4800000000ULL
/* MAX_DIVQ_DIVISOR: maximum output divisor. Selected by DIVQ = 6 */
#define MAX_DIVQ_DIVISOR 64
/* MAX_DIVR_DIVISOR: maximum reference divisor. Selected by DIVR = 63 */
#define MAX_DIVR_DIVISOR 64
/* MAX_LOCK_US: maximum PLL lock time, in microseconds (tLOCK_max) */
#define MAX_LOCK_US 70
/*
* ROUND_SHIFT: number of bits to shift to avoid precision loss in the rounding
* algorithm
*/
#define ROUND_SHIFT 20
/*
* Private functions
*/
/**
* __wrpll_calc_filter_range() - determine PLL loop filter bandwidth
* @post_divr_freq: input clock rate after the R divider
*
* Select the value to be presented to the PLL RANGE input signals, based
* on the input clock frequency after the post-R-divider @post_divr_freq.
* This code follows the recommendations in the PLL datasheet for filter
* range selection.
*
* Return: The RANGE value to be presented to the PLL configuration inputs,
* or a negative return code upon error.
*/
static int __wrpll_calc_filter_range(unsigned long post_divr_freq)
{
if (post_divr_freq < MIN_POST_DIVR_FREQ ||
post_divr_freq > MAX_POST_DIVR_FREQ) {
WARN(1, "%s: post-divider reference freq out of range: %lu",
__func__, post_divr_freq);
return -ERANGE;
}
switch (post_divr_freq) {
case 0 ... 10999999:
return 1;
case 11000000 ... 17999999:
return 2;
case 18000000 ... 29999999:
return 3;
case 30000000 ... 49999999:
return 4;
case 50000000 ... 79999999:
return 5;
case 80000000 ... 129999999:
return 6;
}
return 7;
}
/**
* __wrpll_calc_fbdiv() - return feedback fixed divide value
* @c: ptr to a struct wrpll_cfg record to read from
*
* The internal feedback path includes a fixed by-two divider; the
* external feedback path does not. Return the appropriate divider
* value (2 or 1) depending on whether internal or external feedback
* is enabled. This code doesn't test for invalid configurations
* (e.g. both or neither of WRPLL_FLAGS_*_FEEDBACK are set); it relies
* on the caller to do so.
*
* Context: Any context. Caller must protect the memory pointed to by
* @c from simultaneous modification.
*
* Return: 2 if internal feedback is enabled or 1 if external feedback
* is enabled.
*/
static u8 __wrpll_calc_fbdiv(const struct wrpll_cfg *c)
{
return (c->flags & WRPLL_FLAGS_INT_FEEDBACK_MASK) ? 2 : 1;
}
/**
* __wrpll_calc_divq() - determine DIVQ based on target PLL output clock rate
* @target_rate: target PLL output clock rate
* @vco_rate: pointer to a u64 to store the computed VCO rate into
*
* Determine a reasonable value for the PLL Q post-divider, based on the
* target output rate @target_rate for the PLL. Along with returning the
* computed Q divider value as the return value, this function stores the
* desired target VCO rate into the variable pointed to by @vco_rate.
*
* Context: Any context. Caller must protect the memory pointed to by
* @vco_rate from simultaneous access or modification.
*
* Return: a positive integer DIVQ value to be programmed into the hardware
* upon success, or 0 upon error (since 0 is an invalid DIVQ value)
*/
static u8 __wrpll_calc_divq(u32 target_rate, u64 *vco_rate)
{
u64 s;
u8 divq = 0;
if (!vco_rate) {
WARN_ON(1);
goto wcd_out;
}
s = div_u64(MAX_VCO_FREQ, target_rate);
if (s <= 1) {
divq = 1;
*vco_rate = MAX_VCO_FREQ;
} else if (s > MAX_DIVQ_DIVISOR) {
divq = ilog2(MAX_DIVQ_DIVISOR);
*vco_rate = MIN_VCO_FREQ;
} else {
divq = ilog2(s);
*vco_rate = (u64)target_rate << divq;
}
wcd_out:
return divq;
}
/**
* __wrpll_update_parent_rate() - update PLL data when parent rate changes
* @c: ptr to a struct wrpll_cfg record to write PLL data to
* @parent_rate: PLL input refclk rate (pre-R-divider)
*
* Pre-compute some data used by the PLL configuration algorithm when
* the PLL's reference clock rate changes. The intention is to avoid
* computation when the parent rate remains constant - expected to be
* the common case.
*
* Returns: 0 upon success or -ERANGE if the reference clock rate is
* out of range.
*/
static int __wrpll_update_parent_rate(struct wrpll_cfg *c,
unsigned long parent_rate)
{
u8 max_r_for_parent;
if (parent_rate > MAX_INPUT_FREQ || parent_rate < MIN_POST_DIVR_FREQ)
return -ERANGE;
c->parent_rate = parent_rate;
max_r_for_parent = div_u64(parent_rate, MIN_POST_DIVR_FREQ);
c->max_r = min_t(u8, MAX_DIVR_DIVISOR, max_r_for_parent);
c->init_r = DIV_ROUND_UP_ULL(parent_rate, MAX_POST_DIVR_FREQ);
return 0;
}
/**
* wrpll_configure() - compute PLL configuration for a target rate
* @c: ptr to a struct wrpll_cfg record to write into
* @target_rate: target PLL output clock rate (post-Q-divider)
* @parent_rate: PLL input refclk rate (pre-R-divider)
*
* Compute the appropriate PLL signal configuration values and store
* in PLL context @c. PLL reprogramming is not glitchless, so the
* caller should switch any downstream logic to a different clock
* source or clock-gate it before presenting these values to the PLL
* configuration signals.
*
* The caller must pass this function a pre-initialized struct
* wrpll_cfg record: either initialized to zero (with the
* exception of the .name and .flags fields) or read from the PLL.
*
* Context: Any context. Caller must protect the memory pointed to by @c
* from simultaneous access or modification.
*
* Return: 0 upon success; anything else upon failure.
*/
int wrpll_configure_for_rate(struct wrpll_cfg *c, u32 target_rate,
unsigned long parent_rate)
{
unsigned long ratio;
u64 target_vco_rate, delta, best_delta, f_pre_div, vco, vco_pre;
u32 best_f, f, post_divr_freq;
u8 fbdiv, divq, best_r, r;
int range;
if (c->flags == 0) {
WARN(1, "%s called with uninitialized PLL config", __func__);
return -EINVAL;
}
/* Initialize rounding data if it hasn't been initialized already */
if (parent_rate != c->parent_rate) {
if (__wrpll_update_parent_rate(c, parent_rate)) {
pr_err("%s: PLL input rate is out of range\n",
__func__);
return -ERANGE;
}
}
c->flags &= ~WRPLL_FLAGS_RESET_MASK;
/* Put the PLL into bypass if the user requests the parent clock rate */
if (target_rate == parent_rate) {
c->flags |= WRPLL_FLAGS_BYPASS_MASK;
return 0;
}
c->flags &= ~WRPLL_FLAGS_BYPASS_MASK;
/* Calculate the Q shift and target VCO rate */
divq = __wrpll_calc_divq(target_rate, &target_vco_rate);
if (!divq)
return -1;
c->divq = divq;
/* Precalculate the pre-Q divider target ratio */
ratio = div64_u64((target_vco_rate << ROUND_SHIFT), parent_rate);
fbdiv = __wrpll_calc_fbdiv(c);
best_r = 0;
best_f = 0;
best_delta = MAX_VCO_FREQ;
/*
* Consider all values for R which land within
* [MIN_POST_DIVR_FREQ, MAX_POST_DIVR_FREQ]; prefer smaller R
*/
for (r = c->init_r; r <= c->max_r; ++r) {
f_pre_div = ratio * r;
f = (f_pre_div + (1 << ROUND_SHIFT)) >> ROUND_SHIFT;
f >>= (fbdiv - 1);
post_divr_freq = div_u64(parent_rate, r);
vco_pre = fbdiv * post_divr_freq;
vco = vco_pre * f;
/* Ensure rounding didn't take us out of range */
if (vco > target_vco_rate) {
--f;
vco = vco_pre * f;
} else if (vco < MIN_VCO_FREQ) {
++f;
vco = vco_pre * f;
}
delta = abs(target_rate - vco);
if (delta < best_delta) {
best_delta = delta;
best_r = r;
best_f = f;
}
}
c->divr = best_r - 1;
c->divf = best_f - 1;
post_divr_freq = div_u64(parent_rate, best_r);
/* Pick the best PLL jitter filter */
range = __wrpll_calc_filter_range(post_divr_freq);
if (range < 0)
return range;
c->range = range;
return 0;
}
/**
* wrpll_calc_output_rate() - calculate the PLL's target output rate
* @c: ptr to a struct wrpll_cfg record to read from
* @parent_rate: PLL refclk rate
*
* Given a pointer to the PLL's current input configuration @c and the
* PLL's input reference clock rate @parent_rate (before the R
* pre-divider), calculate the PLL's output clock rate (after the Q
* post-divider).
*
* Context: Any context. Caller must protect the memory pointed to by @c
* from simultaneous modification.
*
* Return: the PLL's output clock rate, in Hz. The return value from
* this function is intended to be convenient to pass directly
* to the Linux clock framework; thus there is no explicit
* error return value.
*/
unsigned long wrpll_calc_output_rate(const struct wrpll_cfg *c,
unsigned long parent_rate)
{
u8 fbdiv;
u64 n;
if (c->flags & WRPLL_FLAGS_EXT_FEEDBACK_MASK) {
WARN(1, "external feedback mode not yet supported");
return ULONG_MAX;
}
fbdiv = __wrpll_calc_fbdiv(c);
n = parent_rate * fbdiv * (c->divf + 1);
n = div_u64(n, c->divr + 1);
n >>= c->divq;
return n;
}
/**
* wrpll_calc_max_lock_us() - return the time for the PLL to lock
* @c: ptr to a struct wrpll_cfg record to read from
*
* Return the minimum amount of time (in microseconds) that the caller
* must wait after reprogramming the PLL to ensure that it is locked
* to the input frequency and stable. This is likely to depend on the DIVR
* value; this is under discussion with the manufacturer.
*
* Return: the minimum amount of time the caller must wait for the PLL
* to lock (in microseconds)
*/
unsigned int wrpll_calc_max_lock_us(const struct wrpll_cfg *c)
{
return MAX_LOCK_US;
}

View File

@ -14,6 +14,8 @@ obj-$(CONFIG_HAVE_AT91_SMD) += clk-smd.o
obj-$(CONFIG_HAVE_AT91_H32MX) += clk-h32mx.o
obj-$(CONFIG_HAVE_AT91_GENERATED_CLK) += clk-generated.o
obj-$(CONFIG_HAVE_AT91_I2S_MUX_CLK) += clk-i2s-mux.o
obj-$(CONFIG_HAVE_AT91_SAM9X60_PLL) += clk-sam9x60-pll.o
obj-$(CONFIG_SOC_AT91SAM9) += at91sam9260.o at91sam9rl.o at91sam9x5.o
obj-$(CONFIG_SOC_SAM9X60) += sam9x60.o
obj-$(CONFIG_SOC_SAMA5D4) += sama5d4.o
obj-$(CONFIG_SOC_SAMA5D2) += sama5d2.o

View File

@ -41,7 +41,7 @@ static u8 sam9260_plla_out[] = { 0, 2 };
static u16 sam9260_plla_icpll[] = { 1, 1 };
static struct clk_range sam9260_plla_outputs[] = {
static const struct clk_range sam9260_plla_outputs[] = {
{ .min = 80000000, .max = 160000000 },
{ .min = 150000000, .max = 240000000 },
};
@ -58,7 +58,7 @@ static u8 sam9260_pllb_out[] = { 1 };
static u16 sam9260_pllb_icpll[] = { 1 };
static struct clk_range sam9260_pllb_outputs[] = {
static const struct clk_range sam9260_pllb_outputs[] = {
{ .min = 70000000, .max = 130000000 },
};
@ -128,7 +128,7 @@ static u8 sam9g20_plla_out[] = { 0, 1, 2, 3, 0, 1, 2, 3 };
static u16 sam9g20_plla_icpll[] = { 0, 0, 0, 0, 1, 1, 1, 1 };
static struct clk_range sam9g20_plla_outputs[] = {
static const struct clk_range sam9g20_plla_outputs[] = {
{ .min = 745000000, .max = 800000000 },
{ .min = 695000000, .max = 750000000 },
{ .min = 645000000, .max = 700000000 },
@ -151,7 +151,7 @@ static u8 sam9g20_pllb_out[] = { 0 };
static u16 sam9g20_pllb_icpll[] = { 0 };
static struct clk_range sam9g20_pllb_outputs[] = {
static const struct clk_range sam9g20_pllb_outputs[] = {
{ .min = 30000000, .max = 100000000 },
};
@ -182,7 +182,7 @@ static const struct clk_master_characteristics sam9261_mck_characteristics = {
.divisors = { 1, 2, 4, 0 },
};
static struct clk_range sam9261_plla_outputs[] = {
static const struct clk_range sam9261_plla_outputs[] = {
{ .min = 80000000, .max = 200000000 },
{ .min = 190000000, .max = 240000000 },
};
@ -199,7 +199,7 @@ static u8 sam9261_pllb_out[] = { 1 };
static u16 sam9261_pllb_icpll[] = { 1 };
static struct clk_range sam9261_pllb_outputs[] = {
static const struct clk_range sam9261_pllb_outputs[] = {
{ .min = 70000000, .max = 130000000 },
};
@ -262,7 +262,7 @@ static const struct clk_master_characteristics sam9263_mck_characteristics = {
.divisors = { 1, 2, 4, 0 },
};
static struct clk_range sam9263_pll_outputs[] = {
static const struct clk_range sam9263_pll_outputs[] = {
{ .min = 80000000, .max = 200000000 },
{ .min = 190000000, .max = 240000000 },
};

View File

@ -14,7 +14,7 @@ static const struct clk_master_characteristics sam9rl_mck_characteristics = {
static u8 sam9rl_plla_out[] = { 0, 2 };
static struct clk_range sam9rl_plla_outputs[] = {
static const struct clk_range sam9rl_plla_outputs[] = {
{ .min = 80000000, .max = 200000000 },
{ .min = 190000000, .max = 240000000 },
};

View File

@ -17,7 +17,7 @@ static u8 plla_out[] = { 0, 1, 2, 3, 0, 1, 2, 3 };
static u16 plla_icpll[] = { 0, 0, 0, 0, 1, 1, 1, 1 };
static struct clk_range plla_outputs[] = {
static const struct clk_range plla_outputs[] = {
{ .min = 745000000, .max = 800000000 },
{ .min = 695000000, .max = 750000000 },
{ .min = 645000000, .max = 700000000 },
@ -49,6 +49,13 @@ static const struct {
{ .n = "pck1", .p = "prog1", .id = 9 },
};
static const struct clk_pcr_layout at91sam9x5_pcr_layout = {
.offset = 0x10c,
.cmd = BIT(12),
.pid_mask = GENMASK(5, 0),
.div_mask = GENMASK(17, 16),
};
struct pck {
char *n;
u8 id;
@ -242,6 +249,7 @@ static void __init at91sam9x5_pmc_setup(struct device_node *np,
for (i = 0; i < ARRAY_SIZE(at91sam9x5_periphck); i++) {
hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
&at91sam9x5_pcr_layout,
at91sam9x5_periphck[i].n,
"masterck",
at91sam9x5_periphck[i].id,
@ -254,6 +262,7 @@ static void __init at91sam9x5_pmc_setup(struct device_node *np,
for (i = 0; extra_pcks[i].id; i++) {
hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
&at91sam9x5_pcr_layout,
extra_pcks[i].n,
"masterck",
extra_pcks[i].id,

View File

@ -11,6 +11,7 @@
*
*/
#include <linux/bitfield.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/clk/at91_pmc.h>
@ -31,6 +32,7 @@ struct clk_generated {
spinlock_t *lock;
u32 id;
u32 gckdiv;
const struct clk_pcr_layout *layout;
u8 parent_id;
bool audio_pll_allowed;
};
@ -47,14 +49,14 @@ static int clk_generated_enable(struct clk_hw *hw)
__func__, gck->gckdiv, gck->parent_id);
spin_lock_irqsave(gck->lock, flags);
regmap_write(gck->regmap, AT91_PMC_PCR,
(gck->id & AT91_PMC_PCR_PID_MASK));
regmap_update_bits(gck->regmap, AT91_PMC_PCR,
AT91_PMC_PCR_GCKDIV_MASK | AT91_PMC_PCR_GCKCSS_MASK |
AT91_PMC_PCR_CMD | AT91_PMC_PCR_GCKEN,
AT91_PMC_PCR_GCKCSS(gck->parent_id) |
AT91_PMC_PCR_CMD |
AT91_PMC_PCR_GCKDIV(gck->gckdiv) |
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,
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);
spin_unlock_irqrestore(gck->lock, flags);
return 0;
@ -66,11 +68,11 @@ static void clk_generated_disable(struct clk_hw *hw)
unsigned long flags;
spin_lock_irqsave(gck->lock, flags);
regmap_write(gck->regmap, AT91_PMC_PCR,
(gck->id & AT91_PMC_PCR_PID_MASK));
regmap_update_bits(gck->regmap, AT91_PMC_PCR,
AT91_PMC_PCR_CMD | AT91_PMC_PCR_GCKEN,
AT91_PMC_PCR_CMD);
regmap_write(gck->regmap, gck->layout->offset,
(gck->id & gck->layout->pid_mask));
regmap_update_bits(gck->regmap, gck->layout->offset,
gck->layout->cmd | AT91_PMC_PCR_GCKEN,
gck->layout->cmd);
spin_unlock_irqrestore(gck->lock, flags);
}
@ -81,9 +83,9 @@ static int clk_generated_is_enabled(struct clk_hw *hw)
unsigned int status;
spin_lock_irqsave(gck->lock, flags);
regmap_write(gck->regmap, AT91_PMC_PCR,
(gck->id & AT91_PMC_PCR_PID_MASK));
regmap_read(gck->regmap, AT91_PMC_PCR, &status);
regmap_write(gck->regmap, gck->layout->offset,
(gck->id & gck->layout->pid_mask));
regmap_read(gck->regmap, gck->layout->offset, &status);
spin_unlock_irqrestore(gck->lock, flags);
return status & AT91_PMC_PCR_GCKEN ? 1 : 0;
@ -259,19 +261,18 @@ static void clk_generated_startup(struct clk_generated *gck)
unsigned long flags;
spin_lock_irqsave(gck->lock, flags);
regmap_write(gck->regmap, AT91_PMC_PCR,
(gck->id & AT91_PMC_PCR_PID_MASK));
regmap_read(gck->regmap, AT91_PMC_PCR, &tmp);
regmap_write(gck->regmap, gck->layout->offset,
(gck->id & gck->layout->pid_mask));
regmap_read(gck->regmap, gck->layout->offset, &tmp);
spin_unlock_irqrestore(gck->lock, flags);
gck->parent_id = (tmp & AT91_PMC_PCR_GCKCSS_MASK)
>> AT91_PMC_PCR_GCKCSS_OFFSET;
gck->gckdiv = (tmp & AT91_PMC_PCR_GCKDIV_MASK)
>> AT91_PMC_PCR_GCKDIV_OFFSET;
gck->parent_id = field_get(gck->layout->gckcss_mask, tmp);
gck->gckdiv = FIELD_GET(AT91_PMC_PCR_GCKDIV_MASK, tmp);
}
struct clk_hw * __init
at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock,
const struct clk_pcr_layout *layout,
const char *name, const char **parent_names,
u8 num_parents, u8 id, bool pll_audio,
const struct clk_range *range)
@ -298,6 +299,7 @@ at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock,
gck->lock = lock;
gck->range = *range;
gck->audio_pll_allowed = pll_audio;
gck->layout = layout;
clk_generated_startup(gck);
hw = &gck->hw;

View File

@ -29,6 +29,7 @@ struct clk_master {
struct regmap *regmap;
const struct clk_master_layout *layout;
const struct clk_master_characteristics *characteristics;
u32 mckr;
};
static inline bool clk_master_ready(struct regmap *regmap)
@ -69,7 +70,7 @@ static unsigned long clk_master_recalc_rate(struct clk_hw *hw,
master->characteristics;
unsigned int mckr;
regmap_read(master->regmap, AT91_PMC_MCKR, &mckr);
regmap_read(master->regmap, master->layout->offset, &mckr);
mckr &= layout->mask;
pres = (mckr >> layout->pres_shift) & MASTER_PRES_MASK;
@ -95,7 +96,7 @@ static u8 clk_master_get_parent(struct clk_hw *hw)
struct clk_master *master = to_clk_master(hw);
unsigned int mckr;
regmap_read(master->regmap, AT91_PMC_MCKR, &mckr);
regmap_read(master->regmap, master->layout->offset, &mckr);
return mckr & AT91_PMC_CSS;
}
@ -147,13 +148,14 @@ at91_clk_register_master(struct regmap *regmap,
return hw;
}
const struct clk_master_layout at91rm9200_master_layout = {
.mask = 0x31F,
.pres_shift = 2,
.offset = AT91_PMC_MCKR,
};
const struct clk_master_layout at91sam9x5_master_layout = {
.mask = 0x373,
.pres_shift = 4,
.offset = AT91_PMC_MCKR,
};

View File

@ -8,6 +8,7 @@
*
*/
#include <linux/bitops.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/clk/at91_pmc.h>
@ -23,9 +24,6 @@ DEFINE_SPINLOCK(pmc_pcr_lock);
#define PERIPHERAL_ID_MAX 31
#define PERIPHERAL_MASK(id) (1 << ((id) & PERIPHERAL_ID_MAX))
#define PERIPHERAL_RSHIFT_MASK 0x3
#define PERIPHERAL_RSHIFT(val) (((val) >> 16) & PERIPHERAL_RSHIFT_MASK)
#define PERIPHERAL_MAX_SHIFT 3
struct clk_peripheral {
@ -43,6 +41,7 @@ struct clk_sam9x5_peripheral {
spinlock_t *lock;
u32 id;
u32 div;
const struct clk_pcr_layout *layout;
bool auto_div;
};
@ -169,13 +168,13 @@ static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
return 0;
spin_lock_irqsave(periph->lock, flags);
regmap_write(periph->regmap, AT91_PMC_PCR,
(periph->id & AT91_PMC_PCR_PID_MASK));
regmap_update_bits(periph->regmap, AT91_PMC_PCR,
AT91_PMC_PCR_DIV_MASK | AT91_PMC_PCR_CMD |
regmap_write(periph->regmap, periph->layout->offset,
(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,
AT91_PMC_PCR_DIV(periph->div) |
AT91_PMC_PCR_CMD |
field_prep(periph->layout->div_mask, periph->div) |
periph->layout->cmd |
AT91_PMC_PCR_EN);
spin_unlock_irqrestore(periph->lock, flags);
@ -191,11 +190,11 @@ static void clk_sam9x5_peripheral_disable(struct clk_hw *hw)
return;
spin_lock_irqsave(periph->lock, flags);
regmap_write(periph->regmap, AT91_PMC_PCR,
(periph->id & AT91_PMC_PCR_PID_MASK));
regmap_update_bits(periph->regmap, AT91_PMC_PCR,
AT91_PMC_PCR_EN | AT91_PMC_PCR_CMD,
AT91_PMC_PCR_CMD);
regmap_write(periph->regmap, periph->layout->offset,
(periph->id & periph->layout->pid_mask));
regmap_update_bits(periph->regmap, periph->layout->offset,
AT91_PMC_PCR_EN | periph->layout->cmd,
periph->layout->cmd);
spin_unlock_irqrestore(periph->lock, flags);
}
@ -209,9 +208,9 @@ static int clk_sam9x5_peripheral_is_enabled(struct clk_hw *hw)
return 1;
spin_lock_irqsave(periph->lock, flags);
regmap_write(periph->regmap, AT91_PMC_PCR,
(periph->id & AT91_PMC_PCR_PID_MASK));
regmap_read(periph->regmap, AT91_PMC_PCR, &status);
regmap_write(periph->regmap, periph->layout->offset,
(periph->id & periph->layout->pid_mask));
regmap_read(periph->regmap, periph->layout->offset, &status);
spin_unlock_irqrestore(periph->lock, flags);
return status & AT91_PMC_PCR_EN ? 1 : 0;
@ -229,13 +228,13 @@ clk_sam9x5_peripheral_recalc_rate(struct clk_hw *hw,
return parent_rate;
spin_lock_irqsave(periph->lock, flags);
regmap_write(periph->regmap, AT91_PMC_PCR,
(periph->id & AT91_PMC_PCR_PID_MASK));
regmap_read(periph->regmap, AT91_PMC_PCR, &status);
regmap_write(periph->regmap, periph->layout->offset,
(periph->id & periph->layout->pid_mask));
regmap_read(periph->regmap, periph->layout->offset, &status);
spin_unlock_irqrestore(periph->lock, flags);
if (status & AT91_PMC_PCR_EN) {
periph->div = PERIPHERAL_RSHIFT(status);
periph->div = field_get(periph->layout->div_mask, status);
periph->auto_div = false;
} else {
clk_sam9x5_peripheral_autodiv(periph);
@ -328,6 +327,7 @@ static const struct clk_ops sam9x5_peripheral_ops = {
struct clk_hw * __init
at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock,
const struct clk_pcr_layout *layout,
const char *name, const char *parent_name,
u32 id, const struct clk_range *range)
{
@ -354,7 +354,9 @@ at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock,
periph->div = 0;
periph->regmap = regmap;
periph->lock = lock;
periph->auto_div = true;
if (layout->div_mask)
periph->auto_div = true;
periph->layout = layout;
periph->range = *range;
hw = &periph->hw;

View File

@ -0,0 +1,330 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019 Microchip Technology Inc.
*
*/
#include <linux/bitfield.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/clk/at91_pmc.h>
#include <linux/of.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include "pmc.h"
#define PMC_PLL_CTRL0 0xc
#define PMC_PLL_CTRL0_DIV_MSK GENMASK(7, 0)
#define PMC_PLL_CTRL0_ENPLL BIT(28)
#define PMC_PLL_CTRL0_ENPLLCK BIT(29)
#define PMC_PLL_CTRL0_ENLOCK BIT(31)
#define PMC_PLL_CTRL1 0x10
#define PMC_PLL_CTRL1_FRACR_MSK GENMASK(21, 0)
#define PMC_PLL_CTRL1_MUL_MSK GENMASK(30, 24)
#define PMC_PLL_ACR 0x18
#define PMC_PLL_ACR_DEFAULT 0x1b040010UL
#define PMC_PLL_ACR_UTMIVR BIT(12)
#define PMC_PLL_ACR_UTMIBG BIT(13)
#define PMC_PLL_ACR_LOOP_FILTER_MSK GENMASK(31, 24)
#define PMC_PLL_UPDT 0x1c
#define PMC_PLL_UPDT_UPDATE BIT(8)
#define PMC_PLL_ISR0 0xec
#define PLL_DIV_MAX (FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, UINT_MAX) + 1)
#define UPLL_DIV 2
#define PLL_MUL_MAX (FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, UINT_MAX) + 1)
#define PLL_MAX_ID 1
struct sam9x60_pll {
struct clk_hw hw;
struct regmap *regmap;
spinlock_t *lock;
const struct clk_pll_characteristics *characteristics;
u32 frac;
u8 id;
u8 div;
u16 mul;
};
#define to_sam9x60_pll(hw) container_of(hw, struct sam9x60_pll, hw)
static inline bool sam9x60_pll_ready(struct regmap *regmap, int id)
{
unsigned int status;
regmap_read(regmap, PMC_PLL_ISR0, &status);
return !!(status & BIT(id));
}
static int sam9x60_pll_prepare(struct clk_hw *hw)
{
struct sam9x60_pll *pll = to_sam9x60_pll(hw);
struct regmap *regmap = pll->regmap;
unsigned long flags;
u8 div;
u16 mul;
u32 val;
spin_lock_irqsave(pll->lock, flags);
regmap_write(regmap, PMC_PLL_UPDT, pll->id);
regmap_read(regmap, PMC_PLL_CTRL0, &val);
div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, val);
regmap_read(regmap, PMC_PLL_CTRL1, &val);
mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, val);
if (sam9x60_pll_ready(regmap, pll->id) &&
(div == pll->div && mul == pll->mul)) {
spin_unlock_irqrestore(pll->lock, flags);
return 0;
}
/* Recommended value for PMC_PLL_ACR */
val = PMC_PLL_ACR_DEFAULT;
regmap_write(regmap, PMC_PLL_ACR, val);
regmap_write(regmap, PMC_PLL_CTRL1,
FIELD_PREP(PMC_PLL_CTRL1_MUL_MSK, pll->mul));
if (pll->characteristics->upll) {
/* Enable the UTMI internal bandgap */
val |= PMC_PLL_ACR_UTMIBG;
regmap_write(regmap, PMC_PLL_ACR, val);
udelay(10);
/* Enable the UTMI internal regulator */
val |= PMC_PLL_ACR_UTMIVR;
regmap_write(regmap, PMC_PLL_ACR, val);
udelay(10);
}
regmap_update_bits(regmap, PMC_PLL_UPDT,
PMC_PLL_UPDT_UPDATE, PMC_PLL_UPDT_UPDATE);
regmap_write(regmap, PMC_PLL_CTRL0,
PMC_PLL_CTRL0_ENLOCK | PMC_PLL_CTRL0_ENPLL |
PMC_PLL_CTRL0_ENPLLCK | pll->div);
regmap_update_bits(regmap, PMC_PLL_UPDT,
PMC_PLL_UPDT_UPDATE, PMC_PLL_UPDT_UPDATE);
while (!sam9x60_pll_ready(regmap, pll->id))
cpu_relax();
spin_unlock_irqrestore(pll->lock, flags);
return 0;
}
static int sam9x60_pll_is_prepared(struct clk_hw *hw)
{
struct sam9x60_pll *pll = to_sam9x60_pll(hw);
return sam9x60_pll_ready(pll->regmap, pll->id);
}
static void sam9x60_pll_unprepare(struct clk_hw *hw)
{
struct sam9x60_pll *pll = to_sam9x60_pll(hw);
unsigned long flags;
spin_lock_irqsave(pll->lock, flags);
regmap_write(pll->regmap, PMC_PLL_UPDT, pll->id);
regmap_update_bits(pll->regmap, PMC_PLL_CTRL0,
PMC_PLL_CTRL0_ENPLLCK, 0);
regmap_update_bits(pll->regmap, PMC_PLL_UPDT,
PMC_PLL_UPDT_UPDATE, PMC_PLL_UPDT_UPDATE);
regmap_update_bits(pll->regmap, PMC_PLL_CTRL0, PMC_PLL_CTRL0_ENPLL, 0);
if (pll->characteristics->upll)
regmap_update_bits(pll->regmap, PMC_PLL_ACR,
PMC_PLL_ACR_UTMIBG | PMC_PLL_ACR_UTMIVR, 0);
regmap_update_bits(pll->regmap, PMC_PLL_UPDT,
PMC_PLL_UPDT_UPDATE, PMC_PLL_UPDT_UPDATE);
spin_unlock_irqrestore(pll->lock, flags);
}
static unsigned long sam9x60_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct sam9x60_pll *pll = to_sam9x60_pll(hw);
return (parent_rate * (pll->mul + 1)) / (pll->div + 1);
}
static long sam9x60_pll_get_best_div_mul(struct sam9x60_pll *pll,
unsigned long rate,
unsigned long parent_rate,
bool update)
{
const struct clk_pll_characteristics *characteristics =
pll->characteristics;
unsigned long bestremainder = ULONG_MAX;
unsigned long maxdiv, mindiv, tmpdiv;
long bestrate = -ERANGE;
unsigned long bestdiv = 0;
unsigned long bestmul = 0;
unsigned long bestfrac = 0;
if (rate < characteristics->output[0].min ||
rate > characteristics->output[0].max)
return -ERANGE;
if (!pll->characteristics->upll) {
mindiv = parent_rate / rate;
if (mindiv < 2)
mindiv = 2;
maxdiv = DIV_ROUND_UP(parent_rate * PLL_MUL_MAX, rate);
if (maxdiv > PLL_DIV_MAX)
maxdiv = PLL_DIV_MAX;
} else {
mindiv = maxdiv = UPLL_DIV;
}
for (tmpdiv = mindiv; tmpdiv <= maxdiv; tmpdiv++) {
unsigned long remainder;
unsigned long tmprate;
unsigned long tmpmul;
unsigned long tmpfrac = 0;
/*
* Calculate the multiplier associated with the current
* divider that provide the closest rate to the requested one.
*/
tmpmul = mult_frac(rate, tmpdiv, parent_rate);
tmprate = mult_frac(parent_rate, tmpmul, tmpdiv);
remainder = rate - tmprate;
if (remainder) {
tmpfrac = DIV_ROUND_CLOSEST_ULL((u64)remainder * tmpdiv * (1 << 22),
parent_rate);
tmprate += DIV_ROUND_CLOSEST_ULL((u64)tmpfrac * parent_rate,
tmpdiv * (1 << 22));
if (tmprate > rate)
remainder = tmprate - rate;
else
remainder = rate - tmprate;
}
/*
* Compare the remainder with the best remainder found until
* now and elect a new best multiplier/divider pair if the
* current remainder is smaller than the best one.
*/
if (remainder < bestremainder) {
bestremainder = remainder;
bestdiv = tmpdiv;
bestmul = tmpmul;
bestrate = tmprate;
bestfrac = tmpfrac;
}
/* We've found a perfect match! */
if (!remainder)
break;
}
/* Check if bestrate is a valid output rate */
if (bestrate < characteristics->output[0].min &&
bestrate > characteristics->output[0].max)
return -ERANGE;
if (update) {
pll->div = bestdiv - 1;
pll->mul = bestmul - 1;
pll->frac = bestfrac;
}
return bestrate;
}
static long sam9x60_pll_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct sam9x60_pll *pll = to_sam9x60_pll(hw);
return sam9x60_pll_get_best_div_mul(pll, rate, *parent_rate, false);
}
static int sam9x60_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct sam9x60_pll *pll = to_sam9x60_pll(hw);
return sam9x60_pll_get_best_div_mul(pll, rate, parent_rate, true);
}
static const struct clk_ops pll_ops = {
.prepare = sam9x60_pll_prepare,
.unprepare = sam9x60_pll_unprepare,
.is_prepared = sam9x60_pll_is_prepared,
.recalc_rate = sam9x60_pll_recalc_rate,
.round_rate = sam9x60_pll_round_rate,
.set_rate = sam9x60_pll_set_rate,
};
struct clk_hw * __init
sam9x60_clk_register_pll(struct regmap *regmap, spinlock_t *lock,
const char *name, const char *parent_name, u8 id,
const struct clk_pll_characteristics *characteristics)
{
struct sam9x60_pll *pll;
struct clk_hw *hw;
struct clk_init_data init;
unsigned int pllr;
int ret;
if (id > PLL_MAX_ID)
return ERR_PTR(-EINVAL);
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
if (!pll)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &pll_ops;
init.parent_names = &parent_name;
init.num_parents = 1;
init.flags = CLK_SET_RATE_GATE;
pll->id = id;
pll->hw.init = &init;
pll->characteristics = characteristics;
pll->regmap = regmap;
pll->lock = lock;
regmap_write(regmap, PMC_PLL_UPDT, id);
regmap_read(regmap, PMC_PLL_CTRL0, &pllr);
pll->div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, pllr);
regmap_read(regmap, PMC_PLL_CTRL1, &pllr);
pll->mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, pllr);
hw = &pll->hw;
ret = clk_hw_register(NULL, hw);
if (ret) {
kfree(pll);
hw = ERR_PTR(ret);
}
return hw;
}

View File

@ -23,9 +23,13 @@
#define RM9200_USB_DIV_SHIFT 28
#define RM9200_USB_DIV_TAB_SIZE 4
#define SAM9X5_USBS_MASK GENMASK(0, 0)
#define SAM9X60_USBS_MASK GENMASK(1, 0)
struct at91sam9x5_clk_usb {
struct clk_hw hw;
struct regmap *regmap;
u32 usbs_mask;
};
#define to_at91sam9x5_clk_usb(hw) \
@ -111,8 +115,7 @@ static int at91sam9x5_clk_usb_set_parent(struct clk_hw *hw, u8 index)
if (index > 1)
return -EINVAL;
regmap_update_bits(usb->regmap, AT91_PMC_USB, AT91_PMC_USBS,
index ? AT91_PMC_USBS : 0);
regmap_update_bits(usb->regmap, AT91_PMC_USB, usb->usbs_mask, index);
return 0;
}
@ -124,7 +127,7 @@ static u8 at91sam9x5_clk_usb_get_parent(struct clk_hw *hw)
regmap_read(usb->regmap, AT91_PMC_USB, &usbr);
return usbr & AT91_PMC_USBS;
return usbr & usb->usbs_mask;
}
static int at91sam9x5_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
@ -190,9 +193,10 @@ static const struct clk_ops at91sam9n12_usb_ops = {
.set_rate = at91sam9x5_clk_usb_set_rate,
};
struct clk_hw * __init
at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name,
const char **parent_names, u8 num_parents)
static struct clk_hw * __init
_at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name,
const char **parent_names, u8 num_parents,
u32 usbs_mask)
{
struct at91sam9x5_clk_usb *usb;
struct clk_hw *hw;
@ -212,6 +216,7 @@ at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name,
usb->hw.init = &init;
usb->regmap = regmap;
usb->usbs_mask = SAM9X5_USBS_MASK;
hw = &usb->hw;
ret = clk_hw_register(NULL, &usb->hw);
@ -223,6 +228,22 @@ at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name,
return hw;
}
struct clk_hw * __init
at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name,
const char **parent_names, u8 num_parents)
{
return _at91sam9x5_clk_register_usb(regmap, name, parent_names,
num_parents, SAM9X5_USBS_MASK);
}
struct clk_hw * __init
sam9x60_clk_register_usb(struct regmap *regmap, const char *name,
const char **parent_names, u8 num_parents)
{
return _at91sam9x5_clk_register_usb(regmap, name, parent_names,
num_parents, SAM9X60_USBS_MASK);
}
struct clk_hw * __init
at91sam9n12_clk_register_usb(struct regmap *regmap, const char *name,
const char *parent_name)

View File

@ -93,6 +93,14 @@ CLK_OF_DECLARE(of_sama5d2_clk_audio_pll_pmc_setup,
of_sama5d2_clk_audio_pll_pmc_setup);
#endif /* CONFIG_HAVE_AT91_AUDIO_PLL */
static const struct clk_pcr_layout dt_pcr_layout = {
.offset = 0x10c,
.cmd = BIT(12),
.pid_mask = GENMASK(5, 0),
.div_mask = GENMASK(17, 16),
.gckcss_mask = GENMASK(10, 8),
};
#ifdef CONFIG_HAVE_AT91_GENERATED_CLK
#define GENERATED_SOURCE_MAX 6
@ -146,7 +154,8 @@ static void __init of_sama5d2_clk_generated_setup(struct device_node *np)
id == GCK_ID_CLASSD))
pll_audio = true;
hw = at91_clk_register_generated(regmap, &pmc_pcr_lock, name,
hw = at91_clk_register_generated(regmap, &pmc_pcr_lock,
&dt_pcr_layout, name,
parent_names, num_parents,
id, pll_audio, &range);
if (IS_ERR(hw))
@ -448,6 +457,7 @@ of_at91_clk_periph_setup(struct device_node *np, u8 type)
hw = at91_clk_register_sam9x5_peripheral(regmap,
&pmc_pcr_lock,
&dt_pcr_layout,
name,
parent_name,
id, &range);

View File

@ -38,6 +38,7 @@ struct clk_range {
#define CLK_RANGE(MIN, MAX) {.min = MIN, .max = MAX,}
struct clk_master_layout {
u32 offset;
u32 mask;
u8 pres_shift;
};
@ -65,9 +66,10 @@ extern const struct clk_pll_layout sama5d3_pll_layout;
struct clk_pll_characteristics {
struct clk_range input;
int num_output;
struct clk_range *output;
const struct clk_range *output;
u16 *icpll;
u8 *out;
u8 upll : 1;
};
struct clk_programmable_layout {
@ -82,6 +84,17 @@ extern const struct clk_programmable_layout at91rm9200_programmable_layout;
extern const struct clk_programmable_layout at91sam9g45_programmable_layout;
extern const struct clk_programmable_layout at91sam9x5_programmable_layout;
struct clk_pcr_layout {
u32 offset;
u32 cmd;
u32 div_mask;
u32 gckcss_mask;
u32 pid_mask;
};
#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1))
#define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask))
#define ndck(a, s) (a[s - 1].id + 1)
#define nck(a) (a[ARRAY_SIZE(a) - 1].id + 1)
struct pmc_data *pmc_data_allocate(unsigned int ncore, unsigned int nsystem,
@ -107,6 +120,7 @@ at91_clk_register_audio_pll_pmc(struct regmap *regmap, const char *name,
struct clk_hw * __init
at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock,
const struct clk_pcr_layout *layout,
const char *name, const char **parent_names,
u8 num_parents, u8 id, bool pll_audio,
const struct clk_range *range);
@ -145,6 +159,7 @@ at91_clk_register_peripheral(struct regmap *regmap, const char *name,
const char *parent_name, u32 id);
struct clk_hw * __init
at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock,
const struct clk_pcr_layout *layout,
const char *name, const char *parent_name,
u32 id, const struct clk_range *range);
@ -157,6 +172,11 @@ struct clk_hw * __init
at91_clk_register_plldiv(struct regmap *regmap, const char *name,
const char *parent_name);
struct clk_hw * __init
sam9x60_clk_register_pll(struct regmap *regmap, spinlock_t *lock,
const char *name, const char *parent_name, u8 id,
const struct clk_pll_characteristics *characteristics);
struct clk_hw * __init
at91_clk_register_programmable(struct regmap *regmap, const char *name,
const char **parent_names, u8 num_parents, u8 id,
@ -183,6 +203,9 @@ struct clk_hw * __init
at91sam9n12_clk_register_usb(struct regmap *regmap, const char *name,
const char *parent_name);
struct clk_hw * __init
sam9x60_clk_register_usb(struct regmap *regmap, const char *name,
const char **parent_names, u8 num_parents);
struct clk_hw * __init
at91rm9200_clk_register_usb(struct regmap *regmap, const char *name,
const char *parent_name, const u32 *divisors);

View File

@ -0,0 +1,307 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/clk-provider.h>
#include <linux/mfd/syscon.h>
#include <linux/slab.h>
#include <dt-bindings/clock/at91.h>
#include "pmc.h"
static DEFINE_SPINLOCK(pmc_pll_lock);
static const struct clk_master_characteristics mck_characteristics = {
.output = { .min = 140000000, .max = 200000000 },
.divisors = { 1, 2, 4, 3 },
.have_div3_pres = 1,
};
static const struct clk_master_layout sam9x60_master_layout = {
.mask = 0x373,
.pres_shift = 4,
.offset = 0x28,
};
static const struct clk_range plla_outputs[] = {
{ .min = 300000000, .max = 600000000 },
};
static const struct clk_pll_characteristics plla_characteristics = {
.input = { .min = 12000000, .max = 48000000 },
.num_output = ARRAY_SIZE(plla_outputs),
.output = plla_outputs,
};
static const struct clk_range upll_outputs[] = {
{ .min = 300000000, .max = 500000000 },
};
static const struct clk_pll_characteristics upll_characteristics = {
.input = { .min = 12000000, .max = 48000000 },
.num_output = ARRAY_SIZE(upll_outputs),
.output = upll_outputs,
.upll = true,
};
static const struct clk_programmable_layout sam9x60_programmable_layout = {
.pres_shift = 8,
.css_mask = 0x1f,
.have_slck_mck = 0,
};
static const struct clk_pcr_layout sam9x60_pcr_layout = {
.offset = 0x88,
.cmd = BIT(31),
.gckcss_mask = GENMASK(12, 8),
.pid_mask = GENMASK(6, 0),
};
static const struct {
char *n;
char *p;
u8 id;
} sam9x60_systemck[] = {
{ .n = "ddrck", .p = "masterck", .id = 2 },
{ .n = "uhpck", .p = "usbck", .id = 6 },
{ .n = "pck0", .p = "prog0", .id = 8 },
{ .n = "pck1", .p = "prog1", .id = 9 },
{ .n = "qspick", .p = "masterck", .id = 19 },
};
static const struct {
char *n;
u8 id;
} sam9x60_periphck[] = {
{ .n = "pioA_clk", .id = 2, },
{ .n = "pioB_clk", .id = 3, },
{ .n = "pioC_clk", .id = 4, },
{ .n = "flex0_clk", .id = 5, },
{ .n = "flex1_clk", .id = 6, },
{ .n = "flex2_clk", .id = 7, },
{ .n = "flex3_clk", .id = 8, },
{ .n = "flex6_clk", .id = 9, },
{ .n = "flex7_clk", .id = 10, },
{ .n = "flex8_clk", .id = 11, },
{ .n = "sdmmc0_clk", .id = 12, },
{ .n = "flex4_clk", .id = 13, },
{ .n = "flex5_clk", .id = 14, },
{ .n = "flex9_clk", .id = 15, },
{ .n = "flex10_clk", .id = 16, },
{ .n = "tcb0_clk", .id = 17, },
{ .n = "pwm_clk", .id = 18, },
{ .n = "adc_clk", .id = 19, },
{ .n = "dma0_clk", .id = 20, },
{ .n = "matrix_clk", .id = 21, },
{ .n = "uhphs_clk", .id = 22, },
{ .n = "udphs_clk", .id = 23, },
{ .n = "macb0_clk", .id = 24, },
{ .n = "lcd_clk", .id = 25, },
{ .n = "sdmmc1_clk", .id = 26, },
{ .n = "macb1_clk", .id = 27, },
{ .n = "ssc_clk", .id = 28, },
{ .n = "can0_clk", .id = 29, },
{ .n = "can1_clk", .id = 30, },
{ .n = "flex11_clk", .id = 32, },
{ .n = "flex12_clk", .id = 33, },
{ .n = "i2s_clk", .id = 34, },
{ .n = "qspi_clk", .id = 35, },
{ .n = "gfx2d_clk", .id = 36, },
{ .n = "pit64b_clk", .id = 37, },
{ .n = "trng_clk", .id = 38, },
{ .n = "aes_clk", .id = 39, },
{ .n = "tdes_clk", .id = 40, },
{ .n = "sha_clk", .id = 41, },
{ .n = "classd_clk", .id = 42, },
{ .n = "isi_clk", .id = 43, },
{ .n = "pioD_clk", .id = 44, },
{ .n = "tcb1_clk", .id = 45, },
{ .n = "dbgu_clk", .id = 47, },
{ .n = "mpddr_clk", .id = 49, },
};
static const struct {
char *n;
u8 id;
struct clk_range r;
bool pll;
} sam9x60_gck[] = {
{ .n = "flex0_gclk", .id = 5, },
{ .n = "flex1_gclk", .id = 6, },
{ .n = "flex2_gclk", .id = 7, },
{ .n = "flex3_gclk", .id = 8, },
{ .n = "flex6_gclk", .id = 9, },
{ .n = "flex7_gclk", .id = 10, },
{ .n = "flex8_gclk", .id = 11, },
{ .n = "sdmmc0_gclk", .id = 12, .r = { .min = 0, .max = 105000000 }, },
{ .n = "flex4_gclk", .id = 13, },
{ .n = "flex5_gclk", .id = 14, },
{ .n = "flex9_gclk", .id = 15, },
{ .n = "flex10_gclk", .id = 16, },
{ .n = "tcb0_gclk", .id = 17, },
{ .n = "adc_gclk", .id = 19, },
{ .n = "lcd_gclk", .id = 25, .r = { .min = 0, .max = 140000000 }, },
{ .n = "sdmmc1_gclk", .id = 26, .r = { .min = 0, .max = 105000000 }, },
{ .n = "flex11_gclk", .id = 32, },
{ .n = "flex12_gclk", .id = 33, },
{ .n = "i2s_gclk", .id = 34, .r = { .min = 0, .max = 105000000 },
.pll = true, },
{ .n = "pit64b_gclk", .id = 37, },
{ .n = "classd_gclk", .id = 42, .r = { .min = 0, .max = 100000000 },
.pll = true, },
{ .n = "tcb1_gclk", .id = 45, },
{ .n = "dbgu_gclk", .id = 47, },
};
static void __init sam9x60_pmc_setup(struct device_node *np)
{
struct clk_range range = CLK_RANGE(0, 0);
const char *td_slck_name, *md_slck_name, *mainxtal_name;
struct pmc_data *sam9x60_pmc;
const char *parent_names[6];
struct regmap *regmap;
struct clk_hw *hw;
int i;
bool bypass;
i = of_property_match_string(np, "clock-names", "td_slck");
if (i < 0)
return;
td_slck_name = of_clk_get_parent_name(np, i);
i = of_property_match_string(np, "clock-names", "md_slck");
if (i < 0)
return;
md_slck_name = of_clk_get_parent_name(np, i);
i = of_property_match_string(np, "clock-names", "main_xtal");
if (i < 0)
return;
mainxtal_name = of_clk_get_parent_name(np, i);
regmap = syscon_node_to_regmap(np);
if (IS_ERR(regmap))
return;
sam9x60_pmc = pmc_data_allocate(PMC_MAIN + 1,
nck(sam9x60_systemck),
nck(sam9x60_periphck),
nck(sam9x60_gck));
if (!sam9x60_pmc)
return;
hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 24000000,
50000000);
if (IS_ERR(hw))
goto err_free;
bypass = of_property_read_bool(np, "atmel,osc-bypass");
hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name,
bypass);
if (IS_ERR(hw))
goto err_free;
parent_names[0] = "main_rc_osc";
parent_names[1] = "main_osc";
hw = at91_clk_register_sam9x5_main(regmap, "mainck", parent_names, 2);
if (IS_ERR(hw))
goto err_free;
sam9x60_pmc->chws[PMC_MAIN] = hw;
hw = sam9x60_clk_register_pll(regmap, &pmc_pll_lock, "pllack",
"mainck", 0, &plla_characteristics);
if (IS_ERR(hw))
goto err_free;
hw = sam9x60_clk_register_pll(regmap, &pmc_pll_lock, "upllck",
"main_osc", 1, &upll_characteristics);
if (IS_ERR(hw))
goto err_free;
sam9x60_pmc->chws[PMC_UTMI] = hw;
parent_names[0] = md_slck_name;
parent_names[1] = "mainck";
parent_names[2] = "pllack";
hw = at91_clk_register_master(regmap, "masterck", 3, parent_names,
&sam9x60_master_layout,
&mck_characteristics);
if (IS_ERR(hw))
goto err_free;
sam9x60_pmc->chws[PMC_MCK] = hw;
parent_names[0] = "pllack";
parent_names[1] = "upllck";
parent_names[2] = "mainck";
parent_names[3] = "mainck";
hw = sam9x60_clk_register_usb(regmap, "usbck", parent_names, 4);
if (IS_ERR(hw))
goto err_free;
parent_names[0] = md_slck_name;
parent_names[1] = td_slck_name;
parent_names[2] = "mainck";
parent_names[3] = "masterck";
parent_names[4] = "pllack";
parent_names[5] = "upllck";
for (i = 0; i < 8; i++) {
char name[6];
snprintf(name, sizeof(name), "prog%d", i);
hw = at91_clk_register_programmable(regmap, name,
parent_names, 6, i,
&sam9x60_programmable_layout);
if (IS_ERR(hw))
goto err_free;
}
for (i = 0; i < ARRAY_SIZE(sam9x60_systemck); i++) {
hw = at91_clk_register_system(regmap, sam9x60_systemck[i].n,
sam9x60_systemck[i].p,
sam9x60_systemck[i].id);
if (IS_ERR(hw))
goto err_free;
sam9x60_pmc->shws[sam9x60_systemck[i].id] = hw;
}
for (i = 0; i < ARRAY_SIZE(sam9x60_periphck); i++) {
hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
&sam9x60_pcr_layout,
sam9x60_periphck[i].n,
"masterck",
sam9x60_periphck[i].id,
&range);
if (IS_ERR(hw))
goto err_free;
sam9x60_pmc->phws[sam9x60_periphck[i].id] = hw;
}
for (i = 0; i < ARRAY_SIZE(sam9x60_gck); i++) {
hw = at91_clk_register_generated(regmap, &pmc_pcr_lock,
&sam9x60_pcr_layout,
sam9x60_gck[i].n,
parent_names, 6,
sam9x60_gck[i].id,
sam9x60_gck[i].pll,
&sam9x60_gck[i].r);
if (IS_ERR(hw))
goto err_free;
sam9x60_pmc->ghws[sam9x60_gck[i].id] = hw;
}
of_clk_add_hw_provider(np, of_clk_hw_pmc_get, sam9x60_pmc);
return;
err_free:
pmc_data_free(sam9x60_pmc);
}
/* Some clks are used for a clocksource */
CLK_OF_DECLARE(sam9x60_pmc, "microchip,sam9x60-pmc", sam9x60_pmc_setup);

View File

@ -16,7 +16,7 @@ static u8 plla_out[] = { 0 };
static u16 plla_icpll[] = { 0 };
static struct clk_range plla_outputs[] = {
static const struct clk_range plla_outputs[] = {
{ .min = 600000000, .max = 1200000000 },
};
@ -28,6 +28,13 @@ static const struct clk_pll_characteristics plla_characteristics = {
.out = plla_out,
};
static const struct clk_pcr_layout sama5d2_pcr_layout = {
.offset = 0x10c,
.cmd = BIT(12),
.gckcss_mask = GENMASK(10, 8),
.pid_mask = GENMASK(6, 0),
};
static const struct {
char *n;
char *p;
@ -274,6 +281,7 @@ static void __init sama5d2_pmc_setup(struct device_node *np)
for (i = 0; i < ARRAY_SIZE(sama5d2_periphck); i++) {
hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
&sama5d2_pcr_layout,
sama5d2_periphck[i].n,
"masterck",
sama5d2_periphck[i].id,
@ -286,6 +294,7 @@ static void __init sama5d2_pmc_setup(struct device_node *np)
for (i = 0; i < ARRAY_SIZE(sama5d2_periph32ck); i++) {
hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
&sama5d2_pcr_layout,
sama5d2_periph32ck[i].n,
"h32mxck",
sama5d2_periph32ck[i].id,
@ -304,6 +313,7 @@ static void __init sama5d2_pmc_setup(struct device_node *np)
parent_names[5] = "audiopll_pmcck";
for (i = 0; i < ARRAY_SIZE(sama5d2_gck); i++) {
hw = at91_clk_register_generated(regmap, &pmc_pcr_lock,
&sama5d2_pcr_layout,
sama5d2_gck[i].n,
parent_names, 6,
sama5d2_gck[i].id,

View File

@ -16,7 +16,7 @@ static u8 plla_out[] = { 0 };
static u16 plla_icpll[] = { 0 };
static struct clk_range plla_outputs[] = {
static const struct clk_range plla_outputs[] = {
{ .min = 600000000, .max = 1200000000 },
};
@ -28,6 +28,12 @@ static const struct clk_pll_characteristics plla_characteristics = {
.out = plla_out,
};
static const struct clk_pcr_layout sama5d4_pcr_layout = {
.offset = 0x10c,
.cmd = BIT(12),
.pid_mask = GENMASK(6, 0),
};
static const struct {
char *n;
char *p;
@ -232,6 +238,7 @@ static void __init sama5d4_pmc_setup(struct device_node *np)
for (i = 0; i < ARRAY_SIZE(sama5d4_periphck); i++) {
hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
&sama5d4_pcr_layout,
sama5d4_periphck[i].n,
"masterck",
sama5d4_periphck[i].id,
@ -244,6 +251,7 @@ static void __init sama5d4_pmc_setup(struct device_node *np)
for (i = 0; i < ARRAY_SIZE(sama5d4_periph32ck); i++) {
hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
&sama5d4_pcr_layout,
sama5d4_periph32ck[i].n,
"h32mxck",
sama5d4_periph32ck[i].id,

View File

@ -152,28 +152,6 @@ at91_clk_register_slow_osc(void __iomem *sckcr,
return hw;
}
static void __init
of_at91sam9x5_clk_slow_osc_setup(struct device_node *np, void __iomem *sckcr)
{
struct clk_hw *hw;
const char *parent_name;
const char *name = np->name;
u32 startup;
bool bypass;
parent_name = of_clk_get_parent_name(np, 0);
of_property_read_string(np, "clock-output-names", &name);
of_property_read_u32(np, "atmel,startup-time-usec", &startup);
bypass = of_property_read_bool(np, "atmel,osc-bypass");
hw = at91_clk_register_slow_osc(sckcr, name, parent_name, startup,
bypass);
if (IS_ERR(hw))
return;
of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
}
static unsigned long clk_slow_rc_osc_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
@ -266,28 +244,6 @@ at91_clk_register_slow_rc_osc(void __iomem *sckcr,
return hw;
}
static void __init
of_at91sam9x5_clk_slow_rc_osc_setup(struct device_node *np, void __iomem *sckcr)
{
struct clk_hw *hw;
u32 frequency = 0;
u32 accuracy = 0;
u32 startup = 0;
const char *name = np->name;
of_property_read_string(np, "clock-output-names", &name);
of_property_read_u32(np, "clock-frequency", &frequency);
of_property_read_u32(np, "clock-accuracy", &accuracy);
of_property_read_u32(np, "atmel,startup-time-usec", &startup);
hw = at91_clk_register_slow_rc_osc(sckcr, name, frequency, accuracy,
startup);
if (IS_ERR(hw))
return;
of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
}
static int clk_sam9x5_slow_set_parent(struct clk_hw *hw, u8 index)
{
struct clk_sam9x5_slow *slowck = to_clk_sam9x5_slow(hw);
@ -365,68 +321,72 @@ at91_clk_register_sam9x5_slow(void __iomem *sckcr,
return hw;
}
static void __init
of_at91sam9x5_clk_slow_setup(struct device_node *np, void __iomem *sckcr)
static void __init at91sam9x5_sckc_register(struct device_node *np,
unsigned int rc_osc_startup_us)
{
struct clk_hw *hw;
const char *parent_names[2];
unsigned int num_parents;
const char *name = np->name;
num_parents = of_clk_get_parent_count(np);
if (num_parents == 0 || num_parents > 2)
return;
of_clk_parent_fill(np, parent_names, num_parents);
of_property_read_string(np, "clock-output-names", &name);
hw = at91_clk_register_sam9x5_slow(sckcr, name, parent_names,
num_parents);
if (IS_ERR(hw))
return;
of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
}
static const struct of_device_id sckc_clk_ids[] __initconst = {
/* Slow clock */
{
.compatible = "atmel,at91sam9x5-clk-slow-osc",
.data = of_at91sam9x5_clk_slow_osc_setup,
},
{
.compatible = "atmel,at91sam9x5-clk-slow-rc-osc",
.data = of_at91sam9x5_clk_slow_rc_osc_setup,
},
{
.compatible = "atmel,at91sam9x5-clk-slow",
.data = of_at91sam9x5_clk_slow_setup,
},
{ /*sentinel*/ }
};
static void __init of_at91sam9x5_sckc_setup(struct device_node *np)
{
struct device_node *childnp;
void (*clk_setup)(struct device_node *, void __iomem *);
const struct of_device_id *clk_id;
const char *parent_names[2] = { "slow_rc_osc", "slow_osc" };
void __iomem *regbase = of_iomap(np, 0);
struct device_node *child = NULL;
const char *xtal_name;
struct clk_hw *hw;
bool bypass;
if (!regbase)
return;
for_each_child_of_node(np, childnp) {
clk_id = of_match_node(sckc_clk_ids, childnp);
if (!clk_id)
continue;
clk_setup = clk_id->data;
clk_setup(childnp, regbase);
hw = at91_clk_register_slow_rc_osc(regbase, parent_names[0], 32768,
50000000, rc_osc_startup_us);
if (IS_ERR(hw))
return;
xtal_name = of_clk_get_parent_name(np, 0);
if (!xtal_name) {
/* DT backward compatibility */
child = of_get_compatible_child(np, "atmel,at91sam9x5-clk-slow-osc");
if (!child)
return;
xtal_name = of_clk_get_parent_name(child, 0);
bypass = of_property_read_bool(child, "atmel,osc-bypass");
child = of_get_compatible_child(np, "atmel,at91sam9x5-clk-slow");
} else {
bypass = of_property_read_bool(np, "atmel,osc-bypass");
}
if (!xtal_name)
return;
hw = at91_clk_register_slow_osc(regbase, parent_names[1], xtal_name,
1200000, bypass);
if (IS_ERR(hw))
return;
hw = at91_clk_register_sam9x5_slow(regbase, "slowck", parent_names, 2);
if (IS_ERR(hw))
return;
of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
/* DT backward compatibility */
if (child)
of_clk_add_hw_provider(child, of_clk_hw_simple_get, hw);
}
static void __init of_at91sam9x5_sckc_setup(struct device_node *np)
{
at91sam9x5_sckc_register(np, 75);
}
CLK_OF_DECLARE(at91sam9x5_clk_sckc, "atmel,at91sam9x5-sckc",
of_at91sam9x5_sckc_setup);
static void __init of_sama5d3_sckc_setup(struct device_node *np)
{
at91sam9x5_sckc_register(np, 500);
}
CLK_OF_DECLARE(sama5d3_clk_sckc, "atmel,sama5d3-sckc",
of_sama5d3_sckc_setup);
static int clk_sama5d4_slow_osc_prepare(struct clk_hw *hw)
{
struct clk_sama5d4_slow_osc *osc = to_clk_sama5d4_slow_osc(hw);

View File

@ -300,6 +300,85 @@ static const struct stm32f4_gate_data stm32f746_gates[] __initconst = {
{ STM32F4_RCC_APB2ENR, 26, "ltdc", "apb2_div" },
};
static const struct stm32f4_gate_data stm32f769_gates[] __initconst = {
{ STM32F4_RCC_AHB1ENR, 0, "gpioa", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 1, "gpiob", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 2, "gpioc", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 3, "gpiod", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 4, "gpioe", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 5, "gpiof", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 6, "gpiog", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 7, "gpioh", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 8, "gpioi", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 9, "gpioj", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 10, "gpiok", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 12, "crc", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 18, "bkpsra", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 20, "dtcmram", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 21, "dma1", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 22, "dma2", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 23, "dma2d", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 25, "ethmac", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 26, "ethmactx", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 27, "ethmacrx", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 28, "ethmacptp", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 29, "otghs", "ahb_div" },
{ STM32F4_RCC_AHB1ENR, 30, "otghsulpi", "ahb_div" },
{ STM32F4_RCC_AHB2ENR, 0, "dcmi", "ahb_div" },
{ STM32F4_RCC_AHB2ENR, 1, "jpeg", "ahb_div" },
{ STM32F4_RCC_AHB2ENR, 4, "cryp", "ahb_div" },
{ STM32F4_RCC_AHB2ENR, 5, "hash", "ahb_div" },
{ STM32F4_RCC_AHB2ENR, 6, "rng", "pll48" },
{ STM32F4_RCC_AHB2ENR, 7, "otgfs", "pll48" },
{ STM32F4_RCC_AHB3ENR, 0, "fmc", "ahb_div",
CLK_IGNORE_UNUSED },
{ STM32F4_RCC_AHB3ENR, 1, "qspi", "ahb_div",
CLK_IGNORE_UNUSED },
{ STM32F4_RCC_APB1ENR, 0, "tim2", "apb1_mul" },
{ STM32F4_RCC_APB1ENR, 1, "tim3", "apb1_mul" },
{ STM32F4_RCC_APB1ENR, 2, "tim4", "apb1_mul" },
{ STM32F4_RCC_APB1ENR, 3, "tim5", "apb1_mul" },
{ STM32F4_RCC_APB1ENR, 4, "tim6", "apb1_mul" },
{ STM32F4_RCC_APB1ENR, 5, "tim7", "apb1_mul" },
{ STM32F4_RCC_APB1ENR, 6, "tim12", "apb1_mul" },
{ STM32F4_RCC_APB1ENR, 7, "tim13", "apb1_mul" },
{ STM32F4_RCC_APB1ENR, 8, "tim14", "apb1_mul" },
{ STM32F4_RCC_APB1ENR, 10, "rtcapb", "apb1_mul" },
{ STM32F4_RCC_APB1ENR, 11, "wwdg", "apb1_div" },
{ STM32F4_RCC_APB1ENR, 13, "can3", "apb1_div" },
{ STM32F4_RCC_APB1ENR, 14, "spi2", "apb1_div" },
{ STM32F4_RCC_APB1ENR, 15, "spi3", "apb1_div" },
{ STM32F4_RCC_APB1ENR, 16, "spdifrx", "apb1_div" },
{ STM32F4_RCC_APB1ENR, 25, "can1", "apb1_div" },
{ STM32F4_RCC_APB1ENR, 26, "can2", "apb1_div" },
{ STM32F4_RCC_APB1ENR, 27, "cec", "apb1_div" },
{ STM32F4_RCC_APB1ENR, 28, "pwr", "apb1_div" },
{ STM32F4_RCC_APB1ENR, 29, "dac", "apb1_div" },
{ STM32F4_RCC_APB2ENR, 0, "tim1", "apb2_mul" },
{ STM32F4_RCC_APB2ENR, 1, "tim8", "apb2_mul" },
{ STM32F4_RCC_APB2ENR, 7, "sdmmc2", "sdmux2" },
{ STM32F4_RCC_APB2ENR, 8, "adc1", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 9, "adc2", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 10, "adc3", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 11, "sdmmc1", "sdmux1" },
{ STM32F4_RCC_APB2ENR, 12, "spi1", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 13, "spi4", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 14, "syscfg", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 16, "tim9", "apb2_mul" },
{ STM32F4_RCC_APB2ENR, 17, "tim10", "apb2_mul" },
{ STM32F4_RCC_APB2ENR, 18, "tim11", "apb2_mul" },
{ STM32F4_RCC_APB2ENR, 20, "spi5", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 21, "spi6", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 22, "sai1", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 23, "sai2", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 26, "ltdc", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 30, "mdio", "apb2_div" },
};
/*
* This bitmask tells us which bit offsets (0..192) on STM32F4[23]xxx
* have gate bits associated with them. Its combined hweight is 71.
@ -318,6 +397,10 @@ static const u64 stm32f746_gate_map[MAX_GATE_MAP] = { 0x000000f17ef417ffull,
0x0000000000000003ull,
0x04f77f833e01c9ffull };
static const u64 stm32f769_gate_map[MAX_GATE_MAP] = { 0x000000f37ef417ffull,
0x0000000000000003ull,
0x44F77F833E01EDFFull };
static const u64 *stm32f4_gate_map;
static struct clk_hw **clks;
@ -1048,6 +1131,10 @@ static const char *rtc_parents[4] = {
"no-clock", "lse", "lsi", "hse-rtc"
};
static const char *pll_src = "pll-src";
static const char *pllsrc_parent[2] = { "hsi", NULL };
static const char *dsi_parent[2] = { NULL, "pll-r" };
static const char *lcd_parent[1] = { "pllsai-r-div" };
@ -1072,6 +1159,9 @@ static const char *uart_parents2[4] = { "apb1_div", "sys", "hsi", "lse" };
static const char *i2c_parents[4] = { "apb1_div", "sys", "hsi", "no-clock" };
static const char * const dfsdm1_src[] = { "apb2_div", "sys" };
static const char * const adsfdm1_parent[] = { "sai1_clk", "sai2_clk" };
struct stm32_aux_clk {
int idx;
const char *name;
@ -1313,6 +1403,177 @@ static const struct stm32_aux_clk stm32f746_aux_clk[] = {
},
};
static const struct stm32_aux_clk stm32f769_aux_clk[] = {
{
CLK_LCD, "lcd-tft", lcd_parent, ARRAY_SIZE(lcd_parent),
NO_MUX, 0, 0,
STM32F4_RCC_APB2ENR, 26,
CLK_SET_RATE_PARENT
},
{
CLK_I2S, "i2s", i2s_parents, ARRAY_SIZE(i2s_parents),
STM32F4_RCC_CFGR, 23, 1,
NO_GATE, 0,
CLK_SET_RATE_PARENT
},
{
CLK_SAI1, "sai1_clk", sai_parents, ARRAY_SIZE(sai_parents),
STM32F4_RCC_DCKCFGR, 20, 3,
STM32F4_RCC_APB2ENR, 22,
CLK_SET_RATE_PARENT
},
{
CLK_SAI2, "sai2_clk", sai_parents, ARRAY_SIZE(sai_parents),
STM32F4_RCC_DCKCFGR, 22, 3,
STM32F4_RCC_APB2ENR, 23,
CLK_SET_RATE_PARENT
},
{
NO_IDX, "pll48", pll48_parents, ARRAY_SIZE(pll48_parents),
STM32F7_RCC_DCKCFGR2, 27, 1,
NO_GATE, 0,
0
},
{
NO_IDX, "sdmux1", sdmux_parents, ARRAY_SIZE(sdmux_parents),
STM32F7_RCC_DCKCFGR2, 28, 1,
NO_GATE, 0,
0
},
{
NO_IDX, "sdmux2", sdmux_parents, ARRAY_SIZE(sdmux_parents),
STM32F7_RCC_DCKCFGR2, 29, 1,
NO_GATE, 0,
0
},
{
CLK_HDMI_CEC, "hdmi-cec",
hdmi_parents, ARRAY_SIZE(hdmi_parents),
STM32F7_RCC_DCKCFGR2, 26, 1,
NO_GATE, 0,
0
},
{
CLK_SPDIF, "spdif-rx",
spdif_parent, ARRAY_SIZE(spdif_parent),
STM32F7_RCC_DCKCFGR2, 22, 3,
STM32F4_RCC_APB2ENR, 23,
CLK_SET_RATE_PARENT
},
{
CLK_USART1, "usart1",
uart_parents1, ARRAY_SIZE(uart_parents1),
STM32F7_RCC_DCKCFGR2, 0, 3,
STM32F4_RCC_APB2ENR, 4,
CLK_SET_RATE_PARENT,
},
{
CLK_USART2, "usart2",
uart_parents2, ARRAY_SIZE(uart_parents1),
STM32F7_RCC_DCKCFGR2, 2, 3,
STM32F4_RCC_APB1ENR, 17,
CLK_SET_RATE_PARENT,
},
{
CLK_USART3, "usart3",
uart_parents2, ARRAY_SIZE(uart_parents1),
STM32F7_RCC_DCKCFGR2, 4, 3,
STM32F4_RCC_APB1ENR, 18,
CLK_SET_RATE_PARENT,
},
{
CLK_UART4, "uart4",
uart_parents2, ARRAY_SIZE(uart_parents1),
STM32F7_RCC_DCKCFGR2, 6, 3,
STM32F4_RCC_APB1ENR, 19,
CLK_SET_RATE_PARENT,
},
{
CLK_UART5, "uart5",
uart_parents2, ARRAY_SIZE(uart_parents1),
STM32F7_RCC_DCKCFGR2, 8, 3,
STM32F4_RCC_APB1ENR, 20,
CLK_SET_RATE_PARENT,
},
{
CLK_USART6, "usart6",
uart_parents1, ARRAY_SIZE(uart_parents1),
STM32F7_RCC_DCKCFGR2, 10, 3,
STM32F4_RCC_APB2ENR, 5,
CLK_SET_RATE_PARENT,
},
{
CLK_UART7, "uart7",
uart_parents2, ARRAY_SIZE(uart_parents1),
STM32F7_RCC_DCKCFGR2, 12, 3,
STM32F4_RCC_APB1ENR, 30,
CLK_SET_RATE_PARENT,
},
{
CLK_UART8, "uart8",
uart_parents2, ARRAY_SIZE(uart_parents1),
STM32F7_RCC_DCKCFGR2, 14, 3,
STM32F4_RCC_APB1ENR, 31,
CLK_SET_RATE_PARENT,
},
{
CLK_I2C1, "i2c1",
i2c_parents, ARRAY_SIZE(i2c_parents),
STM32F7_RCC_DCKCFGR2, 16, 3,
STM32F4_RCC_APB1ENR, 21,
CLK_SET_RATE_PARENT,
},
{
CLK_I2C2, "i2c2",
i2c_parents, ARRAY_SIZE(i2c_parents),
STM32F7_RCC_DCKCFGR2, 18, 3,
STM32F4_RCC_APB1ENR, 22,
CLK_SET_RATE_PARENT,
},
{
CLK_I2C3, "i2c3",
i2c_parents, ARRAY_SIZE(i2c_parents),
STM32F7_RCC_DCKCFGR2, 20, 3,
STM32F4_RCC_APB1ENR, 23,
CLK_SET_RATE_PARENT,
},
{
CLK_I2C4, "i2c4",
i2c_parents, ARRAY_SIZE(i2c_parents),
STM32F7_RCC_DCKCFGR2, 22, 3,
STM32F4_RCC_APB1ENR, 24,
CLK_SET_RATE_PARENT,
},
{
CLK_LPTIMER, "lptim1",
lptim_parent, ARRAY_SIZE(lptim_parent),
STM32F7_RCC_DCKCFGR2, 24, 3,
STM32F4_RCC_APB1ENR, 9,
CLK_SET_RATE_PARENT
},
{
CLK_F769_DSI, "dsi",
dsi_parent, ARRAY_SIZE(dsi_parent),
STM32F7_RCC_DCKCFGR2, 0, 1,
STM32F4_RCC_APB2ENR, 27,
CLK_SET_RATE_PARENT
},
{
CLK_DFSDM1, "dfsdm1",
dfsdm1_src, ARRAY_SIZE(dfsdm1_src),
STM32F4_RCC_DCKCFGR, 25, 1,
STM32F4_RCC_APB2ENR, 29,
CLK_SET_RATE_PARENT
},
{
CLK_ADFSDM1, "adfsdm1",
adsfdm1_parent, ARRAY_SIZE(adsfdm1_parent),
STM32F4_RCC_DCKCFGR, 26, 1,
STM32F4_RCC_APB2ENR, 29,
CLK_SET_RATE_PARENT
},
};
static const struct stm32f4_clk_data stm32f429_clk_data = {
.end_primary = END_PRIMARY_CLK,
.gates_data = stm32f429_gates,
@ -1343,6 +1604,16 @@ static const struct stm32f4_clk_data stm32f746_clk_data = {
.aux_clk_num = ARRAY_SIZE(stm32f746_aux_clk),
};
static const struct stm32f4_clk_data stm32f769_clk_data = {
.end_primary = END_PRIMARY_CLK_F7,
.gates_data = stm32f769_gates,
.gates_map = stm32f769_gate_map,
.gates_num = ARRAY_SIZE(stm32f769_gates),
.pll_data = stm32f469_pll,
.aux_clk = stm32f769_aux_clk,
.aux_clk_num = ARRAY_SIZE(stm32f769_aux_clk),
};
static const struct of_device_id stm32f4_of_match[] = {
{
.compatible = "st,stm32f42xx-rcc",
@ -1356,6 +1627,10 @@ static const struct of_device_id stm32f4_of_match[] = {
.compatible = "st,stm32f746-rcc",
.data = &stm32f746_clk_data
},
{
.compatible = "st,stm32f769-rcc",
.data = &stm32f769_clk_data
},
{}
};
@ -1427,9 +1702,8 @@ static void __init stm32f4_rcc_init(struct device_node *np)
int n;
const struct of_device_id *match;
const struct stm32f4_clk_data *data;
unsigned long pllcfgr;
const char *pllsrc;
unsigned long pllm;
struct clk_hw *pll_src_hw;
base = of_iomap(np, 0);
if (!base) {
@ -1460,21 +1734,33 @@ static void __init stm32f4_rcc_init(struct device_node *np)
hse_clk = of_clk_get_parent_name(np, 0);
dsi_parent[0] = hse_clk;
pllsrc_parent[1] = hse_clk;
i2s_in_clk = of_clk_get_parent_name(np, 1);
i2s_parents[1] = i2s_in_clk;
sai_parents[2] = i2s_in_clk;
if (of_device_is_compatible(np, "st,stm32f769-rcc")) {
clk_hw_register_gate(NULL, "dfsdm1_apb", "apb2_div", 0,
base + STM32F4_RCC_APB2ENR, 29,
CLK_IGNORE_UNUSED, &stm32f4_clk_lock);
dsi_parent[0] = pll_src;
sai_parents[3] = pll_src;
}
clks[CLK_HSI] = clk_hw_register_fixed_rate_with_accuracy(NULL, "hsi",
NULL, 0, 16000000, 160000);
pllcfgr = readl(base + STM32F4_RCC_PLLCFGR);
pllsrc = pllcfgr & BIT(22) ? hse_clk : "hsi";
pllm = pllcfgr & 0x3f;
pll_src_hw = clk_hw_register_mux(NULL, pll_src, pllsrc_parent,
ARRAY_SIZE(pllsrc_parent), 0,
base + STM32F4_RCC_PLLCFGR, 22, 1, 0,
&stm32f4_clk_lock);
clk_hw_register_fixed_factor(NULL, "vco_in", pllsrc,
0, 1, pllm);
pllm = readl(base + STM32F4_RCC_PLLCFGR) & 0x3f;
clk_hw_register_fixed_factor(NULL, "vco_in", pll_src,
0, 1, pllm);
stm32f4_rcc_register_pll("vco_in", &data->pll_data[0],
&stm32f4_clk_lock);
@ -1612,12 +1898,16 @@ static void __init stm32f4_rcc_init(struct device_node *np)
clks[aux_clk->idx] = hw;
}
if (of_device_is_compatible(np, "st,stm32f746-rcc"))
if (of_device_is_compatible(np, "st,stm32f746-rcc")) {
clk_hw_register_fixed_factor(NULL, "hsi_div488", "hsi", 0,
1, 488);
clks[CLK_PLL_SRC] = pll_src_hw;
}
of_clk_add_hw_provider(np, stm32f4_rcc_lookup_clk, NULL);
return;
fail:
kfree(clks);
@ -1626,3 +1916,4 @@ fail:
CLK_OF_DECLARE_DRIVER(stm32f42xx_rcc, "st,stm32f42xx-rcc", stm32f4_rcc_init);
CLK_OF_DECLARE_DRIVER(stm32f46xx_rcc, "st,stm32f469-rcc", stm32f4_rcc_init);
CLK_OF_DECLARE_DRIVER(stm32f746_rcc, "st,stm32f746-rcc", stm32f4_rcc_init);
CLK_OF_DECLARE_DRIVER(stm32f769_rcc, "st,stm32f769-rcc", stm32f4_rcc_init);

View File

@ -1402,6 +1402,7 @@ enum {
G_CRYP1,
G_HASH1,
G_BKPSRAM,
G_DDRPERFM,
G_LAST
};
@ -1488,6 +1489,7 @@ static struct stm32_gate_cfg per_gate_cfg[G_LAST] = {
K_GATE(G_STGENRO, RCC_APB4ENSETR, 20, 0),
K_MGATE(G_USBPHY, RCC_APB4ENSETR, 16, 0),
K_GATE(G_IWDG2, RCC_APB4ENSETR, 15, 0),
K_GATE(G_DDRPERFM, RCC_APB4ENSETR, 8, 0),
K_MGATE(G_DSI, RCC_APB4ENSETR, 4, 0),
K_MGATE(G_LTDC, RCC_APB4ENSETR, 0, 0),
@ -1899,6 +1901,7 @@ static const struct clock_config stm32mp1_clock_cfg[] = {
PCLK(CRC1, "crc1", "ck_axi", 0, G_CRC1),
PCLK(USBH, "usbh", "ck_axi", 0, G_USBH),
PCLK(ETHSTP, "ethstp", "ck_axi", 0, G_ETHSTP),
PCLK(DDRPERFM, "ddrperfm", "pclk4", 0, G_DDRPERFM),
/* Kernel clocks */
KCLK(SDMMC1_K, "sdmmc1_k", sdmmc12_src, 0, G_SDMMC1, M_SDMMC12),

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Clock driver for TI Davinci PSC controllers
*

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Clock driver for TI Davinci PSC controllers
*

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2017, Linaro Limited
* Author: Georgi Djakov <georgi.djakov@linaro.org>

View File

@ -1,5 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0
*
/* SPDX-License-Identifier: GPL-2.0 */
/*
* R-Car Gen2 Clock Pulse Generator
*
* Copyright (C) 2016 Cogent Embedded Inc.

View File

@ -1,5 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0
*
/* SPDX-License-Identifier: GPL-2.0 */
/*
* R-Car Gen3 Clock Pulse Generator
*
* Copyright (C) 2015-2018 Glider bvba

View File

@ -1,5 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0
*
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Renesas Clock Pulse Generator / Module Standby and Software Reset
*
* Copyright (C) 2015 Glider bvba

View File

@ -0,0 +1,18 @@
# SPDX-License-Identifier: GPL-2.0
menuconfig CLK_SIFIVE
bool "SiFive SoC driver support"
help
SoC drivers for SiFive Linux-capable SoCs.
if CLK_SIFIVE
config CLK_SIFIVE_FU540_PRCI
bool "PRCI driver for SiFive FU540 SoCs"
select CLK_ANALOGBITS_WRPLL_CLN28HPC
help
Supports the Power Reset Clock interface (PRCI) IP block found in
FU540 SoCs. If this kernel is meant to run on a SiFive FU540 SoC,
enable this driver.
endif

View File

@ -0,0 +1 @@
obj-$(CONFIG_CLK_SIFIVE_FU540_PRCI) += fu540-prci.o

View File

@ -0,0 +1,626 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2018-2019 SiFive, Inc.
* Wesley Terpstra
* Paul Walmsley
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* The FU540 PRCI implements clock and reset control for the SiFive
* FU540-C000 chip. This driver assumes that it has sole control
* over all PRCI resources.
*
* This driver is based on the PRCI driver written by Wesley Terpstra:
* https://github.com/riscv/riscv-linux/commit/999529edf517ed75b56659d456d221b2ee56bb60
*
* References:
* - SiFive FU540-C000 manual v1p0, Chapter 7 "Clocking and Reset"
*/
#include <dt-bindings/clock/sifive-fu540-prci.h>
#include <linux/clkdev.h>
#include <linux/clk-provider.h>
#include <linux/clk/analogbits-wrpll-cln28hpc.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_clk.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
/*
* EXPECTED_CLK_PARENT_COUNT: how many parent clocks this driver expects:
* hfclk and rtcclk
*/
#define EXPECTED_CLK_PARENT_COUNT 2
/*
* Register offsets and bitmasks
*/
/* COREPLLCFG0 */
#define PRCI_COREPLLCFG0_OFFSET 0x4
# define PRCI_COREPLLCFG0_DIVR_SHIFT 0
# define PRCI_COREPLLCFG0_DIVR_MASK (0x3f << PRCI_COREPLLCFG0_DIVR_SHIFT)
# define PRCI_COREPLLCFG0_DIVF_SHIFT 6
# define PRCI_COREPLLCFG0_DIVF_MASK (0x1ff << PRCI_COREPLLCFG0_DIVF_SHIFT)
# define PRCI_COREPLLCFG0_DIVQ_SHIFT 15
# define PRCI_COREPLLCFG0_DIVQ_MASK (0x7 << PRCI_COREPLLCFG0_DIVQ_SHIFT)
# define PRCI_COREPLLCFG0_RANGE_SHIFT 18
# define PRCI_COREPLLCFG0_RANGE_MASK (0x7 << PRCI_COREPLLCFG0_RANGE_SHIFT)
# define PRCI_COREPLLCFG0_BYPASS_SHIFT 24
# define PRCI_COREPLLCFG0_BYPASS_MASK (0x1 << PRCI_COREPLLCFG0_BYPASS_SHIFT)
# define PRCI_COREPLLCFG0_FSE_SHIFT 25
# define PRCI_COREPLLCFG0_FSE_MASK (0x1 << PRCI_COREPLLCFG0_FSE_SHIFT)
# define PRCI_COREPLLCFG0_LOCK_SHIFT 31
# define PRCI_COREPLLCFG0_LOCK_MASK (0x1 << PRCI_COREPLLCFG0_LOCK_SHIFT)
/* DDRPLLCFG0 */
#define PRCI_DDRPLLCFG0_OFFSET 0xc
# define PRCI_DDRPLLCFG0_DIVR_SHIFT 0
# define PRCI_DDRPLLCFG0_DIVR_MASK (0x3f << PRCI_DDRPLLCFG0_DIVR_SHIFT)
# define PRCI_DDRPLLCFG0_DIVF_SHIFT 6
# define PRCI_DDRPLLCFG0_DIVF_MASK (0x1ff << PRCI_DDRPLLCFG0_DIVF_SHIFT)
# define PRCI_DDRPLLCFG0_DIVQ_SHIFT 15
# define PRCI_DDRPLLCFG0_DIVQ_MASK (0x7 << PRCI_DDRPLLCFG0_DIVQ_SHIFT)
# define PRCI_DDRPLLCFG0_RANGE_SHIFT 18
# define PRCI_DDRPLLCFG0_RANGE_MASK (0x7 << PRCI_DDRPLLCFG0_RANGE_SHIFT)
# define PRCI_DDRPLLCFG0_BYPASS_SHIFT 24
# define PRCI_DDRPLLCFG0_BYPASS_MASK (0x1 << PRCI_DDRPLLCFG0_BYPASS_SHIFT)
# define PRCI_DDRPLLCFG0_FSE_SHIFT 25
# define PRCI_DDRPLLCFG0_FSE_MASK (0x1 << PRCI_DDRPLLCFG0_FSE_SHIFT)
# define PRCI_DDRPLLCFG0_LOCK_SHIFT 31
# define PRCI_DDRPLLCFG0_LOCK_MASK (0x1 << PRCI_DDRPLLCFG0_LOCK_SHIFT)
/* DDRPLLCFG1 */
#define PRCI_DDRPLLCFG1_OFFSET 0x10
# define PRCI_DDRPLLCFG1_CKE_SHIFT 24
# define PRCI_DDRPLLCFG1_CKE_MASK (0x1 << PRCI_DDRPLLCFG1_CKE_SHIFT)
/* GEMGXLPLLCFG0 */
#define PRCI_GEMGXLPLLCFG0_OFFSET 0x1c
# define PRCI_GEMGXLPLLCFG0_DIVR_SHIFT 0
# define PRCI_GEMGXLPLLCFG0_DIVR_MASK (0x3f << PRCI_GEMGXLPLLCFG0_DIVR_SHIFT)
# define PRCI_GEMGXLPLLCFG0_DIVF_SHIFT 6
# define PRCI_GEMGXLPLLCFG0_DIVF_MASK (0x1ff << PRCI_GEMGXLPLLCFG0_DIVF_SHIFT)
# define PRCI_GEMGXLPLLCFG0_DIVQ_SHIFT 15
# define PRCI_GEMGXLPLLCFG0_DIVQ_MASK (0x7 << PRCI_GEMGXLPLLCFG0_DIVQ_SHIFT)
# define PRCI_GEMGXLPLLCFG0_RANGE_SHIFT 18
# define PRCI_GEMGXLPLLCFG0_RANGE_MASK (0x7 << PRCI_GEMGXLPLLCFG0_RANGE_SHIFT)
# define PRCI_GEMGXLPLLCFG0_BYPASS_SHIFT 24
# define PRCI_GEMGXLPLLCFG0_BYPASS_MASK (0x1 << PRCI_GEMGXLPLLCFG0_BYPASS_SHIFT)
# define PRCI_GEMGXLPLLCFG0_FSE_SHIFT 25
# define PRCI_GEMGXLPLLCFG0_FSE_MASK (0x1 << PRCI_GEMGXLPLLCFG0_FSE_SHIFT)
# define PRCI_GEMGXLPLLCFG0_LOCK_SHIFT 31
# define PRCI_GEMGXLPLLCFG0_LOCK_MASK (0x1 << PRCI_GEMGXLPLLCFG0_LOCK_SHIFT)
/* GEMGXLPLLCFG1 */
#define PRCI_GEMGXLPLLCFG1_OFFSET 0x20
# define PRCI_GEMGXLPLLCFG1_CKE_SHIFT 24
# define PRCI_GEMGXLPLLCFG1_CKE_MASK (0x1 << PRCI_GEMGXLPLLCFG1_CKE_SHIFT)
/* CORECLKSEL */
#define PRCI_CORECLKSEL_OFFSET 0x24
# define PRCI_CORECLKSEL_CORECLKSEL_SHIFT 0
# define PRCI_CORECLKSEL_CORECLKSEL_MASK (0x1 << PRCI_CORECLKSEL_CORECLKSEL_SHIFT)
/* DEVICESRESETREG */
#define PRCI_DEVICESRESETREG_OFFSET 0x28
# define PRCI_DEVICESRESETREG_DDR_CTRL_RST_N_SHIFT 0
# define PRCI_DEVICESRESETREG_DDR_CTRL_RST_N_MASK (0x1 << PRCI_DEVICESRESETREG_DDR_CTRL_RST_N_SHIFT)
# define PRCI_DEVICESRESETREG_DDR_AXI_RST_N_SHIFT 1
# define PRCI_DEVICESRESETREG_DDR_AXI_RST_N_MASK (0x1 << PRCI_DEVICESRESETREG_DDR_AXI_RST_N_SHIFT)
# define PRCI_DEVICESRESETREG_DDR_AHB_RST_N_SHIFT 2
# define PRCI_DEVICESRESETREG_DDR_AHB_RST_N_MASK (0x1 << PRCI_DEVICESRESETREG_DDR_AHB_RST_N_SHIFT)
# define PRCI_DEVICESRESETREG_DDR_PHY_RST_N_SHIFT 3
# define PRCI_DEVICESRESETREG_DDR_PHY_RST_N_MASK (0x1 << PRCI_DEVICESRESETREG_DDR_PHY_RST_N_SHIFT)
# define PRCI_DEVICESRESETREG_GEMGXL_RST_N_SHIFT 5
# define PRCI_DEVICESRESETREG_GEMGXL_RST_N_MASK (0x1 << PRCI_DEVICESRESETREG_GEMGXL_RST_N_SHIFT)
/* CLKMUXSTATUSREG */
#define PRCI_CLKMUXSTATUSREG_OFFSET 0x2c
# define PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_SHIFT 1
# define PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_MASK (0x1 << PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_SHIFT)
/*
* Private structures
*/
/**
* struct __prci_data - per-device-instance data
* @va: base virtual address of the PRCI IP block
* @hw_clks: encapsulates struct clk_hw records
*
* PRCI per-device instance data
*/
struct __prci_data {
void __iomem *va;
struct clk_hw_onecell_data hw_clks;
};
/**
* struct __prci_wrpll_data - WRPLL configuration and integration data
* @c: WRPLL current configuration record
* @enable_bypass: fn ptr to code to bypass the WRPLL (if applicable; else NULL)
* @disable_bypass: fn ptr to code to not bypass the WRPLL (or NULL)
* @cfg0_offs: WRPLL CFG0 register offset (in bytes) from the PRCI base address
*
* @enable_bypass and @disable_bypass are used for WRPLL instances
* that contain a separate external glitchless clock mux downstream
* from the PLL. The WRPLL internal bypass mux is not glitchless.
*/
struct __prci_wrpll_data {
struct wrpll_cfg c;
void (*enable_bypass)(struct __prci_data *pd);
void (*disable_bypass)(struct __prci_data *pd);
u8 cfg0_offs;
};
/**
* struct __prci_clock - describes a clock device managed by PRCI
* @name: user-readable clock name string - should match the manual
* @parent_name: parent name for this clock
* @ops: struct clk_ops for the Linux clock framework to use for control
* @hw: Linux-private clock data
* @pwd: WRPLL-specific data, associated with this clock (if not NULL)
* @pd: PRCI-specific data associated with this clock (if not NULL)
*
* PRCI clock data. Used by the PRCI driver to register PRCI-provided
* clocks to the Linux clock infrastructure.
*/
struct __prci_clock {
const char *name;
const char *parent_name;
const struct clk_ops *ops;
struct clk_hw hw;
struct __prci_wrpll_data *pwd;
struct __prci_data *pd;
};
#define clk_hw_to_prci_clock(pwd) container_of(pwd, struct __prci_clock, hw)
/*
* Private functions
*/
/**
* __prci_readl() - read from a PRCI register
* @pd: PRCI context
* @offs: register offset to read from (in bytes, from PRCI base address)
*
* Read the register located at offset @offs from the base virtual
* address of the PRCI register target described by @pd, and return
* the value to the caller.
*
* Context: Any context.
*
* Return: the contents of the register described by @pd and @offs.
*/
static u32 __prci_readl(struct __prci_data *pd, u32 offs)
{
return readl_relaxed(pd->va + offs);
}
static void __prci_writel(u32 v, u32 offs, struct __prci_data *pd)
{
writel_relaxed(v, pd->va + offs);
}
/* WRPLL-related private functions */
/**
* __prci_wrpll_unpack() - unpack WRPLL configuration registers into parameters
* @c: ptr to a struct wrpll_cfg record to write config into
* @r: value read from the PRCI PLL configuration register
*
* Given a value @r read from an FU540 PRCI PLL configuration register,
* split it into fields and populate it into the WRPLL configuration record
* pointed to by @c.
*
* The COREPLLCFG0 macros are used below, but the other *PLLCFG0 macros
* have the same register layout.
*
* Context: Any context.
*/
static void __prci_wrpll_unpack(struct wrpll_cfg *c, u32 r)
{
u32 v;
v = r & PRCI_COREPLLCFG0_DIVR_MASK;
v >>= PRCI_COREPLLCFG0_DIVR_SHIFT;
c->divr = v;
v = r & PRCI_COREPLLCFG0_DIVF_MASK;
v >>= PRCI_COREPLLCFG0_DIVF_SHIFT;
c->divf = v;
v = r & PRCI_COREPLLCFG0_DIVQ_MASK;
v >>= PRCI_COREPLLCFG0_DIVQ_SHIFT;
c->divq = v;
v = r & PRCI_COREPLLCFG0_RANGE_MASK;
v >>= PRCI_COREPLLCFG0_RANGE_SHIFT;
c->range = v;
c->flags &= (WRPLL_FLAGS_INT_FEEDBACK_MASK |
WRPLL_FLAGS_EXT_FEEDBACK_MASK);
/* external feedback mode not supported */
c->flags |= WRPLL_FLAGS_INT_FEEDBACK_MASK;
}
/**
* __prci_wrpll_pack() - pack PLL configuration parameters into a register value
* @c: pointer to a struct wrpll_cfg record containing the PLL's cfg
*
* Using a set of WRPLL configuration values pointed to by @c,
* assemble a PRCI PLL configuration register value, and return it to
* the caller.
*
* Context: Any context. Caller must ensure that the contents of the
* record pointed to by @c do not change during the execution
* of this function.
*
* Returns: a value suitable for writing into a PRCI PLL configuration
* register
*/
static u32 __prci_wrpll_pack(const struct wrpll_cfg *c)
{
u32 r = 0;
r |= c->divr << PRCI_COREPLLCFG0_DIVR_SHIFT;
r |= c->divf << PRCI_COREPLLCFG0_DIVF_SHIFT;
r |= c->divq << PRCI_COREPLLCFG0_DIVQ_SHIFT;
r |= c->range << PRCI_COREPLLCFG0_RANGE_SHIFT;
/* external feedback mode not supported */
r |= PRCI_COREPLLCFG0_FSE_MASK;
return r;
}
/**
* __prci_wrpll_read_cfg() - read the WRPLL configuration from the PRCI
* @pd: PRCI context
* @pwd: PRCI WRPLL metadata
*
* Read the current configuration of the PLL identified by @pwd from
* the PRCI identified by @pd, and store it into the local configuration
* cache in @pwd.
*
* Context: Any context. Caller must prevent the records pointed to by
* @pd and @pwd from changing during execution.
*/
static void __prci_wrpll_read_cfg(struct __prci_data *pd,
struct __prci_wrpll_data *pwd)
{
__prci_wrpll_unpack(&pwd->c, __prci_readl(pd, pwd->cfg0_offs));
}
/**
* __prci_wrpll_write_cfg() - write WRPLL configuration into the PRCI
* @pd: PRCI context
* @pwd: PRCI WRPLL metadata
* @c: WRPLL configuration record to write
*
* Write the WRPLL configuration described by @c into the WRPLL
* configuration register identified by @pwd in the PRCI instance
* described by @c. Make a cached copy of the WRPLL's current
* configuration so it can be used by other code.
*
* Context: Any context. Caller must prevent the records pointed to by
* @pd and @pwd from changing during execution.
*/
static void __prci_wrpll_write_cfg(struct __prci_data *pd,
struct __prci_wrpll_data *pwd,
struct wrpll_cfg *c)
{
__prci_writel(__prci_wrpll_pack(c), pwd->cfg0_offs, pd);
memcpy(&pwd->c, c, sizeof(*c));
}
/* Core clock mux control */
/**
* __prci_coreclksel_use_hfclk() - switch the CORECLK mux to output HFCLK
* @pd: struct __prci_data * for the PRCI containing the CORECLK mux reg
*
* Switch the CORECLK mux to the HFCLK input source; return once complete.
*
* Context: Any context. Caller must prevent concurrent changes to the
* PRCI_CORECLKSEL_OFFSET register.
*/
static void __prci_coreclksel_use_hfclk(struct __prci_data *pd)
{
u32 r;
r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);
r |= PRCI_CORECLKSEL_CORECLKSEL_MASK;
__prci_writel(r, PRCI_CORECLKSEL_OFFSET, pd);
r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET); /* barrier */
}
/**
* __prci_coreclksel_use_corepll() - switch the CORECLK mux to output COREPLL
* @pd: struct __prci_data * for the PRCI containing the CORECLK mux reg
*
* Switch the CORECLK mux to the PLL output clock; return once complete.
*
* Context: Any context. Caller must prevent concurrent changes to the
* PRCI_CORECLKSEL_OFFSET register.
*/
static void __prci_coreclksel_use_corepll(struct __prci_data *pd)
{
u32 r;
r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);
r &= ~PRCI_CORECLKSEL_CORECLKSEL_MASK;
__prci_writel(r, PRCI_CORECLKSEL_OFFSET, pd);
r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET); /* barrier */
}
/*
* Linux clock framework integration
*
* See the Linux clock framework documentation for more information on
* these functions.
*/
static unsigned long sifive_fu540_prci_wrpll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
struct __prci_wrpll_data *pwd = pc->pwd;
return wrpll_calc_output_rate(&pwd->c, parent_rate);
}
static long sifive_fu540_prci_wrpll_round_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long *parent_rate)
{
struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
struct __prci_wrpll_data *pwd = pc->pwd;
struct wrpll_cfg c;
memcpy(&c, &pwd->c, sizeof(c));
wrpll_configure_for_rate(&c, rate, *parent_rate);
return wrpll_calc_output_rate(&c, *parent_rate);
}
static int sifive_fu540_prci_wrpll_set_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate)
{
struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
struct __prci_wrpll_data *pwd = pc->pwd;
struct __prci_data *pd = pc->pd;
int r;
r = wrpll_configure_for_rate(&pwd->c, rate, parent_rate);
if (r)
return r;
if (pwd->enable_bypass)
pwd->enable_bypass(pd);
__prci_wrpll_write_cfg(pd, pwd, &pwd->c);
udelay(wrpll_calc_max_lock_us(&pwd->c));
if (pwd->disable_bypass)
pwd->disable_bypass(pd);
return 0;
}
static const struct clk_ops sifive_fu540_prci_wrpll_clk_ops = {
.set_rate = sifive_fu540_prci_wrpll_set_rate,
.round_rate = sifive_fu540_prci_wrpll_round_rate,
.recalc_rate = sifive_fu540_prci_wrpll_recalc_rate,
};
static const struct clk_ops sifive_fu540_prci_wrpll_ro_clk_ops = {
.recalc_rate = sifive_fu540_prci_wrpll_recalc_rate,
};
/* TLCLKSEL clock integration */
static unsigned long sifive_fu540_prci_tlclksel_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
struct __prci_data *pd = pc->pd;
u32 v;
u8 div;
v = __prci_readl(pd, PRCI_CLKMUXSTATUSREG_OFFSET);
v &= PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_MASK;
div = v ? 1 : 2;
return div_u64(parent_rate, div);
}
static const struct clk_ops sifive_fu540_prci_tlclksel_clk_ops = {
.recalc_rate = sifive_fu540_prci_tlclksel_recalc_rate,
};
/*
* PRCI integration data for each WRPLL instance
*/
static struct __prci_wrpll_data __prci_corepll_data = {
.cfg0_offs = PRCI_COREPLLCFG0_OFFSET,
.enable_bypass = __prci_coreclksel_use_hfclk,
.disable_bypass = __prci_coreclksel_use_corepll,
};
static struct __prci_wrpll_data __prci_ddrpll_data = {
.cfg0_offs = PRCI_DDRPLLCFG0_OFFSET,
};
static struct __prci_wrpll_data __prci_gemgxlpll_data = {
.cfg0_offs = PRCI_GEMGXLPLLCFG0_OFFSET,
};
/*
* List of clock controls provided by the PRCI
*/
static struct __prci_clock __prci_init_clocks[] = {
[PRCI_CLK_COREPLL] = {
.name = "corepll",
.parent_name = "hfclk",
.ops = &sifive_fu540_prci_wrpll_clk_ops,
.pwd = &__prci_corepll_data,
},
[PRCI_CLK_DDRPLL] = {
.name = "ddrpll",
.parent_name = "hfclk",
.ops = &sifive_fu540_prci_wrpll_ro_clk_ops,
.pwd = &__prci_ddrpll_data,
},
[PRCI_CLK_GEMGXLPLL] = {
.name = "gemgxlpll",
.parent_name = "hfclk",
.ops = &sifive_fu540_prci_wrpll_clk_ops,
.pwd = &__prci_gemgxlpll_data,
},
[PRCI_CLK_TLCLK] = {
.name = "tlclk",
.parent_name = "corepll",
.ops = &sifive_fu540_prci_tlclksel_clk_ops,
},
};
/**
* __prci_register_clocks() - register clock controls in the PRCI with Linux
* @dev: Linux struct device *
*
* Register the list of clock controls described in __prci_init_plls[] with
* the Linux clock framework.
*
* Return: 0 upon success or a negative error code upon failure.
*/
static int __prci_register_clocks(struct device *dev, struct __prci_data *pd)
{
struct clk_init_data init = { };
struct __prci_clock *pic;
int parent_count, i, r;
parent_count = of_clk_get_parent_count(dev->of_node);
if (parent_count != EXPECTED_CLK_PARENT_COUNT) {
dev_err(dev, "expected only two parent clocks, found %d\n",
parent_count);
return -EINVAL;
}
/* Register PLLs */
for (i = 0; i < ARRAY_SIZE(__prci_init_clocks); ++i) {
pic = &__prci_init_clocks[i];
init.name = pic->name;
init.parent_names = &pic->parent_name;
init.num_parents = 1;
init.ops = pic->ops;
pic->hw.init = &init;
pic->pd = pd;
if (pic->pwd)
__prci_wrpll_read_cfg(pd, pic->pwd);
r = devm_clk_hw_register(dev, &pic->hw);
if (r) {
dev_warn(dev, "Failed to register clock %s: %d\n",
init.name, r);
return r;
}
r = clk_hw_register_clkdev(&pic->hw, pic->name, dev_name(dev));
if (r) {
dev_warn(dev, "Failed to register clkdev for %s: %d\n",
init.name, r);
return r;
}
pd->hw_clks.hws[i] = &pic->hw;
}
pd->hw_clks.num = i;
r = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
&pd->hw_clks);
if (r) {
dev_err(dev, "could not add hw_provider: %d\n", r);
return r;
}
return 0;
}
/*
* Linux device model integration
*
* See the Linux device model documentation for more information about
* these functions.
*/
static int sifive_fu540_prci_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
struct __prci_data *pd;
int r;
pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
if (!pd)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pd->va = devm_ioremap_resource(dev, res);
if (IS_ERR(pd->va))
return PTR_ERR(pd->va);
r = __prci_register_clocks(dev, pd);
if (r) {
dev_err(dev, "could not register clocks: %d\n", r);
return r;
}
dev_dbg(dev, "SiFive FU540 PRCI probed\n");
return 0;
}
static const struct of_device_id sifive_fu540_prci_of_match[] = {
{ .compatible = "sifive,fu540-c000-prci", },
{}
};
MODULE_DEVICE_TABLE(of, sifive_fu540_prci_of_match);
static struct platform_driver sifive_fu540_prci_driver = {
.driver = {
.name = "sifive-fu540-prci",
.of_match_table = sifive_fu540_prci_of_match,
},
.probe = sifive_fu540_prci_probe,
};
static int __init sifive_fu540_prci_init(void)
{
return platform_driver_register(&sifive_fu540_prci_driver);
}
core_initcall(sifive_fu540_prci_init);

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/* SPDX-License-Identifier: GPL-2.0 */
//
// Spreadtrum clock infrastructure
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/* SPDX-License-Identifier: GPL-2.0 */
//
// Spreadtrum composite clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/* SPDX-License-Identifier: GPL-2.0 */
//
// Spreadtrum divider clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/* SPDX-License-Identifier: GPL-2.0 */
//
// Spreadtrum gate clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/* SPDX-License-Identifier: GPL-2.0 */
//
// Spreadtrum multiplexer clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/* SPDX-License-Identifier: GPL-2.0 */
//
// Spreadtrum pll clock driver
//

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2016 Icenowy Zheng <icenowy@aosc.io>
*/

View File

@ -1,5 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0+
*
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2017 Icenowy Zheng <icenowy@aosc.io>
*
*/

View File

@ -175,6 +175,7 @@ struct clk *tegra_clk_register_mc(const char *name, const char *parent_name,
void __iomem *reg, spinlock_t *lock)
{
return clk_register_divider_table(NULL, name, parent_name,
CLK_IS_CRITICAL, reg, 16, 1, 0,
CLK_IS_CRITICAL,
reg, 16, 1, CLK_DIVIDER_READ_ONLY,
mc_div_table, lock);
}

View File

@ -121,18 +121,28 @@ static int emc_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
struct tegra_clk_emc *tegra;
u8 ram_code = tegra_read_ram_code();
struct emc_timing *timing = NULL;
int i;
int i, k, t;
tegra = container_of(hw, struct tegra_clk_emc, hw);
for (i = 0; i < tegra->num_timings; i++) {
if (tegra->timings[i].ram_code != ram_code)
continue;
for (k = 0; k < tegra->num_timings; k++) {
if (tegra->timings[k].ram_code == ram_code)
break;
}
for (t = k; t < tegra->num_timings; t++) {
if (tegra->timings[t].ram_code != ram_code)
break;
}
for (i = k; i < t; i++) {
timing = tegra->timings + i;
if (timing->rate < req->rate && i != t - 1)
continue;
if (timing->rate > req->max_rate) {
i = max(i, 1);
i = max(i, k + 1);
req->rate = tegra->timings[i - 1].rate;
return 0;
}
@ -140,10 +150,8 @@ static int emc_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
if (timing->rate < req->min_rate)
continue;
if (timing->rate >= req->rate) {
req->rate = timing->rate;
return 0;
}
req->rate = timing->rate;
return 0;
}
if (timing) {
@ -214,7 +222,10 @@ static int emc_set_timing(struct tegra_clk_emc *tegra,
if (emc_get_parent(&tegra->hw) == timing->parent_index &&
clk_get_rate(timing->parent) != timing->parent_rate) {
BUG();
WARN_ONCE(1, "parent %s rate mismatch %lu %lu\n",
__clk_get_name(timing->parent),
clk_get_rate(timing->parent),
timing->parent_rate);
return -EINVAL;
}
@ -282,7 +293,7 @@ static struct emc_timing *get_backup_timing(struct tegra_clk_emc *tegra,
for (i = timing_index+1; i < tegra->num_timings; i++) {
timing = tegra->timings + i;
if (timing->ram_code != ram_code)
continue;
break;
if (emc_parent_clk_sources[timing->parent_index] !=
emc_parent_clk_sources[
@ -293,7 +304,7 @@ static struct emc_timing *get_backup_timing(struct tegra_clk_emc *tegra,
for (i = timing_index-1; i >= 0; --i) {
timing = tegra->timings + i;
if (timing->ram_code != ram_code)
continue;
break;
if (emc_parent_clk_sources[timing->parent_index] !=
emc_parent_clk_sources[
@ -433,19 +444,23 @@ static int load_timings_from_dt(struct tegra_clk_emc *tegra,
struct device_node *node,
u32 ram_code)
{
struct emc_timing *timings_ptr;
struct device_node *child;
int child_count = of_get_child_count(node);
int i = 0, err;
size_t size;
tegra->timings = kcalloc(child_count, sizeof(struct emc_timing),
GFP_KERNEL);
size = (tegra->num_timings + child_count) * sizeof(struct emc_timing);
tegra->timings = krealloc(tegra->timings, size, GFP_KERNEL);
if (!tegra->timings)
return -ENOMEM;
tegra->num_timings = child_count;
timings_ptr = tegra->timings + tegra->num_timings;
tegra->num_timings += child_count;
for_each_child_of_node(node, child) {
struct emc_timing *timing = tegra->timings + (i++);
struct emc_timing *timing = timings_ptr + (i++);
err = load_one_timing_from_dt(tegra, timing, child);
if (err) {
@ -456,7 +471,7 @@ static int load_timings_from_dt(struct tegra_clk_emc *tegra,
timing->ram_code = ram_code;
}
sort(tegra->timings, tegra->num_timings, sizeof(struct emc_timing),
sort(timings_ptr, child_count, sizeof(struct emc_timing),
cmp_timings, NULL);
return 0;
@ -499,10 +514,10 @@ struct clk *tegra_clk_register_emc(void __iomem *base, struct device_node *np,
* fuses until the apbmisc driver is loaded.
*/
err = load_timings_from_dt(tegra, node, node_ram_code);
of_node_put(node);
if (err)
if (err) {
of_node_put(node);
return ERR_PTR(err);
break;
}
}
if (tegra->num_timings == 0)
@ -532,7 +547,5 @@ struct clk *tegra_clk_register_emc(void __iomem *base, struct device_node *np,
/* Allow debugging tools to see the EMC clock */
clk_register_clkdev(clk, "emc", "tegra-clk-debug");
clk_prepare_enable(clk);
return clk;
};

View File

@ -444,6 +444,9 @@ static int clk_pll_enable(struct clk_hw *hw)
unsigned long flags = 0;
int ret;
if (clk_pll_is_enabled(hw))
return 0;
if (pll->lock)
spin_lock_irqsave(pll->lock, flags);
@ -663,8 +666,8 @@ static void _update_pll_mnp(struct tegra_clk_pll *pll,
pll_override_writel(val, params->pmc_divp_reg, pll);
val = pll_override_readl(params->pmc_divnm_reg, pll);
val &= ~(divm_mask(pll) << div_nmp->override_divm_shift) |
~(divn_mask(pll) << div_nmp->override_divn_shift);
val &= ~((divm_mask(pll) << div_nmp->override_divm_shift) |
(divn_mask(pll) << div_nmp->override_divn_shift));
val |= (cfg->m << div_nmp->override_divm_shift) |
(cfg->n << div_nmp->override_divn_shift);
pll_override_writel(val, params->pmc_divnm_reg, pll);
@ -940,11 +943,16 @@ static int clk_plle_training(struct tegra_clk_pll *pll)
static int clk_plle_enable(struct clk_hw *hw)
{
struct tegra_clk_pll *pll = to_clk_pll(hw);
unsigned long input_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
struct tegra_clk_pll_freq_table sel;
unsigned long input_rate;
u32 val;
int err;
if (clk_pll_is_enabled(hw))
return 0;
input_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
if (_get_table_rate(hw, &sel, pll->params->fixed_rate, input_rate))
return -EINVAL;
@ -1355,6 +1363,9 @@ static int clk_pllc_enable(struct clk_hw *hw)
int ret;
unsigned long flags = 0;
if (clk_pll_is_enabled(hw))
return 0;
if (pll->lock)
spin_lock_irqsave(pll->lock, flags);
@ -1567,7 +1578,12 @@ static int clk_plle_tegra114_enable(struct clk_hw *hw)
u32 val;
int ret;
unsigned long flags = 0;
unsigned long input_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
unsigned long input_rate;
if (clk_pll_is_enabled(hw))
return 0;
input_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
if (_get_table_rate(hw, &sel, pll->params->fixed_rate, input_rate))
return -EINVAL;
@ -1704,6 +1720,9 @@ static int clk_pllu_tegra114_enable(struct clk_hw *hw)
return -EINVAL;
}
if (clk_pll_is_enabled(hw))
return 0;
input_rate = clk_hw_get_rate(__clk_get_hw(osc));
if (pll->lock)
@ -2379,6 +2398,16 @@ struct clk *tegra_clk_register_pllre_tegra210(const char *name,
return clk;
}
static int clk_plle_tegra210_is_enabled(struct clk_hw *hw)
{
struct tegra_clk_pll *pll = to_clk_pll(hw);
u32 val;
val = pll_readl_base(pll);
return val & PLLE_BASE_ENABLE ? 1 : 0;
}
static int clk_plle_tegra210_enable(struct clk_hw *hw)
{
struct tegra_clk_pll *pll = to_clk_pll(hw);
@ -2386,7 +2415,12 @@ static int clk_plle_tegra210_enable(struct clk_hw *hw)
u32 val;
int ret = 0;
unsigned long flags = 0;
unsigned long input_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
unsigned long input_rate;
if (clk_plle_tegra210_is_enabled(hw))
return 0;
input_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
if (_get_table_rate(hw, &sel, pll->params->fixed_rate, input_rate))
return -EINVAL;
@ -2497,16 +2531,6 @@ out:
spin_unlock_irqrestore(pll->lock, flags);
}
static int clk_plle_tegra210_is_enabled(struct clk_hw *hw)
{
struct tegra_clk_pll *pll = to_clk_pll(hw);
u32 val;
val = pll_readl_base(pll);
return val & PLLE_BASE_ENABLE ? 1 : 0;
}
static const struct clk_ops tegra_clk_plle_tegra210_ops = {
.is_enabled = clk_plle_tegra210_is_enabled,
.enable = clk_plle_tegra210_enable,

View File

@ -413,7 +413,6 @@ static struct tegra_clk_pll_params pll_m_params = {
.base_reg = PLLM_BASE,
.misc_reg = PLLM_MISC,
.lock_mask = PLL_BASE_LOCK,
.lock_enable_bit_idx = PLL_MISC_LOCK_ENABLE,
.lock_delay = 300,
.max_p = 5,
.pdiv_tohw = pllm_p,
@ -421,7 +420,7 @@ static struct tegra_clk_pll_params pll_m_params = {
.pmc_divnm_reg = PMC_PLLM_WB0_OVERRIDE,
.pmc_divp_reg = PMC_PLLM_WB0_OVERRIDE_2,
.freq_table = pll_m_freq_table,
.flags = TEGRA_PLL_USE_LOCK | TEGRA_PLL_HAS_LOCK_ENABLE,
.flags = TEGRA_PLL_USE_LOCK,
};
static struct tegra_clk_pll_freq_table pll_e_freq_table[] = {

View File

@ -54,7 +54,10 @@
#define CLK_I2C3 28
#define CLK_I2C4 29
#define CLK_LPTIMER 30
#define END_PRIMARY_CLK_F7 31
#define CLK_PLL_SRC 31
#define CLK_DFSDM1 32
#define CLK_ADFSDM1 33
#define CLK_F769_DSI 34
#define END_PRIMARY_CLK_F7 35
#endif

View File

@ -0,0 +1,79 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2018-2019 SiFive, Inc.
* Wesley Terpstra
* Paul Walmsley
*/
#ifndef __LINUX_CLK_ANALOGBITS_WRPLL_CLN28HPC_H
#define __LINUX_CLK_ANALOGBITS_WRPLL_CLN28HPC_H
#include <linux/types.h>
/* DIVQ_VALUES: number of valid DIVQ values */
#define DIVQ_VALUES 6
/*
* Bit definitions for struct wrpll_cfg.flags
*
* WRPLL_FLAGS_BYPASS_FLAG: if set, the PLL is either in bypass, or should be
* programmed to enter bypass
* WRPLL_FLAGS_RESET_FLAG: if set, the PLL is in reset
* WRPLL_FLAGS_INT_FEEDBACK_FLAG: if set, the PLL is configured for internal
* feedback mode
* WRPLL_FLAGS_EXT_FEEDBACK_FLAG: if set, the PLL is configured for external
* feedback mode (not yet supported by this driver)
*/
#define WRPLL_FLAGS_BYPASS_SHIFT 0
#define WRPLL_FLAGS_BYPASS_MASK BIT(WRPLL_FLAGS_BYPASS_SHIFT)
#define WRPLL_FLAGS_RESET_SHIFT 1
#define WRPLL_FLAGS_RESET_MASK BIT(WRPLL_FLAGS_RESET_SHIFT)
#define WRPLL_FLAGS_INT_FEEDBACK_SHIFT 2
#define WRPLL_FLAGS_INT_FEEDBACK_MASK BIT(WRPLL_FLAGS_INT_FEEDBACK_SHIFT)
#define WRPLL_FLAGS_EXT_FEEDBACK_SHIFT 3
#define WRPLL_FLAGS_EXT_FEEDBACK_MASK BIT(WRPLL_FLAGS_EXT_FEEDBACK_SHIFT)
/**
* struct wrpll_cfg - WRPLL configuration values
* @divr: reference divider value (6 bits), as presented to the PLL signals
* @divf: feedback divider value (9 bits), as presented to the PLL signals
* @divq: output divider value (3 bits), as presented to the PLL signals
* @flags: PLL configuration flags. See above for more information
* @range: PLL loop filter range. See below for more information
* @output_rate_cache: cached output rates, swept across DIVQ
* @parent_rate: PLL refclk rate for which values are valid
* @max_r: maximum possible R divider value, given @parent_rate
* @init_r: initial R divider value to start the search from
*
* @divr, @divq, @divq, @range represent what the PLL expects to see
* on its input signals. Thus @divr and @divf are the actual divisors
* minus one. @divq is a power-of-two divider; for example, 1 =
* divide-by-2 and 6 = divide-by-64. 0 is an invalid @divq value.
*
* When initially passing a struct wrpll_cfg record, the
* record should be zero-initialized with the exception of the @flags
* field. The only flag bits that need to be set are either
* WRPLL_FLAGS_INT_FEEDBACK or WRPLL_FLAGS_EXT_FEEDBACK.
*/
struct wrpll_cfg {
u8 divr;
u8 divq;
u8 range;
u8 flags;
u16 divf;
/* private: */
u32 output_rate_cache[DIVQ_VALUES];
unsigned long parent_rate;
u8 max_r;
u8 init_r;
};
int wrpll_configure_for_rate(struct wrpll_cfg *c, u32 target_rate,
unsigned long parent_rate);
unsigned int wrpll_calc_max_lock_us(const struct wrpll_cfg *c);
unsigned long wrpll_calc_output_rate(const struct wrpll_cfg *c,
unsigned long parent_rate);
#endif /* __LINUX_CLK_ANALOGBITS_WRPLL_CLN28HPC_H */

View File

@ -74,6 +74,8 @@
#define AT91_PMC_USBDIV_4 (2 << 28)
#define AT91_PMC_USB96M (1 << 28) /* Divider by 2 Enable (PLLB only) */
#define AT91_PMC_CPU_CKR 0x28 /* CPU Clock Register */
#define AT91_PMC_MCKR 0x30 /* Master Clock Register */
#define AT91_PMC_CSS (3 << 0) /* Master Clock Selection */
#define AT91_PMC_CSS_SLOW (0 << 0)
@ -187,16 +189,8 @@
#define AT91_PMC_PCR 0x10c /* Peripheral Control Register [some SAM9 and SAMA5] */
#define AT91_PMC_PCR_PID_MASK 0x3f
#define AT91_PMC_PCR_GCKCSS_OFFSET 8
#define AT91_PMC_PCR_GCKCSS_MASK (0x7 << AT91_PMC_PCR_GCKCSS_OFFSET)
#define AT91_PMC_PCR_GCKCSS(n) ((n) << AT91_PMC_PCR_GCKCSS_OFFSET) /* GCK Clock Source Selection */
#define AT91_PMC_PCR_CMD (0x1 << 12) /* Command (read=0, write=1) */
#define AT91_PMC_PCR_DIV_OFFSET 16
#define AT91_PMC_PCR_DIV_MASK (0x3 << AT91_PMC_PCR_DIV_OFFSET)
#define AT91_PMC_PCR_DIV(n) ((n) << AT91_PMC_PCR_DIV_OFFSET) /* Divisor Value */
#define AT91_PMC_PCR_GCKDIV_OFFSET 20
#define AT91_PMC_PCR_GCKDIV_MASK (0xff << AT91_PMC_PCR_GCKDIV_OFFSET)
#define AT91_PMC_PCR_GCKDIV(n) ((n) << AT91_PMC_PCR_GCKDIV_OFFSET) /* Generated Clock Divisor Value */
#define AT91_PMC_PCR_GCKDIV_MASK GENMASK(27, 20)
#define AT91_PMC_PCR_EN (0x1 << 28) /* Enable */
#define AT91_PMC_PCR_GCKEN (0x1 << 29) /* GCK Enable */