aboutsummaryrefslogtreecommitdiff
path: root/drivers/clk/clk-aspeed.c
diff options
context:
space:
mode:
authorLibravatar Linus Torvalds <torvalds@linux-foundation.org>2023-02-21 18:24:12 -0800
committerLibravatar Linus Torvalds <torvalds@linux-foundation.org>2023-02-21 18:24:12 -0800
commit5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch)
treecc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/clk/clk-aspeed.c
downloadlinux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz
linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski: "Core: - Add dedicated kmem_cache for typical/small skb->head, avoid having to access struct page at kfree time, and improve memory use. - Introduce sysctl to set default RPS configuration for new netdevs. - Define Netlink protocol specification format which can be used to describe messages used by each family and auto-generate parsers. Add tools for generating kernel data structures and uAPI headers. - Expose all net/core sysctls inside netns. - Remove 4s sleep in netpoll if carrier is instantly detected on boot. - Add configurable limit of MDB entries per port, and port-vlan. - Continue populating drop reasons throughout the stack. - Retire a handful of legacy Qdiscs and classifiers. Protocols: - Support IPv4 big TCP (TSO frames larger than 64kB). - Add IP_LOCAL_PORT_RANGE socket option, to control local port range on socket by socket basis. - Track and report in procfs number of MPTCP sockets used. - Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path manager. - IPv6: don't check net.ipv6.route.max_size and rely on garbage collection to free memory (similarly to IPv4). - Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986). - ICMP: add per-rate limit counters. - Add support for user scanning requests in ieee802154. - Remove static WEP support. - Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate reporting. - WiFi 7 EHT channel puncturing support (client & AP). BPF: - Add a rbtree data structure following the "next-gen data structure" precedent set by recently added linked list, that is, by using kfunc + kptr instead of adding a new BPF map type. - Expose XDP hints via kfuncs with initial support for RX hash and timestamp metadata. - Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to better support decap on GRE tunnel devices not operating in collect metadata. - Improve x86 JIT's codegen for PROBE_MEM runtime error checks. - Remove the need for trace_printk_lock for bpf_trace_printk and bpf_trace_vprintk helpers. - Extend libbpf's bpf_tracing.h support for tracing arguments of kprobes/uprobes and syscall as a special case. - Significantly reduce the search time for module symbols by livepatch and BPF. - Enable cpumasks to be used as kptrs, which is useful for tracing programs tracking which tasks end up running on which CPUs in different time intervals. - Add support for BPF trampoline on s390x and riscv64. - Add capability to export the XDP features supported by the NIC. - Add __bpf_kfunc tag for marking kernel functions as kfuncs. - Add cgroup.memory=nobpf kernel parameter option to disable BPF memory accounting for container environments. Netfilter: - Remove the CLUSTERIP target. It has been marked as obsolete for years, and we still have WARN splats wrt races of the out-of-band /proc interface installed by this target. - Add 'destroy' commands to nf_tables. They are identical to the existing 'delete' commands, but do not return an error if the referenced object (set, chain, rule...) did not exist. Driver API: - Improve cpumask_local_spread() locality to help NICs set the right IRQ affinity on AMD platforms. - Separate C22 and C45 MDIO bus transactions more clearly. - Introduce new DCB table to control DSCP rewrite on egress. - Support configuration of Physical Layer Collision Avoidance (PLCA) Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of shared medium Ethernet. - Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing preemption of low priority frames by high priority frames. - Add support for controlling MACSec offload using netlink SET. - Rework devlink instance refcounts to allow registration and de-registration under the instance lock. Split the code into multiple files, drop some of the unnecessarily granular locks and factor out common parts of netlink operation handling. - Add TX frame aggregation parameters (for USB drivers). - Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning messages with notifications for debug. - Allow offloading of UDP NEW connections via act_ct. - Add support for per action HW stats in TC. - Support hardware miss to TC action (continue processing in SW from a specific point in the action chain). - Warn if old Wireless Extension user space interface is used with modern cfg80211/mac80211 drivers. Do not support Wireless Extensions for Wi-Fi 7 devices at all. Everyone should switch to using nl80211 interface instead. - Improve the CAN bit timing configuration. Use extack to return error messages directly to user space, update the SJW handling, including the definition of a new default value that will benefit CAN-FD controllers, by increasing their oscillator tolerance. New hardware / drivers: - Ethernet: - nVidia BlueField-3 support (control traffic driver) - Ethernet support for imx93 SoCs - Motorcomm yt8531 gigabit Ethernet PHY - onsemi NCN26000 10BASE-T1S PHY (with support for PLCA) - Microchip LAN8841 PHY (incl. cable diagnostics and PTP) - Amlogic gxl MDIO mux - WiFi: - RealTek RTL8188EU (rtl8xxxu) - Qualcomm Wi-Fi 7 devices (ath12k) - CAN: - Renesas R-Car V4H Drivers: - Bluetooth: - Set Per Platform Antenna Gain (PPAG) for Intel controllers. - Ethernet NICs: - Intel (1G, igc): - support TSN / Qbv / packet scheduling features of i226 model - Intel (100G, ice): - use GNSS subsystem instead of TTY - multi-buffer XDP support - extend support for GPIO pins to E823 devices - nVidia/Mellanox: - update the shared buffer configuration on PFC commands - implement PTP adjphase function for HW offset control - TC support for Geneve and GRE with VF tunnel offload - more efficient crypto key management method - multi-port eswitch support - Netronome/Corigine: - add DCB IEEE support - support IPsec offloading for NFP3800 - Freescale/NXP (enetc): - support XDP_REDIRECT for XDP non-linear buffers - improve reconfig, avoid link flap and waiting for idle - support MAC Merge layer - Other NICs: - sfc/ef100: add basic devlink support for ef100 - ionic: rx_push mode operation (writing descriptors via MMIO) - bnxt: use the auxiliary bus abstraction for RDMA - r8169: disable ASPM and reset bus in case of tx timeout - cpsw: support QSGMII mode for J721e CPSW9G - cpts: support pulse-per-second output - ngbe: add an mdio bus driver - usbnet: optimize usbnet_bh() by avoiding unnecessary queuing - r8152: handle devices with FW with NCM support - amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation - virtio-net: support multi buffer XDP - virtio/vsock: replace virtio_vsock_pkt with sk_buff - tsnep: XDP support - Ethernet high-speed switches: - nVidia/Mellanox (mlxsw): - add support for latency TLV (in FW control messages) - Microchip (sparx5): - separate explicit and implicit traffic forwarding rules, make the implicit rules always active - add support for egress DSCP rewrite - IS0 VCAP support (Ingress Classification) - IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS etc.) - ES2 VCAP support (Egress Access Control) - support for Per-Stream Filtering and Policing (802.1Q, 8.6.5.1) - Ethernet embedded switches: - Marvell (mv88e6xxx): - add MAB (port auth) offload support - enable PTP receive for mv88e6390 - NXP (ocelot): - support MAC Merge layer - support for the the vsc7512 internal copper phys - Microchip: - lan9303: convert to PHYLINK - lan966x: support TC flower filter statistics - lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x - lan937x: support Credit Based Shaper configuration - ksz9477: support Energy Efficient Ethernet - other: - qca8k: convert to regmap read/write API, use bulk operations - rswitch: Improve TX timestamp accuracy - Intel WiFi (iwlwifi): - EHT (Wi-Fi 7) rate reporting - STEP equalizer support: transfer some STEP (connection to radio on platforms with integrated wifi) related parameters from the BIOS to the firmware. - Qualcomm 802.11ax WiFi (ath11k): - IPQ5018 support - Fine Timing Measurement (FTM) responder role support - channel 177 support - MediaTek WiFi (mt76): - per-PHY LED support - mt7996: EHT (Wi-Fi 7) support - Wireless Ethernet Dispatch (WED) reset support - switch to using page pool allocator - RealTek WiFi (rtw89): - support new version of Bluetooth co-existance - Mobile: - rmnet: support TX aggregation" * tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits) page_pool: add a comment explaining the fragment counter usage net: ethtool: fix __ethtool_dev_mm_supported() implementation ethtool: pse-pd: Fix double word in comments xsk: add linux/vmalloc.h to xsk.c sefltests: netdevsim: wait for devlink instance after netns removal selftest: fib_tests: Always cleanup before exit net/mlx5e: Align IPsec ASO result memory to be as required by hardware net/mlx5e: TC, Set CT miss to the specific ct action instance net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG net/mlx5: Refactor tc miss handling to a single function net/mlx5: Kconfig: Make tc offload depend on tc skb extension net/sched: flower: Support hardware miss to tc action net/sched: flower: Move filter handle initialization earlier net/sched: cls_api: Support hardware miss to tc action net/sched: Rename user cookie and act cookie sfc: fix builds without CONFIG_RTC_LIB sfc: clean up some inconsistent indentings net/mlx4_en: Introduce flexible array to silence overflow warning net: lan966x: Fix possible deadlock inside PTP net/ulp: Remove redundant ->clone() test in inet_clone_ulp(). ...
Diffstat (limited to 'drivers/clk/clk-aspeed.c')
-rw-r--r--drivers/clk/clk-aspeed.c742
1 files changed, 742 insertions, 0 deletions
diff --git a/drivers/clk/clk-aspeed.c b/drivers/clk/clk-aspeed.c
new file mode 100644
index 000000000..411ff5fb2
--- /dev/null
+++ b/drivers/clk/clk-aspeed.c
@@ -0,0 +1,742 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright IBM Corp
+
+#define pr_fmt(fmt) "clk-aspeed: " fmt
+
+#include <linux/mfd/syscon.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/aspeed-clock.h>
+
+#include "clk-aspeed.h"
+
+#define ASPEED_NUM_CLKS 38
+
+#define ASPEED_RESET2_OFFSET 32
+
+#define ASPEED_RESET_CTRL 0x04
+#define ASPEED_CLK_SELECTION 0x08
+#define ASPEED_CLK_STOP_CTRL 0x0c
+#define ASPEED_MPLL_PARAM 0x20
+#define ASPEED_HPLL_PARAM 0x24
+#define AST2500_HPLL_BYPASS_EN BIT(20)
+#define AST2400_HPLL_PROGRAMMED BIT(18)
+#define AST2400_HPLL_BYPASS_EN BIT(17)
+#define ASPEED_MISC_CTRL 0x2c
+#define UART_DIV13_EN BIT(12)
+#define ASPEED_MAC_CLK_DLY 0x48
+#define ASPEED_STRAP 0x70
+#define CLKIN_25MHZ_EN BIT(23)
+#define AST2400_CLK_SOURCE_SEL BIT(18)
+#define ASPEED_CLK_SELECTION_2 0xd8
+#define ASPEED_RESET_CTRL2 0xd4
+
+/* Globally visible clocks */
+static DEFINE_SPINLOCK(aspeed_clk_lock);
+
+/* Keeps track of all clocks */
+static struct clk_hw_onecell_data *aspeed_clk_data;
+
+static void __iomem *scu_base;
+
+/* TODO: ask Aspeed about the actual parent data */
+static const struct aspeed_gate_data aspeed_gates[] = {
+ /* clk rst name parent flags */
+ [ASPEED_CLK_GATE_ECLK] = { 0, 6, "eclk-gate", "eclk", 0 }, /* Video Engine */
+ [ASPEED_CLK_GATE_GCLK] = { 1, 7, "gclk-gate", NULL, 0 }, /* 2D engine */
+ [ASPEED_CLK_GATE_MCLK] = { 2, -1, "mclk-gate", "mpll", CLK_IS_CRITICAL }, /* SDRAM */
+ [ASPEED_CLK_GATE_VCLK] = { 3, -1, "vclk-gate", NULL, 0 }, /* Video Capture */
+ [ASPEED_CLK_GATE_BCLK] = { 4, 8, "bclk-gate", "bclk", CLK_IS_CRITICAL }, /* PCIe/PCI */
+ [ASPEED_CLK_GATE_DCLK] = { 5, -1, "dclk-gate", NULL, CLK_IS_CRITICAL }, /* DAC */
+ [ASPEED_CLK_GATE_REFCLK] = { 6, -1, "refclk-gate", "clkin", CLK_IS_CRITICAL },
+ [ASPEED_CLK_GATE_USBPORT2CLK] = { 7, 3, "usb-port2-gate", NULL, 0 }, /* USB2.0 Host port 2 */
+ [ASPEED_CLK_GATE_LCLK] = { 8, 5, "lclk-gate", NULL, 0 }, /* LPC */
+ [ASPEED_CLK_GATE_USBUHCICLK] = { 9, 15, "usb-uhci-gate", NULL, 0 }, /* USB1.1 (requires port 2 enabled) */
+ [ASPEED_CLK_GATE_D1CLK] = { 10, 13, "d1clk-gate", NULL, 0 }, /* GFX CRT */
+ [ASPEED_CLK_GATE_YCLK] = { 13, 4, "yclk-gate", NULL, 0 }, /* HAC */
+ [ASPEED_CLK_GATE_USBPORT1CLK] = { 14, 14, "usb-port1-gate", NULL, 0 }, /* USB2 hub/USB2 host port 1/USB1.1 dev */
+ [ASPEED_CLK_GATE_UART1CLK] = { 15, -1, "uart1clk-gate", "uart", 0 }, /* UART1 */
+ [ASPEED_CLK_GATE_UART2CLK] = { 16, -1, "uart2clk-gate", "uart", 0 }, /* UART2 */
+ [ASPEED_CLK_GATE_UART5CLK] = { 17, -1, "uart5clk-gate", "uart", 0 }, /* UART5 */
+ [ASPEED_CLK_GATE_ESPICLK] = { 19, -1, "espiclk-gate", NULL, 0 }, /* eSPI */
+ [ASPEED_CLK_GATE_MAC1CLK] = { 20, 11, "mac1clk-gate", "mac", 0 }, /* MAC1 */
+ [ASPEED_CLK_GATE_MAC2CLK] = { 21, 12, "mac2clk-gate", "mac", 0 }, /* MAC2 */
+ [ASPEED_CLK_GATE_RSACLK] = { 24, -1, "rsaclk-gate", NULL, 0 }, /* RSA */
+ [ASPEED_CLK_GATE_UART3CLK] = { 25, -1, "uart3clk-gate", "uart", 0 }, /* UART3 */
+ [ASPEED_CLK_GATE_UART4CLK] = { 26, -1, "uart4clk-gate", "uart", 0 }, /* UART4 */
+ [ASPEED_CLK_GATE_SDCLK] = { 27, 16, "sdclk-gate", NULL, 0 }, /* SDIO/SD */
+ [ASPEED_CLK_GATE_LHCCLK] = { 28, -1, "lhclk-gate", "lhclk", 0 }, /* LPC master/LPC+ */
+};
+
+static const char * const eclk_parent_names[] = {
+ "mpll",
+ "hpll",
+ "dpll",
+};
+
+static const struct clk_div_table ast2500_eclk_div_table[] = {
+ { 0x0, 2 },
+ { 0x1, 2 },
+ { 0x2, 3 },
+ { 0x3, 4 },
+ { 0x4, 5 },
+ { 0x5, 6 },
+ { 0x6, 7 },
+ { 0x7, 8 },
+ { 0 }
+};
+
+static const struct clk_div_table ast2500_mac_div_table[] = {
+ { 0x0, 4 }, /* Yep, really. Aspeed confirmed this is correct */
+ { 0x1, 4 },
+ { 0x2, 6 },
+ { 0x3, 8 },
+ { 0x4, 10 },
+ { 0x5, 12 },
+ { 0x6, 14 },
+ { 0x7, 16 },
+ { 0 }
+};
+
+static const struct clk_div_table ast2400_div_table[] = {
+ { 0x0, 2 },
+ { 0x1, 4 },
+ { 0x2, 6 },
+ { 0x3, 8 },
+ { 0x4, 10 },
+ { 0x5, 12 },
+ { 0x6, 14 },
+ { 0x7, 16 },
+ { 0 }
+};
+
+static const struct clk_div_table ast2500_div_table[] = {
+ { 0x0, 4 },
+ { 0x1, 8 },
+ { 0x2, 12 },
+ { 0x3, 16 },
+ { 0x4, 20 },
+ { 0x5, 24 },
+ { 0x6, 28 },
+ { 0x7, 32 },
+ { 0 }
+};
+
+static struct clk_hw *aspeed_ast2400_calc_pll(const char *name, u32 val)
+{
+ unsigned int mult, div;
+
+ if (val & AST2400_HPLL_BYPASS_EN) {
+ /* Pass through mode */
+ mult = div = 1;
+ } else {
+ /* F = 24Mhz * (2-OD) * [(N + 2) / (D + 1)] */
+ u32 n = (val >> 5) & 0x3f;
+ u32 od = (val >> 4) & 0x1;
+ u32 d = val & 0xf;
+
+ mult = (2 - od) * (n + 2);
+ div = d + 1;
+ }
+ return clk_hw_register_fixed_factor(NULL, name, "clkin", 0,
+ mult, div);
+};
+
+static struct clk_hw *aspeed_ast2500_calc_pll(const char *name, u32 val)
+{
+ unsigned int mult, div;
+
+ if (val & AST2500_HPLL_BYPASS_EN) {
+ /* Pass through mode */
+ mult = div = 1;
+ } else {
+ /* F = clkin * [(M+1) / (N+1)] / (P + 1) */
+ u32 p = (val >> 13) & 0x3f;
+ u32 m = (val >> 5) & 0xff;
+ u32 n = val & 0x1f;
+
+ mult = (m + 1) / (n + 1);
+ div = p + 1;
+ }
+
+ return clk_hw_register_fixed_factor(NULL, name, "clkin", 0,
+ mult, div);
+}
+
+static const struct aspeed_clk_soc_data ast2500_data = {
+ .div_table = ast2500_div_table,
+ .eclk_div_table = ast2500_eclk_div_table,
+ .mac_div_table = ast2500_mac_div_table,
+ .calc_pll = aspeed_ast2500_calc_pll,
+};
+
+static const struct aspeed_clk_soc_data ast2400_data = {
+ .div_table = ast2400_div_table,
+ .eclk_div_table = ast2400_div_table,
+ .mac_div_table = ast2400_div_table,
+ .calc_pll = aspeed_ast2400_calc_pll,
+};
+
+static int aspeed_clk_is_enabled(struct clk_hw *hw)
+{
+ struct aspeed_clk_gate *gate = to_aspeed_clk_gate(hw);
+ u32 clk = BIT(gate->clock_idx);
+ u32 rst = BIT(gate->reset_idx);
+ u32 enval = (gate->flags & CLK_GATE_SET_TO_DISABLE) ? 0 : clk;
+ u32 reg;
+
+ /*
+ * If the IP is in reset, treat the clock as not enabled,
+ * this happens with some clocks such as the USB one when
+ * coming from cold reset. Without this, aspeed_clk_enable()
+ * will fail to lift the reset.
+ */
+ if (gate->reset_idx >= 0) {
+ regmap_read(gate->map, ASPEED_RESET_CTRL, &reg);
+ if (reg & rst)
+ return 0;
+ }
+
+ regmap_read(gate->map, ASPEED_CLK_STOP_CTRL, &reg);
+
+ return ((reg & clk) == enval) ? 1 : 0;
+}
+
+static int aspeed_clk_enable(struct clk_hw *hw)
+{
+ struct aspeed_clk_gate *gate = to_aspeed_clk_gate(hw);
+ unsigned long flags;
+ u32 clk = BIT(gate->clock_idx);
+ u32 rst = BIT(gate->reset_idx);
+ u32 enval;
+
+ spin_lock_irqsave(gate->lock, flags);
+
+ if (aspeed_clk_is_enabled(hw)) {
+ spin_unlock_irqrestore(gate->lock, flags);
+ return 0;
+ }
+
+ if (gate->reset_idx >= 0) {
+ /* Put IP in reset */
+ regmap_update_bits(gate->map, ASPEED_RESET_CTRL, rst, rst);
+
+ /* Delay 100us */
+ udelay(100);
+ }
+
+ /* Enable clock */
+ enval = (gate->flags & CLK_GATE_SET_TO_DISABLE) ? 0 : clk;
+ regmap_update_bits(gate->map, ASPEED_CLK_STOP_CTRL, clk, enval);
+
+ if (gate->reset_idx >= 0) {
+ /* A delay of 10ms is specified by the ASPEED docs */
+ mdelay(10);
+
+ /* Take IP out of reset */
+ regmap_update_bits(gate->map, ASPEED_RESET_CTRL, rst, 0);
+ }
+
+ spin_unlock_irqrestore(gate->lock, flags);
+
+ return 0;
+}
+
+static void aspeed_clk_disable(struct clk_hw *hw)
+{
+ struct aspeed_clk_gate *gate = to_aspeed_clk_gate(hw);
+ unsigned long flags;
+ u32 clk = BIT(gate->clock_idx);
+ u32 enval;
+
+ spin_lock_irqsave(gate->lock, flags);
+
+ enval = (gate->flags & CLK_GATE_SET_TO_DISABLE) ? clk : 0;
+ regmap_update_bits(gate->map, ASPEED_CLK_STOP_CTRL, clk, enval);
+
+ spin_unlock_irqrestore(gate->lock, flags);
+}
+
+static const struct clk_ops aspeed_clk_gate_ops = {
+ .enable = aspeed_clk_enable,
+ .disable = aspeed_clk_disable,
+ .is_enabled = aspeed_clk_is_enabled,
+};
+
+static const u8 aspeed_resets[] = {
+ /* SCU04 resets */
+ [ASPEED_RESET_XDMA] = 25,
+ [ASPEED_RESET_MCTP] = 24,
+ [ASPEED_RESET_ADC] = 23,
+ [ASPEED_RESET_JTAG_MASTER] = 22,
+ [ASPEED_RESET_MIC] = 18,
+ [ASPEED_RESET_PWM] = 9,
+ [ASPEED_RESET_PECI] = 10,
+ [ASPEED_RESET_I2C] = 2,
+ [ASPEED_RESET_AHB] = 1,
+
+ /*
+ * SCUD4 resets start at an offset to separate them from
+ * the SCU04 resets.
+ */
+ [ASPEED_RESET_CRT1] = ASPEED_RESET2_OFFSET + 5,
+};
+
+static int aspeed_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct aspeed_reset *ar = to_aspeed_reset(rcdev);
+ u32 reg = ASPEED_RESET_CTRL;
+ u32 bit = aspeed_resets[id];
+
+ if (bit >= ASPEED_RESET2_OFFSET) {
+ bit -= ASPEED_RESET2_OFFSET;
+ reg = ASPEED_RESET_CTRL2;
+ }
+
+ return regmap_update_bits(ar->map, reg, BIT(bit), 0);
+}
+
+static int aspeed_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct aspeed_reset *ar = to_aspeed_reset(rcdev);
+ u32 reg = ASPEED_RESET_CTRL;
+ u32 bit = aspeed_resets[id];
+
+ if (bit >= ASPEED_RESET2_OFFSET) {
+ bit -= ASPEED_RESET2_OFFSET;
+ reg = ASPEED_RESET_CTRL2;
+ }
+
+ return regmap_update_bits(ar->map, reg, BIT(bit), BIT(bit));
+}
+
+static int aspeed_reset_status(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct aspeed_reset *ar = to_aspeed_reset(rcdev);
+ u32 reg = ASPEED_RESET_CTRL;
+ u32 bit = aspeed_resets[id];
+ int ret, val;
+
+ if (bit >= ASPEED_RESET2_OFFSET) {
+ bit -= ASPEED_RESET2_OFFSET;
+ reg = ASPEED_RESET_CTRL2;
+ }
+
+ ret = regmap_read(ar->map, reg, &val);
+ if (ret)
+ return ret;
+
+ return !!(val & BIT(bit));
+}
+
+static const struct reset_control_ops aspeed_reset_ops = {
+ .assert = aspeed_reset_assert,
+ .deassert = aspeed_reset_deassert,
+ .status = aspeed_reset_status,
+};
+
+static struct clk_hw *aspeed_clk_hw_register_gate(struct device *dev,
+ const char *name, const char *parent_name, unsigned long flags,
+ struct regmap *map, u8 clock_idx, u8 reset_idx,
+ u8 clk_gate_flags, spinlock_t *lock)
+{
+ struct aspeed_clk_gate *gate;
+ struct clk_init_data init;
+ struct clk_hw *hw;
+ int ret;
+
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &aspeed_clk_gate_ops;
+ init.flags = flags;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.num_parents = parent_name ? 1 : 0;
+
+ gate->map = map;
+ gate->clock_idx = clock_idx;
+ gate->reset_idx = reset_idx;
+ gate->flags = clk_gate_flags;
+ gate->lock = lock;
+ gate->hw.init = &init;
+
+ hw = &gate->hw;
+ ret = clk_hw_register(dev, hw);
+ if (ret) {
+ kfree(gate);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+static int aspeed_clk_probe(struct platform_device *pdev)
+{
+ const struct aspeed_clk_soc_data *soc_data;
+ struct device *dev = &pdev->dev;
+ struct aspeed_reset *ar;
+ struct regmap *map;
+ struct clk_hw *hw;
+ u32 val, rate;
+ int i, ret;
+
+ map = syscon_node_to_regmap(dev->of_node);
+ if (IS_ERR(map)) {
+ dev_err(dev, "no syscon regmap\n");
+ return PTR_ERR(map);
+ }
+
+ ar = devm_kzalloc(dev, sizeof(*ar), GFP_KERNEL);
+ if (!ar)
+ return -ENOMEM;
+
+ ar->map = map;
+ ar->rcdev.owner = THIS_MODULE;
+ ar->rcdev.nr_resets = ARRAY_SIZE(aspeed_resets);
+ ar->rcdev.ops = &aspeed_reset_ops;
+ ar->rcdev.of_node = dev->of_node;
+
+ ret = devm_reset_controller_register(dev, &ar->rcdev);
+ if (ret) {
+ dev_err(dev, "could not register reset controller\n");
+ return ret;
+ }
+
+ /* SoC generations share common layouts but have different divisors */
+ soc_data = of_device_get_match_data(dev);
+ if (!soc_data) {
+ dev_err(dev, "no match data for platform\n");
+ return -EINVAL;
+ }
+
+ /* UART clock div13 setting */
+ regmap_read(map, ASPEED_MISC_CTRL, &val);
+ if (val & UART_DIV13_EN)
+ rate = 24000000 / 13;
+ else
+ rate = 24000000;
+ /* TODO: Find the parent data for the uart clock */
+ hw = clk_hw_register_fixed_rate(dev, "uart", NULL, 0, rate);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ aspeed_clk_data->hws[ASPEED_CLK_UART] = hw;
+
+ /*
+ * Memory controller (M-PLL) PLL. This clock is configured by the
+ * bootloader, and is exposed to Linux as a read-only clock rate.
+ */
+ regmap_read(map, ASPEED_MPLL_PARAM, &val);
+ hw = soc_data->calc_pll("mpll", val);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ aspeed_clk_data->hws[ASPEED_CLK_MPLL] = hw;
+
+ /* SD/SDIO clock divider and gate */
+ hw = clk_hw_register_gate(dev, "sd_extclk_gate", "hpll", 0,
+ scu_base + ASPEED_CLK_SELECTION, 15, 0,
+ &aspeed_clk_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ hw = clk_hw_register_divider_table(dev, "sd_extclk", "sd_extclk_gate",
+ 0, scu_base + ASPEED_CLK_SELECTION, 12, 3, 0,
+ soc_data->div_table,
+ &aspeed_clk_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ aspeed_clk_data->hws[ASPEED_CLK_SDIO] = hw;
+
+ /* MAC AHB bus clock divider */
+ hw = clk_hw_register_divider_table(dev, "mac", "hpll", 0,
+ scu_base + ASPEED_CLK_SELECTION, 16, 3, 0,
+ soc_data->mac_div_table,
+ &aspeed_clk_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ aspeed_clk_data->hws[ASPEED_CLK_MAC] = hw;
+
+ if (of_device_is_compatible(pdev->dev.of_node, "aspeed,ast2500-scu")) {
+ /* RMII 50MHz RCLK */
+ hw = clk_hw_register_fixed_rate(dev, "mac12rclk", "hpll", 0,
+ 50000000);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ /* RMII1 50MHz (RCLK) output enable */
+ hw = clk_hw_register_gate(dev, "mac1rclk", "mac12rclk", 0,
+ scu_base + ASPEED_MAC_CLK_DLY, 29, 0,
+ &aspeed_clk_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ aspeed_clk_data->hws[ASPEED_CLK_MAC1RCLK] = hw;
+
+ /* RMII2 50MHz (RCLK) output enable */
+ hw = clk_hw_register_gate(dev, "mac2rclk", "mac12rclk", 0,
+ scu_base + ASPEED_MAC_CLK_DLY, 30, 0,
+ &aspeed_clk_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ aspeed_clk_data->hws[ASPEED_CLK_MAC2RCLK] = hw;
+ }
+
+ /* LPC Host (LHCLK) clock divider */
+ hw = clk_hw_register_divider_table(dev, "lhclk", "hpll", 0,
+ scu_base + ASPEED_CLK_SELECTION, 20, 3, 0,
+ soc_data->div_table,
+ &aspeed_clk_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ aspeed_clk_data->hws[ASPEED_CLK_LHCLK] = hw;
+
+ /* P-Bus (BCLK) clock divider */
+ hw = clk_hw_register_divider_table(dev, "bclk", "hpll", 0,
+ scu_base + ASPEED_CLK_SELECTION_2, 0, 2, 0,
+ soc_data->div_table,
+ &aspeed_clk_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ aspeed_clk_data->hws[ASPEED_CLK_BCLK] = hw;
+
+ /* Fixed 24MHz clock */
+ hw = clk_hw_register_fixed_rate(NULL, "fixed-24m", "clkin",
+ 0, 24000000);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ aspeed_clk_data->hws[ASPEED_CLK_24M] = hw;
+
+ hw = clk_hw_register_mux(dev, "eclk-mux", eclk_parent_names,
+ ARRAY_SIZE(eclk_parent_names), 0,
+ scu_base + ASPEED_CLK_SELECTION, 2, 0x3, 0,
+ &aspeed_clk_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ aspeed_clk_data->hws[ASPEED_CLK_ECLK_MUX] = hw;
+
+ hw = clk_hw_register_divider_table(dev, "eclk", "eclk-mux", 0,
+ scu_base + ASPEED_CLK_SELECTION, 28,
+ 3, 0, soc_data->eclk_div_table,
+ &aspeed_clk_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ aspeed_clk_data->hws[ASPEED_CLK_ECLK] = hw;
+
+ /*
+ * TODO: There are a number of clocks that not included in this driver
+ * as more information is required:
+ * D2-PLL
+ * D-PLL
+ * YCLK
+ * RGMII
+ * RMII
+ * UART[1..5] clock source mux
+ */
+
+ for (i = 0; i < ARRAY_SIZE(aspeed_gates); i++) {
+ const struct aspeed_gate_data *gd = &aspeed_gates[i];
+ u32 gate_flags;
+
+ /* Special case: the USB port 1 clock (bit 14) is always
+ * working the opposite way from the other ones.
+ */
+ gate_flags = (gd->clock_idx == 14) ? 0 : CLK_GATE_SET_TO_DISABLE;
+ hw = aspeed_clk_hw_register_gate(dev,
+ gd->name,
+ gd->parent_name,
+ gd->flags,
+ map,
+ gd->clock_idx,
+ gd->reset_idx,
+ gate_flags,
+ &aspeed_clk_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ aspeed_clk_data->hws[i] = hw;
+ }
+
+ return 0;
+};
+
+static const struct of_device_id aspeed_clk_dt_ids[] = {
+ { .compatible = "aspeed,ast2400-scu", .data = &ast2400_data },
+ { .compatible = "aspeed,ast2500-scu", .data = &ast2500_data },
+ { }
+};
+
+static struct platform_driver aspeed_clk_driver = {
+ .probe = aspeed_clk_probe,
+ .driver = {
+ .name = "aspeed-clk",
+ .of_match_table = aspeed_clk_dt_ids,
+ .suppress_bind_attrs = true,
+ },
+};
+builtin_platform_driver(aspeed_clk_driver);
+
+static void __init aspeed_ast2400_cc(struct regmap *map)
+{
+ struct clk_hw *hw;
+ u32 val, div, clkin, hpll;
+ const u16 hpll_rates[][4] = {
+ {384, 360, 336, 408},
+ {400, 375, 350, 425},
+ };
+ int rate;
+
+ /*
+ * CLKIN is the crystal oscillator, 24, 48 or 25MHz selected by
+ * strapping
+ */
+ regmap_read(map, ASPEED_STRAP, &val);
+ rate = (val >> 8) & 3;
+ if (val & CLKIN_25MHZ_EN) {
+ clkin = 25000000;
+ hpll = hpll_rates[1][rate];
+ } else if (val & AST2400_CLK_SOURCE_SEL) {
+ clkin = 48000000;
+ hpll = hpll_rates[0][rate];
+ } else {
+ clkin = 24000000;
+ hpll = hpll_rates[0][rate];
+ }
+ hw = clk_hw_register_fixed_rate(NULL, "clkin", NULL, 0, clkin);
+ pr_debug("clkin @%u MHz\n", clkin / 1000000);
+
+ /*
+ * High-speed PLL clock derived from the crystal. This the CPU clock,
+ * and we assume that it is enabled. It can be configured through the
+ * HPLL_PARAM register, or set to a specified frequency by strapping.
+ */
+ regmap_read(map, ASPEED_HPLL_PARAM, &val);
+ if (val & AST2400_HPLL_PROGRAMMED)
+ hw = aspeed_ast2400_calc_pll("hpll", val);
+ else
+ hw = clk_hw_register_fixed_rate(NULL, "hpll", "clkin", 0,
+ hpll * 1000000);
+
+ aspeed_clk_data->hws[ASPEED_CLK_HPLL] = hw;
+
+ /*
+ * Strap bits 11:10 define the CPU/AHB clock frequency ratio (aka HCLK)
+ * 00: Select CPU:AHB = 1:1
+ * 01: Select CPU:AHB = 2:1
+ * 10: Select CPU:AHB = 4:1
+ * 11: Select CPU:AHB = 3:1
+ */
+ regmap_read(map, ASPEED_STRAP, &val);
+ val = (val >> 10) & 0x3;
+ div = val + 1;
+ if (div == 3)
+ div = 4;
+ else if (div == 4)
+ div = 3;
+ hw = clk_hw_register_fixed_factor(NULL, "ahb", "hpll", 0, 1, div);
+ aspeed_clk_data->hws[ASPEED_CLK_AHB] = hw;
+
+ /* APB clock clock selection register SCU08 (aka PCLK) */
+ hw = clk_hw_register_divider_table(NULL, "apb", "hpll", 0,
+ scu_base + ASPEED_CLK_SELECTION, 23, 3, 0,
+ ast2400_div_table,
+ &aspeed_clk_lock);
+ aspeed_clk_data->hws[ASPEED_CLK_APB] = hw;
+}
+
+static void __init aspeed_ast2500_cc(struct regmap *map)
+{
+ struct clk_hw *hw;
+ u32 val, freq, div;
+
+ /* CLKIN is the crystal oscillator, 24 or 25MHz selected by strapping */
+ regmap_read(map, ASPEED_STRAP, &val);
+ if (val & CLKIN_25MHZ_EN)
+ freq = 25000000;
+ else
+ freq = 24000000;
+ hw = clk_hw_register_fixed_rate(NULL, "clkin", NULL, 0, freq);
+ pr_debug("clkin @%u MHz\n", freq / 1000000);
+
+ /*
+ * High-speed PLL clock derived from the crystal. This the CPU clock,
+ * and we assume that it is enabled
+ */
+ regmap_read(map, ASPEED_HPLL_PARAM, &val);
+ aspeed_clk_data->hws[ASPEED_CLK_HPLL] = aspeed_ast2500_calc_pll("hpll", val);
+
+ /* Strap bits 11:9 define the AXI/AHB clock frequency ratio (aka HCLK)*/
+ regmap_read(map, ASPEED_STRAP, &val);
+ val = (val >> 9) & 0x7;
+ WARN(val == 0, "strapping is zero: cannot determine ahb clock");
+ div = 2 * (val + 1);
+ hw = clk_hw_register_fixed_factor(NULL, "ahb", "hpll", 0, 1, div);
+ aspeed_clk_data->hws[ASPEED_CLK_AHB] = hw;
+
+ /* APB clock clock selection register SCU08 (aka PCLK) */
+ regmap_read(map, ASPEED_CLK_SELECTION, &val);
+ val = (val >> 23) & 0x7;
+ div = 4 * (val + 1);
+ hw = clk_hw_register_fixed_factor(NULL, "apb", "hpll", 0, 1, div);
+ aspeed_clk_data->hws[ASPEED_CLK_APB] = hw;
+};
+
+static void __init aspeed_cc_init(struct device_node *np)
+{
+ struct regmap *map;
+ u32 val;
+ int ret;
+ int i;
+
+ scu_base = of_iomap(np, 0);
+ if (!scu_base)
+ return;
+
+ aspeed_clk_data = kzalloc(struct_size(aspeed_clk_data, hws,
+ ASPEED_NUM_CLKS),
+ GFP_KERNEL);
+ if (!aspeed_clk_data)
+ return;
+
+ /*
+ * This way all clocks fetched before the platform device probes,
+ * except those we assign here for early use, will be deferred.
+ */
+ for (i = 0; i < ASPEED_NUM_CLKS; i++)
+ aspeed_clk_data->hws[i] = ERR_PTR(-EPROBE_DEFER);
+
+ map = syscon_node_to_regmap(np);
+ if (IS_ERR(map)) {
+ pr_err("no syscon regmap\n");
+ return;
+ }
+ /*
+ * We check that the regmap works on this very first access,
+ * but as this is an MMIO-backed regmap, subsequent regmap
+ * access is not going to fail and we skip error checks from
+ * this point.
+ */
+ ret = regmap_read(map, ASPEED_STRAP, &val);
+ if (ret) {
+ pr_err("failed to read strapping register\n");
+ return;
+ }
+
+ if (of_device_is_compatible(np, "aspeed,ast2400-scu"))
+ aspeed_ast2400_cc(map);
+ else if (of_device_is_compatible(np, "aspeed,ast2500-scu"))
+ aspeed_ast2500_cc(map);
+ else
+ pr_err("unknown platform, failed to add clocks\n");
+
+ aspeed_clk_data->num = ASPEED_NUM_CLKS;
+ ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, aspeed_clk_data);
+ if (ret)
+ pr_err("failed to add DT provider: %d\n", ret);
+};
+CLK_OF_DECLARE_DRIVER(aspeed_cc_g5, "aspeed,ast2500-scu", aspeed_cc_init);
+CLK_OF_DECLARE_DRIVER(aspeed_cc_g4, "aspeed,ast2400-scu", aspeed_cc_init);