18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 2018 NXP. 48c2ecf20Sopenharmony_ci * Dong Aisheng <aisheng.dong@nxp.com> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/clk-provider.h> 88c2ecf20Sopenharmony_ci#include <linux/err.h> 98c2ecf20Sopenharmony_ci#include <linux/io.h> 108c2ecf20Sopenharmony_ci#include <linux/slab.h> 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include "clk.h" 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_cistruct clk_divider_gate { 158c2ecf20Sopenharmony_ci struct clk_divider divider; 168c2ecf20Sopenharmony_ci u32 cached_val; 178c2ecf20Sopenharmony_ci}; 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistatic inline struct clk_divider_gate *to_clk_divider_gate(struct clk_hw *hw) 208c2ecf20Sopenharmony_ci{ 218c2ecf20Sopenharmony_ci struct clk_divider *div = to_clk_divider(hw); 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci return container_of(div, struct clk_divider_gate, divider); 248c2ecf20Sopenharmony_ci} 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic unsigned long clk_divider_gate_recalc_rate_ro(struct clk_hw *hw, 278c2ecf20Sopenharmony_ci unsigned long parent_rate) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci struct clk_divider *div = to_clk_divider(hw); 308c2ecf20Sopenharmony_ci unsigned int val; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci val = readl(div->reg) >> div->shift; 338c2ecf20Sopenharmony_ci val &= clk_div_mask(div->width); 348c2ecf20Sopenharmony_ci if (!val) 358c2ecf20Sopenharmony_ci return 0; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci return divider_recalc_rate(hw, parent_rate, val, div->table, 388c2ecf20Sopenharmony_ci div->flags, div->width); 398c2ecf20Sopenharmony_ci} 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic unsigned long clk_divider_gate_recalc_rate(struct clk_hw *hw, 428c2ecf20Sopenharmony_ci unsigned long parent_rate) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci struct clk_divider_gate *div_gate = to_clk_divider_gate(hw); 458c2ecf20Sopenharmony_ci struct clk_divider *div = to_clk_divider(hw); 468c2ecf20Sopenharmony_ci unsigned long flags; 478c2ecf20Sopenharmony_ci unsigned int val; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci spin_lock_irqsave(div->lock, flags); 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci if (!clk_hw_is_enabled(hw)) { 528c2ecf20Sopenharmony_ci val = div_gate->cached_val; 538c2ecf20Sopenharmony_ci } else { 548c2ecf20Sopenharmony_ci val = readl(div->reg) >> div->shift; 558c2ecf20Sopenharmony_ci val &= clk_div_mask(div->width); 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci spin_unlock_irqrestore(div->lock, flags); 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci if (!val) 618c2ecf20Sopenharmony_ci return 0; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci return divider_recalc_rate(hw, parent_rate, val, div->table, 648c2ecf20Sopenharmony_ci div->flags, div->width); 658c2ecf20Sopenharmony_ci} 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate, 688c2ecf20Sopenharmony_ci unsigned long *prate) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci return clk_divider_ops.round_rate(hw, rate, prate); 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic int clk_divider_gate_set_rate(struct clk_hw *hw, unsigned long rate, 748c2ecf20Sopenharmony_ci unsigned long parent_rate) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci struct clk_divider_gate *div_gate = to_clk_divider_gate(hw); 778c2ecf20Sopenharmony_ci struct clk_divider *div = to_clk_divider(hw); 788c2ecf20Sopenharmony_ci unsigned long flags; 798c2ecf20Sopenharmony_ci int value; 808c2ecf20Sopenharmony_ci u32 val; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci value = divider_get_val(rate, parent_rate, div->table, 838c2ecf20Sopenharmony_ci div->width, div->flags); 848c2ecf20Sopenharmony_ci if (value < 0) 858c2ecf20Sopenharmony_ci return value; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci spin_lock_irqsave(div->lock, flags); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (clk_hw_is_enabled(hw)) { 908c2ecf20Sopenharmony_ci val = readl(div->reg); 918c2ecf20Sopenharmony_ci val &= ~(clk_div_mask(div->width) << div->shift); 928c2ecf20Sopenharmony_ci val |= (u32)value << div->shift; 938c2ecf20Sopenharmony_ci writel(val, div->reg); 948c2ecf20Sopenharmony_ci } else { 958c2ecf20Sopenharmony_ci div_gate->cached_val = value; 968c2ecf20Sopenharmony_ci } 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci spin_unlock_irqrestore(div->lock, flags); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci return 0; 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic int clk_divider_enable(struct clk_hw *hw) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci struct clk_divider_gate *div_gate = to_clk_divider_gate(hw); 1068c2ecf20Sopenharmony_ci struct clk_divider *div = to_clk_divider(hw); 1078c2ecf20Sopenharmony_ci unsigned long flags; 1088c2ecf20Sopenharmony_ci u32 val; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci if (!div_gate->cached_val) { 1118c2ecf20Sopenharmony_ci pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw)); 1128c2ecf20Sopenharmony_ci return -EINVAL; 1138c2ecf20Sopenharmony_ci } 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci spin_lock_irqsave(div->lock, flags); 1168c2ecf20Sopenharmony_ci /* restore div val */ 1178c2ecf20Sopenharmony_ci val = readl(div->reg); 1188c2ecf20Sopenharmony_ci val |= div_gate->cached_val << div->shift; 1198c2ecf20Sopenharmony_ci writel(val, div->reg); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci spin_unlock_irqrestore(div->lock, flags); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci return 0; 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_cistatic void clk_divider_disable(struct clk_hw *hw) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci struct clk_divider_gate *div_gate = to_clk_divider_gate(hw); 1298c2ecf20Sopenharmony_ci struct clk_divider *div = to_clk_divider(hw); 1308c2ecf20Sopenharmony_ci unsigned long flags; 1318c2ecf20Sopenharmony_ci u32 val; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci spin_lock_irqsave(div->lock, flags); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci /* store the current div val */ 1368c2ecf20Sopenharmony_ci val = readl(div->reg) >> div->shift; 1378c2ecf20Sopenharmony_ci val &= clk_div_mask(div->width); 1388c2ecf20Sopenharmony_ci div_gate->cached_val = val; 1398c2ecf20Sopenharmony_ci writel(0, div->reg); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci spin_unlock_irqrestore(div->lock, flags); 1428c2ecf20Sopenharmony_ci} 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic int clk_divider_is_enabled(struct clk_hw *hw) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci struct clk_divider *div = to_clk_divider(hw); 1478c2ecf20Sopenharmony_ci u32 val; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci val = readl(div->reg) >> div->shift; 1508c2ecf20Sopenharmony_ci val &= clk_div_mask(div->width); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci return val ? 1 : 0; 1538c2ecf20Sopenharmony_ci} 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cistatic const struct clk_ops clk_divider_gate_ro_ops = { 1568c2ecf20Sopenharmony_ci .recalc_rate = clk_divider_gate_recalc_rate_ro, 1578c2ecf20Sopenharmony_ci .round_rate = clk_divider_round_rate, 1588c2ecf20Sopenharmony_ci}; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_cistatic const struct clk_ops clk_divider_gate_ops = { 1618c2ecf20Sopenharmony_ci .recalc_rate = clk_divider_gate_recalc_rate, 1628c2ecf20Sopenharmony_ci .round_rate = clk_divider_round_rate, 1638c2ecf20Sopenharmony_ci .set_rate = clk_divider_gate_set_rate, 1648c2ecf20Sopenharmony_ci .enable = clk_divider_enable, 1658c2ecf20Sopenharmony_ci .disable = clk_divider_disable, 1668c2ecf20Sopenharmony_ci .is_enabled = clk_divider_is_enabled, 1678c2ecf20Sopenharmony_ci}; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci/* 1708c2ecf20Sopenharmony_ci * NOTE: In order to reuse the most code from the common divider, 1718c2ecf20Sopenharmony_ci * we also design our divider following the way that provids an extra 1728c2ecf20Sopenharmony_ci * clk_divider_flags, however it's fixed to CLK_DIVIDER_ONE_BASED by 1738c2ecf20Sopenharmony_ci * default as our HW is. Besides that it supports only CLK_DIVIDER_READ_ONLY 1748c2ecf20Sopenharmony_ci * flag which can be specified by user flexibly. 1758c2ecf20Sopenharmony_ci */ 1768c2ecf20Sopenharmony_cistruct clk_hw *imx_clk_hw_divider_gate(const char *name, const char *parent_name, 1778c2ecf20Sopenharmony_ci unsigned long flags, void __iomem *reg, 1788c2ecf20Sopenharmony_ci u8 shift, u8 width, u8 clk_divider_flags, 1798c2ecf20Sopenharmony_ci const struct clk_div_table *table, 1808c2ecf20Sopenharmony_ci spinlock_t *lock) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci struct clk_init_data init; 1838c2ecf20Sopenharmony_ci struct clk_divider_gate *div_gate; 1848c2ecf20Sopenharmony_ci struct clk_hw *hw; 1858c2ecf20Sopenharmony_ci u32 val; 1868c2ecf20Sopenharmony_ci int ret; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci div_gate = kzalloc(sizeof(*div_gate), GFP_KERNEL); 1898c2ecf20Sopenharmony_ci if (!div_gate) 1908c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci init.name = name; 1938c2ecf20Sopenharmony_ci if (clk_divider_flags & CLK_DIVIDER_READ_ONLY) 1948c2ecf20Sopenharmony_ci init.ops = &clk_divider_gate_ro_ops; 1958c2ecf20Sopenharmony_ci else 1968c2ecf20Sopenharmony_ci init.ops = &clk_divider_gate_ops; 1978c2ecf20Sopenharmony_ci init.flags = flags; 1988c2ecf20Sopenharmony_ci init.parent_names = parent_name ? &parent_name : NULL; 1998c2ecf20Sopenharmony_ci init.num_parents = parent_name ? 1 : 0; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci div_gate->divider.reg = reg; 2028c2ecf20Sopenharmony_ci div_gate->divider.shift = shift; 2038c2ecf20Sopenharmony_ci div_gate->divider.width = width; 2048c2ecf20Sopenharmony_ci div_gate->divider.lock = lock; 2058c2ecf20Sopenharmony_ci div_gate->divider.table = table; 2068c2ecf20Sopenharmony_ci div_gate->divider.hw.init = &init; 2078c2ecf20Sopenharmony_ci div_gate->divider.flags = CLK_DIVIDER_ONE_BASED | clk_divider_flags; 2088c2ecf20Sopenharmony_ci /* cache gate status */ 2098c2ecf20Sopenharmony_ci val = readl(reg) >> shift; 2108c2ecf20Sopenharmony_ci val &= clk_div_mask(width); 2118c2ecf20Sopenharmony_ci div_gate->cached_val = val; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci hw = &div_gate->divider.hw; 2148c2ecf20Sopenharmony_ci ret = clk_hw_register(NULL, hw); 2158c2ecf20Sopenharmony_ci if (ret) { 2168c2ecf20Sopenharmony_ci kfree(div_gate); 2178c2ecf20Sopenharmony_ci hw = ERR_PTR(ret); 2188c2ecf20Sopenharmony_ci } 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci return hw; 2218c2ecf20Sopenharmony_ci} 222