18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * rl6231.c - RL6231 class device shared support 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2014 Realtek Semiconductor Corp. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Oder Chiou <oder_chiou@realtek.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/regmap.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/gcd.h> 148c2ecf20Sopenharmony_ci#include "rl6231.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci/** 178c2ecf20Sopenharmony_ci * rl6231_get_pre_div - Return the value of pre divider. 188c2ecf20Sopenharmony_ci * 198c2ecf20Sopenharmony_ci * @map: map for setting. 208c2ecf20Sopenharmony_ci * @reg: register. 218c2ecf20Sopenharmony_ci * @sft: shift. 228c2ecf20Sopenharmony_ci * 238c2ecf20Sopenharmony_ci * Return the value of pre divider from given register value. 248c2ecf20Sopenharmony_ci * Return negative error code for unexpected register value. 258c2ecf20Sopenharmony_ci */ 268c2ecf20Sopenharmony_ciint rl6231_get_pre_div(struct regmap *map, unsigned int reg, int sft) 278c2ecf20Sopenharmony_ci{ 288c2ecf20Sopenharmony_ci int pd, val; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci regmap_read(map, reg, &val); 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci val = (val >> sft) & 0x7; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci switch (val) { 358c2ecf20Sopenharmony_ci case 0: 368c2ecf20Sopenharmony_ci case 1: 378c2ecf20Sopenharmony_ci case 2: 388c2ecf20Sopenharmony_ci case 3: 398c2ecf20Sopenharmony_ci pd = val + 1; 408c2ecf20Sopenharmony_ci break; 418c2ecf20Sopenharmony_ci case 4: 428c2ecf20Sopenharmony_ci pd = 6; 438c2ecf20Sopenharmony_ci break; 448c2ecf20Sopenharmony_ci case 5: 458c2ecf20Sopenharmony_ci pd = 8; 468c2ecf20Sopenharmony_ci break; 478c2ecf20Sopenharmony_ci case 6: 488c2ecf20Sopenharmony_ci pd = 12; 498c2ecf20Sopenharmony_ci break; 508c2ecf20Sopenharmony_ci case 7: 518c2ecf20Sopenharmony_ci pd = 16; 528c2ecf20Sopenharmony_ci break; 538c2ecf20Sopenharmony_ci default: 548c2ecf20Sopenharmony_ci pd = -EINVAL; 558c2ecf20Sopenharmony_ci break; 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci return pd; 598c2ecf20Sopenharmony_ci} 608c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rl6231_get_pre_div); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci/** 638c2ecf20Sopenharmony_ci * rl6231_calc_dmic_clk - Calculate the frequency divider parameter of dmic. 648c2ecf20Sopenharmony_ci * 658c2ecf20Sopenharmony_ci * @rate: base clock rate. 668c2ecf20Sopenharmony_ci * 678c2ecf20Sopenharmony_ci * Choose divider parameter that gives the highest possible DMIC frequency in 688c2ecf20Sopenharmony_ci * 1MHz - 3MHz range. 698c2ecf20Sopenharmony_ci */ 708c2ecf20Sopenharmony_ciint rl6231_calc_dmic_clk(int rate) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci static const int div[] = {2, 3, 4, 6, 8, 12}; 738c2ecf20Sopenharmony_ci int i; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (rate < 1000000 * div[0]) { 768c2ecf20Sopenharmony_ci pr_warn("Base clock rate %d is too low\n", rate); 778c2ecf20Sopenharmony_ci return -EINVAL; 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(div); i++) { 818c2ecf20Sopenharmony_ci if ((div[i] % 3) == 0) 828c2ecf20Sopenharmony_ci continue; 838c2ecf20Sopenharmony_ci /* find divider that gives DMIC frequency below 1.536MHz */ 848c2ecf20Sopenharmony_ci if (1536000 * div[i] >= rate) 858c2ecf20Sopenharmony_ci return i; 868c2ecf20Sopenharmony_ci } 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci pr_warn("Base clock rate %d is too high\n", rate); 898c2ecf20Sopenharmony_ci return -EINVAL; 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rl6231_calc_dmic_clk); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistruct pll_calc_map { 948c2ecf20Sopenharmony_ci unsigned int pll_in; 958c2ecf20Sopenharmony_ci unsigned int pll_out; 968c2ecf20Sopenharmony_ci int k; 978c2ecf20Sopenharmony_ci int n; 988c2ecf20Sopenharmony_ci int m; 998c2ecf20Sopenharmony_ci bool m_bp; 1008c2ecf20Sopenharmony_ci bool k_bp; 1018c2ecf20Sopenharmony_ci}; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic const struct pll_calc_map pll_preset_table[] = { 1048c2ecf20Sopenharmony_ci {19200000, 4096000, 23, 14, 1, false, false}, 1058c2ecf20Sopenharmony_ci {19200000, 24576000, 3, 30, 3, false, false}, 1068c2ecf20Sopenharmony_ci {48000000, 3840000, 23, 2, 0, false, false}, 1078c2ecf20Sopenharmony_ci {3840000, 24576000, 3, 30, 0, true, false}, 1088c2ecf20Sopenharmony_ci {3840000, 22579200, 3, 5, 0, true, false}, 1098c2ecf20Sopenharmony_ci}; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic unsigned int find_best_div(unsigned int in, 1128c2ecf20Sopenharmony_ci unsigned int max, unsigned int div) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci unsigned int d; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci if (in <= max) 1178c2ecf20Sopenharmony_ci return 1; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci d = in / max; 1208c2ecf20Sopenharmony_ci if (in % max) 1218c2ecf20Sopenharmony_ci d++; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci while (div % d != 0) 1248c2ecf20Sopenharmony_ci d++; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci return d; 1288c2ecf20Sopenharmony_ci} 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci/** 1318c2ecf20Sopenharmony_ci * rl6231_pll_calc - Calcualte PLL M/N/K code. 1328c2ecf20Sopenharmony_ci * @freq_in: external clock provided to codec. 1338c2ecf20Sopenharmony_ci * @freq_out: target clock which codec works on. 1348c2ecf20Sopenharmony_ci * @pll_code: Pointer to structure with M, N, K, m_bypass and k_bypass flag. 1358c2ecf20Sopenharmony_ci * 1368c2ecf20Sopenharmony_ci * Calcualte M/N/K code to configure PLL for codec. 1378c2ecf20Sopenharmony_ci * 1388c2ecf20Sopenharmony_ci * Returns 0 for success or negative error code. 1398c2ecf20Sopenharmony_ci */ 1408c2ecf20Sopenharmony_ciint rl6231_pll_calc(const unsigned int freq_in, 1418c2ecf20Sopenharmony_ci const unsigned int freq_out, struct rl6231_pll_code *pll_code) 1428c2ecf20Sopenharmony_ci{ 1438c2ecf20Sopenharmony_ci int max_n = RL6231_PLL_N_MAX, max_m = RL6231_PLL_M_MAX; 1448c2ecf20Sopenharmony_ci int i, k, n_t; 1458c2ecf20Sopenharmony_ci int k_t, min_k, max_k, n = 0, m = 0, m_t = 0; 1468c2ecf20Sopenharmony_ci unsigned int red, pll_out, in_t, out_t, div, div_t; 1478c2ecf20Sopenharmony_ci unsigned int red_t = abs(freq_out - freq_in); 1488c2ecf20Sopenharmony_ci unsigned int f_in, f_out, f_max; 1498c2ecf20Sopenharmony_ci bool m_bypass = false, k_bypass = false; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci if (RL6231_PLL_INP_MAX < freq_in || RL6231_PLL_INP_MIN > freq_in) 1528c2ecf20Sopenharmony_ci return -EINVAL; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(pll_preset_table); i++) { 1558c2ecf20Sopenharmony_ci if (freq_in == pll_preset_table[i].pll_in && 1568c2ecf20Sopenharmony_ci freq_out == pll_preset_table[i].pll_out) { 1578c2ecf20Sopenharmony_ci k = pll_preset_table[i].k; 1588c2ecf20Sopenharmony_ci m = pll_preset_table[i].m; 1598c2ecf20Sopenharmony_ci n = pll_preset_table[i].n; 1608c2ecf20Sopenharmony_ci m_bypass = pll_preset_table[i].m_bp; 1618c2ecf20Sopenharmony_ci k_bypass = pll_preset_table[i].k_bp; 1628c2ecf20Sopenharmony_ci pr_debug("Use preset PLL parameter table\n"); 1638c2ecf20Sopenharmony_ci goto code_find; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci min_k = 80000000 / freq_out - 2; 1688c2ecf20Sopenharmony_ci max_k = 150000000 / freq_out - 2; 1698c2ecf20Sopenharmony_ci if (max_k > RL6231_PLL_K_MAX) 1708c2ecf20Sopenharmony_ci max_k = RL6231_PLL_K_MAX; 1718c2ecf20Sopenharmony_ci if (min_k > RL6231_PLL_K_MAX) 1728c2ecf20Sopenharmony_ci min_k = max_k = RL6231_PLL_K_MAX; 1738c2ecf20Sopenharmony_ci div_t = gcd(freq_in, freq_out); 1748c2ecf20Sopenharmony_ci f_max = 0xffffffff / RL6231_PLL_N_MAX; 1758c2ecf20Sopenharmony_ci div = find_best_div(freq_in, f_max, div_t); 1768c2ecf20Sopenharmony_ci f_in = freq_in / div; 1778c2ecf20Sopenharmony_ci f_out = freq_out / div; 1788c2ecf20Sopenharmony_ci k = min_k; 1798c2ecf20Sopenharmony_ci if (min_k < -1) 1808c2ecf20Sopenharmony_ci min_k = -1; 1818c2ecf20Sopenharmony_ci for (k_t = min_k; k_t <= max_k; k_t++) { 1828c2ecf20Sopenharmony_ci for (n_t = 0; n_t <= max_n; n_t++) { 1838c2ecf20Sopenharmony_ci in_t = f_in * (n_t + 2); 1848c2ecf20Sopenharmony_ci pll_out = f_out * (k_t + 2); 1858c2ecf20Sopenharmony_ci if (in_t == pll_out) { 1868c2ecf20Sopenharmony_ci m_bypass = true; 1878c2ecf20Sopenharmony_ci n = n_t; 1888c2ecf20Sopenharmony_ci k = k_t; 1898c2ecf20Sopenharmony_ci goto code_find; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci out_t = in_t / (k_t + 2); 1928c2ecf20Sopenharmony_ci red = abs(f_out - out_t); 1938c2ecf20Sopenharmony_ci if (red < red_t) { 1948c2ecf20Sopenharmony_ci m_bypass = true; 1958c2ecf20Sopenharmony_ci n = n_t; 1968c2ecf20Sopenharmony_ci m = 0; 1978c2ecf20Sopenharmony_ci k = k_t; 1988c2ecf20Sopenharmony_ci if (red == 0) 1998c2ecf20Sopenharmony_ci goto code_find; 2008c2ecf20Sopenharmony_ci red_t = red; 2018c2ecf20Sopenharmony_ci } 2028c2ecf20Sopenharmony_ci for (m_t = 0; m_t <= max_m; m_t++) { 2038c2ecf20Sopenharmony_ci out_t = in_t / ((m_t + 2) * (k_t + 2)); 2048c2ecf20Sopenharmony_ci red = abs(f_out - out_t); 2058c2ecf20Sopenharmony_ci if (red < red_t) { 2068c2ecf20Sopenharmony_ci m_bypass = false; 2078c2ecf20Sopenharmony_ci n = n_t; 2088c2ecf20Sopenharmony_ci m = m_t; 2098c2ecf20Sopenharmony_ci k = k_t; 2108c2ecf20Sopenharmony_ci if (red == 0) 2118c2ecf20Sopenharmony_ci goto code_find; 2128c2ecf20Sopenharmony_ci red_t = red; 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci } 2158c2ecf20Sopenharmony_ci } 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci pr_debug("Only get approximation about PLL\n"); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cicode_find: 2208c2ecf20Sopenharmony_ci if (k == -1) { 2218c2ecf20Sopenharmony_ci k_bypass = true; 2228c2ecf20Sopenharmony_ci k = 0; 2238c2ecf20Sopenharmony_ci } 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci pll_code->m_bp = m_bypass; 2268c2ecf20Sopenharmony_ci pll_code->k_bp = k_bypass; 2278c2ecf20Sopenharmony_ci pll_code->m_code = m; 2288c2ecf20Sopenharmony_ci pll_code->n_code = n; 2298c2ecf20Sopenharmony_ci pll_code->k_code = k; 2308c2ecf20Sopenharmony_ci return 0; 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rl6231_pll_calc); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ciint rl6231_get_clk_info(int sclk, int rate) 2358c2ecf20Sopenharmony_ci{ 2368c2ecf20Sopenharmony_ci int i; 2378c2ecf20Sopenharmony_ci static const int pd[] = {1, 2, 3, 4, 6, 8, 12, 16}; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci if (sclk <= 0 || rate <= 0) 2408c2ecf20Sopenharmony_ci return -EINVAL; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci rate = rate << 8; 2438c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(pd); i++) 2448c2ecf20Sopenharmony_ci if (sclk == rate * pd[i]) 2458c2ecf20Sopenharmony_ci return i; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci return -EINVAL; 2488c2ecf20Sopenharmony_ci} 2498c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rl6231_get_clk_info); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("RL6231 class device shared support"); 2528c2ecf20Sopenharmony_ciMODULE_AUTHOR("Oder Chiou <oder_chiou@realtek.com>"); 2538c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 254