18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * DRA7 ATL (Audio Tracking Logic) clock driver 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2013 Texas Instruments, Inc. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Peter Ujfalusi <peter.ujfalusi@ti.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or modify 98c2ecf20Sopenharmony_ci * it under the terms of the GNU General Public License version 2 as 108c2ecf20Sopenharmony_ci * published by the Free Software Foundation. 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * This program is distributed "as is" WITHOUT ANY WARRANTY of any 138c2ecf20Sopenharmony_ci * kind, whether express or implied; without even the implied warranty 148c2ecf20Sopenharmony_ci * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 158c2ecf20Sopenharmony_ci * GNU General Public License for more details. 168c2ecf20Sopenharmony_ci */ 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include <linux/init.h> 198c2ecf20Sopenharmony_ci#include <linux/clk.h> 208c2ecf20Sopenharmony_ci#include <linux/clk-provider.h> 218c2ecf20Sopenharmony_ci#include <linux/slab.h> 228c2ecf20Sopenharmony_ci#include <linux/io.h> 238c2ecf20Sopenharmony_ci#include <linux/of.h> 248c2ecf20Sopenharmony_ci#include <linux/of_address.h> 258c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 268c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h> 278c2ecf20Sopenharmony_ci#include <linux/clk/ti.h> 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#include "clock.h" 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define DRA7_ATL_INSTANCES 4 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define DRA7_ATL_PPMR_REG(id) (0x200 + (id * 0x80)) 348c2ecf20Sopenharmony_ci#define DRA7_ATL_BBSR_REG(id) (0x204 + (id * 0x80)) 358c2ecf20Sopenharmony_ci#define DRA7_ATL_ATLCR_REG(id) (0x208 + (id * 0x80)) 368c2ecf20Sopenharmony_ci#define DRA7_ATL_SWEN_REG(id) (0x210 + (id * 0x80)) 378c2ecf20Sopenharmony_ci#define DRA7_ATL_BWSMUX_REG(id) (0x214 + (id * 0x80)) 388c2ecf20Sopenharmony_ci#define DRA7_ATL_AWSMUX_REG(id) (0x218 + (id * 0x80)) 398c2ecf20Sopenharmony_ci#define DRA7_ATL_PCLKMUX_REG(id) (0x21c + (id * 0x80)) 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci#define DRA7_ATL_SWEN BIT(0) 428c2ecf20Sopenharmony_ci#define DRA7_ATL_DIVIDER_MASK (0x1f) 438c2ecf20Sopenharmony_ci#define DRA7_ATL_PCLKMUX BIT(0) 448c2ecf20Sopenharmony_cistruct dra7_atl_clock_info; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistruct dra7_atl_desc { 478c2ecf20Sopenharmony_ci struct clk *clk; 488c2ecf20Sopenharmony_ci struct clk_hw hw; 498c2ecf20Sopenharmony_ci struct dra7_atl_clock_info *cinfo; 508c2ecf20Sopenharmony_ci int id; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci bool probed; /* the driver for the IP has been loaded */ 538c2ecf20Sopenharmony_ci bool valid; /* configured */ 548c2ecf20Sopenharmony_ci bool enabled; 558c2ecf20Sopenharmony_ci u32 bws; /* Baseband Word Select Mux */ 568c2ecf20Sopenharmony_ci u32 aws; /* Audio Word Select Mux */ 578c2ecf20Sopenharmony_ci u32 divider; /* Cached divider value */ 588c2ecf20Sopenharmony_ci}; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistruct dra7_atl_clock_info { 618c2ecf20Sopenharmony_ci struct device *dev; 628c2ecf20Sopenharmony_ci void __iomem *iobase; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci struct dra7_atl_desc *cdesc; 658c2ecf20Sopenharmony_ci}; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci#define to_atl_desc(_hw) container_of(_hw, struct dra7_atl_desc, hw) 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic inline void atl_write(struct dra7_atl_clock_info *cinfo, u32 reg, 708c2ecf20Sopenharmony_ci u32 val) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci __raw_writel(val, cinfo->iobase + reg); 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic inline int atl_read(struct dra7_atl_clock_info *cinfo, u32 reg) 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci return __raw_readl(cinfo->iobase + reg); 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic int atl_clk_enable(struct clk_hw *hw) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci struct dra7_atl_desc *cdesc = to_atl_desc(hw); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci if (!cdesc->probed) 858c2ecf20Sopenharmony_ci goto out; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (unlikely(!cdesc->valid)) 888c2ecf20Sopenharmony_ci dev_warn(cdesc->cinfo->dev, "atl%d has not been configured\n", 898c2ecf20Sopenharmony_ci cdesc->id); 908c2ecf20Sopenharmony_ci pm_runtime_get_sync(cdesc->cinfo->dev); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci atl_write(cdesc->cinfo, DRA7_ATL_ATLCR_REG(cdesc->id), 938c2ecf20Sopenharmony_ci cdesc->divider - 1); 948c2ecf20Sopenharmony_ci atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), DRA7_ATL_SWEN); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ciout: 978c2ecf20Sopenharmony_ci cdesc->enabled = true; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci return 0; 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic void atl_clk_disable(struct clk_hw *hw) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci struct dra7_atl_desc *cdesc = to_atl_desc(hw); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci if (!cdesc->probed) 1078c2ecf20Sopenharmony_ci goto out; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), 0); 1108c2ecf20Sopenharmony_ci pm_runtime_put_sync(cdesc->cinfo->dev); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ciout: 1138c2ecf20Sopenharmony_ci cdesc->enabled = false; 1148c2ecf20Sopenharmony_ci} 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_cistatic int atl_clk_is_enabled(struct clk_hw *hw) 1178c2ecf20Sopenharmony_ci{ 1188c2ecf20Sopenharmony_ci struct dra7_atl_desc *cdesc = to_atl_desc(hw); 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci return cdesc->enabled; 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic unsigned long atl_clk_recalc_rate(struct clk_hw *hw, 1248c2ecf20Sopenharmony_ci unsigned long parent_rate) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci struct dra7_atl_desc *cdesc = to_atl_desc(hw); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci return parent_rate / cdesc->divider; 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_cistatic long atl_clk_round_rate(struct clk_hw *hw, unsigned long rate, 1328c2ecf20Sopenharmony_ci unsigned long *parent_rate) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci unsigned divider; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci divider = (*parent_rate + rate / 2) / rate; 1378c2ecf20Sopenharmony_ci if (divider > DRA7_ATL_DIVIDER_MASK + 1) 1388c2ecf20Sopenharmony_ci divider = DRA7_ATL_DIVIDER_MASK + 1; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci return *parent_rate / divider; 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic int atl_clk_set_rate(struct clk_hw *hw, unsigned long rate, 1448c2ecf20Sopenharmony_ci unsigned long parent_rate) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci struct dra7_atl_desc *cdesc; 1478c2ecf20Sopenharmony_ci u32 divider; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci if (!hw || !rate) 1508c2ecf20Sopenharmony_ci return -EINVAL; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci cdesc = to_atl_desc(hw); 1538c2ecf20Sopenharmony_ci divider = ((parent_rate + rate / 2) / rate) - 1; 1548c2ecf20Sopenharmony_ci if (divider > DRA7_ATL_DIVIDER_MASK) 1558c2ecf20Sopenharmony_ci divider = DRA7_ATL_DIVIDER_MASK; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci cdesc->divider = divider + 1; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci return 0; 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_cistatic const struct clk_ops atl_clk_ops = { 1638c2ecf20Sopenharmony_ci .enable = atl_clk_enable, 1648c2ecf20Sopenharmony_ci .disable = atl_clk_disable, 1658c2ecf20Sopenharmony_ci .is_enabled = atl_clk_is_enabled, 1668c2ecf20Sopenharmony_ci .recalc_rate = atl_clk_recalc_rate, 1678c2ecf20Sopenharmony_ci .round_rate = atl_clk_round_rate, 1688c2ecf20Sopenharmony_ci .set_rate = atl_clk_set_rate, 1698c2ecf20Sopenharmony_ci}; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_cistatic void __init of_dra7_atl_clock_setup(struct device_node *node) 1728c2ecf20Sopenharmony_ci{ 1738c2ecf20Sopenharmony_ci struct dra7_atl_desc *clk_hw = NULL; 1748c2ecf20Sopenharmony_ci struct clk_init_data init = { NULL }; 1758c2ecf20Sopenharmony_ci const char **parent_names = NULL; 1768c2ecf20Sopenharmony_ci const char *name; 1778c2ecf20Sopenharmony_ci struct clk *clk; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL); 1808c2ecf20Sopenharmony_ci if (!clk_hw) { 1818c2ecf20Sopenharmony_ci pr_err("%s: could not allocate dra7_atl_desc\n", __func__); 1828c2ecf20Sopenharmony_ci return; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci clk_hw->hw.init = &init; 1868c2ecf20Sopenharmony_ci clk_hw->divider = 1; 1878c2ecf20Sopenharmony_ci name = ti_dt_clk_name(node); 1888c2ecf20Sopenharmony_ci init.name = name; 1898c2ecf20Sopenharmony_ci init.ops = &atl_clk_ops; 1908c2ecf20Sopenharmony_ci init.flags = CLK_IGNORE_UNUSED; 1918c2ecf20Sopenharmony_ci init.num_parents = of_clk_get_parent_count(node); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if (init.num_parents != 1) { 1948c2ecf20Sopenharmony_ci pr_err("%s: atl clock %pOFn must have 1 parent\n", __func__, 1958c2ecf20Sopenharmony_ci node); 1968c2ecf20Sopenharmony_ci goto cleanup; 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci parent_names = kzalloc(sizeof(char *), GFP_KERNEL); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci if (!parent_names) 2028c2ecf20Sopenharmony_ci goto cleanup; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci parent_names[0] = of_clk_get_parent_name(node, 0); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci init.parent_names = parent_names; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci clk = of_ti_clk_register(node, &clk_hw->hw, name); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci if (!IS_ERR(clk)) { 2118c2ecf20Sopenharmony_ci of_clk_add_provider(node, of_clk_src_simple_get, clk); 2128c2ecf20Sopenharmony_ci kfree(parent_names); 2138c2ecf20Sopenharmony_ci return; 2148c2ecf20Sopenharmony_ci } 2158c2ecf20Sopenharmony_cicleanup: 2168c2ecf20Sopenharmony_ci kfree(parent_names); 2178c2ecf20Sopenharmony_ci kfree(clk_hw); 2188c2ecf20Sopenharmony_ci} 2198c2ecf20Sopenharmony_ciCLK_OF_DECLARE(dra7_atl_clock, "ti,dra7-atl-clock", of_dra7_atl_clock_setup); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_cistatic int of_dra7_atl_clk_probe(struct platform_device *pdev) 2228c2ecf20Sopenharmony_ci{ 2238c2ecf20Sopenharmony_ci struct device_node *node = pdev->dev.of_node; 2248c2ecf20Sopenharmony_ci struct dra7_atl_clock_info *cinfo; 2258c2ecf20Sopenharmony_ci int i; 2268c2ecf20Sopenharmony_ci int ret = 0; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci if (!node) 2298c2ecf20Sopenharmony_ci return -ENODEV; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL); 2328c2ecf20Sopenharmony_ci if (!cinfo) 2338c2ecf20Sopenharmony_ci return -ENOMEM; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci cinfo->iobase = of_iomap(node, 0); 2368c2ecf20Sopenharmony_ci cinfo->dev = &pdev->dev; 2378c2ecf20Sopenharmony_ci pm_runtime_enable(cinfo->dev); 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci pm_runtime_get_sync(cinfo->dev); 2408c2ecf20Sopenharmony_ci atl_write(cinfo, DRA7_ATL_PCLKMUX_REG(0), DRA7_ATL_PCLKMUX); 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci for (i = 0; i < DRA7_ATL_INSTANCES; i++) { 2438c2ecf20Sopenharmony_ci struct device_node *cfg_node; 2448c2ecf20Sopenharmony_ci char prop[5]; 2458c2ecf20Sopenharmony_ci struct dra7_atl_desc *cdesc; 2468c2ecf20Sopenharmony_ci struct of_phandle_args clkspec; 2478c2ecf20Sopenharmony_ci struct clk *clk; 2488c2ecf20Sopenharmony_ci int rc; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci rc = of_parse_phandle_with_args(node, "ti,provided-clocks", 2518c2ecf20Sopenharmony_ci NULL, i, &clkspec); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci if (rc) { 2548c2ecf20Sopenharmony_ci pr_err("%s: failed to lookup atl clock %d\n", __func__, 2558c2ecf20Sopenharmony_ci i); 2568c2ecf20Sopenharmony_ci ret = -EINVAL; 2578c2ecf20Sopenharmony_ci goto pm_put; 2588c2ecf20Sopenharmony_ci } 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci clk = of_clk_get_from_provider(&clkspec); 2618c2ecf20Sopenharmony_ci if (IS_ERR(clk)) { 2628c2ecf20Sopenharmony_ci pr_err("%s: failed to get atl clock %d from provider\n", 2638c2ecf20Sopenharmony_ci __func__, i); 2648c2ecf20Sopenharmony_ci ret = PTR_ERR(clk); 2658c2ecf20Sopenharmony_ci goto pm_put; 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci cdesc = to_atl_desc(__clk_get_hw(clk)); 2698c2ecf20Sopenharmony_ci cdesc->cinfo = cinfo; 2708c2ecf20Sopenharmony_ci cdesc->id = i; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci /* Get configuration for the ATL instances */ 2738c2ecf20Sopenharmony_ci snprintf(prop, sizeof(prop), "atl%u", i); 2748c2ecf20Sopenharmony_ci cfg_node = of_get_child_by_name(node, prop); 2758c2ecf20Sopenharmony_ci if (cfg_node) { 2768c2ecf20Sopenharmony_ci ret = of_property_read_u32(cfg_node, "bws", 2778c2ecf20Sopenharmony_ci &cdesc->bws); 2788c2ecf20Sopenharmony_ci ret |= of_property_read_u32(cfg_node, "aws", 2798c2ecf20Sopenharmony_ci &cdesc->aws); 2808c2ecf20Sopenharmony_ci if (!ret) { 2818c2ecf20Sopenharmony_ci cdesc->valid = true; 2828c2ecf20Sopenharmony_ci atl_write(cinfo, DRA7_ATL_BWSMUX_REG(i), 2838c2ecf20Sopenharmony_ci cdesc->bws); 2848c2ecf20Sopenharmony_ci atl_write(cinfo, DRA7_ATL_AWSMUX_REG(i), 2858c2ecf20Sopenharmony_ci cdesc->aws); 2868c2ecf20Sopenharmony_ci } 2878c2ecf20Sopenharmony_ci of_node_put(cfg_node); 2888c2ecf20Sopenharmony_ci } 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci cdesc->probed = true; 2918c2ecf20Sopenharmony_ci /* 2928c2ecf20Sopenharmony_ci * Enable the clock if it has been asked prior to loading the 2938c2ecf20Sopenharmony_ci * hw driver 2948c2ecf20Sopenharmony_ci */ 2958c2ecf20Sopenharmony_ci if (cdesc->enabled) 2968c2ecf20Sopenharmony_ci atl_clk_enable(__clk_get_hw(clk)); 2978c2ecf20Sopenharmony_ci } 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_cipm_put: 3008c2ecf20Sopenharmony_ci pm_runtime_put_sync(cinfo->dev); 3018c2ecf20Sopenharmony_ci return ret; 3028c2ecf20Sopenharmony_ci} 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_cistatic const struct of_device_id of_dra7_atl_clk_match_tbl[] = { 3058c2ecf20Sopenharmony_ci { .compatible = "ti,dra7-atl", }, 3068c2ecf20Sopenharmony_ci {}, 3078c2ecf20Sopenharmony_ci}; 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_cistatic struct platform_driver dra7_atl_clk_driver = { 3108c2ecf20Sopenharmony_ci .driver = { 3118c2ecf20Sopenharmony_ci .name = "dra7-atl", 3128c2ecf20Sopenharmony_ci .suppress_bind_attrs = true, 3138c2ecf20Sopenharmony_ci .of_match_table = of_dra7_atl_clk_match_tbl, 3148c2ecf20Sopenharmony_ci }, 3158c2ecf20Sopenharmony_ci .probe = of_dra7_atl_clk_probe, 3168c2ecf20Sopenharmony_ci}; 3178c2ecf20Sopenharmony_cibuiltin_platform_driver(dra7_atl_clk_driver); 318