mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 14:11:52 +00:00
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
This commit is contained in:
commit
ff060019f4
@ -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,
|
||||
|
@ -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>;
|
||||
};
|
@ -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:
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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/
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
//
|
||||
// OWL common clock driver
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
//
|
||||
// OWL composite clock driver
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
//
|
||||
// OWL divider clock driver
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
//
|
||||
// OWL factor clock driver
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
//
|
||||
// OWL fixed factor clock driver
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
//
|
||||
// OWL gate clock driver
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
//
|
||||
// OWL mux clock driver
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
//
|
||||
// OWL pll clock driver
|
||||
//
|
||||
|
@ -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
|
||||
//
|
||||
|
2
drivers/clk/analogbits/Kconfig
Normal file
2
drivers/clk/analogbits/Kconfig
Normal file
@ -0,0 +1,2 @@
|
||||
config CLK_ANALOGBITS_WRPLL_CLN28HPC
|
||||
bool
|
3
drivers/clk/analogbits/Makefile
Normal file
3
drivers/clk/analogbits/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_CLK_ANALOGBITS_WRPLL_CLN28HPC) += wrpll-cln28hpc.o
|
364
drivers/clk/analogbits/wrpll-cln28hpc.c
Normal file
364
drivers/clk/analogbits/wrpll-cln28hpc.c
Normal 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;
|
||||
}
|
@ -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
|
||||
|
@ -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 },
|
||||
};
|
||||
|
@ -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 },
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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;
|
||||
|
330
drivers/clk/at91/clk-sam9x60-pll.c
Normal file
330
drivers/clk/at91/clk-sam9x60-pll.c
Normal 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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
307
drivers/clk/at91/sam9x60.c
Normal file
307
drivers/clk/at91/sam9x60.c
Normal 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);
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Clock driver for TI Davinci PSC controllers
|
||||
*
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Clock driver for TI Davinci PSC controllers
|
||||
*
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
18
drivers/clk/sifive/Kconfig
Normal file
18
drivers/clk/sifive/Kconfig
Normal 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
|
1
drivers/clk/sifive/Makefile
Normal file
1
drivers/clk/sifive/Makefile
Normal file
@ -0,0 +1 @@
|
||||
obj-$(CONFIG_CLK_SIFIVE_FU540_PRCI) += fu540-prci.o
|
626
drivers/clk/sifive/fu540-prci.c
Normal file
626
drivers/clk/sifive/fu540-prci.c
Normal 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);
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
//
|
||||
// Spreadtrum clock infrastructure
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
//
|
||||
// Spreadtrum composite clock driver
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
//
|
||||
// Spreadtrum divider clock driver
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
//
|
||||
// Spreadtrum gate clock driver
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
//
|
||||
// Spreadtrum multiplexer clock driver
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
//
|
||||
// Spreadtrum pll clock driver
|
||||
//
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright 2016 Icenowy Zheng <icenowy@aosc.io>
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Copyright 2017 Icenowy Zheng <icenowy@aosc.io>
|
||||
*
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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[] = {
|
||||
|
@ -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
|
||||
|
79
include/linux/clk/analogbits-wrpll-cln28hpc.h
Normal file
79
include/linux/clk/analogbits-wrpll-cln28hpc.h
Normal 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 */
|
@ -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 */
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user