diff options
author | José Pekkarinen <jose.pekkarinen@nokia.com> | 2016-04-11 10:41:07 +0300 |
---|---|---|
committer | José Pekkarinen <jose.pekkarinen@nokia.com> | 2016-04-13 08:17:18 +0300 |
commit | e09b41010ba33a20a87472ee821fa407a5b8da36 (patch) | |
tree | d10dc367189862e7ca5c592f033dc3726e1df4e3 /kernel/drivers/clk/bcm | |
parent | f93b97fd65072de626c074dbe099a1fff05ce060 (diff) |
These changes are the raw update to linux-4.4.6-rt14. Kernel sources
are taken from kernel.org, and rt patch from the rt wiki download page.
During the rebasing, the following patch collided:
Force tick interrupt and get rid of softirq magic(I70131fb85).
Collisions have been removed because its logic was found on the
source already.
Change-Id: I7f57a4081d9deaa0d9ccfc41a6c8daccdee3b769
Signed-off-by: José Pekkarinen <jose.pekkarinen@nokia.com>
Diffstat (limited to 'kernel/drivers/clk/bcm')
-rw-r--r-- | kernel/drivers/clk/bcm/Kconfig | 9 | ||||
-rw-r--r-- | kernel/drivers/clk/bcm/Makefile | 6 | ||||
-rw-r--r-- | kernel/drivers/clk/bcm/clk-bcm2835.c | 1575 | ||||
-rw-r--r-- | kernel/drivers/clk/bcm/clk-cygnus.c | 270 | ||||
-rw-r--r-- | kernel/drivers/clk/bcm/clk-iproc-armpll.c | 282 | ||||
-rw-r--r-- | kernel/drivers/clk/bcm/clk-iproc-asiu.c | 272 | ||||
-rw-r--r-- | kernel/drivers/clk/bcm/clk-iproc-pll.c | 735 | ||||
-rw-r--r-- | kernel/drivers/clk/bcm/clk-iproc.h | 198 | ||||
-rw-r--r-- | kernel/drivers/clk/bcm/clk-kona-setup.c | 4 | ||||
-rw-r--r-- | kernel/drivers/clk/bcm/clk-kona.c | 55 | ||||
-rw-r--r-- | kernel/drivers/clk/bcm/clk-kona.h | 2 | ||||
-rw-r--r-- | kernel/drivers/clk/bcm/clk-ns2.c | 288 | ||||
-rw-r--r-- | kernel/drivers/clk/bcm/clk-nsp.c | 139 |
13 files changed, 3805 insertions, 30 deletions
diff --git a/kernel/drivers/clk/bcm/Kconfig b/kernel/drivers/clk/bcm/Kconfig index 75506e530..85260fb96 100644 --- a/kernel/drivers/clk/bcm/Kconfig +++ b/kernel/drivers/clk/bcm/Kconfig @@ -1,9 +1,16 @@ config CLK_BCM_KONA bool "Broadcom Kona CCU clock support" - depends on ARCH_BCM_MOBILE + depends on ARCH_BCM_MOBILE || COMPILE_TEST depends on COMMON_CLK default y help Enable common clock framework support for Broadcom SoCs using "Kona" style clock control units, including those in the BCM281xx and BCM21664 families. + +config COMMON_CLK_IPROC + bool + depends on COMMON_CLK + help + Enable common clock framework support for Broadcom SoCs + based on the iProc architecture diff --git a/kernel/drivers/clk/bcm/Makefile b/kernel/drivers/clk/bcm/Makefile index 6297d05a9..3fc95060d 100644 --- a/kernel/drivers/clk/bcm/Makefile +++ b/kernel/drivers/clk/bcm/Makefile @@ -2,3 +2,9 @@ obj-$(CONFIG_CLK_BCM_KONA) += clk-kona.o obj-$(CONFIG_CLK_BCM_KONA) += clk-kona-setup.o obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm281xx.o obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm21664.o +obj-$(CONFIG_COMMON_CLK_IPROC) += clk-iproc-armpll.o clk-iproc-pll.o clk-iproc-asiu.o +obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835.o +obj-$(CONFIG_COMMON_CLK_IPROC) += clk-ns2.o +obj-$(CONFIG_ARCH_BCM_CYGNUS) += clk-cygnus.o +obj-$(CONFIG_ARCH_BCM_NSP) += clk-nsp.o +obj-$(CONFIG_ARCH_BCM_5301X) += clk-nsp.o diff --git a/kernel/drivers/clk/bcm/clk-bcm2835.c b/kernel/drivers/clk/bcm/clk-bcm2835.c new file mode 100644 index 000000000..39bf58202 --- /dev/null +++ b/kernel/drivers/clk/bcm/clk-bcm2835.c @@ -0,0 +1,1575 @@ +/* + * Copyright (C) 2010,2015 Broadcom + * Copyright (C) 2012 Stephen Warren + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** + * DOC: BCM2835 CPRMAN (clock manager for the "audio" domain) + * + * The clock tree on the 2835 has several levels. There's a root + * oscillator running at 19.2Mhz. After the oscillator there are 5 + * PLLs, roughly divided as "camera", "ARM", "core", "DSI displays", + * and "HDMI displays". Those 5 PLLs each can divide their output to + * produce up to 4 channels. Finally, there is the level of clocks to + * be consumed by other hardware components (like "H264" or "HDMI + * state machine"), which divide off of some subset of the PLL + * channels. + * + * All of the clocks in the tree are exposed in the DT, because the DT + * may want to make assignments of the final layer of clocks to the + * PLL channels, and some components of the hardware will actually + * skip layers of the tree (for example, the pixel clock comes + * directly from the PLLH PIX channel without using a CM_*CTL clock + * generator). + */ + +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/clk/bcm2835.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <dt-bindings/clock/bcm2835.h> + +#define CM_PASSWORD 0x5a000000 + +#define CM_GNRICCTL 0x000 +#define CM_GNRICDIV 0x004 +# define CM_DIV_FRAC_BITS 12 + +#define CM_VPUCTL 0x008 +#define CM_VPUDIV 0x00c +#define CM_SYSCTL 0x010 +#define CM_SYSDIV 0x014 +#define CM_PERIACTL 0x018 +#define CM_PERIADIV 0x01c +#define CM_PERIICTL 0x020 +#define CM_PERIIDIV 0x024 +#define CM_H264CTL 0x028 +#define CM_H264DIV 0x02c +#define CM_ISPCTL 0x030 +#define CM_ISPDIV 0x034 +#define CM_V3DCTL 0x038 +#define CM_V3DDIV 0x03c +#define CM_CAM0CTL 0x040 +#define CM_CAM0DIV 0x044 +#define CM_CAM1CTL 0x048 +#define CM_CAM1DIV 0x04c +#define CM_CCP2CTL 0x050 +#define CM_CCP2DIV 0x054 +#define CM_DSI0ECTL 0x058 +#define CM_DSI0EDIV 0x05c +#define CM_DSI0PCTL 0x060 +#define CM_DSI0PDIV 0x064 +#define CM_DPICTL 0x068 +#define CM_DPIDIV 0x06c +#define CM_GP0CTL 0x070 +#define CM_GP0DIV 0x074 +#define CM_GP1CTL 0x078 +#define CM_GP1DIV 0x07c +#define CM_GP2CTL 0x080 +#define CM_GP2DIV 0x084 +#define CM_HSMCTL 0x088 +#define CM_HSMDIV 0x08c +#define CM_OTPCTL 0x090 +#define CM_OTPDIV 0x094 +#define CM_PWMCTL 0x0a0 +#define CM_PWMDIV 0x0a4 +#define CM_SMICTL 0x0b0 +#define CM_SMIDIV 0x0b4 +#define CM_TSENSCTL 0x0e0 +#define CM_TSENSDIV 0x0e4 +#define CM_TIMERCTL 0x0e8 +#define CM_TIMERDIV 0x0ec +#define CM_UARTCTL 0x0f0 +#define CM_UARTDIV 0x0f4 +#define CM_VECCTL 0x0f8 +#define CM_VECDIV 0x0fc +#define CM_PULSECTL 0x190 +#define CM_PULSEDIV 0x194 +#define CM_SDCCTL 0x1a8 +#define CM_SDCDIV 0x1ac +#define CM_ARMCTL 0x1b0 +#define CM_EMMCCTL 0x1c0 +#define CM_EMMCDIV 0x1c4 + +/* General bits for the CM_*CTL regs */ +# define CM_ENABLE BIT(4) +# define CM_KILL BIT(5) +# define CM_GATE_BIT 6 +# define CM_GATE BIT(CM_GATE_BIT) +# define CM_BUSY BIT(7) +# define CM_BUSYD BIT(8) +# define CM_SRC_SHIFT 0 +# define CM_SRC_BITS 4 +# define CM_SRC_MASK 0xf +# define CM_SRC_GND 0 +# define CM_SRC_OSC 1 +# define CM_SRC_TESTDEBUG0 2 +# define CM_SRC_TESTDEBUG1 3 +# define CM_SRC_PLLA_CORE 4 +# define CM_SRC_PLLA_PER 4 +# define CM_SRC_PLLC_CORE0 5 +# define CM_SRC_PLLC_PER 5 +# define CM_SRC_PLLC_CORE1 8 +# define CM_SRC_PLLD_CORE 6 +# define CM_SRC_PLLD_PER 6 +# define CM_SRC_PLLH_AUX 7 +# define CM_SRC_PLLC_CORE1 8 +# define CM_SRC_PLLC_CORE2 9 + +#define CM_OSCCOUNT 0x100 + +#define CM_PLLA 0x104 +# define CM_PLL_ANARST BIT(8) +# define CM_PLLA_HOLDPER BIT(7) +# define CM_PLLA_LOADPER BIT(6) +# define CM_PLLA_HOLDCORE BIT(5) +# define CM_PLLA_LOADCORE BIT(4) +# define CM_PLLA_HOLDCCP2 BIT(3) +# define CM_PLLA_LOADCCP2 BIT(2) +# define CM_PLLA_HOLDDSI0 BIT(1) +# define CM_PLLA_LOADDSI0 BIT(0) + +#define CM_PLLC 0x108 +# define CM_PLLC_HOLDPER BIT(7) +# define CM_PLLC_LOADPER BIT(6) +# define CM_PLLC_HOLDCORE2 BIT(5) +# define CM_PLLC_LOADCORE2 BIT(4) +# define CM_PLLC_HOLDCORE1 BIT(3) +# define CM_PLLC_LOADCORE1 BIT(2) +# define CM_PLLC_HOLDCORE0 BIT(1) +# define CM_PLLC_LOADCORE0 BIT(0) + +#define CM_PLLD 0x10c +# define CM_PLLD_HOLDPER BIT(7) +# define CM_PLLD_LOADPER BIT(6) +# define CM_PLLD_HOLDCORE BIT(5) +# define CM_PLLD_LOADCORE BIT(4) +# define CM_PLLD_HOLDDSI1 BIT(3) +# define CM_PLLD_LOADDSI1 BIT(2) +# define CM_PLLD_HOLDDSI0 BIT(1) +# define CM_PLLD_LOADDSI0 BIT(0) + +#define CM_PLLH 0x110 +# define CM_PLLH_LOADRCAL BIT(2) +# define CM_PLLH_LOADAUX BIT(1) +# define CM_PLLH_LOADPIX BIT(0) + +#define CM_LOCK 0x114 +# define CM_LOCK_FLOCKH BIT(12) +# define CM_LOCK_FLOCKD BIT(11) +# define CM_LOCK_FLOCKC BIT(10) +# define CM_LOCK_FLOCKB BIT(9) +# define CM_LOCK_FLOCKA BIT(8) + +#define CM_EVENT 0x118 +#define CM_DSI1ECTL 0x158 +#define CM_DSI1EDIV 0x15c +#define CM_DSI1PCTL 0x160 +#define CM_DSI1PDIV 0x164 +#define CM_DFTCTL 0x168 +#define CM_DFTDIV 0x16c + +#define CM_PLLB 0x170 +# define CM_PLLB_HOLDARM BIT(1) +# define CM_PLLB_LOADARM BIT(0) + +#define A2W_PLLA_CTRL 0x1100 +#define A2W_PLLC_CTRL 0x1120 +#define A2W_PLLD_CTRL 0x1140 +#define A2W_PLLH_CTRL 0x1160 +#define A2W_PLLB_CTRL 0x11e0 +# define A2W_PLL_CTRL_PRST_DISABLE BIT(17) +# define A2W_PLL_CTRL_PWRDN BIT(16) +# define A2W_PLL_CTRL_PDIV_MASK 0x000007000 +# define A2W_PLL_CTRL_PDIV_SHIFT 12 +# define A2W_PLL_CTRL_NDIV_MASK 0x0000003ff +# define A2W_PLL_CTRL_NDIV_SHIFT 0 + +#define A2W_PLLA_ANA0 0x1010 +#define A2W_PLLC_ANA0 0x1030 +#define A2W_PLLD_ANA0 0x1050 +#define A2W_PLLH_ANA0 0x1070 +#define A2W_PLLB_ANA0 0x10f0 + +#define A2W_PLL_KA_SHIFT 7 +#define A2W_PLL_KA_MASK GENMASK(9, 7) +#define A2W_PLL_KI_SHIFT 19 +#define A2W_PLL_KI_MASK GENMASK(21, 19) +#define A2W_PLL_KP_SHIFT 15 +#define A2W_PLL_KP_MASK GENMASK(18, 15) + +#define A2W_PLLH_KA_SHIFT 19 +#define A2W_PLLH_KA_MASK GENMASK(21, 19) +#define A2W_PLLH_KI_LOW_SHIFT 22 +#define A2W_PLLH_KI_LOW_MASK GENMASK(23, 22) +#define A2W_PLLH_KI_HIGH_SHIFT 0 +#define A2W_PLLH_KI_HIGH_MASK GENMASK(0, 0) +#define A2W_PLLH_KP_SHIFT 1 +#define A2W_PLLH_KP_MASK GENMASK(4, 1) + +#define A2W_XOSC_CTRL 0x1190 +# define A2W_XOSC_CTRL_PLLB_ENABLE BIT(7) +# define A2W_XOSC_CTRL_PLLA_ENABLE BIT(6) +# define A2W_XOSC_CTRL_PLLD_ENABLE BIT(5) +# define A2W_XOSC_CTRL_DDR_ENABLE BIT(4) +# define A2W_XOSC_CTRL_CPR1_ENABLE BIT(3) +# define A2W_XOSC_CTRL_USB_ENABLE BIT(2) +# define A2W_XOSC_CTRL_HDMI_ENABLE BIT(1) +# define A2W_XOSC_CTRL_PLLC_ENABLE BIT(0) + +#define A2W_PLLA_FRAC 0x1200 +#define A2W_PLLC_FRAC 0x1220 +#define A2W_PLLD_FRAC 0x1240 +#define A2W_PLLH_FRAC 0x1260 +#define A2W_PLLB_FRAC 0x12e0 +# define A2W_PLL_FRAC_MASK ((1 << A2W_PLL_FRAC_BITS) - 1) +# define A2W_PLL_FRAC_BITS 20 + +#define A2W_PLL_CHANNEL_DISABLE BIT(8) +#define A2W_PLL_DIV_BITS 8 +#define A2W_PLL_DIV_SHIFT 0 + +#define A2W_PLLA_DSI0 0x1300 +#define A2W_PLLA_CORE 0x1400 +#define A2W_PLLA_PER 0x1500 +#define A2W_PLLA_CCP2 0x1600 + +#define A2W_PLLC_CORE2 0x1320 +#define A2W_PLLC_CORE1 0x1420 +#define A2W_PLLC_PER 0x1520 +#define A2W_PLLC_CORE0 0x1620 + +#define A2W_PLLD_DSI0 0x1340 +#define A2W_PLLD_CORE 0x1440 +#define A2W_PLLD_PER 0x1540 +#define A2W_PLLD_DSI1 0x1640 + +#define A2W_PLLH_AUX 0x1360 +#define A2W_PLLH_RCAL 0x1460 +#define A2W_PLLH_PIX 0x1560 +#define A2W_PLLH_STS 0x1660 + +#define A2W_PLLH_CTRLR 0x1960 +#define A2W_PLLH_FRACR 0x1a60 +#define A2W_PLLH_AUXR 0x1b60 +#define A2W_PLLH_RCALR 0x1c60 +#define A2W_PLLH_PIXR 0x1d60 +#define A2W_PLLH_STSR 0x1e60 + +#define A2W_PLLB_ARM 0x13e0 +#define A2W_PLLB_SP0 0x14e0 +#define A2W_PLLB_SP1 0x15e0 +#define A2W_PLLB_SP2 0x16e0 + +#define LOCK_TIMEOUT_NS 100000000 +#define BCM2835_MAX_FB_RATE 1750000000u + +struct bcm2835_cprman { + struct device *dev; + void __iomem *regs; + spinlock_t regs_lock; + const char *osc_name; + + struct clk_onecell_data onecell; + struct clk *clks[BCM2835_CLOCK_COUNT]; +}; + +static inline void cprman_write(struct bcm2835_cprman *cprman, u32 reg, u32 val) +{ + writel(CM_PASSWORD | val, cprman->regs + reg); +} + +static inline u32 cprman_read(struct bcm2835_cprman *cprman, u32 reg) +{ + return readl(cprman->regs + reg); +} + +/* + * These are fixed clocks. They're probably not all root clocks and it may + * be possible to turn them on and off but until this is mapped out better + * it's the only way they can be used. + */ +void __init bcm2835_init_clocks(void) +{ + struct clk *clk; + int ret; + + clk = clk_register_fixed_rate(NULL, "apb_pclk", NULL, CLK_IS_ROOT, + 126000000); + if (IS_ERR(clk)) + pr_err("apb_pclk not registered\n"); + + clk = clk_register_fixed_rate(NULL, "uart0_pclk", NULL, CLK_IS_ROOT, + 3000000); + if (IS_ERR(clk)) + pr_err("uart0_pclk not registered\n"); + ret = clk_register_clkdev(clk, NULL, "20201000.uart"); + if (ret) + pr_err("uart0_pclk alias not registered\n"); + + clk = clk_register_fixed_rate(NULL, "uart1_pclk", NULL, CLK_IS_ROOT, + 125000000); + if (IS_ERR(clk)) + pr_err("uart1_pclk not registered\n"); + ret = clk_register_clkdev(clk, NULL, "20215000.uart"); + if (ret) + pr_err("uart1_pclk alias not registered\n"); +} + +struct bcm2835_pll_data { + const char *name; + u32 cm_ctrl_reg; + u32 a2w_ctrl_reg; + u32 frac_reg; + u32 ana_reg_base; + u32 reference_enable_mask; + /* Bit in CM_LOCK to indicate when the PLL has locked. */ + u32 lock_mask; + + const struct bcm2835_pll_ana_bits *ana; + + unsigned long min_rate; + unsigned long max_rate; + /* + * Highest rate for the VCO before we have to use the + * pre-divide-by-2. + */ + unsigned long max_fb_rate; +}; + +struct bcm2835_pll_ana_bits { + u32 mask0; + u32 set0; + u32 mask1; + u32 set1; + u32 mask3; + u32 set3; + u32 fb_prediv_mask; +}; + +static const struct bcm2835_pll_ana_bits bcm2835_ana_default = { + .mask0 = 0, + .set0 = 0, + .mask1 = ~(A2W_PLL_KI_MASK | A2W_PLL_KP_MASK), + .set1 = (2 << A2W_PLL_KI_SHIFT) | (8 << A2W_PLL_KP_SHIFT), + .mask3 = ~A2W_PLL_KA_MASK, + .set3 = (2 << A2W_PLL_KA_SHIFT), + .fb_prediv_mask = BIT(14), +}; + +static const struct bcm2835_pll_ana_bits bcm2835_ana_pllh = { + .mask0 = ~(A2W_PLLH_KA_MASK | A2W_PLLH_KI_LOW_MASK), + .set0 = (2 << A2W_PLLH_KA_SHIFT) | (2 << A2W_PLLH_KI_LOW_SHIFT), + .mask1 = ~(A2W_PLLH_KI_HIGH_MASK | A2W_PLLH_KP_MASK), + .set1 = (6 << A2W_PLLH_KP_SHIFT), + .mask3 = 0, + .set3 = 0, + .fb_prediv_mask = BIT(11), +}; + +/* + * PLLA is the auxiliary PLL, used to drive the CCP2 (Compact Camera + * Port 2) transmitter clock. + * + * It is in the PX LDO power domain, which is on when the AUDIO domain + * is on. + */ +static const struct bcm2835_pll_data bcm2835_plla_data = { + .name = "plla", + .cm_ctrl_reg = CM_PLLA, + .a2w_ctrl_reg = A2W_PLLA_CTRL, + .frac_reg = A2W_PLLA_FRAC, + .ana_reg_base = A2W_PLLA_ANA0, + .reference_enable_mask = A2W_XOSC_CTRL_PLLA_ENABLE, + .lock_mask = CM_LOCK_FLOCKA, + + .ana = &bcm2835_ana_default, + + .min_rate = 600000000u, + .max_rate = 2400000000u, + .max_fb_rate = BCM2835_MAX_FB_RATE, +}; + +/* PLLB is used for the ARM's clock. */ +static const struct bcm2835_pll_data bcm2835_pllb_data = { + .name = "pllb", + .cm_ctrl_reg = CM_PLLB, + .a2w_ctrl_reg = A2W_PLLB_CTRL, + .frac_reg = A2W_PLLB_FRAC, + .ana_reg_base = A2W_PLLB_ANA0, + .reference_enable_mask = A2W_XOSC_CTRL_PLLB_ENABLE, + .lock_mask = CM_LOCK_FLOCKB, + + .ana = &bcm2835_ana_default, + + .min_rate = 600000000u, + .max_rate = 3000000000u, + .max_fb_rate = BCM2835_MAX_FB_RATE, +}; + +/* + * PLLC is the core PLL, used to drive the core VPU clock. + * + * It is in the PX LDO power domain, which is on when the AUDIO domain + * is on. +*/ +static const struct bcm2835_pll_data bcm2835_pllc_data = { + .name = "pllc", + .cm_ctrl_reg = CM_PLLC, + .a2w_ctrl_reg = A2W_PLLC_CTRL, + .frac_reg = A2W_PLLC_FRAC, + .ana_reg_base = A2W_PLLC_ANA0, + .reference_enable_mask = A2W_XOSC_CTRL_PLLC_ENABLE, + .lock_mask = CM_LOCK_FLOCKC, + + .ana = &bcm2835_ana_default, + + .min_rate = 600000000u, + .max_rate = 3000000000u, + .max_fb_rate = BCM2835_MAX_FB_RATE, +}; + +/* + * PLLD is the display PLL, used to drive DSI display panels. + * + * It is in the PX LDO power domain, which is on when the AUDIO domain + * is on. + */ +static const struct bcm2835_pll_data bcm2835_plld_data = { + .name = "plld", + .cm_ctrl_reg = CM_PLLD, + .a2w_ctrl_reg = A2W_PLLD_CTRL, + .frac_reg = A2W_PLLD_FRAC, + .ana_reg_base = A2W_PLLD_ANA0, + .reference_enable_mask = A2W_XOSC_CTRL_DDR_ENABLE, + .lock_mask = CM_LOCK_FLOCKD, + + .ana = &bcm2835_ana_default, + + .min_rate = 600000000u, + .max_rate = 2400000000u, + .max_fb_rate = BCM2835_MAX_FB_RATE, +}; + +/* + * PLLH is used to supply the pixel clock or the AUX clock for the TV + * encoder. + * + * It is in the HDMI power domain. + */ +static const struct bcm2835_pll_data bcm2835_pllh_data = { + "pllh", + .cm_ctrl_reg = CM_PLLH, + .a2w_ctrl_reg = A2W_PLLH_CTRL, + .frac_reg = A2W_PLLH_FRAC, + .ana_reg_base = A2W_PLLH_ANA0, + .reference_enable_mask = A2W_XOSC_CTRL_PLLC_ENABLE, + .lock_mask = CM_LOCK_FLOCKH, + + .ana = &bcm2835_ana_pllh, + + .min_rate = 600000000u, + .max_rate = 3000000000u, + .max_fb_rate = BCM2835_MAX_FB_RATE, +}; + +struct bcm2835_pll_divider_data { + const char *name; + const struct bcm2835_pll_data *source_pll; + u32 cm_reg; + u32 a2w_reg; + + u32 load_mask; + u32 hold_mask; + u32 fixed_divider; +}; + +static const struct bcm2835_pll_divider_data bcm2835_plla_core_data = { + .name = "plla_core", + .source_pll = &bcm2835_plla_data, + .cm_reg = CM_PLLA, + .a2w_reg = A2W_PLLA_CORE, + .load_mask = CM_PLLA_LOADCORE, + .hold_mask = CM_PLLA_HOLDCORE, + .fixed_divider = 1, +}; + +static const struct bcm2835_pll_divider_data bcm2835_plla_per_data = { + .name = "plla_per", + .source_pll = &bcm2835_plla_data, + .cm_reg = CM_PLLA, + .a2w_reg = A2W_PLLA_PER, + .load_mask = CM_PLLA_LOADPER, + .hold_mask = CM_PLLA_HOLDPER, + .fixed_divider = 1, +}; + +static const struct bcm2835_pll_divider_data bcm2835_pllb_arm_data = { + .name = "pllb_arm", + .source_pll = &bcm2835_pllb_data, + .cm_reg = CM_PLLB, + .a2w_reg = A2W_PLLB_ARM, + .load_mask = CM_PLLB_LOADARM, + .hold_mask = CM_PLLB_HOLDARM, + .fixed_divider = 1, +}; + +static const struct bcm2835_pll_divider_data bcm2835_pllc_core0_data = { + .name = "pllc_core0", + .source_pll = &bcm2835_pllc_data, + .cm_reg = CM_PLLC, + .a2w_reg = A2W_PLLC_CORE0, + .load_mask = CM_PLLC_LOADCORE0, + .hold_mask = CM_PLLC_HOLDCORE0, + .fixed_divider = 1, +}; + +static const struct bcm2835_pll_divider_data bcm2835_pllc_core1_data = { + .name = "pllc_core1", .source_pll = &bcm2835_pllc_data, + .cm_reg = CM_PLLC, A2W_PLLC_CORE1, + .load_mask = CM_PLLC_LOADCORE1, + .hold_mask = CM_PLLC_HOLDCORE1, + .fixed_divider = 1, +}; + +static const struct bcm2835_pll_divider_data bcm2835_pllc_core2_data = { + .name = "pllc_core2", + .source_pll = &bcm2835_pllc_data, + .cm_reg = CM_PLLC, + .a2w_reg = A2W_PLLC_CORE2, + .load_mask = CM_PLLC_LOADCORE2, + .hold_mask = CM_PLLC_HOLDCORE2, + .fixed_divider = 1, +}; + +static const struct bcm2835_pll_divider_data bcm2835_pllc_per_data = { + .name = "pllc_per", + .source_pll = &bcm2835_pllc_data, + .cm_reg = CM_PLLC, + .a2w_reg = A2W_PLLC_PER, + .load_mask = CM_PLLC_LOADPER, + .hold_mask = CM_PLLC_HOLDPER, + .fixed_divider = 1, +}; + +static const struct bcm2835_pll_divider_data bcm2835_plld_core_data = { + .name = "plld_core", + .source_pll = &bcm2835_plld_data, + .cm_reg = CM_PLLD, + .a2w_reg = A2W_PLLD_CORE, + .load_mask = CM_PLLD_LOADCORE, + .hold_mask = CM_PLLD_HOLDCORE, + .fixed_divider = 1, +}; + +static const struct bcm2835_pll_divider_data bcm2835_plld_per_data = { + .name = "plld_per", + .source_pll = &bcm2835_plld_data, + .cm_reg = CM_PLLD, + .a2w_reg = A2W_PLLD_PER, + .load_mask = CM_PLLD_LOADPER, + .hold_mask = CM_PLLD_HOLDPER, + .fixed_divider = 1, +}; + +static const struct bcm2835_pll_divider_data bcm2835_pllh_rcal_data = { + .name = "pllh_rcal", + .source_pll = &bcm2835_pllh_data, + .cm_reg = CM_PLLH, + .a2w_reg = A2W_PLLH_RCAL, + .load_mask = CM_PLLH_LOADRCAL, + .hold_mask = 0, + .fixed_divider = 10, +}; + +static const struct bcm2835_pll_divider_data bcm2835_pllh_aux_data = { + .name = "pllh_aux", + .source_pll = &bcm2835_pllh_data, + .cm_reg = CM_PLLH, + .a2w_reg = A2W_PLLH_AUX, + .load_mask = CM_PLLH_LOADAUX, + .hold_mask = 0, + .fixed_divider = 10, +}; + +static const struct bcm2835_pll_divider_data bcm2835_pllh_pix_data = { + .name = "pllh_pix", + .source_pll = &bcm2835_pllh_data, + .cm_reg = CM_PLLH, + .a2w_reg = A2W_PLLH_PIX, + .load_mask = CM_PLLH_LOADPIX, + .hold_mask = 0, + .fixed_divider = 10, +}; + +struct bcm2835_clock_data { + const char *name; + + const char *const *parents; + int num_mux_parents; + + u32 ctl_reg; + u32 div_reg; + + /* Number of integer bits in the divider */ + u32 int_bits; + /* Number of fractional bits in the divider */ + u32 frac_bits; + + bool is_vpu_clock; +}; + +static const char *const bcm2835_clock_per_parents[] = { + "gnd", + "xosc", + "testdebug0", + "testdebug1", + "plla_per", + "pllc_per", + "plld_per", + "pllh_aux", +}; + +static const char *const bcm2835_clock_vpu_parents[] = { + "gnd", + "xosc", + "testdebug0", + "testdebug1", + "plla_core", + "pllc_core0", + "plld_core", + "pllh_aux", + "pllc_core1", + "pllc_core2", +}; + +static const char *const bcm2835_clock_osc_parents[] = { + "gnd", + "xosc", + "testdebug0", + "testdebug1" +}; + +/* + * Used for a 1Mhz clock for the system clocksource, and also used by + * the watchdog timer and the camera pulse generator. + */ +static const struct bcm2835_clock_data bcm2835_clock_timer_data = { + .name = "timer", + .num_mux_parents = ARRAY_SIZE(bcm2835_clock_osc_parents), + .parents = bcm2835_clock_osc_parents, + .ctl_reg = CM_TIMERCTL, + .div_reg = CM_TIMERDIV, + .int_bits = 6, + .frac_bits = 12, +}; + +/* One Time Programmable Memory clock. Maximum 10Mhz. */ +static const struct bcm2835_clock_data bcm2835_clock_otp_data = { + .name = "otp", + .num_mux_parents = ARRAY_SIZE(bcm2835_clock_osc_parents), + .parents = bcm2835_clock_osc_parents, + .ctl_reg = CM_OTPCTL, + .div_reg = CM_OTPDIV, + .int_bits = 4, + .frac_bits = 0, +}; + +/* + * VPU clock. This doesn't have an enable bit, since it drives the + * bus for everything else, and is special so it doesn't need to be + * gated for rate changes. It is also known as "clk_audio" in various + * hardware documentation. + */ +static const struct bcm2835_clock_data bcm2835_clock_vpu_data = { + .name = "vpu", + .num_mux_parents = ARRAY_SIZE(bcm2835_clock_vpu_parents), + .parents = bcm2835_clock_vpu_parents, + .ctl_reg = CM_VPUCTL, + .div_reg = CM_VPUDIV, + .int_bits = 12, + .frac_bits = 8, + .is_vpu_clock = true, +}; + +static const struct bcm2835_clock_data bcm2835_clock_v3d_data = { + .name = "v3d", + .num_mux_parents = ARRAY_SIZE(bcm2835_clock_vpu_parents), + .parents = bcm2835_clock_vpu_parents, + .ctl_reg = CM_V3DCTL, + .div_reg = CM_V3DDIV, + .int_bits = 4, + .frac_bits = 8, +}; + +static const struct bcm2835_clock_data bcm2835_clock_isp_data = { + .name = "isp", + .num_mux_parents = ARRAY_SIZE(bcm2835_clock_vpu_parents), + .parents = bcm2835_clock_vpu_parents, + .ctl_reg = CM_ISPCTL, + .div_reg = CM_ISPDIV, + .int_bits = 4, + .frac_bits = 8, +}; + +static const struct bcm2835_clock_data bcm2835_clock_h264_data = { + .name = "h264", + .num_mux_parents = ARRAY_SIZE(bcm2835_clock_vpu_parents), + .parents = bcm2835_clock_vpu_parents, + .ctl_reg = CM_H264CTL, + .div_reg = CM_H264DIV, + .int_bits = 4, + .frac_bits = 8, +}; + +/* TV encoder clock. Only operating frequency is 108Mhz. */ +static const struct bcm2835_clock_data bcm2835_clock_vec_data = { + .name = "vec", + .num_mux_parents = ARRAY_SIZE(bcm2835_clock_per_parents), + .parents = bcm2835_clock_per_parents, + .ctl_reg = CM_VECCTL, + .div_reg = CM_VECDIV, + .int_bits = 4, + .frac_bits = 0, +}; + +static const struct bcm2835_clock_data bcm2835_clock_uart_data = { + .name = "uart", + .num_mux_parents = ARRAY_SIZE(bcm2835_clock_per_parents), + .parents = bcm2835_clock_per_parents, + .ctl_reg = CM_UARTCTL, + .div_reg = CM_UARTDIV, + .int_bits = 10, + .frac_bits = 12, +}; + +/* HDMI state machine */ +static const struct bcm2835_clock_data bcm2835_clock_hsm_data = { + .name = "hsm", + .num_mux_parents = ARRAY_SIZE(bcm2835_clock_per_parents), + .parents = bcm2835_clock_per_parents, + .ctl_reg = CM_HSMCTL, + .div_reg = CM_HSMDIV, + .int_bits = 4, + .frac_bits = 8, +}; + +/* + * Secondary SDRAM clock. Used for low-voltage modes when the PLL in + * the SDRAM controller can't be used. + */ +static const struct bcm2835_clock_data bcm2835_clock_sdram_data = { + .name = "sdram", + .num_mux_parents = ARRAY_SIZE(bcm2835_clock_vpu_parents), + .parents = bcm2835_clock_vpu_parents, + .ctl_reg = CM_SDCCTL, + .div_reg = CM_SDCDIV, + .int_bits = 6, + .frac_bits = 0, +}; + +/* Clock for the temperature sensor. Generally run at 2Mhz, max 5Mhz. */ +static const struct bcm2835_clock_data bcm2835_clock_tsens_data = { + .name = "tsens", + .num_mux_parents = ARRAY_SIZE(bcm2835_clock_osc_parents), + .parents = bcm2835_clock_osc_parents, + .ctl_reg = CM_TSENSCTL, + .div_reg = CM_TSENSDIV, + .int_bits = 5, + .frac_bits = 0, +}; + +/* Arasan EMMC clock */ +static const struct bcm2835_clock_data bcm2835_clock_emmc_data = { + .name = "emmc", + .num_mux_parents = ARRAY_SIZE(bcm2835_clock_per_parents), + .parents = bcm2835_clock_per_parents, + .ctl_reg = CM_EMMCCTL, + .div_reg = CM_EMMCDIV, + .int_bits = 4, + .frac_bits = 8, +}; + +struct bcm2835_pll { + struct clk_hw hw; + struct bcm2835_cprman *cprman; + const struct bcm2835_pll_data *data; +}; + +static int bcm2835_pll_is_on(struct clk_hw *hw) +{ + struct bcm2835_pll *pll = container_of(hw, struct bcm2835_pll, hw); + struct bcm2835_cprman *cprman = pll->cprman; + const struct bcm2835_pll_data *data = pll->data; + + return cprman_read(cprman, data->a2w_ctrl_reg) & + A2W_PLL_CTRL_PRST_DISABLE; +} + +static void bcm2835_pll_choose_ndiv_and_fdiv(unsigned long rate, + unsigned long parent_rate, + u32 *ndiv, u32 *fdiv) +{ + u64 div; + + div = (u64)rate << A2W_PLL_FRAC_BITS; + do_div(div, parent_rate); + + *ndiv = div >> A2W_PLL_FRAC_BITS; + *fdiv = div & ((1 << A2W_PLL_FRAC_BITS) - 1); +} + +static long bcm2835_pll_rate_from_divisors(unsigned long parent_rate, + u32 ndiv, u32 fdiv, u32 pdiv) +{ + u64 rate; + + if (pdiv == 0) + return 0; + + rate = (u64)parent_rate * ((ndiv << A2W_PLL_FRAC_BITS) + fdiv); + do_div(rate, pdiv); + return rate >> A2W_PLL_FRAC_BITS; +} + +static long bcm2835_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + u32 ndiv, fdiv; + + bcm2835_pll_choose_ndiv_and_fdiv(rate, *parent_rate, &ndiv, &fdiv); + + return bcm2835_pll_rate_from_divisors(*parent_rate, ndiv, fdiv, 1); +} + +static unsigned long bcm2835_pll_get_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct bcm2835_pll *pll = container_of(hw, struct bcm2835_pll, hw); + struct bcm2835_cprman *cprman = pll->cprman; + const struct bcm2835_pll_data *data = pll->data; + u32 a2wctrl = cprman_read(cprman, data->a2w_ctrl_reg); + u32 ndiv, pdiv, fdiv; + bool using_prediv; + + if (parent_rate == 0) + return 0; + + fdiv = cprman_read(cprman, data->frac_reg) & A2W_PLL_FRAC_MASK; + ndiv = (a2wctrl & A2W_PLL_CTRL_NDIV_MASK) >> A2W_PLL_CTRL_NDIV_SHIFT; + pdiv = (a2wctrl & A2W_PLL_CTRL_PDIV_MASK) >> A2W_PLL_CTRL_PDIV_SHIFT; + using_prediv = cprman_read(cprman, data->ana_reg_base + 4) & + data->ana->fb_prediv_mask; + + if (using_prediv) + ndiv *= 2; + + return bcm2835_pll_rate_from_divisors(parent_rate, ndiv, fdiv, pdiv); +} + +static void bcm2835_pll_off(struct clk_hw *hw) +{ + struct bcm2835_pll *pll = container_of(hw, struct bcm2835_pll, hw); + struct bcm2835_cprman *cprman = pll->cprman; + const struct bcm2835_pll_data *data = pll->data; + + cprman_write(cprman, data->cm_ctrl_reg, CM_PLL_ANARST); + cprman_write(cprman, data->a2w_ctrl_reg, A2W_PLL_CTRL_PWRDN); +} + +static int bcm2835_pll_on(struct clk_hw *hw) +{ + struct bcm2835_pll *pll = container_of(hw, struct bcm2835_pll, hw); + struct bcm2835_cprman *cprman = pll->cprman; + const struct bcm2835_pll_data *data = pll->data; + ktime_t timeout; + + /* Take the PLL out of reset. */ + cprman_write(cprman, data->cm_ctrl_reg, + cprman_read(cprman, data->cm_ctrl_reg) & ~CM_PLL_ANARST); + + /* Wait for the PLL to lock. */ + timeout = ktime_add_ns(ktime_get(), LOCK_TIMEOUT_NS); + while (!(cprman_read(cprman, CM_LOCK) & data->lock_mask)) { + if (ktime_after(ktime_get(), timeout)) { + dev_err(cprman->dev, "%s: couldn't lock PLL\n", + clk_hw_get_name(hw)); + return -ETIMEDOUT; + } + + cpu_relax(); + } + + return 0; +} + +static void +bcm2835_pll_write_ana(struct bcm2835_cprman *cprman, u32 ana_reg_base, u32 *ana) +{ + int i; + + /* + * ANA register setup is done as a series of writes to + * ANA3-ANA0, in that order. This lets us write all 4 + * registers as a single cycle of the serdes interface (taking + * 100 xosc clocks), whereas if we were to update ana0, 1, and + * 3 individually through their partial-write registers, each + * would be their own serdes cycle. + */ + for (i = 3; i >= 0; i--) + cprman_write(cprman, ana_reg_base + i * 4, ana[i]); +} + +static int bcm2835_pll_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct bcm2835_pll *pll = container_of(hw, struct bcm2835_pll, hw); + struct bcm2835_cprman *cprman = pll->cprman; + const struct bcm2835_pll_data *data = pll->data; + bool was_using_prediv, use_fb_prediv, do_ana_setup_first; + u32 ndiv, fdiv, a2w_ctl; + u32 ana[4]; + int i; + + if (rate < data->min_rate || rate > data->max_rate) { + dev_err(cprman->dev, "%s: rate out of spec: %lu vs (%lu, %lu)\n", + clk_hw_get_name(hw), rate, + data->min_rate, data->max_rate); + return -EINVAL; + } + + if (rate > data->max_fb_rate) { + use_fb_prediv = true; + rate /= 2; + } else { + use_fb_prediv = false; + } + + bcm2835_pll_choose_ndiv_and_fdiv(rate, parent_rate, &ndiv, &fdiv); + + for (i = 3; i >= 0; i--) + ana[i] = cprman_read(cprman, data->ana_reg_base + i * 4); + + was_using_prediv = ana[1] & data->ana->fb_prediv_mask; + + ana[0] &= ~data->ana->mask0; + ana[0] |= data->ana->set0; + ana[1] &= ~data->ana->mask1; + ana[1] |= data->ana->set1; + ana[3] &= ~data->ana->mask3; + ana[3] |= data->ana->set3; + + if (was_using_prediv && !use_fb_prediv) { + ana[1] &= ~data->ana->fb_prediv_mask; + do_ana_setup_first = true; + } else if (!was_using_prediv && use_fb_prediv) { + ana[1] |= data->ana->fb_prediv_mask; + do_ana_setup_first = false; + } else { + do_ana_setup_first = true; + } + + /* Unmask the reference clock from the oscillator. */ + cprman_write(cprman, A2W_XOSC_CTRL, + cprman_read(cprman, A2W_XOSC_CTRL) | + data->reference_enable_mask); + + if (do_ana_setup_first) + bcm2835_pll_write_ana(cprman, data->ana_reg_base, ana); + + /* Set the PLL multiplier from the oscillator. */ + cprman_write(cprman, data->frac_reg, fdiv); + + a2w_ctl = cprman_read(cprman, data->a2w_ctrl_reg); + a2w_ctl &= ~A2W_PLL_CTRL_NDIV_MASK; + a2w_ctl |= ndiv << A2W_PLL_CTRL_NDIV_SHIFT; + a2w_ctl &= ~A2W_PLL_CTRL_PDIV_MASK; + a2w_ctl |= 1 << A2W_PLL_CTRL_PDIV_SHIFT; + cprman_write(cprman, data->a2w_ctrl_reg, a2w_ctl); + + if (!do_ana_setup_first) + bcm2835_pll_write_ana(cprman, data->ana_reg_base, ana); + + return 0; +} + +static const struct clk_ops bcm2835_pll_clk_ops = { + .is_prepared = bcm2835_pll_is_on, + .prepare = bcm2835_pll_on, + .unprepare = bcm2835_pll_off, + .recalc_rate = bcm2835_pll_get_rate, + .set_rate = bcm2835_pll_set_rate, + .round_rate = bcm2835_pll_round_rate, +}; + +struct bcm2835_pll_divider { + struct clk_divider div; + struct bcm2835_cprman *cprman; + const struct bcm2835_pll_divider_data *data; +}; + +static struct bcm2835_pll_divider * +bcm2835_pll_divider_from_hw(struct clk_hw *hw) +{ + return container_of(hw, struct bcm2835_pll_divider, div.hw); +} + +static int bcm2835_pll_divider_is_on(struct clk_hw *hw) +{ + struct bcm2835_pll_divider *divider = bcm2835_pll_divider_from_hw(hw); + struct bcm2835_cprman *cprman = divider->cprman; + const struct bcm2835_pll_divider_data *data = divider->data; + + return !(cprman_read(cprman, data->a2w_reg) & A2W_PLL_CHANNEL_DISABLE); +} + +static long bcm2835_pll_divider_round_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long *parent_rate) +{ + return clk_divider_ops.round_rate(hw, rate, parent_rate); +} + +static unsigned long bcm2835_pll_divider_get_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct bcm2835_pll_divider *divider = bcm2835_pll_divider_from_hw(hw); + struct bcm2835_cprman *cprman = divider->cprman; + const struct bcm2835_pll_divider_data *data = divider->data; + u32 div = cprman_read(cprman, data->a2w_reg); + + div &= (1 << A2W_PLL_DIV_BITS) - 1; + if (div == 0) + div = 256; + + return parent_rate / div; +} + +static void bcm2835_pll_divider_off(struct clk_hw *hw) +{ + struct bcm2835_pll_divider *divider = bcm2835_pll_divider_from_hw(hw); + struct bcm2835_cprman *cprman = divider->cprman; + const struct bcm2835_pll_divider_data *data = divider->data; + + cprman_write(cprman, data->cm_reg, + (cprman_read(cprman, data->cm_reg) & + ~data->load_mask) | data->hold_mask); + cprman_write(cprman, data->a2w_reg, A2W_PLL_CHANNEL_DISABLE); +} + +static int bcm2835_pll_divider_on(struct clk_hw *hw) +{ + struct bcm2835_pll_divider *divider = bcm2835_pll_divider_from_hw(hw); + struct bcm2835_cprman *cprman = divider->cprman; + const struct bcm2835_pll_divider_data *data = divider->data; + + cprman_write(cprman, data->a2w_reg, + cprman_read(cprman, data->a2w_reg) & + ~A2W_PLL_CHANNEL_DISABLE); + + cprman_write(cprman, data->cm_reg, + cprman_read(cprman, data->cm_reg) & ~data->hold_mask); + + return 0; +} + +static int bcm2835_pll_divider_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct bcm2835_pll_divider *divider = bcm2835_pll_divider_from_hw(hw); + struct bcm2835_cprman *cprman = divider->cprman; + const struct bcm2835_pll_divider_data *data = divider->data; + u32 cm; + int ret; + + ret = clk_divider_ops.set_rate(hw, rate, parent_rate); + if (ret) + return ret; + + cm = cprman_read(cprman, data->cm_reg); + cprman_write(cprman, data->cm_reg, cm | data->load_mask); + cprman_write(cprman, data->cm_reg, cm & ~data->load_mask); + + return 0; +} + +static const struct clk_ops bcm2835_pll_divider_clk_ops = { + .is_prepared = bcm2835_pll_divider_is_on, + .prepare = bcm2835_pll_divider_on, + .unprepare = bcm2835_pll_divider_off, + .recalc_rate = bcm2835_pll_divider_get_rate, + .set_rate = bcm2835_pll_divider_set_rate, + .round_rate = bcm2835_pll_divider_round_rate, +}; + +/* + * The CM dividers do fixed-point division, so we can't use the + * generic integer divider code like the PLL dividers do (and we can't + * fake it by having some fixed shifts preceding it in the clock tree, + * because we'd run out of bits in a 32-bit unsigned long). + */ +struct bcm2835_clock { + struct clk_hw hw; + struct bcm2835_cprman *cprman; + const struct bcm2835_clock_data *data; +}; + +static struct bcm2835_clock *bcm2835_clock_from_hw(struct clk_hw *hw) +{ + return container_of(hw, struct bcm2835_clock, hw); +} + +static int bcm2835_clock_is_on(struct clk_hw *hw) +{ + struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw); + struct bcm2835_cprman *cprman = clock->cprman; + const struct bcm2835_clock_data *data = clock->data; + + return (cprman_read(cprman, data->ctl_reg) & CM_ENABLE) != 0; +} + +static u32 bcm2835_clock_choose_div(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw); + const struct bcm2835_clock_data *data = clock->data; + u32 unused_frac_mask = GENMASK(CM_DIV_FRAC_BITS - data->frac_bits, 0); + u64 temp = (u64)parent_rate << CM_DIV_FRAC_BITS; + u32 div; + + do_div(temp, rate); + div = temp; + + /* Round and mask off the unused bits */ + if (unused_frac_mask != 0) { + div += unused_frac_mask >> 1; + div &= ~unused_frac_mask; + } + + /* Clamp to the limits. */ + div = max(div, unused_frac_mask + 1); + div = min_t(u32, div, GENMASK(data->int_bits + CM_DIV_FRAC_BITS - 1, + CM_DIV_FRAC_BITS - data->frac_bits)); + + return div; +} + +static long bcm2835_clock_rate_from_divisor(struct bcm2835_clock *clock, + unsigned long parent_rate, + u32 div) +{ + const struct bcm2835_clock_data *data = clock->data; + u64 temp; + + /* + * The divisor is a 12.12 fixed point field, but only some of + * the bits are populated in any given clock. + */ + div >>= CM_DIV_FRAC_BITS - data->frac_bits; + div &= (1 << (data->int_bits + data->frac_bits)) - 1; + + if (div == 0) + return 0; + + temp = (u64)parent_rate << data->frac_bits; + + do_div(temp, div); + + return temp; +} + +static long bcm2835_clock_round_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long *parent_rate) +{ + struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw); + u32 div = bcm2835_clock_choose_div(hw, rate, *parent_rate); + + return bcm2835_clock_rate_from_divisor(clock, *parent_rate, div); +} + +static unsigned long bcm2835_clock_get_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw); + struct bcm2835_cprman *cprman = clock->cprman; + const struct bcm2835_clock_data *data = clock->data; + u32 div = cprman_read(cprman, data->div_reg); + + return bcm2835_clock_rate_from_divisor(clock, parent_rate, div); +} + +static void bcm2835_clock_wait_busy(struct bcm2835_clock *clock) +{ + struct bcm2835_cprman *cprman = clock->cprman; + const struct bcm2835_clock_data *data = clock->data; + ktime_t timeout = ktime_add_ns(ktime_get(), LOCK_TIMEOUT_NS); + + while (cprman_read(cprman, data->ctl_reg) & CM_BUSY) { + if (ktime_after(ktime_get(), timeout)) { + dev_err(cprman->dev, "%s: couldn't lock PLL\n", + clk_hw_get_name(&clock->hw)); + return; + } + cpu_relax(); + } +} + +static void bcm2835_clock_off(struct clk_hw *hw) +{ + struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw); + struct bcm2835_cprman *cprman = clock->cprman; + const struct bcm2835_clock_data *data = clock->data; + + spin_lock(&cprman->regs_lock); + cprman_write(cprman, data->ctl_reg, + cprman_read(cprman, data->ctl_reg) & ~CM_ENABLE); + spin_unlock(&cprman->regs_lock); + + /* BUSY will remain high until the divider completes its cycle. */ + bcm2835_clock_wait_busy(clock); +} + +static int bcm2835_clock_on(struct clk_hw *hw) +{ + struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw); + struct bcm2835_cprman *cprman = clock->cprman; + const struct bcm2835_clock_data *data = clock->data; + + spin_lock(&cprman->regs_lock); + cprman_write(cprman, data->ctl_reg, + cprman_read(cprman, data->ctl_reg) | + CM_ENABLE | + CM_GATE); + spin_unlock(&cprman->regs_lock); + + return 0; +} + +static int bcm2835_clock_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw); + struct bcm2835_cprman *cprman = clock->cprman; + const struct bcm2835_clock_data *data = clock->data; + u32 div = bcm2835_clock_choose_div(hw, rate, parent_rate); + + cprman_write(cprman, data->div_reg, div); + + return 0; +} + +static const struct clk_ops bcm2835_clock_clk_ops = { + .is_prepared = bcm2835_clock_is_on, + .prepare = bcm2835_clock_on, + .unprepare = bcm2835_clock_off, + .recalc_rate = bcm2835_clock_get_rate, + .set_rate = bcm2835_clock_set_rate, + .round_rate = bcm2835_clock_round_rate, +}; + +static int bcm2835_vpu_clock_is_on(struct clk_hw *hw) +{ + return true; +} + +/* + * The VPU clock can never be disabled (it doesn't have an ENABLE + * bit), so it gets its own set of clock ops. + */ +static const struct clk_ops bcm2835_vpu_clock_clk_ops = { + .is_prepared = bcm2835_vpu_clock_is_on, + .recalc_rate = bcm2835_clock_get_rate, + .set_rate = bcm2835_clock_set_rate, + .round_rate = bcm2835_clock_round_rate, +}; + +static struct clk *bcm2835_register_pll(struct bcm2835_cprman *cprman, + const struct bcm2835_pll_data *data) +{ + struct bcm2835_pll *pll; + struct clk_init_data init; + + memset(&init, 0, sizeof(init)); + + /* All of the PLLs derive from the external oscillator. */ + init.parent_names = &cprman->osc_name; + init.num_parents = 1; + init.name = data->name; + init.ops = &bcm2835_pll_clk_ops; + init.flags = CLK_IGNORE_UNUSED; + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (!pll) + return NULL; + + pll->cprman = cprman; + pll->data = data; + pll->hw.init = &init; + + return devm_clk_register(cprman->dev, &pll->hw); +} + +static struct clk * +bcm2835_register_pll_divider(struct bcm2835_cprman *cprman, + const struct bcm2835_pll_divider_data *data) +{ + struct bcm2835_pll_divider *divider; + struct clk_init_data init; + struct clk *clk; + const char *divider_name; + + if (data->fixed_divider != 1) { + divider_name = devm_kasprintf(cprman->dev, GFP_KERNEL, + "%s_prediv", data->name); + if (!divider_name) + return NULL; + } else { + divider_name = data->name; + } + + memset(&init, 0, sizeof(init)); + + init.parent_names = &data->source_pll->name; + init.num_parents = 1; + init.name = divider_name; + init.ops = &bcm2835_pll_divider_clk_ops; + init.flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED; + + divider = devm_kzalloc(cprman->dev, sizeof(*divider), GFP_KERNEL); + if (!divider) + return NULL; + + divider->div.reg = cprman->regs + data->a2w_reg; + divider->div.shift = A2W_PLL_DIV_SHIFT; + divider->div.width = A2W_PLL_DIV_BITS; + divider->div.flags = 0; + divider->div.lock = &cprman->regs_lock; + divider->div.hw.init = &init; + divider->div.table = NULL; + + divider->cprman = cprman; + divider->data = data; + + clk = devm_clk_register(cprman->dev, ÷r->div.hw); + if (IS_ERR(clk)) + return clk; + + /* + * PLLH's channels have a fixed divide by 10 afterwards, which + * is what our consumers are actually using. + */ + if (data->fixed_divider != 1) { + return clk_register_fixed_factor(cprman->dev, data->name, + divider_name, + CLK_SET_RATE_PARENT, + 1, + data->fixed_divider); + } + + return clk; +} + +static struct clk *bcm2835_register_clock(struct bcm2835_cprman *cprman, + const struct bcm2835_clock_data *data) +{ + struct bcm2835_clock *clock; + struct clk_init_data init; + const char *parent; + + /* + * Most of the clock generators have a mux field, so we + * instantiate a generic mux as our parent to handle it. + */ + if (data->num_mux_parents) { + const char *parents[1 << CM_SRC_BITS]; + int i; + + parent = devm_kasprintf(cprman->dev, GFP_KERNEL, + "mux_%s", data->name); + if (!parent) + return NULL; + + /* + * Replace our "xosc" references with the oscillator's + * actual name. + */ + for (i = 0; i < data->num_mux_parents; i++) { + if (strcmp(data->parents[i], "xosc") == 0) + parents[i] = cprman->osc_name; + else + parents[i] = data->parents[i]; + } + + clk_register_mux(cprman->dev, parent, + parents, data->num_mux_parents, + CLK_SET_RATE_PARENT, + cprman->regs + data->ctl_reg, + CM_SRC_SHIFT, CM_SRC_BITS, + 0, &cprman->regs_lock); + } else { + parent = data->parents[0]; + } + + memset(&init, 0, sizeof(init)); + init.parent_names = &parent; + init.num_parents = 1; + init.name = data->name; + init.flags = CLK_IGNORE_UNUSED; + + if (data->is_vpu_clock) { + init.ops = &bcm2835_vpu_clock_clk_ops; + } else { + init.ops = &bcm2835_clock_clk_ops; + init.flags |= CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; + } + + clock = devm_kzalloc(cprman->dev, sizeof(*clock), GFP_KERNEL); + if (!clock) + return NULL; + + clock->cprman = cprman; + clock->data = data; + clock->hw.init = &init; + + return devm_clk_register(cprman->dev, &clock->hw); +} + +static int bcm2835_clk_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct clk **clks; + struct bcm2835_cprman *cprman; + struct resource *res; + + cprman = devm_kzalloc(dev, sizeof(*cprman), GFP_KERNEL); + if (!cprman) + return -ENOMEM; + + spin_lock_init(&cprman->regs_lock); + cprman->dev = dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + cprman->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(cprman->regs)) + return PTR_ERR(cprman->regs); + + cprman->osc_name = of_clk_get_parent_name(dev->of_node, 0); + if (!cprman->osc_name) + return -ENODEV; + + platform_set_drvdata(pdev, cprman); + + cprman->onecell.clk_num = BCM2835_CLOCK_COUNT; + cprman->onecell.clks = cprman->clks; + clks = cprman->clks; + + clks[BCM2835_PLLA] = bcm2835_register_pll(cprman, &bcm2835_plla_data); + clks[BCM2835_PLLB] = bcm2835_register_pll(cprman, &bcm2835_pllb_data); + clks[BCM2835_PLLC] = bcm2835_register_pll(cprman, &bcm2835_pllc_data); + clks[BCM2835_PLLD] = bcm2835_register_pll(cprman, &bcm2835_plld_data); + clks[BCM2835_PLLH] = bcm2835_register_pll(cprman, &bcm2835_pllh_data); + + clks[BCM2835_PLLA_CORE] = + bcm2835_register_pll_divider(cprman, &bcm2835_plla_core_data); + clks[BCM2835_PLLA_PER] = + bcm2835_register_pll_divider(cprman, &bcm2835_plla_per_data); + clks[BCM2835_PLLC_CORE0] = + bcm2835_register_pll_divider(cprman, &bcm2835_pllc_core0_data); + clks[BCM2835_PLLC_CORE1] = + bcm2835_register_pll_divider(cprman, &bcm2835_pllc_core1_data); + clks[BCM2835_PLLC_CORE2] = + bcm2835_register_pll_divider(cprman, &bcm2835_pllc_core2_data); + clks[BCM2835_PLLC_PER] = + bcm2835_register_pll_divider(cprman, &bcm2835_pllc_per_data); + clks[BCM2835_PLLD_CORE] = + bcm2835_register_pll_divider(cprman, &bcm2835_plld_core_data); + clks[BCM2835_PLLD_PER] = + bcm2835_register_pll_divider(cprman, &bcm2835_plld_per_data); + clks[BCM2835_PLLH_RCAL] = + bcm2835_register_pll_divider(cprman, &bcm2835_pllh_rcal_data); + clks[BCM2835_PLLH_AUX] = + bcm2835_register_pll_divider(cprman, &bcm2835_pllh_aux_data); + clks[BCM2835_PLLH_PIX] = + bcm2835_register_pll_divider(cprman, &bcm2835_pllh_pix_data); + + clks[BCM2835_CLOCK_TIMER] = + bcm2835_register_clock(cprman, &bcm2835_clock_timer_data); + clks[BCM2835_CLOCK_OTP] = + bcm2835_register_clock(cprman, &bcm2835_clock_otp_data); + clks[BCM2835_CLOCK_TSENS] = + bcm2835_register_clock(cprman, &bcm2835_clock_tsens_data); + clks[BCM2835_CLOCK_VPU] = + bcm2835_register_clock(cprman, &bcm2835_clock_vpu_data); + clks[BCM2835_CLOCK_V3D] = + bcm2835_register_clock(cprman, &bcm2835_clock_v3d_data); + clks[BCM2835_CLOCK_ISP] = + bcm2835_register_clock(cprman, &bcm2835_clock_isp_data); + clks[BCM2835_CLOCK_H264] = + bcm2835_register_clock(cprman, &bcm2835_clock_h264_data); + clks[BCM2835_CLOCK_V3D] = + bcm2835_register_clock(cprman, &bcm2835_clock_v3d_data); + clks[BCM2835_CLOCK_SDRAM] = + bcm2835_register_clock(cprman, &bcm2835_clock_sdram_data); + clks[BCM2835_CLOCK_UART] = + bcm2835_register_clock(cprman, &bcm2835_clock_uart_data); + clks[BCM2835_CLOCK_VEC] = + bcm2835_register_clock(cprman, &bcm2835_clock_vec_data); + clks[BCM2835_CLOCK_HSM] = + bcm2835_register_clock(cprman, &bcm2835_clock_hsm_data); + clks[BCM2835_CLOCK_EMMC] = + bcm2835_register_clock(cprman, &bcm2835_clock_emmc_data); + + /* + * CM_PERIICTL (and CM_PERIACTL, CM_SYSCTL and CM_VPUCTL if + * you have the debug bit set in the power manager, which we + * don't bother exposing) are individual gates off of the + * non-stop vpu clock. + */ + clks[BCM2835_CLOCK_PERI_IMAGE] = + clk_register_gate(dev, "peri_image", "vpu", + CLK_IGNORE_UNUSED | CLK_SET_RATE_GATE, + cprman->regs + CM_PERIICTL, CM_GATE_BIT, + 0, &cprman->regs_lock); + + return of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, + &cprman->onecell); +} + +static const struct of_device_id bcm2835_clk_of_match[] = { + { .compatible = "brcm,bcm2835-cprman", }, + {} +}; +MODULE_DEVICE_TABLE(of, bcm2835_clk_of_match); + +static struct platform_driver bcm2835_clk_driver = { + .driver = { + .name = "bcm2835-clk", + .of_match_table = bcm2835_clk_of_match, + }, + .probe = bcm2835_clk_probe, +}; + +builtin_platform_driver(bcm2835_clk_driver); + +MODULE_AUTHOR("Eric Anholt <eric@anholt.net>"); +MODULE_DESCRIPTION("BCM2835 clock driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/clk/bcm/clk-cygnus.c b/kernel/drivers/clk/bcm/clk-cygnus.c new file mode 100644 index 000000000..3a228b6d4 --- /dev/null +++ b/kernel/drivers/clk/bcm/clk-cygnus.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2014 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/clkdev.h> +#include <linux/of_address.h> +#include <linux/delay.h> + +#include <dt-bindings/clock/bcm-cygnus.h> +#include "clk-iproc.h" + +#define REG_VAL(o, s, w) { .offset = o, .shift = s, .width = w, } + +#define AON_VAL(o, pw, ps, is) { .offset = o, .pwr_width = pw, \ + .pwr_shift = ps, .iso_shift = is } + +#define SW_CTRL_VAL(o, s) { .offset = o, .shift = s, } + +#define ASIU_DIV_VAL(o, es, hs, hw, ls, lw) \ + { .offset = o, .en_shift = es, .high_shift = hs, \ + .high_width = hw, .low_shift = ls, .low_width = lw } + +#define RESET_VAL(o, rs, prs) { .offset = o, .reset_shift = rs, \ + .p_reset_shift = prs } + +#define DF_VAL(o, kis, kiw, kps, kpw, kas, kaw) { .offset = o, .ki_shift = kis,\ + .ki_width = kiw, .kp_shift = kps, .kp_width = kpw, .ka_shift = kas, \ + .ka_width = kaw } + +#define VCO_CTRL_VAL(uo, lo) { .u_offset = uo, .l_offset = lo } + +#define ENABLE_VAL(o, es, hs, bs) { .offset = o, .enable_shift = es, \ + .hold_shift = hs, .bypass_shift = bs } + +#define ASIU_GATE_VAL(o, es) { .offset = o, .en_shift = es } + +static void __init cygnus_armpll_init(struct device_node *node) +{ + iproc_armpll_setup(node); +} +CLK_OF_DECLARE(cygnus_armpll, "brcm,cygnus-armpll", cygnus_armpll_init); + +static const struct iproc_pll_ctrl genpll = { + .flags = IPROC_CLK_AON | IPROC_CLK_PLL_HAS_NDIV_FRAC | + IPROC_CLK_PLL_NEEDS_SW_CFG, + .aon = AON_VAL(0x0, 2, 1, 0), + .reset = RESET_VAL(0x0, 11, 10), + .dig_filter = DF_VAL(0x0, 4, 3, 0, 4, 7, 3), + .sw_ctrl = SW_CTRL_VAL(0x10, 31), + .ndiv_int = REG_VAL(0x10, 20, 10), + .ndiv_frac = REG_VAL(0x10, 0, 20), + .pdiv = REG_VAL(0x14, 0, 4), + .vco_ctrl = VCO_CTRL_VAL(0x18, 0x1c), + .status = REG_VAL(0x28, 12, 1), +}; + +static const struct iproc_clk_ctrl genpll_clk[] = { + [BCM_CYGNUS_GENPLL_AXI21_CLK] = { + .channel = BCM_CYGNUS_GENPLL_AXI21_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x4, 6, 0, 12), + .mdiv = REG_VAL(0x20, 0, 8), + }, + [BCM_CYGNUS_GENPLL_250MHZ_CLK] = { + .channel = BCM_CYGNUS_GENPLL_250MHZ_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x4, 7, 1, 13), + .mdiv = REG_VAL(0x20, 10, 8), + }, + [BCM_CYGNUS_GENPLL_IHOST_SYS_CLK] = { + .channel = BCM_CYGNUS_GENPLL_IHOST_SYS_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x4, 8, 2, 14), + .mdiv = REG_VAL(0x20, 20, 8), + }, + [BCM_CYGNUS_GENPLL_ENET_SW_CLK] = { + .channel = BCM_CYGNUS_GENPLL_ENET_SW_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x4, 9, 3, 15), + .mdiv = REG_VAL(0x24, 0, 8), + }, + [BCM_CYGNUS_GENPLL_AUDIO_125_CLK] = { + .channel = BCM_CYGNUS_GENPLL_AUDIO_125_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x4, 10, 4, 16), + .mdiv = REG_VAL(0x24, 10, 8), + }, + [BCM_CYGNUS_GENPLL_CAN_CLK] = { + .channel = BCM_CYGNUS_GENPLL_CAN_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x4, 11, 5, 17), + .mdiv = REG_VAL(0x24, 20, 8), + }, +}; + +static void __init cygnus_genpll_clk_init(struct device_node *node) +{ + iproc_pll_clk_setup(node, &genpll, NULL, 0, genpll_clk, + ARRAY_SIZE(genpll_clk)); +} +CLK_OF_DECLARE(cygnus_genpll, "brcm,cygnus-genpll", cygnus_genpll_clk_init); + +static const struct iproc_pll_ctrl lcpll0 = { + .flags = IPROC_CLK_AON | IPROC_CLK_PLL_NEEDS_SW_CFG, + .aon = AON_VAL(0x0, 2, 5, 4), + .reset = RESET_VAL(0x0, 31, 30), + .dig_filter = DF_VAL(0x0, 27, 3, 23, 4, 19, 4), + .sw_ctrl = SW_CTRL_VAL(0x4, 31), + .ndiv_int = REG_VAL(0x4, 16, 10), + .pdiv = REG_VAL(0x4, 26, 4), + .vco_ctrl = VCO_CTRL_VAL(0x10, 0x14), + .status = REG_VAL(0x18, 12, 1), +}; + +static const struct iproc_clk_ctrl lcpll0_clk[] = { + [BCM_CYGNUS_LCPLL0_PCIE_PHY_REF_CLK] = { + .channel = BCM_CYGNUS_LCPLL0_PCIE_PHY_REF_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 7, 1, 13), + .mdiv = REG_VAL(0x8, 0, 8), + }, + [BCM_CYGNUS_LCPLL0_DDR_PHY_CLK] = { + .channel = BCM_CYGNUS_LCPLL0_DDR_PHY_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 8, 2, 14), + .mdiv = REG_VAL(0x8, 10, 8), + }, + [BCM_CYGNUS_LCPLL0_SDIO_CLK] = { + .channel = BCM_CYGNUS_LCPLL0_SDIO_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 9, 3, 15), + .mdiv = REG_VAL(0x8, 20, 8), + }, + [BCM_CYGNUS_LCPLL0_USB_PHY_REF_CLK] = { + .channel = BCM_CYGNUS_LCPLL0_USB_PHY_REF_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 10, 4, 16), + .mdiv = REG_VAL(0xc, 0, 8), + }, + [BCM_CYGNUS_LCPLL0_SMART_CARD_CLK] = { + .channel = BCM_CYGNUS_LCPLL0_SMART_CARD_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 11, 5, 17), + .mdiv = REG_VAL(0xc, 10, 8), + }, + [BCM_CYGNUS_LCPLL0_CH5_UNUSED] = { + .channel = BCM_CYGNUS_LCPLL0_CH5_UNUSED, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 12, 6, 18), + .mdiv = REG_VAL(0xc, 20, 8), + }, +}; + +static void __init cygnus_lcpll0_clk_init(struct device_node *node) +{ + iproc_pll_clk_setup(node, &lcpll0, NULL, 0, lcpll0_clk, + ARRAY_SIZE(lcpll0_clk)); +} +CLK_OF_DECLARE(cygnus_lcpll0, "brcm,cygnus-lcpll0", cygnus_lcpll0_clk_init); + +/* + * MIPI PLL VCO frequency parameter table + */ +static const struct iproc_pll_vco_param mipipll_vco_params[] = { + /* rate (Hz) ndiv_int ndiv_frac pdiv */ + { 750000000UL, 30, 0, 1 }, + { 1000000000UL, 40, 0, 1 }, + { 1350000000ul, 54, 0, 1 }, + { 2000000000UL, 80, 0, 1 }, + { 2100000000UL, 84, 0, 1 }, + { 2250000000UL, 90, 0, 1 }, + { 2500000000UL, 100, 0, 1 }, + { 2700000000UL, 54, 0, 0 }, + { 2975000000UL, 119, 0, 1 }, + { 3100000000UL, 124, 0, 1 }, + { 3150000000UL, 126, 0, 1 }, +}; + +static const struct iproc_pll_ctrl mipipll = { + .flags = IPROC_CLK_PLL_ASIU | IPROC_CLK_PLL_HAS_NDIV_FRAC | + IPROC_CLK_NEEDS_READ_BACK, + .aon = AON_VAL(0x0, 4, 17, 16), + .asiu = ASIU_GATE_VAL(0x0, 3), + .reset = RESET_VAL(0x0, 11, 10), + .dig_filter = DF_VAL(0x0, 4, 3, 0, 4, 7, 4), + .ndiv_int = REG_VAL(0x10, 20, 10), + .ndiv_frac = REG_VAL(0x10, 0, 20), + .pdiv = REG_VAL(0x14, 0, 4), + .vco_ctrl = VCO_CTRL_VAL(0x18, 0x1c), + .status = REG_VAL(0x28, 12, 1), +}; + +static const struct iproc_clk_ctrl mipipll_clk[] = { + [BCM_CYGNUS_MIPIPLL_CH0_UNUSED] = { + .channel = BCM_CYGNUS_MIPIPLL_CH0_UNUSED, + .flags = IPROC_CLK_NEEDS_READ_BACK, + .enable = ENABLE_VAL(0x4, 12, 6, 18), + .mdiv = REG_VAL(0x20, 0, 8), + }, + [BCM_CYGNUS_MIPIPLL_CH1_LCD] = { + .channel = BCM_CYGNUS_MIPIPLL_CH1_LCD, + .flags = IPROC_CLK_NEEDS_READ_BACK, + .enable = ENABLE_VAL(0x4, 13, 7, 19), + .mdiv = REG_VAL(0x20, 10, 8), + }, + [BCM_CYGNUS_MIPIPLL_CH2_V3D] = { + .channel = BCM_CYGNUS_MIPIPLL_CH2_V3D, + .flags = IPROC_CLK_NEEDS_READ_BACK, + .enable = ENABLE_VAL(0x4, 14, 8, 20), + .mdiv = REG_VAL(0x20, 20, 8), + }, + [BCM_CYGNUS_MIPIPLL_CH3_UNUSED] = { + .channel = BCM_CYGNUS_MIPIPLL_CH3_UNUSED, + .flags = IPROC_CLK_NEEDS_READ_BACK, + .enable = ENABLE_VAL(0x4, 15, 9, 21), + .mdiv = REG_VAL(0x24, 0, 8), + }, + [BCM_CYGNUS_MIPIPLL_CH4_UNUSED] = { + .channel = BCM_CYGNUS_MIPIPLL_CH4_UNUSED, + .flags = IPROC_CLK_NEEDS_READ_BACK, + .enable = ENABLE_VAL(0x4, 16, 10, 22), + .mdiv = REG_VAL(0x24, 10, 8), + }, + [BCM_CYGNUS_MIPIPLL_CH5_UNUSED] = { + .channel = BCM_CYGNUS_MIPIPLL_CH5_UNUSED, + .flags = IPROC_CLK_NEEDS_READ_BACK, + .enable = ENABLE_VAL(0x4, 17, 11, 23), + .mdiv = REG_VAL(0x24, 20, 8), + }, +}; + +static void __init cygnus_mipipll_clk_init(struct device_node *node) +{ + iproc_pll_clk_setup(node, &mipipll, mipipll_vco_params, + ARRAY_SIZE(mipipll_vco_params), mipipll_clk, + ARRAY_SIZE(mipipll_clk)); +} +CLK_OF_DECLARE(cygnus_mipipll, "brcm,cygnus-mipipll", cygnus_mipipll_clk_init); + +static const struct iproc_asiu_div asiu_div[] = { + [BCM_CYGNUS_ASIU_KEYPAD_CLK] = ASIU_DIV_VAL(0x0, 31, 16, 10, 0, 10), + [BCM_CYGNUS_ASIU_ADC_CLK] = ASIU_DIV_VAL(0x4, 31, 16, 10, 0, 10), + [BCM_CYGNUS_ASIU_PWM_CLK] = ASIU_DIV_VAL(0x8, 31, 16, 10, 0, 10), +}; + +static const struct iproc_asiu_gate asiu_gate[] = { + [BCM_CYGNUS_ASIU_KEYPAD_CLK] = ASIU_GATE_VAL(0x0, 7), + [BCM_CYGNUS_ASIU_ADC_CLK] = ASIU_GATE_VAL(0x0, 9), + [BCM_CYGNUS_ASIU_PWM_CLK] = ASIU_GATE_VAL(IPROC_CLK_INVALID_OFFSET, 0), +}; + +static void __init cygnus_asiu_init(struct device_node *node) +{ + iproc_asiu_setup(node, asiu_div, asiu_gate, ARRAY_SIZE(asiu_div)); +} +CLK_OF_DECLARE(cygnus_asiu_clk, "brcm,cygnus-asiu-clk", cygnus_asiu_init); diff --git a/kernel/drivers/clk/bcm/clk-iproc-armpll.c b/kernel/drivers/clk/bcm/clk-iproc-armpll.c new file mode 100644 index 000000000..a196ee28a --- /dev/null +++ b/kernel/drivers/clk/bcm/clk-iproc-armpll.c @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2014 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/clkdev.h> +#include <linux/of_address.h> + +#define IPROC_CLK_MAX_FREQ_POLICY 0x3 +#define IPROC_CLK_POLICY_FREQ_OFFSET 0x008 +#define IPROC_CLK_POLICY_FREQ_POLICY_FREQ_SHIFT 8 +#define IPROC_CLK_POLICY_FREQ_POLICY_FREQ_MASK 0x7 + +#define IPROC_CLK_PLLARMA_OFFSET 0xc00 +#define IPROC_CLK_PLLARMA_LOCK_SHIFT 28 +#define IPROC_CLK_PLLARMA_PDIV_SHIFT 24 +#define IPROC_CLK_PLLARMA_PDIV_MASK 0xf +#define IPROC_CLK_PLLARMA_NDIV_INT_SHIFT 8 +#define IPROC_CLK_PLLARMA_NDIV_INT_MASK 0x3ff + +#define IPROC_CLK_PLLARMB_OFFSET 0xc04 +#define IPROC_CLK_PLLARMB_NDIV_FRAC_MASK 0xfffff + +#define IPROC_CLK_PLLARMC_OFFSET 0xc08 +#define IPROC_CLK_PLLARMC_BYPCLK_EN_SHIFT 8 +#define IPROC_CLK_PLLARMC_MDIV_MASK 0xff + +#define IPROC_CLK_PLLARMCTL5_OFFSET 0xc20 +#define IPROC_CLK_PLLARMCTL5_H_MDIV_MASK 0xff + +#define IPROC_CLK_PLLARM_OFFSET_OFFSET 0xc24 +#define IPROC_CLK_PLLARM_SW_CTL_SHIFT 29 +#define IPROC_CLK_PLLARM_NDIV_INT_OFFSET_SHIFT 20 +#define IPROC_CLK_PLLARM_NDIV_INT_OFFSET_MASK 0xff +#define IPROC_CLK_PLLARM_NDIV_FRAC_OFFSET_MASK 0xfffff + +#define IPROC_CLK_ARM_DIV_OFFSET 0xe00 +#define IPROC_CLK_ARM_DIV_PLL_SELECT_OVERRIDE_SHIFT 4 +#define IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_MASK 0xf + +#define IPROC_CLK_POLICY_DBG_OFFSET 0xec0 +#define IPROC_CLK_POLICY_DBG_ACT_FREQ_SHIFT 12 +#define IPROC_CLK_POLICY_DBG_ACT_FREQ_MASK 0x7 + +enum iproc_arm_pll_fid { + ARM_PLL_FID_CRYSTAL_CLK = 0, + ARM_PLL_FID_SYS_CLK = 2, + ARM_PLL_FID_CH0_SLOW_CLK = 6, + ARM_PLL_FID_CH1_FAST_CLK = 7 +}; + +struct iproc_arm_pll { + struct clk_hw hw; + void __iomem *base; + unsigned long rate; +}; + +#define to_iproc_arm_pll(hw) container_of(hw, struct iproc_arm_pll, hw) + +static unsigned int __get_fid(struct iproc_arm_pll *pll) +{ + u32 val; + unsigned int policy, fid, active_fid; + + val = readl(pll->base + IPROC_CLK_ARM_DIV_OFFSET); + if (val & (1 << IPROC_CLK_ARM_DIV_PLL_SELECT_OVERRIDE_SHIFT)) + policy = val & IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_MASK; + else + policy = 0; + + /* something is seriously wrong */ + BUG_ON(policy > IPROC_CLK_MAX_FREQ_POLICY); + + val = readl(pll->base + IPROC_CLK_POLICY_FREQ_OFFSET); + fid = (val >> (IPROC_CLK_POLICY_FREQ_POLICY_FREQ_SHIFT * policy)) & + IPROC_CLK_POLICY_FREQ_POLICY_FREQ_MASK; + + val = readl(pll->base + IPROC_CLK_POLICY_DBG_OFFSET); + active_fid = IPROC_CLK_POLICY_DBG_ACT_FREQ_MASK & + (val >> IPROC_CLK_POLICY_DBG_ACT_FREQ_SHIFT); + if (fid != active_fid) { + pr_debug("%s: fid override %u->%u\n", __func__, fid, + active_fid); + fid = active_fid; + } + + pr_debug("%s: active fid: %u\n", __func__, fid); + + return fid; +} + +/* + * Determine the mdiv (post divider) based on the frequency ID being used. + * There are 4 sources that can be used to derive the output clock rate: + * - 25 MHz Crystal + * - System clock + * - PLL channel 0 (slow clock) + * - PLL channel 1 (fast clock) + */ +static int __get_mdiv(struct iproc_arm_pll *pll) +{ + unsigned int fid; + int mdiv; + u32 val; + + fid = __get_fid(pll); + + switch (fid) { + case ARM_PLL_FID_CRYSTAL_CLK: + case ARM_PLL_FID_SYS_CLK: + mdiv = 1; + break; + + case ARM_PLL_FID_CH0_SLOW_CLK: + val = readl(pll->base + IPROC_CLK_PLLARMC_OFFSET); + mdiv = val & IPROC_CLK_PLLARMC_MDIV_MASK; + if (mdiv == 0) + mdiv = 256; + break; + + case ARM_PLL_FID_CH1_FAST_CLK: + val = readl(pll->base + IPROC_CLK_PLLARMCTL5_OFFSET); + mdiv = val & IPROC_CLK_PLLARMCTL5_H_MDIV_MASK; + if (mdiv == 0) + mdiv = 256; + break; + + default: + mdiv = -EFAULT; + } + + return mdiv; +} + +static unsigned int __get_ndiv(struct iproc_arm_pll *pll) +{ + u32 val; + unsigned int ndiv_int, ndiv_frac, ndiv; + + val = readl(pll->base + IPROC_CLK_PLLARM_OFFSET_OFFSET); + if (val & (1 << IPROC_CLK_PLLARM_SW_CTL_SHIFT)) { + /* + * offset mode is active. Read the ndiv from the PLLARM OFFSET + * register + */ + ndiv_int = (val >> IPROC_CLK_PLLARM_NDIV_INT_OFFSET_SHIFT) & + IPROC_CLK_PLLARM_NDIV_INT_OFFSET_MASK; + if (ndiv_int == 0) + ndiv_int = 256; + + ndiv_frac = val & IPROC_CLK_PLLARM_NDIV_FRAC_OFFSET_MASK; + } else { + /* offset mode not active */ + val = readl(pll->base + IPROC_CLK_PLLARMA_OFFSET); + ndiv_int = (val >> IPROC_CLK_PLLARMA_NDIV_INT_SHIFT) & + IPROC_CLK_PLLARMA_NDIV_INT_MASK; + if (ndiv_int == 0) + ndiv_int = 1024; + + val = readl(pll->base + IPROC_CLK_PLLARMB_OFFSET); + ndiv_frac = val & IPROC_CLK_PLLARMB_NDIV_FRAC_MASK; + } + + ndiv = (ndiv_int << 20) | ndiv_frac; + + return ndiv; +} + +/* + * The output frequency of the ARM PLL is calculated based on the ARM PLL + * divider values: + * pdiv = ARM PLL pre-divider + * ndiv = ARM PLL multiplier + * mdiv = ARM PLL post divider + * + * The frequency is calculated by: + * ((ndiv * parent clock rate) / pdiv) / mdiv + */ +static unsigned long iproc_arm_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct iproc_arm_pll *pll = to_iproc_arm_pll(hw); + u32 val; + int mdiv; + u64 ndiv; + unsigned int pdiv; + + /* in bypass mode, use parent rate */ + val = readl(pll->base + IPROC_CLK_PLLARMC_OFFSET); + if (val & (1 << IPROC_CLK_PLLARMC_BYPCLK_EN_SHIFT)) { + pll->rate = parent_rate; + return pll->rate; + } + + /* PLL needs to be locked */ + val = readl(pll->base + IPROC_CLK_PLLARMA_OFFSET); + if (!(val & (1 << IPROC_CLK_PLLARMA_LOCK_SHIFT))) { + pll->rate = 0; + return 0; + } + + pdiv = (val >> IPROC_CLK_PLLARMA_PDIV_SHIFT) & + IPROC_CLK_PLLARMA_PDIV_MASK; + if (pdiv == 0) + pdiv = 16; + + ndiv = __get_ndiv(pll); + mdiv = __get_mdiv(pll); + if (mdiv <= 0) { + pll->rate = 0; + return 0; + } + pll->rate = (ndiv * parent_rate) >> 20; + pll->rate = (pll->rate / pdiv) / mdiv; + + pr_debug("%s: ARM PLL rate: %lu. parent rate: %lu\n", __func__, + pll->rate, parent_rate); + pr_debug("%s: ndiv_int: %u, pdiv: %u, mdiv: %d\n", __func__, + (unsigned int)(ndiv >> 20), pdiv, mdiv); + + return pll->rate; +} + +static const struct clk_ops iproc_arm_pll_ops = { + .recalc_rate = iproc_arm_pll_recalc_rate, +}; + +void __init iproc_armpll_setup(struct device_node *node) +{ + int ret; + struct clk *clk; + struct iproc_arm_pll *pll; + struct clk_init_data init; + const char *parent_name; + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (WARN_ON(!pll)) + return; + + pll->base = of_iomap(node, 0); + if (WARN_ON(!pll->base)) + goto err_free_pll; + + init.name = node->name; + init.ops = &iproc_arm_pll_ops; + init.flags = 0; + parent_name = of_clk_get_parent_name(node, 0); + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + pll->hw.init = &init; + + clk = clk_register(NULL, &pll->hw); + if (WARN_ON(IS_ERR(clk))) + goto err_iounmap; + + ret = of_clk_add_provider(node, of_clk_src_simple_get, clk); + if (WARN_ON(ret)) + goto err_clk_unregister; + + return; + +err_clk_unregister: + clk_unregister(clk); +err_iounmap: + iounmap(pll->base); +err_free_pll: + kfree(pll); +} diff --git a/kernel/drivers/clk/bcm/clk-iproc-asiu.c b/kernel/drivers/clk/bcm/clk-iproc-asiu.c new file mode 100644 index 000000000..f630e1bbd --- /dev/null +++ b/kernel/drivers/clk/bcm/clk-iproc-asiu.c @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2014 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/clkdev.h> +#include <linux/of_address.h> +#include <linux/delay.h> + +#include "clk-iproc.h" + +struct iproc_asiu; + +struct iproc_asiu_clk { + struct clk_hw hw; + const char *name; + struct iproc_asiu *asiu; + unsigned long rate; + struct iproc_asiu_div div; + struct iproc_asiu_gate gate; +}; + +struct iproc_asiu { + void __iomem *div_base; + void __iomem *gate_base; + + struct clk_onecell_data clk_data; + struct iproc_asiu_clk *clks; +}; + +#define to_asiu_clk(hw) container_of(hw, struct iproc_asiu_clk, hw) + +static int iproc_asiu_clk_enable(struct clk_hw *hw) +{ + struct iproc_asiu_clk *clk = to_asiu_clk(hw); + struct iproc_asiu *asiu = clk->asiu; + u32 val; + + /* some clocks at the ASIU level are always enabled */ + if (clk->gate.offset == IPROC_CLK_INVALID_OFFSET) + return 0; + + val = readl(asiu->gate_base + clk->gate.offset); + val |= (1 << clk->gate.en_shift); + writel(val, asiu->gate_base + clk->gate.offset); + + return 0; +} + +static void iproc_asiu_clk_disable(struct clk_hw *hw) +{ + struct iproc_asiu_clk *clk = to_asiu_clk(hw); + struct iproc_asiu *asiu = clk->asiu; + u32 val; + + /* some clocks at the ASIU level are always enabled */ + if (clk->gate.offset == IPROC_CLK_INVALID_OFFSET) + return; + + val = readl(asiu->gate_base + clk->gate.offset); + val &= ~(1 << clk->gate.en_shift); + writel(val, asiu->gate_base + clk->gate.offset); +} + +static unsigned long iproc_asiu_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct iproc_asiu_clk *clk = to_asiu_clk(hw); + struct iproc_asiu *asiu = clk->asiu; + u32 val; + unsigned int div_h, div_l; + + if (parent_rate == 0) { + clk->rate = 0; + return 0; + } + + /* if clock divisor is not enabled, simply return parent rate */ + val = readl(asiu->div_base + clk->div.offset); + if ((val & (1 << clk->div.en_shift)) == 0) { + clk->rate = parent_rate; + return parent_rate; + } + + /* clock rate = parent rate / (high_div + 1) + (low_div + 1) */ + div_h = (val >> clk->div.high_shift) & bit_mask(clk->div.high_width); + div_h++; + div_l = (val >> clk->div.low_shift) & bit_mask(clk->div.low_width); + div_l++; + + clk->rate = parent_rate / (div_h + div_l); + pr_debug("%s: rate: %lu. parent rate: %lu div_h: %u div_l: %u\n", + __func__, clk->rate, parent_rate, div_h, div_l); + + return clk->rate; +} + +static long iproc_asiu_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned int div; + + if (rate == 0 || *parent_rate == 0) + return -EINVAL; + + if (rate == *parent_rate) + return *parent_rate; + + div = DIV_ROUND_UP(*parent_rate, rate); + if (div < 2) + return *parent_rate; + + return *parent_rate / div; +} + +static int iproc_asiu_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct iproc_asiu_clk *clk = to_asiu_clk(hw); + struct iproc_asiu *asiu = clk->asiu; + unsigned int div, div_h, div_l; + u32 val; + + if (rate == 0 || parent_rate == 0) + return -EINVAL; + + /* simply disable the divisor if one wants the same rate as parent */ + if (rate == parent_rate) { + val = readl(asiu->div_base + clk->div.offset); + val &= ~(1 << clk->div.en_shift); + writel(val, asiu->div_base + clk->div.offset); + return 0; + } + + div = DIV_ROUND_UP(parent_rate, rate); + if (div < 2) + return -EINVAL; + + div_h = div_l = div >> 1; + div_h--; + div_l--; + + val = readl(asiu->div_base + clk->div.offset); + val |= 1 << clk->div.en_shift; + if (div_h) { + val &= ~(bit_mask(clk->div.high_width) + << clk->div.high_shift); + val |= div_h << clk->div.high_shift; + } else { + val &= ~(bit_mask(clk->div.high_width) + << clk->div.high_shift); + } + if (div_l) { + val &= ~(bit_mask(clk->div.low_width) << clk->div.low_shift); + val |= div_l << clk->div.low_shift; + } else { + val &= ~(bit_mask(clk->div.low_width) << clk->div.low_shift); + } + writel(val, asiu->div_base + clk->div.offset); + + return 0; +} + +static const struct clk_ops iproc_asiu_ops = { + .enable = iproc_asiu_clk_enable, + .disable = iproc_asiu_clk_disable, + .recalc_rate = iproc_asiu_clk_recalc_rate, + .round_rate = iproc_asiu_clk_round_rate, + .set_rate = iproc_asiu_clk_set_rate, +}; + +void __init iproc_asiu_setup(struct device_node *node, + const struct iproc_asiu_div *div, + const struct iproc_asiu_gate *gate, + unsigned int num_clks) +{ + int i, ret; + struct iproc_asiu *asiu; + + if (WARN_ON(!gate || !div)) + return; + + asiu = kzalloc(sizeof(*asiu), GFP_KERNEL); + if (WARN_ON(!asiu)) + return; + + asiu->clk_data.clk_num = num_clks; + asiu->clk_data.clks = kcalloc(num_clks, sizeof(*asiu->clk_data.clks), + GFP_KERNEL); + if (WARN_ON(!asiu->clk_data.clks)) + goto err_clks; + + asiu->clks = kcalloc(num_clks, sizeof(*asiu->clks), GFP_KERNEL); + if (WARN_ON(!asiu->clks)) + goto err_asiu_clks; + + asiu->div_base = of_iomap(node, 0); + if (WARN_ON(!asiu->div_base)) + goto err_iomap_div; + + asiu->gate_base = of_iomap(node, 1); + if (WARN_ON(!asiu->gate_base)) + goto err_iomap_gate; + + for (i = 0; i < num_clks; i++) { + struct clk_init_data init; + struct clk *clk; + const char *parent_name; + struct iproc_asiu_clk *asiu_clk; + const char *clk_name; + + ret = of_property_read_string_index(node, "clock-output-names", + i, &clk_name); + if (WARN_ON(ret)) + goto err_clk_register; + + asiu_clk = &asiu->clks[i]; + asiu_clk->name = clk_name; + asiu_clk->asiu = asiu; + asiu_clk->div = div[i]; + asiu_clk->gate = gate[i]; + init.name = clk_name; + init.ops = &iproc_asiu_ops; + init.flags = 0; + parent_name = of_clk_get_parent_name(node, 0); + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + asiu_clk->hw.init = &init; + + clk = clk_register(NULL, &asiu_clk->hw); + if (WARN_ON(IS_ERR(clk))) + goto err_clk_register; + asiu->clk_data.clks[i] = clk; + } + + ret = of_clk_add_provider(node, of_clk_src_onecell_get, + &asiu->clk_data); + if (WARN_ON(ret)) + goto err_clk_register; + + return; + +err_clk_register: + for (i = 0; i < num_clks; i++) + clk_unregister(asiu->clk_data.clks[i]); + iounmap(asiu->gate_base); + +err_iomap_gate: + iounmap(asiu->div_base); + +err_iomap_div: + kfree(asiu->clks); + +err_asiu_clks: + kfree(asiu->clk_data.clks); + +err_clks: + kfree(asiu); +} diff --git a/kernel/drivers/clk/bcm/clk-iproc-pll.c b/kernel/drivers/clk/bcm/clk-iproc-pll.c new file mode 100644 index 000000000..afd5891ac --- /dev/null +++ b/kernel/drivers/clk/bcm/clk-iproc-pll.c @@ -0,0 +1,735 @@ +/* + * Copyright (C) 2014 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/clkdev.h> +#include <linux/of_address.h> +#include <linux/delay.h> + +#include "clk-iproc.h" + +#define PLL_VCO_HIGH_SHIFT 19 +#define PLL_VCO_LOW_SHIFT 30 + +/* number of delay loops waiting for PLL to lock */ +#define LOCK_DELAY 100 + +/* number of VCO frequency bands */ +#define NUM_FREQ_BANDS 8 + +#define NUM_KP_BANDS 3 +enum kp_band { + KP_BAND_MID = 0, + KP_BAND_HIGH, + KP_BAND_HIGH_HIGH +}; + +static const unsigned int kp_table[NUM_KP_BANDS][NUM_FREQ_BANDS] = { + { 5, 6, 6, 7, 7, 8, 9, 10 }, + { 4, 4, 5, 5, 6, 7, 8, 9 }, + { 4, 5, 5, 6, 7, 8, 9, 10 }, +}; + +static const unsigned long ref_freq_table[NUM_FREQ_BANDS][2] = { + { 10000000, 12500000 }, + { 12500000, 15000000 }, + { 15000000, 20000000 }, + { 20000000, 25000000 }, + { 25000000, 50000000 }, + { 50000000, 75000000 }, + { 75000000, 100000000 }, + { 100000000, 125000000 }, +}; + +enum vco_freq_range { + VCO_LOW = 700000000U, + VCO_MID = 1200000000U, + VCO_HIGH = 2200000000U, + VCO_HIGH_HIGH = 3100000000U, + VCO_MAX = 4000000000U, +}; + +struct iproc_pll; + +struct iproc_clk { + struct clk_hw hw; + const char *name; + struct iproc_pll *pll; + unsigned long rate; + const struct iproc_clk_ctrl *ctrl; +}; + +struct iproc_pll { + void __iomem *status_base; + void __iomem *control_base; + void __iomem *pwr_base; + void __iomem *asiu_base; + + const struct iproc_pll_ctrl *ctrl; + const struct iproc_pll_vco_param *vco_param; + unsigned int num_vco_entries; + + struct clk_onecell_data clk_data; + struct iproc_clk *clks; +}; + +#define to_iproc_clk(hw) container_of(hw, struct iproc_clk, hw) + +/* + * Based on the target frequency, find a match from the VCO frequency parameter + * table and return its index + */ +static int pll_get_rate_index(struct iproc_pll *pll, unsigned int target_rate) +{ + int i; + + for (i = 0; i < pll->num_vco_entries; i++) + if (target_rate == pll->vco_param[i].rate) + break; + + if (i >= pll->num_vco_entries) + return -EINVAL; + + return i; +} + +static int get_kp(unsigned long ref_freq, enum kp_band kp_index) +{ + int i; + + if (ref_freq < ref_freq_table[0][0]) + return -EINVAL; + + for (i = 0; i < NUM_FREQ_BANDS; i++) { + if (ref_freq >= ref_freq_table[i][0] && + ref_freq < ref_freq_table[i][1]) + return kp_table[kp_index][i]; + } + return -EINVAL; +} + +static int pll_wait_for_lock(struct iproc_pll *pll) +{ + int i; + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + + for (i = 0; i < LOCK_DELAY; i++) { + u32 val = readl(pll->status_base + ctrl->status.offset); + + if (val & (1 << ctrl->status.shift)) + return 0; + udelay(10); + } + + return -EIO; +} + +static void iproc_pll_write(const struct iproc_pll *pll, void __iomem *base, + const u32 offset, u32 val) +{ + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + + writel(val, base + offset); + + if (unlikely(ctrl->flags & IPROC_CLK_NEEDS_READ_BACK && + (base == pll->status_base || base == pll->control_base))) + val = readl(base + offset); +} + +static void __pll_disable(struct iproc_pll *pll) +{ + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + u32 val; + + if (ctrl->flags & IPROC_CLK_PLL_ASIU) { + val = readl(pll->asiu_base + ctrl->asiu.offset); + val &= ~(1 << ctrl->asiu.en_shift); + iproc_pll_write(pll, pll->asiu_base, ctrl->asiu.offset, val); + } + + if (ctrl->flags & IPROC_CLK_EMBED_PWRCTRL) { + val = readl(pll->control_base + ctrl->aon.offset); + val |= bit_mask(ctrl->aon.pwr_width) << ctrl->aon.pwr_shift; + iproc_pll_write(pll, pll->control_base, ctrl->aon.offset, val); + } + + if (pll->pwr_base) { + /* latch input value so core power can be shut down */ + val = readl(pll->pwr_base + ctrl->aon.offset); + val |= 1 << ctrl->aon.iso_shift; + iproc_pll_write(pll, pll->pwr_base, ctrl->aon.offset, val); + + /* power down the core */ + val &= ~(bit_mask(ctrl->aon.pwr_width) << ctrl->aon.pwr_shift); + iproc_pll_write(pll, pll->pwr_base, ctrl->aon.offset, val); + } +} + +static int __pll_enable(struct iproc_pll *pll) +{ + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + u32 val; + + if (ctrl->flags & IPROC_CLK_EMBED_PWRCTRL) { + val = readl(pll->control_base + ctrl->aon.offset); + val &= ~(bit_mask(ctrl->aon.pwr_width) << ctrl->aon.pwr_shift); + iproc_pll_write(pll, pll->control_base, ctrl->aon.offset, val); + } + + if (pll->pwr_base) { + /* power up the PLL and make sure it's not latched */ + val = readl(pll->pwr_base + ctrl->aon.offset); + val |= bit_mask(ctrl->aon.pwr_width) << ctrl->aon.pwr_shift; + val &= ~(1 << ctrl->aon.iso_shift); + iproc_pll_write(pll, pll->pwr_base, ctrl->aon.offset, val); + } + + /* certain PLLs also need to be ungated from the ASIU top level */ + if (ctrl->flags & IPROC_CLK_PLL_ASIU) { + val = readl(pll->asiu_base + ctrl->asiu.offset); + val |= (1 << ctrl->asiu.en_shift); + iproc_pll_write(pll, pll->asiu_base, ctrl->asiu.offset, val); + } + + return 0; +} + +static void __pll_put_in_reset(struct iproc_pll *pll) +{ + u32 val; + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + const struct iproc_pll_reset_ctrl *reset = &ctrl->reset; + + val = readl(pll->control_base + reset->offset); + val &= ~(1 << reset->reset_shift | 1 << reset->p_reset_shift); + iproc_pll_write(pll, pll->control_base, reset->offset, val); +} + +static void __pll_bring_out_reset(struct iproc_pll *pll, unsigned int kp, + unsigned int ka, unsigned int ki) +{ + u32 val; + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + const struct iproc_pll_reset_ctrl *reset = &ctrl->reset; + const struct iproc_pll_dig_filter_ctrl *dig_filter = &ctrl->dig_filter; + + val = readl(pll->control_base + dig_filter->offset); + val &= ~(bit_mask(dig_filter->ki_width) << dig_filter->ki_shift | + bit_mask(dig_filter->kp_width) << dig_filter->kp_shift | + bit_mask(dig_filter->ka_width) << dig_filter->ka_shift); + val |= ki << dig_filter->ki_shift | kp << dig_filter->kp_shift | + ka << dig_filter->ka_shift; + iproc_pll_write(pll, pll->control_base, dig_filter->offset, val); + + val = readl(pll->control_base + reset->offset); + val |= 1 << reset->reset_shift | 1 << reset->p_reset_shift; + iproc_pll_write(pll, pll->control_base, reset->offset, val); +} + +static int pll_set_rate(struct iproc_clk *clk, unsigned int rate_index, + unsigned long parent_rate) +{ + struct iproc_pll *pll = clk->pll; + const struct iproc_pll_vco_param *vco = &pll->vco_param[rate_index]; + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + int ka = 0, ki, kp, ret; + unsigned long rate = vco->rate; + u32 val; + enum kp_band kp_index; + unsigned long ref_freq; + + /* + * reference frequency = parent frequency / PDIV + * If PDIV = 0, then it becomes a multiplier (x2) + */ + if (vco->pdiv == 0) + ref_freq = parent_rate * 2; + else + ref_freq = parent_rate / vco->pdiv; + + /* determine Ki and Kp index based on target VCO frequency */ + if (rate >= VCO_LOW && rate < VCO_HIGH) { + ki = 4; + kp_index = KP_BAND_MID; + } else if (rate >= VCO_HIGH && rate && rate < VCO_HIGH_HIGH) { + ki = 3; + kp_index = KP_BAND_HIGH; + } else if (rate >= VCO_HIGH_HIGH && rate < VCO_MAX) { + ki = 3; + kp_index = KP_BAND_HIGH_HIGH; + } else { + pr_err("%s: pll: %s has invalid rate: %lu\n", __func__, + clk->name, rate); + return -EINVAL; + } + + kp = get_kp(ref_freq, kp_index); + if (kp < 0) { + pr_err("%s: pll: %s has invalid kp\n", __func__, clk->name); + return kp; + } + + ret = __pll_enable(pll); + if (ret) { + pr_err("%s: pll: %s fails to enable\n", __func__, clk->name); + return ret; + } + + /* put PLL in reset */ + __pll_put_in_reset(pll); + + iproc_pll_write(pll, pll->control_base, ctrl->vco_ctrl.u_offset, 0); + + val = readl(pll->control_base + ctrl->vco_ctrl.l_offset); + + if (rate >= VCO_LOW && rate < VCO_MID) + val |= (1 << PLL_VCO_LOW_SHIFT); + + if (rate < VCO_HIGH) + val &= ~(1 << PLL_VCO_HIGH_SHIFT); + else + val |= (1 << PLL_VCO_HIGH_SHIFT); + + iproc_pll_write(pll, pll->control_base, ctrl->vco_ctrl.l_offset, val); + + /* program integer part of NDIV */ + val = readl(pll->control_base + ctrl->ndiv_int.offset); + val &= ~(bit_mask(ctrl->ndiv_int.width) << ctrl->ndiv_int.shift); + val |= vco->ndiv_int << ctrl->ndiv_int.shift; + iproc_pll_write(pll, pll->control_base, ctrl->ndiv_int.offset, val); + + /* program fractional part of NDIV */ + if (ctrl->flags & IPROC_CLK_PLL_HAS_NDIV_FRAC) { + val = readl(pll->control_base + ctrl->ndiv_frac.offset); + val &= ~(bit_mask(ctrl->ndiv_frac.width) << + ctrl->ndiv_frac.shift); + val |= vco->ndiv_frac << ctrl->ndiv_frac.shift; + iproc_pll_write(pll, pll->control_base, ctrl->ndiv_frac.offset, + val); + } + + /* program PDIV */ + val = readl(pll->control_base + ctrl->pdiv.offset); + val &= ~(bit_mask(ctrl->pdiv.width) << ctrl->pdiv.shift); + val |= vco->pdiv << ctrl->pdiv.shift; + iproc_pll_write(pll, pll->control_base, ctrl->pdiv.offset, val); + + __pll_bring_out_reset(pll, kp, ka, ki); + + ret = pll_wait_for_lock(pll); + if (ret < 0) { + pr_err("%s: pll: %s failed to lock\n", __func__, clk->name); + return ret; + } + + return 0; +} + +static int iproc_pll_enable(struct clk_hw *hw) +{ + struct iproc_clk *clk = to_iproc_clk(hw); + struct iproc_pll *pll = clk->pll; + + return __pll_enable(pll); +} + +static void iproc_pll_disable(struct clk_hw *hw) +{ + struct iproc_clk *clk = to_iproc_clk(hw); + struct iproc_pll *pll = clk->pll; + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + + if (ctrl->flags & IPROC_CLK_AON) + return; + + __pll_disable(pll); +} + +static unsigned long iproc_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct iproc_clk *clk = to_iproc_clk(hw); + struct iproc_pll *pll = clk->pll; + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + u32 val; + u64 ndiv, ndiv_int, ndiv_frac; + unsigned int pdiv; + + if (parent_rate == 0) + return 0; + + /* PLL needs to be locked */ + val = readl(pll->status_base + ctrl->status.offset); + if ((val & (1 << ctrl->status.shift)) == 0) { + clk->rate = 0; + return 0; + } + + /* + * PLL output frequency = + * + * ((ndiv_int + ndiv_frac / 2^20) * (parent clock rate / pdiv) + */ + val = readl(pll->control_base + ctrl->ndiv_int.offset); + ndiv_int = (val >> ctrl->ndiv_int.shift) & + bit_mask(ctrl->ndiv_int.width); + ndiv = ndiv_int << 20; + + if (ctrl->flags & IPROC_CLK_PLL_HAS_NDIV_FRAC) { + val = readl(pll->control_base + ctrl->ndiv_frac.offset); + ndiv_frac = (val >> ctrl->ndiv_frac.shift) & + bit_mask(ctrl->ndiv_frac.width); + ndiv += ndiv_frac; + } + + val = readl(pll->control_base + ctrl->pdiv.offset); + pdiv = (val >> ctrl->pdiv.shift) & bit_mask(ctrl->pdiv.width); + + clk->rate = (ndiv * parent_rate) >> 20; + + if (pdiv == 0) + clk->rate *= 2; + else + clk->rate /= pdiv; + + return clk->rate; +} + +static long iproc_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned i; + struct iproc_clk *clk = to_iproc_clk(hw); + struct iproc_pll *pll = clk->pll; + + if (rate == 0 || *parent_rate == 0 || !pll->vco_param) + return -EINVAL; + + for (i = 0; i < pll->num_vco_entries; i++) { + if (rate <= pll->vco_param[i].rate) + break; + } + + if (i == pll->num_vco_entries) + i--; + + return pll->vco_param[i].rate; +} + +static int iproc_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct iproc_clk *clk = to_iproc_clk(hw); + struct iproc_pll *pll = clk->pll; + int rate_index, ret; + + rate_index = pll_get_rate_index(pll, rate); + if (rate_index < 0) + return rate_index; + + ret = pll_set_rate(clk, rate_index, parent_rate); + return ret; +} + +static const struct clk_ops iproc_pll_ops = { + .enable = iproc_pll_enable, + .disable = iproc_pll_disable, + .recalc_rate = iproc_pll_recalc_rate, + .round_rate = iproc_pll_round_rate, + .set_rate = iproc_pll_set_rate, +}; + +static int iproc_clk_enable(struct clk_hw *hw) +{ + struct iproc_clk *clk = to_iproc_clk(hw); + const struct iproc_clk_ctrl *ctrl = clk->ctrl; + struct iproc_pll *pll = clk->pll; + u32 val; + + /* channel enable is active low */ + val = readl(pll->control_base + ctrl->enable.offset); + val &= ~(1 << ctrl->enable.enable_shift); + iproc_pll_write(pll, pll->control_base, ctrl->enable.offset, val); + + /* also make sure channel is not held */ + val = readl(pll->control_base + ctrl->enable.offset); + val &= ~(1 << ctrl->enable.hold_shift); + iproc_pll_write(pll, pll->control_base, ctrl->enable.offset, val); + + return 0; +} + +static void iproc_clk_disable(struct clk_hw *hw) +{ + struct iproc_clk *clk = to_iproc_clk(hw); + const struct iproc_clk_ctrl *ctrl = clk->ctrl; + struct iproc_pll *pll = clk->pll; + u32 val; + + if (ctrl->flags & IPROC_CLK_AON) + return; + + val = readl(pll->control_base + ctrl->enable.offset); + val |= 1 << ctrl->enable.enable_shift; + iproc_pll_write(pll, pll->control_base, ctrl->enable.offset, val); +} + +static unsigned long iproc_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct iproc_clk *clk = to_iproc_clk(hw); + const struct iproc_clk_ctrl *ctrl = clk->ctrl; + struct iproc_pll *pll = clk->pll; + u32 val; + unsigned int mdiv; + + if (parent_rate == 0) + return 0; + + val = readl(pll->control_base + ctrl->mdiv.offset); + mdiv = (val >> ctrl->mdiv.shift) & bit_mask(ctrl->mdiv.width); + if (mdiv == 0) + mdiv = 256; + + clk->rate = parent_rate / mdiv; + + return clk->rate; +} + +static long iproc_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned int div; + + if (rate == 0 || *parent_rate == 0) + return -EINVAL; + + if (rate == *parent_rate) + return *parent_rate; + + div = DIV_ROUND_UP(*parent_rate, rate); + if (div < 2) + return *parent_rate; + + if (div > 256) + div = 256; + + return *parent_rate / div; +} + +static int iproc_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct iproc_clk *clk = to_iproc_clk(hw); + const struct iproc_clk_ctrl *ctrl = clk->ctrl; + struct iproc_pll *pll = clk->pll; + u32 val; + unsigned int div; + + if (rate == 0 || parent_rate == 0) + return -EINVAL; + + div = DIV_ROUND_UP(parent_rate, rate); + if (div > 256) + return -EINVAL; + + val = readl(pll->control_base + ctrl->mdiv.offset); + if (div == 256) { + val &= ~(bit_mask(ctrl->mdiv.width) << ctrl->mdiv.shift); + } else { + val &= ~(bit_mask(ctrl->mdiv.width) << ctrl->mdiv.shift); + val |= div << ctrl->mdiv.shift; + } + iproc_pll_write(pll, pll->control_base, ctrl->mdiv.offset, val); + clk->rate = parent_rate / div; + + return 0; +} + +static const struct clk_ops iproc_clk_ops = { + .enable = iproc_clk_enable, + .disable = iproc_clk_disable, + .recalc_rate = iproc_clk_recalc_rate, + .round_rate = iproc_clk_round_rate, + .set_rate = iproc_clk_set_rate, +}; + +/** + * Some PLLs require the PLL SW override bit to be set before changes can be + * applied to the PLL + */ +static void iproc_pll_sw_cfg(struct iproc_pll *pll) +{ + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + + if (ctrl->flags & IPROC_CLK_PLL_NEEDS_SW_CFG) { + u32 val; + + val = readl(pll->control_base + ctrl->sw_ctrl.offset); + val |= BIT(ctrl->sw_ctrl.shift); + iproc_pll_write(pll, pll->control_base, ctrl->sw_ctrl.offset, + val); + } +} + +void __init iproc_pll_clk_setup(struct device_node *node, + const struct iproc_pll_ctrl *pll_ctrl, + const struct iproc_pll_vco_param *vco, + unsigned int num_vco_entries, + const struct iproc_clk_ctrl *clk_ctrl, + unsigned int num_clks) +{ + int i, ret; + struct clk *clk; + struct iproc_pll *pll; + struct iproc_clk *iclk; + struct clk_init_data init; + const char *parent_name; + + if (WARN_ON(!pll_ctrl) || WARN_ON(!clk_ctrl)) + return; + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (WARN_ON(!pll)) + return; + + pll->clk_data.clk_num = num_clks; + pll->clk_data.clks = kcalloc(num_clks, sizeof(*pll->clk_data.clks), + GFP_KERNEL); + if (WARN_ON(!pll->clk_data.clks)) + goto err_clk_data; + + pll->clks = kcalloc(num_clks, sizeof(*pll->clks), GFP_KERNEL); + if (WARN_ON(!pll->clks)) + goto err_clks; + + pll->control_base = of_iomap(node, 0); + if (WARN_ON(!pll->control_base)) + goto err_pll_iomap; + + /* Some SoCs do not require the pwr_base, thus failing is not fatal */ + pll->pwr_base = of_iomap(node, 1); + + /* some PLLs require gating control at the top ASIU level */ + if (pll_ctrl->flags & IPROC_CLK_PLL_ASIU) { + pll->asiu_base = of_iomap(node, 2); + if (WARN_ON(!pll->asiu_base)) + goto err_asiu_iomap; + } + + if (pll_ctrl->flags & IPROC_CLK_PLL_SPLIT_STAT_CTRL) { + /* Some SoCs have a split status/control. If this does not + * exist, assume they are unified. + */ + pll->status_base = of_iomap(node, 2); + if (!pll->status_base) + goto err_status_iomap; + } else + pll->status_base = pll->control_base; + + /* initialize and register the PLL itself */ + pll->ctrl = pll_ctrl; + + iclk = &pll->clks[0]; + iclk->pll = pll; + iclk->name = node->name; + + init.name = node->name; + init.ops = &iproc_pll_ops; + init.flags = 0; + parent_name = of_clk_get_parent_name(node, 0); + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + iclk->hw.init = &init; + + if (vco) { + pll->num_vco_entries = num_vco_entries; + pll->vco_param = vco; + } + + iproc_pll_sw_cfg(pll); + + clk = clk_register(NULL, &iclk->hw); + if (WARN_ON(IS_ERR(clk))) + goto err_pll_register; + + pll->clk_data.clks[0] = clk; + + /* now initialize and register all leaf clocks */ + for (i = 1; i < num_clks; i++) { + const char *clk_name; + + memset(&init, 0, sizeof(init)); + parent_name = node->name; + + ret = of_property_read_string_index(node, "clock-output-names", + i, &clk_name); + if (WARN_ON(ret)) + goto err_clk_register; + + iclk = &pll->clks[i]; + iclk->name = clk_name; + iclk->pll = pll; + iclk->ctrl = &clk_ctrl[i]; + + init.name = clk_name; + init.ops = &iproc_clk_ops; + init.flags = 0; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + iclk->hw.init = &init; + + clk = clk_register(NULL, &iclk->hw); + if (WARN_ON(IS_ERR(clk))) + goto err_clk_register; + + pll->clk_data.clks[i] = clk; + } + + ret = of_clk_add_provider(node, of_clk_src_onecell_get, &pll->clk_data); + if (WARN_ON(ret)) + goto err_clk_register; + + return; + +err_clk_register: + for (i = 0; i < num_clks; i++) + clk_unregister(pll->clk_data.clks[i]); + +err_pll_register: + if (pll->status_base != pll->control_base) + iounmap(pll->status_base); + +err_status_iomap: + if (pll->asiu_base) + iounmap(pll->asiu_base); + +err_asiu_iomap: + if (pll->pwr_base) + iounmap(pll->pwr_base); + + iounmap(pll->control_base); + +err_pll_iomap: + kfree(pll->clks); + +err_clks: + kfree(pll->clk_data.clks); + +err_clk_data: + kfree(pll); +} diff --git a/kernel/drivers/clk/bcm/clk-iproc.h b/kernel/drivers/clk/bcm/clk-iproc.h new file mode 100644 index 000000000..8988de70a --- /dev/null +++ b/kernel/drivers/clk/bcm/clk-iproc.h @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2014 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CLK_IPROC_H +#define _CLK_IPROC_H + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/of.h> +#include <linux/clk-provider.h> + +#define IPROC_CLK_NAME_LEN 25 +#define IPROC_CLK_INVALID_OFFSET 0xffffffff +#define bit_mask(width) ((1 << (width)) - 1) + +/* clocks that should not be disabled at runtime */ +#define IPROC_CLK_AON BIT(0) + +/* PLL that requires gating through ASIU */ +#define IPROC_CLK_PLL_ASIU BIT(1) + +/* PLL that has fractional part of the NDIV */ +#define IPROC_CLK_PLL_HAS_NDIV_FRAC BIT(2) + +/* + * Some of the iProc PLL/clocks may have an ASIC bug that requires read back + * of the same register following the write to flush the write transaction into + * the intended register + */ +#define IPROC_CLK_NEEDS_READ_BACK BIT(3) + +/* + * Some PLLs require the PLL SW override bit to be set before changes can be + * applied to the PLL + */ +#define IPROC_CLK_PLL_NEEDS_SW_CFG BIT(4) + +/* + * Some PLLs use a different way to control clock power, via the PWRDWN bit in + * the PLL control register + */ +#define IPROC_CLK_EMBED_PWRCTRL BIT(5) + +/* + * Some PLLs have separate registers for Status and Control. Identify this to + * let the driver know if additional registers need to be used + */ +#define IPROC_CLK_PLL_SPLIT_STAT_CTRL BIT(6) + +/* + * Parameters for VCO frequency configuration + * + * VCO frequency = + * ((ndiv_int + ndiv_frac / 2^20) * (ref freqeuncy / pdiv) + */ +struct iproc_pll_vco_param { + unsigned long rate; + unsigned int ndiv_int; + unsigned int ndiv_frac; + unsigned int pdiv; +}; + +struct iproc_clk_reg_op { + unsigned int offset; + unsigned int shift; + unsigned int width; +}; + +/* + * Clock gating control at the top ASIU level + */ +struct iproc_asiu_gate { + unsigned int offset; + unsigned int en_shift; +}; + +/* + * Control of powering on/off of a PLL + * + * Before powering off a PLL, input isolation (ISO) needs to be enabled + */ +struct iproc_pll_aon_pwr_ctrl { + unsigned int offset; + unsigned int pwr_width; + unsigned int pwr_shift; + unsigned int iso_shift; +}; + +/* + * Control of the PLL reset + */ +struct iproc_pll_reset_ctrl { + unsigned int offset; + unsigned int reset_shift; + unsigned int p_reset_shift; +}; + +/* + * Control of the Ki, Kp, and Ka parameters + */ +struct iproc_pll_dig_filter_ctrl { + unsigned int offset; + unsigned int ki_shift; + unsigned int ki_width; + unsigned int kp_shift; + unsigned int kp_width; + unsigned int ka_shift; + unsigned int ka_width; +}; + +/* + * To enable SW control of the PLL + */ +struct iproc_pll_sw_ctrl { + unsigned int offset; + unsigned int shift; +}; + +struct iproc_pll_vco_ctrl { + unsigned int u_offset; + unsigned int l_offset; +}; + +/* + * Main PLL control parameters + */ +struct iproc_pll_ctrl { + unsigned long flags; + struct iproc_pll_aon_pwr_ctrl aon; + struct iproc_asiu_gate asiu; + struct iproc_pll_reset_ctrl reset; + struct iproc_pll_dig_filter_ctrl dig_filter; + struct iproc_pll_sw_ctrl sw_ctrl; + struct iproc_clk_reg_op ndiv_int; + struct iproc_clk_reg_op ndiv_frac; + struct iproc_clk_reg_op pdiv; + struct iproc_pll_vco_ctrl vco_ctrl; + struct iproc_clk_reg_op status; +}; + +/* + * Controls enabling/disabling a PLL derived clock + */ +struct iproc_clk_enable_ctrl { + unsigned int offset; + unsigned int enable_shift; + unsigned int hold_shift; + unsigned int bypass_shift; +}; + +/* + * Main clock control parameters for clocks derived from the PLLs + */ +struct iproc_clk_ctrl { + unsigned int channel; + unsigned long flags; + struct iproc_clk_enable_ctrl enable; + struct iproc_clk_reg_op mdiv; +}; + +/* + * Divisor of the ASIU clocks + */ +struct iproc_asiu_div { + unsigned int offset; + unsigned int en_shift; + unsigned int high_shift; + unsigned int high_width; + unsigned int low_shift; + unsigned int low_width; +}; + +void __init iproc_armpll_setup(struct device_node *node); +void __init iproc_pll_clk_setup(struct device_node *node, + const struct iproc_pll_ctrl *pll_ctrl, + const struct iproc_pll_vco_param *vco, + unsigned int num_vco_entries, + const struct iproc_clk_ctrl *clk_ctrl, + unsigned int num_clks); +void __init iproc_asiu_setup(struct device_node *node, + const struct iproc_asiu_div *div, + const struct iproc_asiu_gate *gate, + unsigned int num_clks); + +#endif /* _CLK_IPROC_H */ diff --git a/kernel/drivers/clk/bcm/clk-kona-setup.c b/kernel/drivers/clk/bcm/clk-kona-setup.c index e5aededdd..deaa7f962 100644 --- a/kernel/drivers/clk/bcm/clk-kona-setup.c +++ b/kernel/drivers/clk/bcm/clk-kona-setup.c @@ -21,8 +21,6 @@ #define selector_clear_exists(sel) ((sel)->width = 0) #define trigger_clear_exists(trig) FLAG_CLEAR(trig, TRIG, EXISTS) -LIST_HEAD(ccu_list); /* The list of set up CCUs */ - /* Validity checking */ static bool ccu_data_offsets_valid(struct ccu_data *ccu) @@ -773,7 +771,6 @@ static void kona_ccu_teardown(struct ccu_data *ccu) of_clk_del_provider(ccu->node); /* safe if never added */ ccu_clks_teardown(ccu); - list_del(&ccu->links); of_node_put(ccu->node); ccu->node = NULL; iounmap(ccu->base); @@ -847,7 +844,6 @@ void __init kona_dt_ccu_setup(struct ccu_data *ccu, goto out_err; } ccu->node = of_node_get(node); - list_add_tail(&ccu->links, &ccu_list); /* * Set up each defined kona clock and save the result in diff --git a/kernel/drivers/clk/bcm/clk-kona.c b/kernel/drivers/clk/bcm/clk-kona.c index a0ef4f75d..3a15347b4 100644 --- a/kernel/drivers/clk/bcm/clk-kona.c +++ b/kernel/drivers/clk/bcm/clk-kona.c @@ -16,6 +16,7 @@ #include <linux/delay.h> #include <linux/kernel.h> +#include <linux/clk.h> /* * "Policies" affect the frequencies of bus clocks provided by a @@ -1010,25 +1011,23 @@ static long kona_peri_clk_round_rate(struct clk_hw *hw, unsigned long rate, struct bcm_clk_div *div = &bcm_clk->u.peri->div; if (!divider_exists(div)) - return __clk_get_rate(hw->clk); + return clk_hw_get_rate(hw); /* Quietly avoid a zero rate */ return round_rate(bcm_clk->ccu, div, &bcm_clk->u.peri->pre_div, rate ? rate : 1, *parent_rate, NULL); } -static long kona_peri_clk_determine_rate(struct clk_hw *hw, unsigned long rate, - unsigned long min_rate, - unsigned long max_rate, - unsigned long *best_parent_rate, struct clk_hw **best_parent) +static int kona_peri_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) { struct kona_clk *bcm_clk = to_kona_clk(hw); - struct clk *clk = hw->clk; - struct clk *current_parent; + struct clk_hw *current_parent; unsigned long parent_rate; unsigned long best_delta; unsigned long best_rate; u32 parent_count; + long rate; u32 which; /* @@ -1037,18 +1036,25 @@ static long kona_peri_clk_determine_rate(struct clk_hw *hw, unsigned long rate, */ WARN_ON_ONCE(bcm_clk->init_data.flags & CLK_SET_RATE_NO_REPARENT); parent_count = (u32)bcm_clk->init_data.num_parents; - if (parent_count < 2) - return kona_peri_clk_round_rate(hw, rate, best_parent_rate); + if (parent_count < 2) { + rate = kona_peri_clk_round_rate(hw, req->rate, + &req->best_parent_rate); + if (rate < 0) + return rate; + + req->rate = rate; + return 0; + } /* Unless we can do better, stick with current parent */ - current_parent = clk_get_parent(clk); - parent_rate = __clk_get_rate(current_parent); - best_rate = kona_peri_clk_round_rate(hw, rate, &parent_rate); - best_delta = abs(best_rate - rate); + current_parent = clk_hw_get_parent(hw); + parent_rate = clk_hw_get_rate(current_parent); + best_rate = kona_peri_clk_round_rate(hw, req->rate, &parent_rate); + best_delta = abs(best_rate - req->rate); /* Check whether any other parent clock can produce a better result */ for (which = 0; which < parent_count; which++) { - struct clk *parent = clk_get_parent_by_index(clk, which); + struct clk_hw *parent = clk_hw_get_parent_by_index(hw, which); unsigned long delta; unsigned long other_rate; @@ -1057,18 +1063,20 @@ static long kona_peri_clk_determine_rate(struct clk_hw *hw, unsigned long rate, continue; /* We don't support CLK_SET_RATE_PARENT */ - parent_rate = __clk_get_rate(parent); - other_rate = kona_peri_clk_round_rate(hw, rate, &parent_rate); - delta = abs(other_rate - rate); + parent_rate = clk_hw_get_rate(parent); + other_rate = kona_peri_clk_round_rate(hw, req->rate, + &parent_rate); + delta = abs(other_rate - req->rate); if (delta < best_delta) { best_delta = delta; best_rate = other_rate; - *best_parent = __clk_get_hw(parent); - *best_parent_rate = parent_rate; + req->best_parent_hw = parent; + req->best_parent_rate = parent_rate; } } - return best_rate; + req->rate = best_rate; + return 0; } static int kona_peri_clk_set_parent(struct clk_hw *hw, u8 index) @@ -1130,7 +1138,7 @@ static int kona_peri_clk_set_rate(struct clk_hw *hw, unsigned long rate, if (parent_rate > (unsigned long)LONG_MAX) return -EINVAL; - if (rate == __clk_get_rate(hw->clk)) + if (rate == clk_hw_get_rate(hw)) return 0; if (!divider_exists(div)) @@ -1240,7 +1248,7 @@ static bool __kona_clk_init(struct kona_clk *bcm_clk) default: BUG(); } - return -EINVAL; + return false; } /* Set a CCU and all its clocks into their desired initial state */ @@ -1249,6 +1257,7 @@ bool __init kona_ccu_init(struct ccu_data *ccu) unsigned long flags; unsigned int which; struct clk **clks = ccu->clk_data.clks; + struct kona_clk *kona_clks = ccu->kona_clks; bool success = true; flags = ccu_lock(ccu); @@ -1259,7 +1268,7 @@ bool __init kona_ccu_init(struct ccu_data *ccu) if (!clks[which]) continue; - bcm_clk = to_kona_clk(__clk_get_hw(clks[which])); + bcm_clk = &kona_clks[which]; success &= __kona_clk_init(bcm_clk); } diff --git a/kernel/drivers/clk/bcm/clk-kona.h b/kernel/drivers/clk/bcm/clk-kona.h index 6849a64ba..906576ec9 100644 --- a/kernel/drivers/clk/bcm/clk-kona.h +++ b/kernel/drivers/clk/bcm/clk-kona.h @@ -480,7 +480,6 @@ struct ccu_data { spinlock_t lock; /* serialization lock */ bool write_enabled; /* write access is currently enabled */ struct ccu_policy policy; - struct list_head links; /* for ccu_list */ struct device_node *node; struct clk_onecell_data clk_data; const char *name; @@ -492,7 +491,6 @@ struct ccu_data { #define KONA_CCU_COMMON(_prefix, _name, _ccuname) \ .name = #_name "_ccu", \ .lock = __SPIN_LOCK_UNLOCKED(_name ## _ccu_data.lock), \ - .links = LIST_HEAD_INIT(_name ## _ccu_data.links), \ .clk_data = { \ .clk_num = _prefix ## _ ## _ccuname ## _CCU_CLOCK_COUNT, \ } diff --git a/kernel/drivers/clk/bcm/clk-ns2.c b/kernel/drivers/clk/bcm/clk-ns2.c new file mode 100644 index 000000000..a564e9248 --- /dev/null +++ b/kernel/drivers/clk/bcm/clk-ns2.c @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2015 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include <dt-bindings/clock/bcm-ns2.h> +#include "clk-iproc.h" + +#define REG_VAL(o, s, w) { .offset = o, .shift = s, .width = w, } + +#define AON_VAL(o, pw, ps, is) { .offset = o, .pwr_width = pw, \ + .pwr_shift = ps, .iso_shift = is } + +#define RESET_VAL(o, rs, prs) { .offset = o, .reset_shift = rs, \ + .p_reset_shift = prs } + +#define DF_VAL(o, kis, kiw, kps, kpw, kas, kaw) { .offset = o, .ki_shift = kis,\ + .ki_width = kiw, .kp_shift = kps, .kp_width = kpw, .ka_shift = kas, \ + .ka_width = kaw } + +#define VCO_CTRL_VAL(uo, lo) { .u_offset = uo, .l_offset = lo } + +#define ENABLE_VAL(o, es, hs, bs) { .offset = o, .enable_shift = es, \ + .hold_shift = hs, .bypass_shift = bs } + +static const struct iproc_pll_ctrl genpll_scr = { + .flags = IPROC_CLK_AON | IPROC_CLK_PLL_SPLIT_STAT_CTRL, + .aon = AON_VAL(0x0, 1, 15, 12), + .reset = RESET_VAL(0x4, 2, 1), + .dig_filter = DF_VAL(0x0, 9, 3, 5, 4, 2, 3), + .ndiv_int = REG_VAL(0x8, 4, 10), + .pdiv = REG_VAL(0x8, 0, 4), + .vco_ctrl = VCO_CTRL_VAL(0x10, 0xc), + .status = REG_VAL(0x0, 27, 1), +}; + + +static const struct iproc_clk_ctrl genpll_scr_clk[] = { + /* bypass_shift, the last value passed into ENABLE_VAL(), is not defined + * in NS2. However, it doesn't appear to be used anywhere, so setting + * it to 0. + */ + [BCM_NS2_GENPLL_SCR_SCR_CLK] = { + .channel = BCM_NS2_GENPLL_SCR_SCR_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 18, 12, 0), + .mdiv = REG_VAL(0x18, 0, 8), + }, + [BCM_NS2_GENPLL_SCR_FS_CLK] = { + .channel = BCM_NS2_GENPLL_SCR_FS_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 19, 13, 0), + .mdiv = REG_VAL(0x18, 8, 8), + }, + [BCM_NS2_GENPLL_SCR_AUDIO_CLK] = { + .channel = BCM_NS2_GENPLL_SCR_AUDIO_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 20, 14, 0), + .mdiv = REG_VAL(0x14, 0, 8), + }, + [BCM_NS2_GENPLL_SCR_CH3_UNUSED] = { + .channel = BCM_NS2_GENPLL_SCR_CH3_UNUSED, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 21, 15, 0), + .mdiv = REG_VAL(0x14, 8, 8), + }, + [BCM_NS2_GENPLL_SCR_CH4_UNUSED] = { + .channel = BCM_NS2_GENPLL_SCR_CH4_UNUSED, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 22, 16, 0), + .mdiv = REG_VAL(0x14, 16, 8), + }, + [BCM_NS2_GENPLL_SCR_CH5_UNUSED] = { + .channel = BCM_NS2_GENPLL_SCR_CH5_UNUSED, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 23, 17, 0), + .mdiv = REG_VAL(0x14, 24, 8), + }, +}; + +static void __init ns2_genpll_scr_clk_init(struct device_node *node) +{ + iproc_pll_clk_setup(node, &genpll_scr, NULL, 0, genpll_scr_clk, + ARRAY_SIZE(genpll_scr_clk)); +} +CLK_OF_DECLARE(ns2_genpll_src_clk, "brcm,ns2-genpll-scr", + ns2_genpll_scr_clk_init); + +static const struct iproc_pll_ctrl genpll_sw = { + .flags = IPROC_CLK_AON | IPROC_CLK_PLL_SPLIT_STAT_CTRL, + .aon = AON_VAL(0x0, 2, 9, 8), + .reset = RESET_VAL(0x4, 2, 1), + .dig_filter = DF_VAL(0x0, 9, 3, 5, 4, 2, 3), + .ndiv_int = REG_VAL(0x8, 4, 10), + .pdiv = REG_VAL(0x8, 0, 4), + .vco_ctrl = VCO_CTRL_VAL(0x10, 0xc), + .status = REG_VAL(0x0, 13, 1), +}; + +static const struct iproc_clk_ctrl genpll_sw_clk[] = { + /* bypass_shift, the last value passed into ENABLE_VAL(), is not defined + * in NS2. However, it doesn't appear to be used anywhere, so setting + * it to 0. + */ + [BCM_NS2_GENPLL_SW_RPE_CLK] = { + .channel = BCM_NS2_GENPLL_SW_RPE_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 18, 12, 0), + .mdiv = REG_VAL(0x18, 0, 8), + }, + [BCM_NS2_GENPLL_SW_250_CLK] = { + .channel = BCM_NS2_GENPLL_SW_250_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 19, 13, 0), + .mdiv = REG_VAL(0x18, 8, 8), + }, + [BCM_NS2_GENPLL_SW_NIC_CLK] = { + .channel = BCM_NS2_GENPLL_SW_NIC_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 20, 14, 0), + .mdiv = REG_VAL(0x14, 0, 8), + }, + [BCM_NS2_GENPLL_SW_CHIMP_CLK] = { + .channel = BCM_NS2_GENPLL_SW_CHIMP_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 21, 15, 0), + .mdiv = REG_VAL(0x14, 8, 8), + }, + [BCM_NS2_GENPLL_SW_PORT_CLK] = { + .channel = BCM_NS2_GENPLL_SW_PORT_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 22, 16, 0), + .mdiv = REG_VAL(0x14, 16, 8), + }, + [BCM_NS2_GENPLL_SW_SDIO_CLK] = { + .channel = BCM_NS2_GENPLL_SW_SDIO_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 23, 17, 0), + .mdiv = REG_VAL(0x14, 24, 8), + }, +}; + +static void __init ns2_genpll_sw_clk_init(struct device_node *node) +{ + iproc_pll_clk_setup(node, &genpll_sw, NULL, 0, genpll_sw_clk, + ARRAY_SIZE(genpll_sw_clk)); +} +CLK_OF_DECLARE(ns2_genpll_sw_clk, "brcm,ns2-genpll-sw", + ns2_genpll_sw_clk_init); + +static const struct iproc_pll_ctrl lcpll_ddr = { + .flags = IPROC_CLK_AON | IPROC_CLK_PLL_SPLIT_STAT_CTRL, + .aon = AON_VAL(0x0, 2, 1, 0), + .reset = RESET_VAL(0x4, 2, 1), + .dig_filter = DF_VAL(0x0, 9, 3, 5, 4, 1, 4), + .ndiv_int = REG_VAL(0x8, 4, 10), + .pdiv = REG_VAL(0x8, 0, 4), + .vco_ctrl = VCO_CTRL_VAL(0x10, 0xc), + .status = REG_VAL(0x0, 0, 1), +}; + +static const struct iproc_clk_ctrl lcpll_ddr_clk[] = { + /* bypass_shift, the last value passed into ENABLE_VAL(), is not defined + * in NS2. However, it doesn't appear to be used anywhere, so setting + * it to 0. + */ + [BCM_NS2_LCPLL_DDR_PCIE_SATA_USB_CLK] = { + .channel = BCM_NS2_LCPLL_DDR_PCIE_SATA_USB_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 18, 12, 0), + .mdiv = REG_VAL(0x14, 0, 8), + }, + [BCM_NS2_LCPLL_DDR_DDR_CLK] = { + .channel = BCM_NS2_LCPLL_DDR_DDR_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 19, 13, 0), + .mdiv = REG_VAL(0x14, 8, 8), + }, + [BCM_NS2_LCPLL_DDR_CH2_UNUSED] = { + .channel = BCM_NS2_LCPLL_DDR_CH2_UNUSED, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 20, 14, 0), + .mdiv = REG_VAL(0x10, 0, 8), + }, + [BCM_NS2_LCPLL_DDR_CH3_UNUSED] = { + .channel = BCM_NS2_LCPLL_DDR_CH3_UNUSED, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 21, 15, 0), + .mdiv = REG_VAL(0x10, 8, 8), + }, + [BCM_NS2_LCPLL_DDR_CH4_UNUSED] = { + .channel = BCM_NS2_LCPLL_DDR_CH4_UNUSED, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 22, 16, 0), + .mdiv = REG_VAL(0x10, 16, 8), + }, + [BCM_NS2_LCPLL_DDR_CH5_UNUSED] = { + .channel = BCM_NS2_LCPLL_DDR_CH5_UNUSED, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 23, 17, 0), + .mdiv = REG_VAL(0x10, 24, 8), + }, +}; + +static void __init ns2_lcpll_ddr_clk_init(struct device_node *node) +{ + iproc_pll_clk_setup(node, &lcpll_ddr, NULL, 0, lcpll_ddr_clk, + ARRAY_SIZE(lcpll_ddr_clk)); +} +CLK_OF_DECLARE(ns2_lcpll_ddr_clk, "brcm,ns2-lcpll-ddr", + ns2_lcpll_ddr_clk_init); + +static const struct iproc_pll_ctrl lcpll_ports = { + .flags = IPROC_CLK_AON | IPROC_CLK_PLL_SPLIT_STAT_CTRL, + .aon = AON_VAL(0x0, 2, 5, 4), + .reset = RESET_VAL(0x4, 2, 1), + .dig_filter = DF_VAL(0x0, 9, 3, 5, 4, 1, 4), + .ndiv_int = REG_VAL(0x8, 4, 10), + .pdiv = REG_VAL(0x8, 0, 4), + .vco_ctrl = VCO_CTRL_VAL(0x10, 0xc), + .status = REG_VAL(0x0, 0, 1), +}; + +static const struct iproc_clk_ctrl lcpll_ports_clk[] = { + /* bypass_shift, the last value passed into ENABLE_VAL(), is not defined + * in NS2. However, it doesn't appear to be used anywhere, so setting + * it to 0. + */ + [BCM_NS2_LCPLL_PORTS_WAN_CLK] = { + .channel = BCM_NS2_LCPLL_PORTS_WAN_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 18, 12, 0), + .mdiv = REG_VAL(0x14, 0, 8), + }, + [BCM_NS2_LCPLL_PORTS_RGMII_CLK] = { + .channel = BCM_NS2_LCPLL_PORTS_RGMII_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 19, 13, 0), + .mdiv = REG_VAL(0x14, 8, 8), + }, + [BCM_NS2_LCPLL_PORTS_CH2_UNUSED] = { + .channel = BCM_NS2_LCPLL_PORTS_CH2_UNUSED, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 20, 14, 0), + .mdiv = REG_VAL(0x10, 0, 8), + }, + [BCM_NS2_LCPLL_PORTS_CH3_UNUSED] = { + .channel = BCM_NS2_LCPLL_PORTS_CH3_UNUSED, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 21, 15, 0), + .mdiv = REG_VAL(0x10, 8, 8), + }, + [BCM_NS2_LCPLL_PORTS_CH4_UNUSED] = { + .channel = BCM_NS2_LCPLL_PORTS_CH4_UNUSED, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 22, 16, 0), + .mdiv = REG_VAL(0x10, 16, 8), + }, + [BCM_NS2_LCPLL_PORTS_CH5_UNUSED] = { + .channel = BCM_NS2_LCPLL_PORTS_CH5_UNUSED, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 23, 17, 0), + .mdiv = REG_VAL(0x10, 24, 8), + }, +}; + +static void __init ns2_lcpll_ports_clk_init(struct device_node *node) +{ + iproc_pll_clk_setup(node, &lcpll_ports, NULL, 0, lcpll_ports_clk, + ARRAY_SIZE(lcpll_ports_clk)); +} +CLK_OF_DECLARE(ns2_lcpll_ports_clk, "brcm,ns2-lcpll-ports", + ns2_lcpll_ports_clk_init); diff --git a/kernel/drivers/clk/bcm/clk-nsp.c b/kernel/drivers/clk/bcm/clk-nsp.c new file mode 100644 index 000000000..cf66f640a --- /dev/null +++ b/kernel/drivers/clk/bcm/clk-nsp.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2015 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include <dt-bindings/clock/bcm-nsp.h> +#include "clk-iproc.h" + +#define REG_VAL(o, s, w) { .offset = o, .shift = s, .width = w, } + +#define AON_VAL(o, pw, ps, is) { .offset = o, .pwr_width = pw, \ + .pwr_shift = ps, .iso_shift = is } + +#define RESET_VAL(o, rs, prs) { .offset = o, .reset_shift = rs, \ + .p_reset_shift = prs } + +#define DF_VAL(o, kis, kiw, kps, kpw, kas, kaw) { .offset = o, .ki_shift = kis,\ + .ki_width = kiw, .kp_shift = kps, .kp_width = kpw, .ka_shift = kas, \ + .ka_width = kaw } + +#define ENABLE_VAL(o, es, hs, bs) { .offset = o, .enable_shift = es, \ + .hold_shift = hs, .bypass_shift = bs } + +static void __init nsp_armpll_init(struct device_node *node) +{ + iproc_armpll_setup(node); +} +CLK_OF_DECLARE(nsp_armpll, "brcm,nsp-armpll", nsp_armpll_init); + +static const struct iproc_pll_ctrl genpll = { + .flags = IPROC_CLK_PLL_HAS_NDIV_FRAC | IPROC_CLK_EMBED_PWRCTRL, + .aon = AON_VAL(0x0, 1, 12, 0), + .reset = RESET_VAL(0x0, 11, 10), + .dig_filter = DF_VAL(0x0, 4, 3, 0, 4, 7, 3), + .ndiv_int = REG_VAL(0x14, 20, 10), + .ndiv_frac = REG_VAL(0x14, 0, 20), + .pdiv = REG_VAL(0x18, 24, 3), + .status = REG_VAL(0x20, 12, 1), +}; + +static const struct iproc_clk_ctrl genpll_clk[] = { + [BCM_NSP_GENPLL_PHY_CLK] = { + .channel = BCM_NSP_GENPLL_PHY_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x4, 12, 6, 18), + .mdiv = REG_VAL(0x18, 16, 8), + }, + [BCM_NSP_GENPLL_ENET_SW_CLK] = { + .channel = BCM_NSP_GENPLL_ENET_SW_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x4, 13, 7, 19), + .mdiv = REG_VAL(0x18, 8, 8), + }, + [BCM_NSP_GENPLL_USB_PHY_REF_CLK] = { + .channel = BCM_NSP_GENPLL_USB_PHY_REF_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x4, 14, 8, 20), + .mdiv = REG_VAL(0x18, 0, 8), + }, + [BCM_NSP_GENPLL_IPROCFAST_CLK] = { + .channel = BCM_NSP_GENPLL_IPROCFAST_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x4, 15, 9, 21), + .mdiv = REG_VAL(0x1c, 16, 8), + }, + [BCM_NSP_GENPLL_SATA1_CLK] = { + .channel = BCM_NSP_GENPLL_SATA1_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x4, 16, 10, 22), + .mdiv = REG_VAL(0x1c, 8, 8), + }, + [BCM_NSP_GENPLL_SATA2_CLK] = { + .channel = BCM_NSP_GENPLL_SATA2_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x4, 17, 11, 23), + .mdiv = REG_VAL(0x1c, 0, 8), + }, +}; + +static void __init nsp_genpll_clk_init(struct device_node *node) +{ + iproc_pll_clk_setup(node, &genpll, NULL, 0, genpll_clk, + ARRAY_SIZE(genpll_clk)); +} +CLK_OF_DECLARE(nsp_genpll_clk, "brcm,nsp-genpll", nsp_genpll_clk_init); + +static const struct iproc_pll_ctrl lcpll0 = { + .flags = IPROC_CLK_PLL_HAS_NDIV_FRAC | IPROC_CLK_EMBED_PWRCTRL, + .aon = AON_VAL(0x0, 1, 24, 0), + .reset = RESET_VAL(0x0, 23, 22), + .dig_filter = DF_VAL(0x0, 16, 3, 12, 4, 19, 4), + .ndiv_int = REG_VAL(0x4, 20, 8), + .ndiv_frac = REG_VAL(0x4, 0, 20), + .pdiv = REG_VAL(0x4, 28, 3), + .status = REG_VAL(0x10, 12, 1), +}; + +static const struct iproc_clk_ctrl lcpll0_clk[] = { + [BCM_NSP_LCPLL0_PCIE_PHY_REF_CLK] = { + .channel = BCM_NSP_LCPLL0_PCIE_PHY_REF_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 6, 3, 9), + .mdiv = REG_VAL(0x8, 24, 8), + }, + [BCM_NSP_LCPLL0_SDIO_CLK] = { + .channel = BCM_NSP_LCPLL0_SDIO_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 7, 4, 10), + .mdiv = REG_VAL(0x8, 16, 8), + }, + [BCM_NSP_LCPLL0_DDR_PHY_CLK] = { + .channel = BCM_NSP_LCPLL0_DDR_PHY_CLK, + .flags = IPROC_CLK_AON, + .enable = ENABLE_VAL(0x0, 8, 5, 11), + .mdiv = REG_VAL(0x8, 8, 8), + }, +}; + +static void __init nsp_lcpll0_clk_init(struct device_node *node) +{ + iproc_pll_clk_setup(node, &lcpll0, NULL, 0, lcpll0_clk, + ARRAY_SIZE(lcpll0_clk)); +} +CLK_OF_DECLARE(nsp_lcpll0_clk, "brcm,nsp-lcpll0", nsp_lcpll0_clk_init); |