19fcb6be3SA.s. Dong // SPDX-License-Identifier: GPL-2.0+ 29fcb6be3SA.s. Dong /* 39fcb6be3SA.s. Dong * Copyright (C) 2016 Freescale Semiconductor, Inc. 49fcb6be3SA.s. Dong * Copyright 2017~2018 NXP 59fcb6be3SA.s. Dong * 69fcb6be3SA.s. Dong * Author: Dong Aisheng <aisheng.dong@nxp.com> 79fcb6be3SA.s. Dong * 89fcb6be3SA.s. Dong */ 99fcb6be3SA.s. Dong 109fcb6be3SA.s. Dong #include <linux/clk-provider.h> 119fcb6be3SA.s. Dong #include <linux/err.h> 1262e59c4eSStephen Boyd #include <linux/io.h> 139fcb6be3SA.s. Dong #include <linux/iopoll.h> 149fcb6be3SA.s. Dong #include <linux/slab.h> 159fcb6be3SA.s. Dong 169fcb6be3SA.s. Dong #include "clk.h" 179fcb6be3SA.s. Dong 189fcb6be3SA.s. Dong /** 199fcb6be3SA.s. Dong * struct clk_pfdv2 - IMX PFD clock 209fcb6be3SA.s. Dong * @clk_hw: clock source 219fcb6be3SA.s. Dong * @reg: PFD register address 229fcb6be3SA.s. Dong * @gate_bit: Gate bit offset 239fcb6be3SA.s. Dong * @vld_bit: Valid bit offset 249fcb6be3SA.s. Dong * @frac_off: PLL Fractional Divider offset 259fcb6be3SA.s. Dong */ 269fcb6be3SA.s. Dong 279fcb6be3SA.s. Dong struct clk_pfdv2 { 289fcb6be3SA.s. Dong struct clk_hw hw; 299fcb6be3SA.s. Dong void __iomem *reg; 309fcb6be3SA.s. Dong u8 gate_bit; 319fcb6be3SA.s. Dong u8 vld_bit; 329fcb6be3SA.s. Dong u8 frac_off; 339fcb6be3SA.s. Dong }; 349fcb6be3SA.s. Dong 359fcb6be3SA.s. Dong #define to_clk_pfdv2(_hw) container_of(_hw, struct clk_pfdv2, hw) 369fcb6be3SA.s. Dong 379fcb6be3SA.s. Dong #define CLK_PFDV2_FRAC_MASK 0x3f 389fcb6be3SA.s. Dong 399fcb6be3SA.s. Dong #define LOCK_TIMEOUT_US USEC_PER_MSEC 409fcb6be3SA.s. Dong 419fcb6be3SA.s. Dong static DEFINE_SPINLOCK(pfd_lock); 429fcb6be3SA.s. Dong 439fcb6be3SA.s. Dong static int clk_pfdv2_wait(struct clk_pfdv2 *pfd) 449fcb6be3SA.s. Dong { 459fcb6be3SA.s. Dong u32 val; 469fcb6be3SA.s. Dong 47a5a627c6SAnson Huang return readl_poll_timeout(pfd->reg, val, val & (1 << pfd->vld_bit), 489fcb6be3SA.s. Dong 0, LOCK_TIMEOUT_US); 499fcb6be3SA.s. Dong } 509fcb6be3SA.s. Dong 519fcb6be3SA.s. Dong static int clk_pfdv2_enable(struct clk_hw *hw) 529fcb6be3SA.s. Dong { 539fcb6be3SA.s. Dong struct clk_pfdv2 *pfd = to_clk_pfdv2(hw); 549fcb6be3SA.s. Dong unsigned long flags; 559fcb6be3SA.s. Dong u32 val; 569fcb6be3SA.s. Dong 579fcb6be3SA.s. Dong spin_lock_irqsave(&pfd_lock, flags); 589fcb6be3SA.s. Dong val = readl_relaxed(pfd->reg); 59a5a627c6SAnson Huang val &= ~(1 << pfd->gate_bit); 609fcb6be3SA.s. Dong writel_relaxed(val, pfd->reg); 619fcb6be3SA.s. Dong spin_unlock_irqrestore(&pfd_lock, flags); 629fcb6be3SA.s. Dong 639fcb6be3SA.s. Dong return clk_pfdv2_wait(pfd); 649fcb6be3SA.s. Dong } 659fcb6be3SA.s. Dong 669fcb6be3SA.s. Dong static void clk_pfdv2_disable(struct clk_hw *hw) 679fcb6be3SA.s. Dong { 689fcb6be3SA.s. Dong struct clk_pfdv2 *pfd = to_clk_pfdv2(hw); 699fcb6be3SA.s. Dong unsigned long flags; 709fcb6be3SA.s. Dong u32 val; 719fcb6be3SA.s. Dong 729fcb6be3SA.s. Dong spin_lock_irqsave(&pfd_lock, flags); 739fcb6be3SA.s. Dong val = readl_relaxed(pfd->reg); 74a5a627c6SAnson Huang val |= (1 << pfd->gate_bit); 759fcb6be3SA.s. Dong writel_relaxed(val, pfd->reg); 769fcb6be3SA.s. Dong spin_unlock_irqrestore(&pfd_lock, flags); 779fcb6be3SA.s. Dong } 789fcb6be3SA.s. Dong 799fcb6be3SA.s. Dong static unsigned long clk_pfdv2_recalc_rate(struct clk_hw *hw, 809fcb6be3SA.s. Dong unsigned long parent_rate) 819fcb6be3SA.s. Dong { 829fcb6be3SA.s. Dong struct clk_pfdv2 *pfd = to_clk_pfdv2(hw); 839fcb6be3SA.s. Dong u64 tmp = parent_rate; 849fcb6be3SA.s. Dong u8 frac; 859fcb6be3SA.s. Dong 869fcb6be3SA.s. Dong frac = (readl_relaxed(pfd->reg) >> pfd->frac_off) 879fcb6be3SA.s. Dong & CLK_PFDV2_FRAC_MASK; 889fcb6be3SA.s. Dong 899fcb6be3SA.s. Dong if (!frac) { 909fcb6be3SA.s. Dong pr_debug("clk_pfdv2: %s invalid pfd frac value 0\n", 919fcb6be3SA.s. Dong clk_hw_get_name(hw)); 929fcb6be3SA.s. Dong return 0; 939fcb6be3SA.s. Dong } 949fcb6be3SA.s. Dong 959fcb6be3SA.s. Dong tmp *= 18; 969fcb6be3SA.s. Dong do_div(tmp, frac); 979fcb6be3SA.s. Dong 989fcb6be3SA.s. Dong return tmp; 999fcb6be3SA.s. Dong } 1009fcb6be3SA.s. Dong 1019fcb6be3SA.s. Dong static long clk_pfdv2_round_rate(struct clk_hw *hw, unsigned long rate, 1029fcb6be3SA.s. Dong unsigned long *prate) 1039fcb6be3SA.s. Dong { 1049fcb6be3SA.s. Dong u64 tmp = *prate; 1059fcb6be3SA.s. Dong u8 frac; 1069fcb6be3SA.s. Dong 1079fcb6be3SA.s. Dong tmp = tmp * 18 + rate / 2; 1089fcb6be3SA.s. Dong do_div(tmp, rate); 1099fcb6be3SA.s. Dong frac = tmp; 1109fcb6be3SA.s. Dong 1119fcb6be3SA.s. Dong if (frac < 12) 1129fcb6be3SA.s. Dong frac = 12; 1139fcb6be3SA.s. Dong else if (frac > 35) 1149fcb6be3SA.s. Dong frac = 35; 1159fcb6be3SA.s. Dong 1169fcb6be3SA.s. Dong tmp = *prate; 1179fcb6be3SA.s. Dong tmp *= 18; 1189fcb6be3SA.s. Dong do_div(tmp, frac); 1199fcb6be3SA.s. Dong 1209fcb6be3SA.s. Dong return tmp; 1219fcb6be3SA.s. Dong } 1229fcb6be3SA.s. Dong 1239fcb6be3SA.s. Dong static int clk_pfdv2_is_enabled(struct clk_hw *hw) 1249fcb6be3SA.s. Dong { 1259fcb6be3SA.s. Dong struct clk_pfdv2 *pfd = to_clk_pfdv2(hw); 1269fcb6be3SA.s. Dong 127a5a627c6SAnson Huang if (readl_relaxed(pfd->reg) & (1 << pfd->gate_bit)) 1289fcb6be3SA.s. Dong return 0; 1299fcb6be3SA.s. Dong 1309fcb6be3SA.s. Dong return 1; 1319fcb6be3SA.s. Dong } 1329fcb6be3SA.s. Dong 1339fcb6be3SA.s. Dong static int clk_pfdv2_set_rate(struct clk_hw *hw, unsigned long rate, 1349fcb6be3SA.s. Dong unsigned long parent_rate) 1359fcb6be3SA.s. Dong { 1369fcb6be3SA.s. Dong struct clk_pfdv2 *pfd = to_clk_pfdv2(hw); 1379fcb6be3SA.s. Dong unsigned long flags; 1389fcb6be3SA.s. Dong u64 tmp = parent_rate; 1399fcb6be3SA.s. Dong u32 val; 1409fcb6be3SA.s. Dong u8 frac; 1419fcb6be3SA.s. Dong 14228b2f82eSAnson Huang if (!rate) 14328b2f82eSAnson Huang return -EINVAL; 14428b2f82eSAnson Huang 14528b2f82eSAnson Huang /* PFD can NOT change rate without gating */ 14628b2f82eSAnson Huang WARN_ON(clk_pfdv2_is_enabled(hw)); 14728b2f82eSAnson Huang 1489fcb6be3SA.s. Dong tmp = tmp * 18 + rate / 2; 1499fcb6be3SA.s. Dong do_div(tmp, rate); 1509fcb6be3SA.s. Dong frac = tmp; 1519fcb6be3SA.s. Dong if (frac < 12) 1529fcb6be3SA.s. Dong frac = 12; 1539fcb6be3SA.s. Dong else if (frac > 35) 1549fcb6be3SA.s. Dong frac = 35; 1559fcb6be3SA.s. Dong 1569fcb6be3SA.s. Dong spin_lock_irqsave(&pfd_lock, flags); 1579fcb6be3SA.s. Dong val = readl_relaxed(pfd->reg); 1589fcb6be3SA.s. Dong val &= ~(CLK_PFDV2_FRAC_MASK << pfd->frac_off); 1599fcb6be3SA.s. Dong val |= frac << pfd->frac_off; 1609fcb6be3SA.s. Dong writel_relaxed(val, pfd->reg); 1619fcb6be3SA.s. Dong spin_unlock_irqrestore(&pfd_lock, flags); 1629fcb6be3SA.s. Dong 1639fcb6be3SA.s. Dong return 0; 1649fcb6be3SA.s. Dong } 1659fcb6be3SA.s. Dong 1669fcb6be3SA.s. Dong static const struct clk_ops clk_pfdv2_ops = { 1679fcb6be3SA.s. Dong .enable = clk_pfdv2_enable, 1689fcb6be3SA.s. Dong .disable = clk_pfdv2_disable, 1699fcb6be3SA.s. Dong .recalc_rate = clk_pfdv2_recalc_rate, 1709fcb6be3SA.s. Dong .round_rate = clk_pfdv2_round_rate, 1719fcb6be3SA.s. Dong .set_rate = clk_pfdv2_set_rate, 1729fcb6be3SA.s. Dong .is_enabled = clk_pfdv2_is_enabled, 1739fcb6be3SA.s. Dong }; 1749fcb6be3SA.s. Dong 17540ad61d6SAbel Vesa struct clk_hw *imx_clk_hw_pfdv2(const char *name, const char *parent_name, 1769fcb6be3SA.s. Dong void __iomem *reg, u8 idx) 1779fcb6be3SA.s. Dong { 1789fcb6be3SA.s. Dong struct clk_init_data init; 1799fcb6be3SA.s. Dong struct clk_pfdv2 *pfd; 1809fcb6be3SA.s. Dong struct clk_hw *hw; 1819fcb6be3SA.s. Dong int ret; 1829fcb6be3SA.s. Dong 1839fcb6be3SA.s. Dong WARN_ON(idx > 3); 1849fcb6be3SA.s. Dong 1859fcb6be3SA.s. Dong pfd = kzalloc(sizeof(*pfd), GFP_KERNEL); 1869fcb6be3SA.s. Dong if (!pfd) 1879fcb6be3SA.s. Dong return ERR_PTR(-ENOMEM); 1889fcb6be3SA.s. Dong 1899fcb6be3SA.s. Dong pfd->reg = reg; 190a5a627c6SAnson Huang pfd->gate_bit = (idx + 1) * 8 - 1; 1919fcb6be3SA.s. Dong pfd->vld_bit = pfd->gate_bit - 1; 1929fcb6be3SA.s. Dong pfd->frac_off = idx * 8; 1939fcb6be3SA.s. Dong 1949fcb6be3SA.s. Dong init.name = name; 1959fcb6be3SA.s. Dong init.ops = &clk_pfdv2_ops; 1969fcb6be3SA.s. Dong init.parent_names = &parent_name; 1979fcb6be3SA.s. Dong init.num_parents = 1; 1989fcb6be3SA.s. Dong init.flags = CLK_SET_RATE_GATE; 1999fcb6be3SA.s. Dong 2009fcb6be3SA.s. Dong pfd->hw.init = &init; 2019fcb6be3SA.s. Dong 2029fcb6be3SA.s. Dong hw = &pfd->hw; 2039fcb6be3SA.s. Dong ret = clk_hw_register(NULL, hw); 2049fcb6be3SA.s. Dong if (ret) { 2059fcb6be3SA.s. Dong kfree(pfd); 2069fcb6be3SA.s. Dong hw = ERR_PTR(ret); 2079fcb6be3SA.s. Dong } 2089fcb6be3SA.s. Dong 2099fcb6be3SA.s. Dong return hw; 2109fcb6be3SA.s. Dong } 211