18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com> 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/clk-provider.h> 78c2ecf20Sopenharmony_ci#include <linux/clkdev.h> 88c2ecf20Sopenharmony_ci#include <linux/clk/at91_pmc.h> 98c2ecf20Sopenharmony_ci#include <linux/of.h> 108c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h> 118c2ecf20Sopenharmony_ci#include <linux/regmap.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include "pmc.h" 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#define SMD_DIV_SHIFT 8 168c2ecf20Sopenharmony_ci#define SMD_MAX_DIV 0xf 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_cistruct at91sam9x5_clk_smd { 198c2ecf20Sopenharmony_ci struct clk_hw hw; 208c2ecf20Sopenharmony_ci struct regmap *regmap; 218c2ecf20Sopenharmony_ci}; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define to_at91sam9x5_clk_smd(hw) \ 248c2ecf20Sopenharmony_ci container_of(hw, struct at91sam9x5_clk_smd, hw) 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic unsigned long at91sam9x5_clk_smd_recalc_rate(struct clk_hw *hw, 278c2ecf20Sopenharmony_ci unsigned long parent_rate) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw); 308c2ecf20Sopenharmony_ci unsigned int smdr; 318c2ecf20Sopenharmony_ci u8 smddiv; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci regmap_read(smd->regmap, AT91_PMC_SMD, &smdr); 348c2ecf20Sopenharmony_ci smddiv = (smdr & AT91_PMC_SMD_DIV) >> SMD_DIV_SHIFT; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci return parent_rate / (smddiv + 1); 378c2ecf20Sopenharmony_ci} 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic long at91sam9x5_clk_smd_round_rate(struct clk_hw *hw, unsigned long rate, 408c2ecf20Sopenharmony_ci unsigned long *parent_rate) 418c2ecf20Sopenharmony_ci{ 428c2ecf20Sopenharmony_ci unsigned long div; 438c2ecf20Sopenharmony_ci unsigned long bestrate; 448c2ecf20Sopenharmony_ci unsigned long tmp; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci if (rate >= *parent_rate) 478c2ecf20Sopenharmony_ci return *parent_rate; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci div = *parent_rate / rate; 508c2ecf20Sopenharmony_ci if (div > SMD_MAX_DIV) 518c2ecf20Sopenharmony_ci return *parent_rate / (SMD_MAX_DIV + 1); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci bestrate = *parent_rate / div; 548c2ecf20Sopenharmony_ci tmp = *parent_rate / (div + 1); 558c2ecf20Sopenharmony_ci if (bestrate - rate > rate - tmp) 568c2ecf20Sopenharmony_ci bestrate = tmp; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci return bestrate; 598c2ecf20Sopenharmony_ci} 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic int at91sam9x5_clk_smd_set_parent(struct clk_hw *hw, u8 index) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci if (index > 1) 668c2ecf20Sopenharmony_ci return -EINVAL; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci regmap_update_bits(smd->regmap, AT91_PMC_SMD, AT91_PMC_SMDS, 698c2ecf20Sopenharmony_ci index ? AT91_PMC_SMDS : 0); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci return 0; 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic u8 at91sam9x5_clk_smd_get_parent(struct clk_hw *hw) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw); 778c2ecf20Sopenharmony_ci unsigned int smdr; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci regmap_read(smd->regmap, AT91_PMC_SMD, &smdr); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci return smdr & AT91_PMC_SMDS; 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic int at91sam9x5_clk_smd_set_rate(struct clk_hw *hw, unsigned long rate, 858c2ecf20Sopenharmony_ci unsigned long parent_rate) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw); 888c2ecf20Sopenharmony_ci unsigned long div = parent_rate / rate; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci if (parent_rate % rate || div < 1 || div > (SMD_MAX_DIV + 1)) 918c2ecf20Sopenharmony_ci return -EINVAL; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci regmap_update_bits(smd->regmap, AT91_PMC_SMD, AT91_PMC_SMD_DIV, 948c2ecf20Sopenharmony_ci (div - 1) << SMD_DIV_SHIFT); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci return 0; 978c2ecf20Sopenharmony_ci} 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic const struct clk_ops at91sam9x5_smd_ops = { 1008c2ecf20Sopenharmony_ci .recalc_rate = at91sam9x5_clk_smd_recalc_rate, 1018c2ecf20Sopenharmony_ci .round_rate = at91sam9x5_clk_smd_round_rate, 1028c2ecf20Sopenharmony_ci .get_parent = at91sam9x5_clk_smd_get_parent, 1038c2ecf20Sopenharmony_ci .set_parent = at91sam9x5_clk_smd_set_parent, 1048c2ecf20Sopenharmony_ci .set_rate = at91sam9x5_clk_smd_set_rate, 1058c2ecf20Sopenharmony_ci}; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_cistruct clk_hw * __init 1088c2ecf20Sopenharmony_ciat91sam9x5_clk_register_smd(struct regmap *regmap, const char *name, 1098c2ecf20Sopenharmony_ci const char **parent_names, u8 num_parents) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci struct at91sam9x5_clk_smd *smd; 1128c2ecf20Sopenharmony_ci struct clk_hw *hw; 1138c2ecf20Sopenharmony_ci struct clk_init_data init; 1148c2ecf20Sopenharmony_ci int ret; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci smd = kzalloc(sizeof(*smd), GFP_KERNEL); 1178c2ecf20Sopenharmony_ci if (!smd) 1188c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci init.name = name; 1218c2ecf20Sopenharmony_ci init.ops = &at91sam9x5_smd_ops; 1228c2ecf20Sopenharmony_ci init.parent_names = parent_names; 1238c2ecf20Sopenharmony_ci init.num_parents = num_parents; 1248c2ecf20Sopenharmony_ci init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci smd->hw.init = &init; 1278c2ecf20Sopenharmony_ci smd->regmap = regmap; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci hw = &smd->hw; 1308c2ecf20Sopenharmony_ci ret = clk_hw_register(NULL, &smd->hw); 1318c2ecf20Sopenharmony_ci if (ret) { 1328c2ecf20Sopenharmony_ci kfree(smd); 1338c2ecf20Sopenharmony_ci hw = ERR_PTR(ret); 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci return hw; 1378c2ecf20Sopenharmony_ci} 138