From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.215.171 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794434268; b=MphK2/fJ/jzPp0Ff34r5Gw8MGYmlu5E6+dcJSHUSfAbOn1ovBNyxfNnS1bbDjTei6ID1 2QWdgVSk0iSMPZ7ZGXuS4JK50cCuLPSjzWfSDxwvwwIXBGJVWgaWQ5gSFS7zaZy2Thgme 4gW1CpfmJ1UHp5dNlo+2jWtoQgB1q86yDWw5hoLm2We9ZkzpKZ4XRogn2bZfr77YlzaBj tUwKoXjwEQufKgEXC/iJoeiAChfb308ah4bDx2hqSvQBHGORU8SGu7DiCTmwPf5qV3qWe 6r0rdRR2Slq0Rm9PUWx4b9GVyVDGcNQLCWwB2npzcSKYdGAsBzCmg6sxvUQZ6Ate/rA== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794434268; h=DMARC-Filter:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding; bh=oLrHy3WtoKaBl+d3fnxDJQ8KkDGA3RIn3I2lI5k5h64=; b=MiFPdGn/pKVmqk5igzGlLVRrWS7Zpn8KsUzeDPH7wx8IqiZ44Kl+5QirbKtNiQQx85Fy fHQFMADbxL/nSyaCqGbLe/i+xR7LO0L3M8+LWyCEpUfiFNmBSSLIoLPzhDIslrO0K3BuP 42kLDn1iq0b86Rao3SPW8+xGpHmHaLPurkHMRsfXHewgShGJ4UjpM2MNtzw5/0TSn0BZb Wp6uJvmGn24xbfAQTkyaZ3hcXIDEdR7wa1S+CjB9Z7zbhwLFhudH8CGkIkNvyHpN5Tzjr 01WmUAL0Jc7kE1HEcJh0/LX0QHVg5jcODuJlqk62bTVebSW9IxwFuY+7tGMOU7b+NVw== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=T04n7cDQ; arc=none smtp.remote-ip=209.85.215.171 Received: from mail-pg1-f171.google.com (mail-pg1-f171.google.com [209.85.215.171]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A8D5D60A3827 for ; Wed, 11 Nov 2026 21:57:47 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org A8D5D60A3827 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=T04n7cDQ Received: by mail-pg1-f171.google.com with SMTP id 41be03b00d2f7-cc751eddf25so1939223a12.3 for ; Wed, 11 Nov 2026 13:57:47 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1794434267; x=1795039067; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=oLrHy3WtoKaBl+d3fnxDJQ8KkDGA3RIn3I2lI5k5h64=; b=T04n7cDQnLGcxma6kLnirjffMh90eIwWn7d81K3U7yuIogYd/70ZxPbqXJj6jfhlvH xFqlQ3t42BkSRm580FYm8KZVYQPlGRDv3vmnrUrDGO7bsrpYpqqDz4ghf1P5n/3m0N5Y EV4E1+M9zGY9gvsDm61QBHyUlfIHt5cXTV6OM3vTZLG3Cnuzy4kGiqWeEK77Jume6HwF 5l3ji8ypZqfoQK6C80I+hTtKwy5X/OCZNT/f1ZE6HT8YYbWTxjFSwWS7uo5QbpSY3GkM 4NtQjTdN3Q2ncM2YG6Tgo17oAiX928yCztwbvKlza97Vk5CQCj6oEZQ816nJVQLjqdzN +e1w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1794434267; x=1795039067; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=oLrHy3WtoKaBl+d3fnxDJQ8KkDGA3RIn3I2lI5k5h64=; b=pZxyOZ2YypxIOyZhw1Q1GZVrVOCC7RsvH8A2EOSnNOgQKIkjaGEvZNmXTEUVSpDuhR uZWzWuGlKHKyyNiOfPbruk0mvxIwu7RIQJ5GPBxUcl54Bxmh4nUt9bHmEnp3L0krZ/ze yig7LmKd9Lg28BkM2gyJ6cihr/t/4zme0sY4OJzsVbPhfeHsA39NlWoM+8VpJNYDqWAf 7g6vnFk00lhwgFKhr1/FFsSVEngdnJLynkjkOc0V2PxJpcSYHG+9qbLdfUAdK6VU97rx MrCIRPB/HfffIHNSPzPPtKSS8uSjzh89EiSX3lq7fTTBdzPbGCa16ju1/KKxy4sm1NfN KRbA== X-Gm-Message-State: APuU4Z9Ks49HPOUnTzgQeIkw2HrgxxTUFdAVL1jYBUxh2Hd8qvgD6Twd yd9SUh+tJu/1Csb5fuIRqJvUU6AqEFBxqDzpfXO3BIPtP6fXCfZGEHz8 X-Gm-Gg: AYBFou2xCjOxUdThDlk9dhaDaFwNYQnlrOvV31m8bB1CzhGr9MIGLn/QX/rOE5CSzXj GSq0mQqKja0wnqm2jgr0MXLBmNTLKEQZQ30LNYY4F8BDQMQeqaS7Dh2c8HftHs+gQDAs+lOnXDI CXo3VeSSM1I5bMDDUZPuxWmDV6B5m9yRcLCf82DaWEVLG6z0j06We9TnSrwPg+Qfc5m2To/ijzQ bNDCbQPzwvX2X6gTAwvfS5WoAROVxMoMlWX/uURRsJJSq7v2uJU76tRTETME52fCEL3W1CDgT5R kji9/dD890+ZZ5wr4oD/CSyXrNDuXhSujIJwhBTRcMws1w6xR87JeAcUMEghbcLk7qtexcpzUf8 ftmbfBd2bGr4iB3We9eL7N7pZDf+dp8rYDPAfu4U2DnignZEqQlByfu6jxBnDhRXszZ2Nzpk+U0 wY5JHVSXmH6OCI/NHgeIBbutQwQ9aABQskOmOK+WqApydZUkUGH9nVqOpuiaxtdX/U4KrLew6Ql dXhrrgbzSkelY4yhh0IVeRaerqRB7Id9pgP5l2YCiOAvuJgqNe8oU1UqdFduHKNeP/AitGt2k5Z UtyD2srBNS/AAGbVuCcG4CVXRI+pExSZPwJNlPB/0eYqoRPtTFRNhvlyT/mtTFW8jvON8aIEJNE 1TXOSgykGQeZw1fhukQLC5laRKcsQrVwo8TDWBkyFbsY+vcnuIBOyttCc5J0/EsgqCYpib5r2Ob Fhpw== X-Received: by 2002:a17:90b:3a08:b0:3ac:7556:8ee0 with SMTP id 98e67ed59e1d1-3b182e94148mr3462408a91.19.1794434266790; Wed, 11 Nov 2026 13:57:46 -0800 (PST) Received: from ryzen.lan ([2601:644:8000:37f::e34]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b197ce556dsm965429a91.6.2026.11.11.13.57.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 11 Nov 2026 13:57:46 -0800 (PST) From: Rosen Penev To: Thomas Bogendoerfer , Stephen Boyd , Brian Masney , Jerome Brunet , Alban Bedel , Antony Pavlov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-kernel@vger.kernel.org, linux-mips@vger.kernel.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org Subject: [PATCH v3 6/7] clk: add a driver for the Atheros/Qualcomm ath79 PLL controller Date: Wed, 11 Nov 2026 13:57:28 -0800 Message-ID: <20261111215729.10683-7-rosenp@gmail.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261111215729.10683-1-rosenp@gmail.com> References: <20261111215729.10683-1-rosenp@gmail.com> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit The ath79 clocks are set up by arch/mips/ath79/clock.c, a CLK_OF_DECLARE driver that registers every clock before the driver core is up. Add a replacement in drivers/clk. Only the reference and CPU clocks are registered early, with CLK_OF_DECLARE_DRIVER(), as the MIPS timer needs the CPU rate from plat_time_init(). The other clocks come from a platform driver for the same node, like the MT7621 clocks do. The early provider returns -EPROBE_DEFER for them until the platform driver fills them in. The bootloader sets up the PLLs, and the CPU, DDR and AHB clocks are read back from the registers as fixed rates, as before. Compared to the arch code: - Each SoC is described by match data: a function that reads the rates, and for AR934x and later the bits of CPU_DDR_CLOCK_CONTROL that select a PLL for each clock. The bypass bits and post dividers of that register are the same on every SoC, so one function handles them. AR934x, QCA953x and QCA955x share the fractional PLL calculation, done with 64-bit intermediates. - The AR724x and AR933x clocks were fixed factors of a parent named "ref". They are fixed rates now, like the others, all with the reference clock's clk_hw as the parent. - Invalid register contents, a reference clock that is missing and registration failures are reported instead of causing a panic or a division by zero. - The AR934x SRIF block, which holds the CPU and DDR DPLLs, is still mapped from its fixed address. It lies inside the register space of the wireless MAC, which ath9k claims, so it can't be a reg entry of the PLL node. The driver only reads it and doesn't claim it. - The clocks are no longer registered with clkdev. No driver looks them up by name. - AR934x gets its UART1 clock: 100 MHz when UART1_CLK_SEL is set in SWITCH_CLOCK_CONTROL, the reference clock otherwise and on the other SoCs. This is based on an OpenWrt patch by Daniel Golle. - The PCIe PLL is a clock, so that a PCIe host driver can use the clk API instead of poking the PLL block through the arch code. Prepare powers the PLL up and takes it out of bypass, and unprepare does the reverse, waiting for UPDATING to clear after each step. Every write to the register freezes the high speed clocks for about 60 us (AR7242 datasheet, 4.4.4), so a PLL the bootloader already started is left alone. AR724x holds the PLL in reset instead of powering it down, and the register is at 0x10 on AR724x, AR934x and QCA953x, 0x0c on QCA955x and 0x14 on QCA956x. The clock is CLK_IGNORE_UNUSED, so a PLL that no driver claims stays as the bootloader left it. The rates match the arch code for every SoC, checked in userspace against it with random register values. They differ only where the arch code overflowed 32 bits with register values that no real setup uses. COMMON_CLK_ATH79 defaults to ATH79, and arch/mips/ath79/clock.c is no longer built when it is enabled. Signed-off-by: Rosen Penev --- MAINTAINERS | 8 + arch/mips/ath79/Makefile | 5 +- drivers/clk/Kconfig | 12 + drivers/clk/Makefile | 1 + drivers/clk/clk-ath79.c | 782 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 807 insertions(+), 1 deletion(-) create mode 100644 drivers/clk/clk-ath79.c diff --git a/MAINTAINERS b/MAINTAINERS index c57be10c8729..98f42425b37d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4341,6 +4341,14 @@ S: Supported F: Documentation/devicetree/bindings/rtc/andestech,atcrtc100.yaml F: drivers/rtc/rtc-atcrtc100.c +ATHEROS 71XX/9XXX CLOCK DRIVER +M: Alban Bedel +M: Antony Pavlov +S: Maintained +F: Documentation/devicetree/bindings/clock/qca,ath79-pll.yaml +F: drivers/clk/clk-ath79.c +F: include/dt-bindings/clock/ath79-clk.h + ATHEROS 71XX/9XXX GPIO DRIVER M: Alban Bedel S: Maintained diff --git a/arch/mips/ath79/Makefile b/arch/mips/ath79/Makefile index 0fb3aaf42149..b78ffdc9b389 100644 --- a/arch/mips/ath79/Makefile +++ b/arch/mips/ath79/Makefile @@ -6,6 +6,9 @@ # Copyright (C) 2008 Imre Kaloz # -obj-y := prom.o setup.o common.o clock.o +obj-y := prom.o setup.o common.o + +# replaced by the clk-ath79 driver +obj-$(if $(CONFIG_COMMON_CLK_ATH79),,y) += clock.o obj-$(CONFIG_EARLY_PRINTK) += early_printk.o diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 37a8e35d4178..bd5f77cddfe0 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -66,6 +66,18 @@ config COMMON_CLK_APPLE_NCO found on Apple SoCs such as t8103 (M1). The blocks are typically generators of audio clocks. +config COMMON_CLK_ATH79 + bool "Clock driver for Atheros/Qualcomm ath79 SoCs" if COMPILE_TEST + depends on ATH79 || COMPILE_TEST + depends on OF + default ATH79 + help + Support for the PLL controller of the Atheros/Qualcomm ath79 + family of MIPS SoCs: AR71xx, AR724x, AR913x, AR933x, AR934x, + QCA953x, QCA955x and QCA956x. It provides the CPU, DDR, AHB, + MDIO, UART1 and PCIe clocks, derived from the board's reference + clock. + config COMMON_CLK_MAX77686 tristate "Clock driver for Maxim 77620/77686/77802 MFD" depends on MFD_MAX77686 || MFD_MAX77620 || COMPILE_TEST diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 52d138d4ffc8..589d2ef9dc89 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -58,6 +58,7 @@ endif # please keep this section sorted lexicographically by file path name obj-$(CONFIG_COMMON_CLK_APPLE_NCO) += clk-apple-nco.o obj-$(CONFIG_MACH_ASM9260) += clk-asm9260.o +obj-$(CONFIG_COMMON_CLK_ATH79) += clk-ath79.o obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN) += clk-axi-clkgen.o obj-$(CONFIG_ARCH_AXXIA) += clk-axm5516.o obj-$(CONFIG_COMMON_CLK_BD718XX) += clk-bd718x7.o diff --git a/drivers/clk/clk-ath79.c b/drivers/clk/clk-ath79.c new file mode 100644 index 000000000000..6eab4cd1a91f --- /dev/null +++ b/drivers/clk/clk-ath79.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Atheros/Qualcomm ath79 PLL controller + * + * Copyright (C) 2010-2011 Jaiganesh Narayanan + * Copyright (C) 2011 Gabor Juhos + * + * Parts of this file are based on Atheros' 2.6.15/2.6.31 BSP + * + * The bootloader sets up the CPU, DDR and AHB clocks, and they are read + * back here as fixed rates. The CPU clock is registered early, as the + * MIPS timer needs it from plat_time_init(). The other clocks are + * registered by the platform driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ATH79_CLK_NUM (ATH79_CLK_PCIE + 1) + +#define AR71XX_PLL_CPU_CONFIG_REG 0x00 +#define AR71XX_PLL_FB GENMASK(7, 3) +#define AR71XX_CPU_DIV GENMASK(17, 16) +#define AR71XX_DDR_DIV GENMASK(19, 18) +#define AR71XX_AHB_DIV GENMASK(22, 20) + +#define AR724X_PLL_CPU_CONFIG_REG 0x00 +#define AR724X_PLL_FB GENMASK(9, 0) +#define AR724X_PLL_REF_DIV GENMASK(13, 10) +#define AR724X_AHB_DIV GENMASK(19, 19) +#define AR724X_DDR_DIV GENMASK(23, 22) +#define AR724X_PLL_PCIE_CONFIG_REG 0x10 +#define AR724X_PLL_PCIE_CONFIG_PLL_BYPASS BIT(16) +#define AR724X_PLL_PCIE_CONFIG_UPDATING BIT(17) +#define AR724X_PLL_PCIE_CONFIG_PLL_RESET BIT(25) + +#define AR933X_PLL_CPU_CONFIG_REG 0x00 +#define AR933X_PLL_CLOCK_CTRL_REG 0x08 +#define AR933X_PLL_CPU_CONFIG_NINT GENMASK(15, 10) +#define AR933X_PLL_CPU_CONFIG_REFDIV GENMASK(20, 16) +#define AR933X_PLL_CPU_CONFIG_OUTDIV GENMASK(25, 23) +#define AR933X_PLL_CLOCK_CTRL_BYPASS BIT(2) +#define AR933X_PLL_CLOCK_CTRL_CPU_DIV GENMASK(6, 5) +#define AR933X_PLL_CLOCK_CTRL_DDR_DIV GENMASK(11, 10) +#define AR933X_PLL_CLOCK_CTRL_AHB_DIV GENMASK(17, 15) + +/* AR934x, QCA953x and QCA955x */ +#define AR934X_PLL_CPU_CONFIG_REG 0x00 +#define AR934X_PLL_DDR_CONFIG_REG 0x04 +#define AR934X_PLL_CLK_CTRL_REG 0x08 +#define AR934X_PLL_PCIE_CONFIG_REG 0x10 +#define AR934X_PLL_SWITCH_CLOCK_CONTROL_REG 0x24 +#define AR934X_PLL_CPU_CONFIG_NFRAC GENMASK(5, 0) +#define AR934X_PLL_CPU_CONFIG_NINT GENMASK(11, 6) +#define AR934X_PLL_CPU_CONFIG_REFDIV GENMASK(16, 12) +#define AR934X_PLL_CPU_CONFIG_OUTDIV GENMASK(20, 19) +#define QCA953X_PLL_CPU_CONFIG_OUTDIV GENMASK(21, 19) +#define AR934X_PLL_DDR_CONFIG_NFRAC GENMASK(9, 0) +#define AR934X_PLL_DDR_CONFIG_NINT GENMASK(15, 10) +#define AR934X_PLL_DDR_CONFIG_REFDIV GENMASK(20, 16) +#define AR934X_PLL_DDR_CONFIG_OUTDIV GENMASK(25, 23) +#define AR934X_PLL_SWITCH_CLOCK_CONTROL_MDIO_CLK_SEL BIT(6) +#define AR934X_PLL_SWITCH_CLOCK_CONTROL_UART1_CLK_SEL BIT(7) +#define AR934X_PLL_PCIE_CONFIG_PLL_BYPASS BIT(16) +#define AR934X_PLL_PCIE_CONFIG_PLL_PWD BIT(30) +#define AR934X_PLL_PCIE_CONFIG_UPDATING BIT(31) + +#define QCA955X_PLL_PCIE_CONFIG_REG 0x0c + +#define QCA956X_PLL_CPU_CONFIG_REG 0x00 +#define QCA956X_PLL_CPU_CONFIG1_REG 0x04 +#define QCA956X_PLL_DDR_CONFIG_REG 0x08 +#define QCA956X_PLL_DDR_CONFIG1_REG 0x0c +#define QCA956X_PLL_CLK_CTRL_REG 0x10 +#define QCA956X_PLL_PCIE_CONFIG_REG 0x14 +#define QCA956X_PLL_CPU_CONFIG_REFDIV GENMASK(16, 12) +#define QCA956X_PLL_CPU_CONFIG_OUTDIV GENMASK(21, 19) +#define QCA956X_PLL_DDR_CONFIG_REFDIV GENMASK(20, 16) +#define QCA956X_PLL_DDR_CONFIG_OUTDIV GENMASK(25, 23) +#define QCA956X_PLL_CONFIG1_NFRAC_L GENMASK(4, 0) +#define QCA956X_PLL_CONFIG1_NFRAC_H GENMASK(17, 5) +#define QCA956X_PLL_CONFIG1_NINT GENMASK(26, 18) + +/* + * CPU_DDR_CLOCK_CONTROL on AR934x and later: a bypass bit and a post + * divider for each of the CPU, DDR and AHB clocks, at the same place on + * every SoC. Which PLL a clock takes and the bit selecting it differ. + */ +#define ATH79_CLK_CTRL_BYPASS(i) BIT(2 + (i)) +#define ATH79_CLK_CTRL_POST_DIV_SHIFT(i) (5 + 5 * (i)) +#define ATH79_CLK_CTRL_POST_DIV_MASK 0x1f + +/* + * The SRIF block holds the AR934x DPLLs. It sits inside the register + * space of the wireless MAC, which claims it, so it is mapped from its + * fixed address and only read. + */ +#define AR934X_SRIF_BASE 0x18116000 +#define AR934X_SRIF_SIZE 0x1000 +#define AR934X_SRIF_CPU_DPLL1_REG 0x1c0 +#define AR934X_SRIF_CPU_DPLL2_REG 0x1c4 +#define AR934X_SRIF_DDR_DPLL1_REG 0x240 +#define AR934X_SRIF_DDR_DPLL2_REG 0x244 +#define AR934X_SRIF_DPLL1_REFDIV GENMASK(31, 27) +#define AR934X_SRIF_DPLL1_NINT GENMASK(26, 18) +#define AR934X_SRIF_DPLL1_NFRAC GENMASK(17, 0) +#define AR934X_SRIF_DPLL2_LOCAL_PLL BIT(30) +#define AR934X_SRIF_DPLL2_OUTDIV GENMASK(15, 13) + +#define ATH79_SWITCH_CLK_RATE 100000000 + +enum { ATH79_OUT_CPU, ATH79_OUT_DDR, ATH79_OUT_AHB, ATH79_OUT_NUM }; + +struct ath79_clk_regs { + void __iomem *pll; + void __iomem *srif; /* AR934x only */ +}; + +struct ath79_clk_ctrl_sel { + u32 bit; + bool set_is_cpu_pll; /* the bit selects the CPU PLL, not the DDR PLL */ +}; + +struct ath79_pcie_pll_desc { + unsigned int reg; + u32 pwd; /* powers the PLL down or holds it in reset */ + u32 bypass; + u32 updating; /* set while a new setting is being applied */ +}; + +struct ath79_clk_soc_data { + int (*get_rates)(const struct ath79_clk_soc_data *soc, + const struct ath79_clk_regs *regs, unsigned long ref, + unsigned long *rates); + /* AR934x, QCA953x and QCA955x */ + u32 cpu_outdiv; + /* AR934x and later */ + struct ath79_clk_ctrl_sel sel[ATH79_OUT_NUM]; + const struct ath79_pcie_pll_desc *pcie; + bool has_srif; + bool has_switch_clk; /* AR934x MDIO and UART1 clock selection */ +}; + +struct ath79_pcie_pll { + struct clk_hw hw; + void __iomem *reg; + u32 pwd; + u32 bypass; + u32 updating; +}; + +#define to_ath79_pcie_pll(_hw) container_of(_hw, struct ath79_pcie_pll, hw) + +/* + * The provider registered early. It holds the reference and CPU clocks, + * and the platform driver fills in the others. + */ +static struct clk_hw_onecell_data *ath79_clk_data; + +/* ref * mult / div, or 0 for an unset divider */ +static unsigned long ath79_ref_mul_div(unsigned long ref, u32 mult, u32 div) +{ + return div ? div_u64((u64)ref * mult, div) : 0; +} + +/* a fractional PLL: ref / ref_div * (nint + nfrac / frac) / 2^out_div */ +static unsigned long ath79_pll_rate(unsigned long ref, u32 ref_div, u32 nint, + u32 nfrac, u32 frac, u32 out_div) +{ + u64 rate; + + if (!ref_div) + return 0; + + rate = div_u64((u64)ref * nint, ref_div); + rate += div_u64((u64)ref * nfrac, ref_div * frac); + + return rate >> out_div; +} + +static int ar71xx_get_rates(const struct ath79_clk_soc_data *soc, + const struct ath79_clk_regs *regs, + unsigned long ref, unsigned long *rates) +{ + unsigned long freq; + u32 pll; + + pll = readl(regs->pll + AR71XX_PLL_CPU_CONFIG_REG); + + freq = ref * (FIELD_GET(AR71XX_PLL_FB, pll) + 1); + rates[ATH79_OUT_CPU] = freq / (FIELD_GET(AR71XX_CPU_DIV, pll) + 1); + rates[ATH79_OUT_DDR] = freq / (FIELD_GET(AR71XX_DDR_DIV, pll) + 1); + rates[ATH79_OUT_AHB] = rates[ATH79_OUT_CPU] / + ((FIELD_GET(AR71XX_AHB_DIV, pll) + 1) * 2); + + return 0; +} + +static int ar724x_get_rates(const struct ath79_clk_soc_data *soc, + const struct ath79_clk_regs *regs, + unsigned long ref, unsigned long *rates) +{ + u32 pll, mult, div, ddr_div, ahb_div; + + pll = readl(regs->pll + AR724X_PLL_CPU_CONFIG_REG); + + mult = FIELD_GET(AR724X_PLL_FB, pll); + div = FIELD_GET(AR724X_PLL_REF_DIV, pll) * 2; + ddr_div = FIELD_GET(AR724X_DDR_DIV, pll) + 1; + ahb_div = (FIELD_GET(AR724X_AHB_DIV, pll) + 1) * 2; + + rates[ATH79_OUT_CPU] = ath79_ref_mul_div(ref, mult, div); + rates[ATH79_OUT_DDR] = ath79_ref_mul_div(ref, mult, div * ddr_div); + rates[ATH79_OUT_AHB] = ath79_ref_mul_div(ref, mult, div * ahb_div); + + return 0; +} + +static int ar933x_get_rates(const struct ath79_clk_soc_data *soc, + const struct ath79_clk_regs *regs, + unsigned long ref, unsigned long *rates) +{ + u32 ctrl, pll, nint, div, out_div, cpu_div, ddr_div, ahb_div; + + ctrl = readl(regs->pll + AR933X_PLL_CLOCK_CTRL_REG); + if (ctrl & AR933X_PLL_CLOCK_CTRL_BYPASS) { + rates[ATH79_OUT_CPU] = ref; + rates[ATH79_OUT_DDR] = ref; + rates[ATH79_OUT_AHB] = ref; + return 0; + } + + pll = readl(regs->pll + AR933X_PLL_CPU_CONFIG_REG); + + nint = FIELD_GET(AR933X_PLL_CPU_CONFIG_NINT, pll); + out_div = FIELD_GET(AR933X_PLL_CPU_CONFIG_OUTDIV, pll); + div = FIELD_GET(AR933X_PLL_CPU_CONFIG_REFDIV, pll) << max(out_div, 1U); + cpu_div = FIELD_GET(AR933X_PLL_CLOCK_CTRL_CPU_DIV, ctrl) + 1; + ddr_div = FIELD_GET(AR933X_PLL_CLOCK_CTRL_DDR_DIV, ctrl) + 1; + ahb_div = FIELD_GET(AR933X_PLL_CLOCK_CTRL_AHB_DIV, ctrl) + 1; + + rates[ATH79_OUT_CPU] = ath79_ref_mul_div(ref, nint, div * cpu_div); + rates[ATH79_OUT_DDR] = ath79_ref_mul_div(ref, nint, div * ddr_div); + rates[ATH79_OUT_AHB] = ath79_ref_mul_div(ref, nint, div * ahb_div); + + return 0; +} + +/* the CPU, DDR and AHB clocks from CPU_DDR_CLOCK_CONTROL */ +static void ath79_clk_ctrl_rates(const struct ath79_clk_soc_data *soc, + u32 ctrl, unsigned long ref, + unsigned long cpu_pll, unsigned long ddr_pll, + unsigned long *rates) +{ + const struct ath79_clk_ctrl_sel *sel; + unsigned long pll; + u32 post_div; + int i; + + for (i = 0; i < ATH79_OUT_NUM; i++) { + if (ctrl & ATH79_CLK_CTRL_BYPASS(i)) { + rates[i] = ref; + continue; + } + + sel = &soc->sel[i]; + pll = !!(ctrl & sel->bit) == sel->set_is_cpu_pll ? cpu_pll + : ddr_pll; + post_div = (ctrl >> ATH79_CLK_CTRL_POST_DIV_SHIFT(i)) & + ATH79_CLK_CTRL_POST_DIV_MASK; + rates[i] = pll / (post_div + 1); + } +} + +/* the AR934x DPLLs in the SRIF block, when they drive the CPU or DDR clock */ +static bool ar934x_srif_pll_rate(const struct ath79_clk_regs *regs, + unsigned int dpll1, unsigned int dpll2, + unsigned long ref, unsigned long *rate) +{ + u32 pll, out_div; + + if (!regs->srif) + return false; + + pll = readl(regs->srif + dpll2); + if (!(pll & AR934X_SRIF_DPLL2_LOCAL_PLL)) + return false; + + out_div = FIELD_GET(AR934X_SRIF_DPLL2_OUTDIV, pll); + pll = readl(regs->srif + dpll1); + *rate = ath79_pll_rate(ref, FIELD_GET(AR934X_SRIF_DPLL1_REFDIV, pll), + FIELD_GET(AR934X_SRIF_DPLL1_NINT, pll), + FIELD_GET(AR934X_SRIF_DPLL1_NFRAC, pll), + 1 << 18, out_div); + return true; +} + +/* AR934x, QCA953x and QCA955x */ +static int ar934x_get_rates(const struct ath79_clk_soc_data *soc, + const struct ath79_clk_regs *regs, + unsigned long ref, unsigned long *rates) +{ + unsigned long cpu_pll, ddr_pll; + u32 pll, ref_div, nint, nfrac, out_div; + + if (!ar934x_srif_pll_rate(regs, AR934X_SRIF_CPU_DPLL1_REG, + AR934X_SRIF_CPU_DPLL2_REG, ref, &cpu_pll)) { + pll = readl(regs->pll + AR934X_PLL_CPU_CONFIG_REG); + ref_div = FIELD_GET(AR934X_PLL_CPU_CONFIG_REFDIV, pll); + nint = FIELD_GET(AR934X_PLL_CPU_CONFIG_NINT, pll); + nfrac = FIELD_GET(AR934X_PLL_CPU_CONFIG_NFRAC, pll); + out_div = (pll & soc->cpu_outdiv) >> __ffs(soc->cpu_outdiv); + cpu_pll = ath79_pll_rate(ref, ref_div, nint, nfrac, 1 << 6, + out_div); + } + + if (!ar934x_srif_pll_rate(regs, AR934X_SRIF_DDR_DPLL1_REG, + AR934X_SRIF_DDR_DPLL2_REG, ref, &ddr_pll)) { + pll = readl(regs->pll + AR934X_PLL_DDR_CONFIG_REG); + ref_div = FIELD_GET(AR934X_PLL_DDR_CONFIG_REFDIV, pll); + nint = FIELD_GET(AR934X_PLL_DDR_CONFIG_NINT, pll); + nfrac = FIELD_GET(AR934X_PLL_DDR_CONFIG_NFRAC, pll); + out_div = FIELD_GET(AR934X_PLL_DDR_CONFIG_OUTDIV, pll); + ddr_pll = ath79_pll_rate(ref, ref_div, nint, nfrac, 1 << 10, + out_div); + } + + ath79_clk_ctrl_rates(soc, readl(regs->pll + AR934X_PLL_CLK_CTRL_REG), + ref, cpu_pll, ddr_pll, rates); + return 0; +} + +static unsigned long qca956x_pll_rate(unsigned long ref, u32 config, + u32 config1, u32 refdiv_mask, + u32 outdiv_mask) +{ + u32 ref_div, out_div, nint, hfrac, lfrac; + u64 rate; + + ref_div = (config & refdiv_mask) >> __ffs(refdiv_mask); + out_div = (config & outdiv_mask) >> __ffs(outdiv_mask); + nint = FIELD_GET(QCA956X_PLL_CONFIG1_NINT, config1); + hfrac = FIELD_GET(QCA956X_PLL_CONFIG1_NFRAC_H, config1); + lfrac = FIELD_GET(QCA956X_PLL_CONFIG1_NFRAC_L, config1); + + if (!ref_div) + return 0; + + rate = div_u64((u64)ref * nint, ref_div); + rate += div_u64((u64)ref * lfrac, (ref_div * 25) << 13); + rate += div_u64((u64)ref * (hfrac >> 13), ref_div); + + return rate >> out_div; +} + +static int qca956x_get_rates(const struct ath79_clk_soc_data *soc, + const struct ath79_clk_regs *regs, + unsigned long ref, unsigned long *rates) +{ + unsigned long cpu_pll, ddr_pll; + + cpu_pll = qca956x_pll_rate(ref, + readl(regs->pll + QCA956X_PLL_CPU_CONFIG_REG), + readl(regs->pll + QCA956X_PLL_CPU_CONFIG1_REG), + QCA956X_PLL_CPU_CONFIG_REFDIV, + QCA956X_PLL_CPU_CONFIG_OUTDIV); + ddr_pll = qca956x_pll_rate(ref, + readl(regs->pll + QCA956X_PLL_DDR_CONFIG_REG), + readl(regs->pll + QCA956X_PLL_DDR_CONFIG1_REG), + QCA956X_PLL_DDR_CONFIG_REFDIV, + QCA956X_PLL_DDR_CONFIG_OUTDIV); + + ath79_clk_ctrl_rates(soc, readl(regs->pll + QCA956X_PLL_CLK_CTRL_REG), + ref, cpu_pll, ddr_pll, rates); + return 0; +} + +static int ath79_get_rates(const struct ath79_clk_soc_data *soc, + const struct ath79_clk_regs *regs, unsigned long ref, + unsigned long *rates) +{ + int ret, i; + + ret = soc->get_rates(soc, regs, ref, rates); + if (ret) + return ret; + + /* a zero divider in the registers */ + for (i = 0; i < ATH79_OUT_NUM; i++) + if (!rates[i]) + return -EINVAL; + + return 0; +} + +static int ath79_pcie_pll_wait(struct ath79_pcie_pll *pll) +{ + u32 t; + + return readl_poll_timeout(pll->reg, t, !(t & pll->updating), 10, 1000); +} + +static int ath79_pcie_pll_is_prepared(struct clk_hw *hw) +{ + struct ath79_pcie_pll *pll = to_ath79_pcie_pll(hw); + + return !(readl(pll->reg) & (pll->pwd | pll->bypass)); +} + +static int ath79_pcie_pll_prepare(struct clk_hw *hw) +{ + struct ath79_pcie_pll *pll = to_ath79_pcie_pll(hw); + u32 t; + int ret; + + /* + * Every write freezes the high speed clocks for about 60 us, so + * leave a PLL that the bootloader already started alone. + */ + if (ath79_pcie_pll_is_prepared(hw)) + return 0; + + t = readl(pll->reg) & ~pll->pwd; + writel(t, pll->reg); + ret = ath79_pcie_pll_wait(pll); + if (ret) + return ret; + + writel(t & ~pll->bypass, pll->reg); + return ath79_pcie_pll_wait(pll); +} + +static void ath79_pcie_pll_unprepare(struct clk_hw *hw) +{ + struct ath79_pcie_pll *pll = to_ath79_pcie_pll(hw); + u32 t; + + t = readl(pll->reg) | pll->bypass; + writel(t, pll->reg); + if (ath79_pcie_pll_wait(pll)) + return; + + writel(t | pll->pwd, pll->reg); + ath79_pcie_pll_wait(pll); +} + +static const struct clk_ops ath79_pcie_pll_ops = { + .prepare = ath79_pcie_pll_prepare, + .unprepare = ath79_pcie_pll_unprepare, + .is_prepared = ath79_pcie_pll_is_prepared, +}; + +static struct clk_hw *ath79_pcie_pll_register(struct device *dev, + void __iomem *base, + const struct ath79_pcie_pll_desc *desc) +{ + struct clk_init_data init = { + .name = "pcie", + .ops = &ath79_pcie_pll_ops, + /* keep a PLL that no driver claims as the bootloader left it */ + .flags = CLK_IGNORE_UNUSED, + }; + struct ath79_pcie_pll *pll; + int ret; + + pll = devm_kzalloc_obj(dev, *pll); + if (!pll) + return ERR_PTR(-ENOMEM); + + pll->reg = base + desc->reg; + pll->pwd = desc->pwd; + pll->bypass = desc->bypass; + pll->updating = desc->updating; + pll->hw.init = &init; + + ret = devm_clk_hw_register(dev, &pll->hw); + if (ret) + return ERR_PTR(ret); + + return &pll->hw; +} + +/* AR724x holds the PLL in reset instead of powering it down */ +static const struct ath79_pcie_pll_desc ar724x_pcie_pll = { + .reg = AR724X_PLL_PCIE_CONFIG_REG, + .pwd = AR724X_PLL_PCIE_CONFIG_PLL_RESET, + .bypass = AR724X_PLL_PCIE_CONFIG_PLL_BYPASS, + .updating = AR724X_PLL_PCIE_CONFIG_UPDATING, +}; + +#define AR934X_PCIE_PLL(_reg) \ + { \ + .reg = (_reg), \ + .pwd = AR934X_PLL_PCIE_CONFIG_PLL_PWD, \ + .bypass = AR934X_PLL_PCIE_CONFIG_PLL_BYPASS, \ + .updating = AR934X_PLL_PCIE_CONFIG_UPDATING, \ + } + +static const struct ath79_pcie_pll_desc ar934x_pcie_pll = + AR934X_PCIE_PLL(AR934X_PLL_PCIE_CONFIG_REG); +static const struct ath79_pcie_pll_desc qca955x_pcie_pll = + AR934X_PCIE_PLL(QCA955X_PLL_PCIE_CONFIG_REG); +static const struct ath79_pcie_pll_desc qca956x_pcie_pll = + AR934X_PCIE_PLL(QCA956X_PLL_PCIE_CONFIG_REG); + +static const struct ath79_clk_soc_data ar7100_clk_data = { + .get_rates = ar71xx_get_rates, +}; + +static const struct ath79_clk_soc_data ar7240_clk_data = { + .get_rates = ar724x_get_rates, + .pcie = &ar724x_pcie_pll, +}; + +/* AR913x is an AR724x without PCIe */ +static const struct ath79_clk_soc_data ar9130_clk_data = { + .get_rates = ar724x_get_rates, +}; + +static const struct ath79_clk_soc_data ar9330_clk_data = { + .get_rates = ar933x_get_rates, +}; + +static const struct ath79_clk_soc_data ar9340_clk_data = { + .get_rates = ar934x_get_rates, + .cpu_outdiv = AR934X_PLL_CPU_CONFIG_OUTDIV, + .sel = { + [ATH79_OUT_CPU] = { BIT(20), true }, + [ATH79_OUT_DDR] = { BIT(21), false }, + [ATH79_OUT_AHB] = { BIT(24), false }, + }, + .pcie = &ar934x_pcie_pll, + .has_srif = true, + .has_switch_clk = true, +}; + +static const struct ath79_clk_soc_data qca9530_clk_data = { + .get_rates = ar934x_get_rates, + .cpu_outdiv = QCA953X_PLL_CPU_CONFIG_OUTDIV, + .sel = { + [ATH79_OUT_CPU] = { BIT(20), true }, + [ATH79_OUT_DDR] = { BIT(21), false }, + [ATH79_OUT_AHB] = { BIT(24), false }, + }, + .pcie = &ar934x_pcie_pll, +}; + +static const struct ath79_clk_soc_data qca9550_clk_data = { + .get_rates = ar934x_get_rates, + .cpu_outdiv = AR934X_PLL_CPU_CONFIG_OUTDIV, + .sel = { + [ATH79_OUT_CPU] = { BIT(20), false }, + [ATH79_OUT_DDR] = { BIT(21), true }, + [ATH79_OUT_AHB] = { BIT(24), false }, + }, + .pcie = &qca955x_pcie_pll, +}; + +static const struct ath79_clk_soc_data qca9560_clk_data = { + .get_rates = qca956x_get_rates, + .sel = { + [ATH79_OUT_CPU] = { BIT(21), false }, + [ATH79_OUT_DDR] = { BIT(20), true }, + [ATH79_OUT_AHB] = { BIT(24), false }, + }, + .pcie = &qca956x_pcie_pll, +}; + +static const struct of_device_id ath79_clk_of_match[] = { + { .compatible = "qca,ar7100-pll", .data = &ar7100_clk_data }, + { .compatible = "qca,ar7240-pll", .data = &ar7240_clk_data }, + { .compatible = "qca,ar9130-pll", .data = &ar9130_clk_data }, + { .compatible = "qca,ar9330-pll", .data = &ar9330_clk_data }, + { .compatible = "qca,ar9340-pll", .data = &ar9340_clk_data }, + { .compatible = "qca,qca9530-pll", .data = &qca9530_clk_data }, + { .compatible = "qca,qca9550-pll", .data = &qca9550_clk_data }, + { .compatible = "qca,qca9560-pll", .data = &qca9560_clk_data }, + { /* sentinel */ } +}; + +static void __init ath79_clk_early_init(struct device_node *np) +{ + const struct ath79_clk_soc_data *soc; + unsigned long rates[ATH79_OUT_NUM]; + struct ath79_clk_regs regs = {}; + struct clk_hw *ref, *cpu; + struct clk *ref_clk; + int ret, i; + + soc = of_match_node(ath79_clk_of_match, np)->data; + + /* + * The board's crystal. When the device tree leaves its rate out, the + * arch code registers it from the bootstrap pins. + */ + ref_clk = of_clk_get_by_name(np, "ref"); + if (IS_ERR(ref_clk)) { + pr_err("%pOF: no reference clock: %pe\n", np, ref_clk); + return; + } + ref = __clk_get_hw(ref_clk); + + regs.pll = of_iomap(np, 0); + if (!regs.pll) { + pr_err("%pOF: can't map the PLL registers\n", np); + goto err_put_ref; + } + + if (soc->has_srif) { + regs.srif = ioremap(AR934X_SRIF_BASE, AR934X_SRIF_SIZE); + if (!regs.srif) { + pr_err("%pOF: can't map the SRIF registers\n", np); + iounmap(regs.pll); + goto err_put_ref; + } + } + + ret = ath79_get_rates(soc, ®s, clk_hw_get_rate(ref), rates); + + if (regs.srif) + iounmap(regs.srif); + iounmap(regs.pll); + + if (ret) { + pr_err("%pOF: invalid PLL setup\n", np); + goto err_put_ref; + } + + ath79_clk_data = kzalloc_flex(*ath79_clk_data, hws, ATH79_CLK_NUM); + if (!ath79_clk_data) + goto err_put_ref; + + ath79_clk_data->num = ATH79_CLK_NUM; + for (i = 0; i < ATH79_CLK_NUM; i++) + ath79_clk_data->hws[i] = ERR_PTR(-EPROBE_DEFER); + + cpu = clk_hw_register_fixed_rate_parent_hw(NULL, "cpu", ref, 0, + rates[ATH79_OUT_CPU]); + if (IS_ERR(cpu)) { + pr_err("%pOF: failed to register the CPU clock: %pe\n", np, cpu); + goto err_free_data; + } + + ath79_clk_data->hws[ATH79_CLK_REF] = ref; + ath79_clk_data->hws[ATH79_CLK_CPU] = cpu; + + ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, ath79_clk_data); + if (ret) { + pr_err("%pOF: failed to add the clock provider: %pe\n", np, + ERR_PTR(ret)); + clk_hw_unregister_fixed_rate(cpu); + goto err_free_data; + } + + return; + +err_free_data: + kfree(ath79_clk_data); + ath79_clk_data = NULL; +err_put_ref: + clk_put(ref_clk); +} + +CLK_OF_DECLARE_DRIVER(ar7100_clk, "qca,ar7100-pll", ath79_clk_early_init); +CLK_OF_DECLARE_DRIVER(ar7240_clk, "qca,ar7240-pll", ath79_clk_early_init); +CLK_OF_DECLARE_DRIVER(ar9130_clk, "qca,ar9130-pll", ath79_clk_early_init); +CLK_OF_DECLARE_DRIVER(ar9330_clk, "qca,ar9330-pll", ath79_clk_early_init); +CLK_OF_DECLARE_DRIVER(ar9340_clk, "qca,ar9340-pll", ath79_clk_early_init); +CLK_OF_DECLARE_DRIVER(qca9530_clk, "qca,qca9530-pll", ath79_clk_early_init); +CLK_OF_DECLARE_DRIVER(qca9550_clk, "qca,qca9550-pll", ath79_clk_early_init); +CLK_OF_DECLARE_DRIVER(qca9560_clk, "qca,qca9560-pll", ath79_clk_early_init); + +static struct clk_hw *ath79_clk_register(struct device *dev, const char *name, + struct clk_hw *parent, + unsigned long rate) +{ + struct clk_parent_data pdata = { .hw = parent }; + + return devm_clk_hw_register_fixed_rate_parent_data(dev, name, + parent ? &pdata : NULL, + 0, rate); +} + +static int ath79_clk_probe(struct platform_device *pdev) +{ + struct clk_hw *hws[ATH79_CLK_NUM]; + const struct ath79_clk_soc_data *soc; + unsigned long rates[ATH79_OUT_NUM]; + struct device *dev = &pdev->dev; + struct ath79_clk_regs regs = {}; + struct clk_hw *ref; + int ret, i; + + if (!ath79_clk_data) + return dev_err_probe(dev, -ENODEV, + "the early clock setup failed\n"); + + soc = device_get_match_data(dev); + ref = ath79_clk_data->hws[ATH79_CLK_REF]; + + regs.pll = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs.pll)) + return PTR_ERR(regs.pll); + + if (soc->has_srif) { + regs.srif = devm_ioremap(dev, AR934X_SRIF_BASE, + AR934X_SRIF_SIZE); + if (!regs.srif) + return -ENOMEM; + } + + ret = ath79_get_rates(soc, ®s, clk_hw_get_rate(ref), rates); + if (ret) + return dev_err_probe(dev, ret, "invalid PLL setup\n"); + + hws[ATH79_CLK_REF] = ref; + hws[ATH79_CLK_CPU] = ath79_clk_data->hws[ATH79_CLK_CPU]; + hws[ATH79_CLK_DDR] = ath79_clk_register(dev, "ddr", ref, + rates[ATH79_OUT_DDR]); + hws[ATH79_CLK_AHB] = ath79_clk_register(dev, "ahb", ref, + rates[ATH79_OUT_AHB]); + hws[ATH79_CLK_MDIO] = ref; + hws[ATH79_CLK_UART1] = ref; + hws[ATH79_CLK_PCIE] = ERR_PTR(-ENOENT); + + if (soc->has_switch_clk) { + u32 ctrl = readl(regs.pll + AR934X_PLL_SWITCH_CLOCK_CONTROL_REG); + + if (ctrl & AR934X_PLL_SWITCH_CLOCK_CONTROL_MDIO_CLK_SEL) + hws[ATH79_CLK_MDIO] = + ath79_clk_register(dev, "mdio", NULL, + ATH79_SWITCH_CLK_RATE); + + if (ctrl & AR934X_PLL_SWITCH_CLOCK_CONTROL_UART1_CLK_SEL) + hws[ATH79_CLK_UART1] = + ath79_clk_register(dev, "uart1", NULL, + ATH79_SWITCH_CLK_RATE); + } + + if (soc->pcie) + hws[ATH79_CLK_PCIE] = ath79_pcie_pll_register(dev, regs.pll, + soc->pcie); + + for (i = 0; i < ATH79_CLK_NUM; i++) { + if (IS_ERR(hws[i]) && PTR_ERR(hws[i]) != -ENOENT) + return dev_err_probe(dev, PTR_ERR(hws[i]), + "failed to register clock %d\n", i); + } + + /* + * Hand the clocks to the provider registered early. Nothing can + * fail from here on, and the driver can't be unbound. + */ + for (i = 0; i < ATH79_CLK_NUM; i++) + ath79_clk_data->hws[i] = hws[i]; + + return 0; +} + +static struct platform_driver ath79_clk_driver = { + .probe = ath79_clk_probe, + .driver = { + .name = "ath79-clk", + .of_match_table = ath79_clk_of_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(ath79_clk_driver); -- 2.56.0