19ac33b0cSPeter Ujfalusi /* 29ac33b0cSPeter Ujfalusi * DRA7 ATL (Audio Tracking Logic) clock driver 39ac33b0cSPeter Ujfalusi * 49ac33b0cSPeter Ujfalusi * Copyright (C) 2013 Texas Instruments, Inc. 59ac33b0cSPeter Ujfalusi * 69ac33b0cSPeter Ujfalusi * Peter Ujfalusi <peter.ujfalusi@ti.com> 79ac33b0cSPeter Ujfalusi * 89ac33b0cSPeter Ujfalusi * This program is free software; you can redistribute it and/or modify 99ac33b0cSPeter Ujfalusi * it under the terms of the GNU General Public License version 2 as 109ac33b0cSPeter Ujfalusi * published by the Free Software Foundation. 119ac33b0cSPeter Ujfalusi * 129ac33b0cSPeter Ujfalusi * This program is distributed "as is" WITHOUT ANY WARRANTY of any 139ac33b0cSPeter Ujfalusi * kind, whether express or implied; without even the implied warranty 149ac33b0cSPeter Ujfalusi * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 159ac33b0cSPeter Ujfalusi * GNU General Public License for more details. 169ac33b0cSPeter Ujfalusi */ 179ac33b0cSPeter Ujfalusi 18172ff5a2SPaul Gortmaker #include <linux/init.h> 191b29e601SStephen Boyd #include <linux/clk.h> 209ac33b0cSPeter Ujfalusi #include <linux/clk-provider.h> 219ac33b0cSPeter Ujfalusi #include <linux/slab.h> 229ac33b0cSPeter Ujfalusi #include <linux/io.h> 239ac33b0cSPeter Ujfalusi #include <linux/of.h> 249ac33b0cSPeter Ujfalusi #include <linux/of_address.h> 259ac33b0cSPeter Ujfalusi #include <linux/platform_device.h> 269ac33b0cSPeter Ujfalusi #include <linux/pm_runtime.h> 271ae79c46STero Kristo #include <linux/clk/ti.h> 281ae79c46STero Kristo 291ae79c46STero Kristo #include "clock.h" 309ac33b0cSPeter Ujfalusi 319ac33b0cSPeter Ujfalusi #define DRA7_ATL_INSTANCES 4 329ac33b0cSPeter Ujfalusi 339ac33b0cSPeter Ujfalusi #define DRA7_ATL_PPMR_REG(id) (0x200 + (id * 0x80)) 349ac33b0cSPeter Ujfalusi #define DRA7_ATL_BBSR_REG(id) (0x204 + (id * 0x80)) 359ac33b0cSPeter Ujfalusi #define DRA7_ATL_ATLCR_REG(id) (0x208 + (id * 0x80)) 369ac33b0cSPeter Ujfalusi #define DRA7_ATL_SWEN_REG(id) (0x210 + (id * 0x80)) 379ac33b0cSPeter Ujfalusi #define DRA7_ATL_BWSMUX_REG(id) (0x214 + (id * 0x80)) 389ac33b0cSPeter Ujfalusi #define DRA7_ATL_AWSMUX_REG(id) (0x218 + (id * 0x80)) 399ac33b0cSPeter Ujfalusi #define DRA7_ATL_PCLKMUX_REG(id) (0x21c + (id * 0x80)) 409ac33b0cSPeter Ujfalusi 419ac33b0cSPeter Ujfalusi #define DRA7_ATL_SWEN BIT(0) 429ac33b0cSPeter Ujfalusi #define DRA7_ATL_DIVIDER_MASK (0x1f) 439ac33b0cSPeter Ujfalusi #define DRA7_ATL_PCLKMUX BIT(0) 449ac33b0cSPeter Ujfalusi struct dra7_atl_clock_info; 459ac33b0cSPeter Ujfalusi 469ac33b0cSPeter Ujfalusi struct dra7_atl_desc { 479ac33b0cSPeter Ujfalusi struct clk *clk; 489ac33b0cSPeter Ujfalusi struct clk_hw hw; 499ac33b0cSPeter Ujfalusi struct dra7_atl_clock_info *cinfo; 509ac33b0cSPeter Ujfalusi int id; 519ac33b0cSPeter Ujfalusi 529ac33b0cSPeter Ujfalusi bool probed; /* the driver for the IP has been loaded */ 539ac33b0cSPeter Ujfalusi bool valid; /* configured */ 549ac33b0cSPeter Ujfalusi bool enabled; 559ac33b0cSPeter Ujfalusi u32 bws; /* Baseband Word Select Mux */ 569ac33b0cSPeter Ujfalusi u32 aws; /* Audio Word Select Mux */ 579ac33b0cSPeter Ujfalusi u32 divider; /* Cached divider value */ 589ac33b0cSPeter Ujfalusi }; 599ac33b0cSPeter Ujfalusi 609ac33b0cSPeter Ujfalusi struct dra7_atl_clock_info { 619ac33b0cSPeter Ujfalusi struct device *dev; 629ac33b0cSPeter Ujfalusi void __iomem *iobase; 639ac33b0cSPeter Ujfalusi 649ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc; 659ac33b0cSPeter Ujfalusi }; 669ac33b0cSPeter Ujfalusi 679ac33b0cSPeter Ujfalusi #define to_atl_desc(_hw) container_of(_hw, struct dra7_atl_desc, hw) 689ac33b0cSPeter Ujfalusi 699ac33b0cSPeter Ujfalusi static inline void atl_write(struct dra7_atl_clock_info *cinfo, u32 reg, 709ac33b0cSPeter Ujfalusi u32 val) 719ac33b0cSPeter Ujfalusi { 729ac33b0cSPeter Ujfalusi __raw_writel(val, cinfo->iobase + reg); 739ac33b0cSPeter Ujfalusi } 749ac33b0cSPeter Ujfalusi 759ac33b0cSPeter Ujfalusi static inline int atl_read(struct dra7_atl_clock_info *cinfo, u32 reg) 769ac33b0cSPeter Ujfalusi { 779ac33b0cSPeter Ujfalusi return __raw_readl(cinfo->iobase + reg); 789ac33b0cSPeter Ujfalusi } 799ac33b0cSPeter Ujfalusi 809ac33b0cSPeter Ujfalusi static int atl_clk_enable(struct clk_hw *hw) 819ac33b0cSPeter Ujfalusi { 829ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc = to_atl_desc(hw); 839ac33b0cSPeter Ujfalusi 849ac33b0cSPeter Ujfalusi if (!cdesc->probed) 859ac33b0cSPeter Ujfalusi goto out; 869ac33b0cSPeter Ujfalusi 879ac33b0cSPeter Ujfalusi if (unlikely(!cdesc->valid)) 889ac33b0cSPeter Ujfalusi dev_warn(cdesc->cinfo->dev, "atl%d has not been configured\n", 899ac33b0cSPeter Ujfalusi cdesc->id); 909ac33b0cSPeter Ujfalusi pm_runtime_get_sync(cdesc->cinfo->dev); 919ac33b0cSPeter Ujfalusi 929ac33b0cSPeter Ujfalusi atl_write(cdesc->cinfo, DRA7_ATL_ATLCR_REG(cdesc->id), 939ac33b0cSPeter Ujfalusi cdesc->divider - 1); 949ac33b0cSPeter Ujfalusi atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), DRA7_ATL_SWEN); 959ac33b0cSPeter Ujfalusi 969ac33b0cSPeter Ujfalusi out: 979ac33b0cSPeter Ujfalusi cdesc->enabled = true; 989ac33b0cSPeter Ujfalusi 999ac33b0cSPeter Ujfalusi return 0; 1009ac33b0cSPeter Ujfalusi } 1019ac33b0cSPeter Ujfalusi 1029ac33b0cSPeter Ujfalusi static void atl_clk_disable(struct clk_hw *hw) 1039ac33b0cSPeter Ujfalusi { 1049ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc = to_atl_desc(hw); 1059ac33b0cSPeter Ujfalusi 1069ac33b0cSPeter Ujfalusi if (!cdesc->probed) 1079ac33b0cSPeter Ujfalusi goto out; 1089ac33b0cSPeter Ujfalusi 1099ac33b0cSPeter Ujfalusi atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), 0); 1109ac33b0cSPeter Ujfalusi pm_runtime_put_sync(cdesc->cinfo->dev); 1119ac33b0cSPeter Ujfalusi 1129ac33b0cSPeter Ujfalusi out: 1139ac33b0cSPeter Ujfalusi cdesc->enabled = false; 1149ac33b0cSPeter Ujfalusi } 1159ac33b0cSPeter Ujfalusi 1169ac33b0cSPeter Ujfalusi static int atl_clk_is_enabled(struct clk_hw *hw) 1179ac33b0cSPeter Ujfalusi { 1189ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc = to_atl_desc(hw); 1199ac33b0cSPeter Ujfalusi 1209ac33b0cSPeter Ujfalusi return cdesc->enabled; 1219ac33b0cSPeter Ujfalusi } 1229ac33b0cSPeter Ujfalusi 1239ac33b0cSPeter Ujfalusi static unsigned long atl_clk_recalc_rate(struct clk_hw *hw, 1249ac33b0cSPeter Ujfalusi unsigned long parent_rate) 1259ac33b0cSPeter Ujfalusi { 1269ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc = to_atl_desc(hw); 1279ac33b0cSPeter Ujfalusi 1289ac33b0cSPeter Ujfalusi return parent_rate / cdesc->divider; 1299ac33b0cSPeter Ujfalusi } 1309ac33b0cSPeter Ujfalusi 1319ac33b0cSPeter Ujfalusi static long atl_clk_round_rate(struct clk_hw *hw, unsigned long rate, 1329ac33b0cSPeter Ujfalusi unsigned long *parent_rate) 1339ac33b0cSPeter Ujfalusi { 1349ac33b0cSPeter Ujfalusi unsigned divider; 1359ac33b0cSPeter Ujfalusi 1369ac33b0cSPeter Ujfalusi divider = (*parent_rate + rate / 2) / rate; 1379ac33b0cSPeter Ujfalusi if (divider > DRA7_ATL_DIVIDER_MASK + 1) 1389ac33b0cSPeter Ujfalusi divider = DRA7_ATL_DIVIDER_MASK + 1; 1399ac33b0cSPeter Ujfalusi 1409ac33b0cSPeter Ujfalusi return *parent_rate / divider; 1419ac33b0cSPeter Ujfalusi } 1429ac33b0cSPeter Ujfalusi 1439ac33b0cSPeter Ujfalusi static int atl_clk_set_rate(struct clk_hw *hw, unsigned long rate, 1449ac33b0cSPeter Ujfalusi unsigned long parent_rate) 1459ac33b0cSPeter Ujfalusi { 14620411dadSNishanth Menon struct dra7_atl_desc *cdesc; 1479ac33b0cSPeter Ujfalusi u32 divider; 1489ac33b0cSPeter Ujfalusi 14920411dadSNishanth Menon if (!hw || !rate) 15020411dadSNishanth Menon return -EINVAL; 15120411dadSNishanth Menon 15220411dadSNishanth Menon cdesc = to_atl_desc(hw); 1539ac33b0cSPeter Ujfalusi divider = ((parent_rate + rate / 2) / rate) - 1; 1549ac33b0cSPeter Ujfalusi if (divider > DRA7_ATL_DIVIDER_MASK) 1559ac33b0cSPeter Ujfalusi divider = DRA7_ATL_DIVIDER_MASK; 1569ac33b0cSPeter Ujfalusi 1579ac33b0cSPeter Ujfalusi cdesc->divider = divider + 1; 1589ac33b0cSPeter Ujfalusi 1599ac33b0cSPeter Ujfalusi return 0; 1609ac33b0cSPeter Ujfalusi } 1619ac33b0cSPeter Ujfalusi 162412d6b47SStephen Boyd static const struct clk_ops atl_clk_ops = { 1639ac33b0cSPeter Ujfalusi .enable = atl_clk_enable, 1649ac33b0cSPeter Ujfalusi .disable = atl_clk_disable, 1659ac33b0cSPeter Ujfalusi .is_enabled = atl_clk_is_enabled, 1669ac33b0cSPeter Ujfalusi .recalc_rate = atl_clk_recalc_rate, 1679ac33b0cSPeter Ujfalusi .round_rate = atl_clk_round_rate, 1689ac33b0cSPeter Ujfalusi .set_rate = atl_clk_set_rate, 1699ac33b0cSPeter Ujfalusi }; 1709ac33b0cSPeter Ujfalusi 1719ac33b0cSPeter Ujfalusi static void __init of_dra7_atl_clock_setup(struct device_node *node) 1729ac33b0cSPeter Ujfalusi { 1739ac33b0cSPeter Ujfalusi struct dra7_atl_desc *clk_hw = NULL; 174412d6b47SStephen Boyd struct clk_init_data init = { NULL }; 1759ac33b0cSPeter Ujfalusi const char **parent_names = NULL; 1769ac33b0cSPeter Ujfalusi struct clk *clk; 1771ae79c46STero Kristo int ret; 1789ac33b0cSPeter Ujfalusi 1799ac33b0cSPeter Ujfalusi clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL); 1809ac33b0cSPeter Ujfalusi if (!clk_hw) { 1819ac33b0cSPeter Ujfalusi pr_err("%s: could not allocate dra7_atl_desc\n", __func__); 1829ac33b0cSPeter Ujfalusi return; 1839ac33b0cSPeter Ujfalusi } 1849ac33b0cSPeter Ujfalusi 1859ac33b0cSPeter Ujfalusi clk_hw->hw.init = &init; 1869ac33b0cSPeter Ujfalusi clk_hw->divider = 1; 1879ac33b0cSPeter Ujfalusi init.name = node->name; 1889ac33b0cSPeter Ujfalusi init.ops = &atl_clk_ops; 1899ac33b0cSPeter Ujfalusi init.flags = CLK_IGNORE_UNUSED; 1909ac33b0cSPeter Ujfalusi init.num_parents = of_clk_get_parent_count(node); 1919ac33b0cSPeter Ujfalusi 1929ac33b0cSPeter Ujfalusi if (init.num_parents != 1) { 1939ac33b0cSPeter Ujfalusi pr_err("%s: atl clock %s must have 1 parent\n", __func__, 1949ac33b0cSPeter Ujfalusi node->name); 1959ac33b0cSPeter Ujfalusi goto cleanup; 1969ac33b0cSPeter Ujfalusi } 1979ac33b0cSPeter Ujfalusi 1989ac33b0cSPeter Ujfalusi parent_names = kzalloc(sizeof(char *), GFP_KERNEL); 1999ac33b0cSPeter Ujfalusi 2009ac33b0cSPeter Ujfalusi if (!parent_names) 2019ac33b0cSPeter Ujfalusi goto cleanup; 2029ac33b0cSPeter Ujfalusi 2039ac33b0cSPeter Ujfalusi parent_names[0] = of_clk_get_parent_name(node, 0); 2049ac33b0cSPeter Ujfalusi 2059ac33b0cSPeter Ujfalusi init.parent_names = parent_names; 2069ac33b0cSPeter Ujfalusi 2071ae79c46STero Kristo clk = ti_clk_register(NULL, &clk_hw->hw, node->name); 2089ac33b0cSPeter Ujfalusi 2099ac33b0cSPeter Ujfalusi if (!IS_ERR(clk)) { 2101ae79c46STero Kristo ret = ti_clk_add_alias(NULL, clk, node->name); 2111ae79c46STero Kristo if (ret) { 2121ae79c46STero Kristo clk_unregister(clk); 2131ae79c46STero Kristo goto cleanup; 2141ae79c46STero Kristo } 2159ac33b0cSPeter Ujfalusi of_clk_add_provider(node, of_clk_src_simple_get, clk); 21673b5d5f7STero Kristo kfree(parent_names); 2179ac33b0cSPeter Ujfalusi return; 2189ac33b0cSPeter Ujfalusi } 2199ac33b0cSPeter Ujfalusi cleanup: 2209ac33b0cSPeter Ujfalusi kfree(parent_names); 2219ac33b0cSPeter Ujfalusi kfree(clk_hw); 2229ac33b0cSPeter Ujfalusi } 2239ac33b0cSPeter Ujfalusi CLK_OF_DECLARE(dra7_atl_clock, "ti,dra7-atl-clock", of_dra7_atl_clock_setup); 2249ac33b0cSPeter Ujfalusi 2259ac33b0cSPeter Ujfalusi static int of_dra7_atl_clk_probe(struct platform_device *pdev) 2269ac33b0cSPeter Ujfalusi { 2279ac33b0cSPeter Ujfalusi struct device_node *node = pdev->dev.of_node; 2289ac33b0cSPeter Ujfalusi struct dra7_atl_clock_info *cinfo; 2299ac33b0cSPeter Ujfalusi int i; 2309ac33b0cSPeter Ujfalusi int ret = 0; 2319ac33b0cSPeter Ujfalusi 2329ac33b0cSPeter Ujfalusi if (!node) 2339ac33b0cSPeter Ujfalusi return -ENODEV; 2349ac33b0cSPeter Ujfalusi 2359ac33b0cSPeter Ujfalusi cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL); 2369ac33b0cSPeter Ujfalusi if (!cinfo) 2379ac33b0cSPeter Ujfalusi return -ENOMEM; 2389ac33b0cSPeter Ujfalusi 2399ac33b0cSPeter Ujfalusi cinfo->iobase = of_iomap(node, 0); 2409ac33b0cSPeter Ujfalusi cinfo->dev = &pdev->dev; 2419ac33b0cSPeter Ujfalusi pm_runtime_enable(cinfo->dev); 24204ed831fSPeter Ujfalusi pm_runtime_irq_safe(cinfo->dev); 2439ac33b0cSPeter Ujfalusi 2449ac33b0cSPeter Ujfalusi pm_runtime_get_sync(cinfo->dev); 2459ac33b0cSPeter Ujfalusi atl_write(cinfo, DRA7_ATL_PCLKMUX_REG(0), DRA7_ATL_PCLKMUX); 2469ac33b0cSPeter Ujfalusi 2479ac33b0cSPeter Ujfalusi for (i = 0; i < DRA7_ATL_INSTANCES; i++) { 2489ac33b0cSPeter Ujfalusi struct device_node *cfg_node; 2499ac33b0cSPeter Ujfalusi char prop[5]; 2509ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc; 2519ac33b0cSPeter Ujfalusi struct of_phandle_args clkspec; 2529ac33b0cSPeter Ujfalusi struct clk *clk; 2539ac33b0cSPeter Ujfalusi int rc; 2549ac33b0cSPeter Ujfalusi 2559ac33b0cSPeter Ujfalusi rc = of_parse_phandle_with_args(node, "ti,provided-clocks", 2569ac33b0cSPeter Ujfalusi NULL, i, &clkspec); 2579ac33b0cSPeter Ujfalusi 2589ac33b0cSPeter Ujfalusi if (rc) { 2599ac33b0cSPeter Ujfalusi pr_err("%s: failed to lookup atl clock %d\n", __func__, 2609ac33b0cSPeter Ujfalusi i); 2619ac33b0cSPeter Ujfalusi return -EINVAL; 2629ac33b0cSPeter Ujfalusi } 2639ac33b0cSPeter Ujfalusi 2649ac33b0cSPeter Ujfalusi clk = of_clk_get_from_provider(&clkspec); 265e0cdcda5SKrzysztof Kozlowski if (IS_ERR(clk)) { 266e0cdcda5SKrzysztof Kozlowski pr_err("%s: failed to get atl clock %d from provider\n", 267e0cdcda5SKrzysztof Kozlowski __func__, i); 268e0cdcda5SKrzysztof Kozlowski return PTR_ERR(clk); 269e0cdcda5SKrzysztof Kozlowski } 2709ac33b0cSPeter Ujfalusi 2719ac33b0cSPeter Ujfalusi cdesc = to_atl_desc(__clk_get_hw(clk)); 2729ac33b0cSPeter Ujfalusi cdesc->cinfo = cinfo; 2739ac33b0cSPeter Ujfalusi cdesc->id = i; 2749ac33b0cSPeter Ujfalusi 2759ac33b0cSPeter Ujfalusi /* Get configuration for the ATL instances */ 2769ac33b0cSPeter Ujfalusi snprintf(prop, sizeof(prop), "atl%u", i); 27733ec6dbcSJohan Hovold cfg_node = of_get_child_by_name(node, prop); 2789ac33b0cSPeter Ujfalusi if (cfg_node) { 2799ac33b0cSPeter Ujfalusi ret = of_property_read_u32(cfg_node, "bws", 2809ac33b0cSPeter Ujfalusi &cdesc->bws); 2819ac33b0cSPeter Ujfalusi ret |= of_property_read_u32(cfg_node, "aws", 2829ac33b0cSPeter Ujfalusi &cdesc->aws); 2839ac33b0cSPeter Ujfalusi if (!ret) { 2849ac33b0cSPeter Ujfalusi cdesc->valid = true; 2859ac33b0cSPeter Ujfalusi atl_write(cinfo, DRA7_ATL_BWSMUX_REG(i), 2869ac33b0cSPeter Ujfalusi cdesc->bws); 2879ac33b0cSPeter Ujfalusi atl_write(cinfo, DRA7_ATL_AWSMUX_REG(i), 2889ac33b0cSPeter Ujfalusi cdesc->aws); 2899ac33b0cSPeter Ujfalusi } 290660e1551SPeter Ujfalusi of_node_put(cfg_node); 2919ac33b0cSPeter Ujfalusi } 2929ac33b0cSPeter Ujfalusi 2939ac33b0cSPeter Ujfalusi cdesc->probed = true; 2949ac33b0cSPeter Ujfalusi /* 2959ac33b0cSPeter Ujfalusi * Enable the clock if it has been asked prior to loading the 2969ac33b0cSPeter Ujfalusi * hw driver 2979ac33b0cSPeter Ujfalusi */ 2989ac33b0cSPeter Ujfalusi if (cdesc->enabled) 2999ac33b0cSPeter Ujfalusi atl_clk_enable(__clk_get_hw(clk)); 3009ac33b0cSPeter Ujfalusi } 3019ac33b0cSPeter Ujfalusi pm_runtime_put_sync(cinfo->dev); 3029ac33b0cSPeter Ujfalusi 3039ac33b0cSPeter Ujfalusi return ret; 3049ac33b0cSPeter Ujfalusi } 3059ac33b0cSPeter Ujfalusi 306f375573cSFabian Frederick static const struct of_device_id of_dra7_atl_clk_match_tbl[] = { 3079ac33b0cSPeter Ujfalusi { .compatible = "ti,dra7-atl", }, 3089ac33b0cSPeter Ujfalusi {}, 3099ac33b0cSPeter Ujfalusi }; 3109ac33b0cSPeter Ujfalusi 3119ac33b0cSPeter Ujfalusi static struct platform_driver dra7_atl_clk_driver = { 3129ac33b0cSPeter Ujfalusi .driver = { 3139ac33b0cSPeter Ujfalusi .name = "dra7-atl", 314172ff5a2SPaul Gortmaker .suppress_bind_attrs = true, 3159ac33b0cSPeter Ujfalusi .of_match_table = of_dra7_atl_clk_match_tbl, 3169ac33b0cSPeter Ujfalusi }, 3179ac33b0cSPeter Ujfalusi .probe = of_dra7_atl_clk_probe, 3189ac33b0cSPeter Ujfalusi }; 319172ff5a2SPaul Gortmaker builtin_platform_driver(dra7_atl_clk_driver); 320