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 189ac33b0cSPeter Ujfalusi #include <linux/module.h> 199ac33b0cSPeter Ujfalusi #include <linux/clk-provider.h> 209ac33b0cSPeter Ujfalusi #include <linux/slab.h> 219ac33b0cSPeter Ujfalusi #include <linux/io.h> 229ac33b0cSPeter Ujfalusi #include <linux/of.h> 239ac33b0cSPeter Ujfalusi #include <linux/of_address.h> 249ac33b0cSPeter Ujfalusi #include <linux/platform_device.h> 259ac33b0cSPeter Ujfalusi #include <linux/pm_runtime.h> 269ac33b0cSPeter Ujfalusi 279ac33b0cSPeter Ujfalusi #define DRA7_ATL_INSTANCES 4 289ac33b0cSPeter Ujfalusi 299ac33b0cSPeter Ujfalusi #define DRA7_ATL_PPMR_REG(id) (0x200 + (id * 0x80)) 309ac33b0cSPeter Ujfalusi #define DRA7_ATL_BBSR_REG(id) (0x204 + (id * 0x80)) 319ac33b0cSPeter Ujfalusi #define DRA7_ATL_ATLCR_REG(id) (0x208 + (id * 0x80)) 329ac33b0cSPeter Ujfalusi #define DRA7_ATL_SWEN_REG(id) (0x210 + (id * 0x80)) 339ac33b0cSPeter Ujfalusi #define DRA7_ATL_BWSMUX_REG(id) (0x214 + (id * 0x80)) 349ac33b0cSPeter Ujfalusi #define DRA7_ATL_AWSMUX_REG(id) (0x218 + (id * 0x80)) 359ac33b0cSPeter Ujfalusi #define DRA7_ATL_PCLKMUX_REG(id) (0x21c + (id * 0x80)) 369ac33b0cSPeter Ujfalusi 379ac33b0cSPeter Ujfalusi #define DRA7_ATL_SWEN BIT(0) 389ac33b0cSPeter Ujfalusi #define DRA7_ATL_DIVIDER_MASK (0x1f) 399ac33b0cSPeter Ujfalusi #define DRA7_ATL_PCLKMUX BIT(0) 409ac33b0cSPeter Ujfalusi struct dra7_atl_clock_info; 419ac33b0cSPeter Ujfalusi 429ac33b0cSPeter Ujfalusi struct dra7_atl_desc { 439ac33b0cSPeter Ujfalusi struct clk *clk; 449ac33b0cSPeter Ujfalusi struct clk_hw hw; 459ac33b0cSPeter Ujfalusi struct dra7_atl_clock_info *cinfo; 469ac33b0cSPeter Ujfalusi int id; 479ac33b0cSPeter Ujfalusi 489ac33b0cSPeter Ujfalusi bool probed; /* the driver for the IP has been loaded */ 499ac33b0cSPeter Ujfalusi bool valid; /* configured */ 509ac33b0cSPeter Ujfalusi bool enabled; 519ac33b0cSPeter Ujfalusi u32 bws; /* Baseband Word Select Mux */ 529ac33b0cSPeter Ujfalusi u32 aws; /* Audio Word Select Mux */ 539ac33b0cSPeter Ujfalusi u32 divider; /* Cached divider value */ 549ac33b0cSPeter Ujfalusi }; 559ac33b0cSPeter Ujfalusi 569ac33b0cSPeter Ujfalusi struct dra7_atl_clock_info { 579ac33b0cSPeter Ujfalusi struct device *dev; 589ac33b0cSPeter Ujfalusi void __iomem *iobase; 599ac33b0cSPeter Ujfalusi 609ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc; 619ac33b0cSPeter Ujfalusi }; 629ac33b0cSPeter Ujfalusi 639ac33b0cSPeter Ujfalusi #define to_atl_desc(_hw) container_of(_hw, struct dra7_atl_desc, hw) 649ac33b0cSPeter Ujfalusi 659ac33b0cSPeter Ujfalusi static inline void atl_write(struct dra7_atl_clock_info *cinfo, u32 reg, 669ac33b0cSPeter Ujfalusi u32 val) 679ac33b0cSPeter Ujfalusi { 689ac33b0cSPeter Ujfalusi __raw_writel(val, cinfo->iobase + reg); 699ac33b0cSPeter Ujfalusi } 709ac33b0cSPeter Ujfalusi 719ac33b0cSPeter Ujfalusi static inline int atl_read(struct dra7_atl_clock_info *cinfo, u32 reg) 729ac33b0cSPeter Ujfalusi { 739ac33b0cSPeter Ujfalusi return __raw_readl(cinfo->iobase + reg); 749ac33b0cSPeter Ujfalusi } 759ac33b0cSPeter Ujfalusi 769ac33b0cSPeter Ujfalusi static int atl_clk_enable(struct clk_hw *hw) 779ac33b0cSPeter Ujfalusi { 789ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc = to_atl_desc(hw); 799ac33b0cSPeter Ujfalusi 809ac33b0cSPeter Ujfalusi if (!cdesc->probed) 819ac33b0cSPeter Ujfalusi goto out; 829ac33b0cSPeter Ujfalusi 839ac33b0cSPeter Ujfalusi if (unlikely(!cdesc->valid)) 849ac33b0cSPeter Ujfalusi dev_warn(cdesc->cinfo->dev, "atl%d has not been configured\n", 859ac33b0cSPeter Ujfalusi cdesc->id); 869ac33b0cSPeter Ujfalusi pm_runtime_get_sync(cdesc->cinfo->dev); 879ac33b0cSPeter Ujfalusi 889ac33b0cSPeter Ujfalusi atl_write(cdesc->cinfo, DRA7_ATL_ATLCR_REG(cdesc->id), 899ac33b0cSPeter Ujfalusi cdesc->divider - 1); 909ac33b0cSPeter Ujfalusi atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), DRA7_ATL_SWEN); 919ac33b0cSPeter Ujfalusi 929ac33b0cSPeter Ujfalusi out: 939ac33b0cSPeter Ujfalusi cdesc->enabled = true; 949ac33b0cSPeter Ujfalusi 959ac33b0cSPeter Ujfalusi return 0; 969ac33b0cSPeter Ujfalusi } 979ac33b0cSPeter Ujfalusi 989ac33b0cSPeter Ujfalusi static void atl_clk_disable(struct clk_hw *hw) 999ac33b0cSPeter Ujfalusi { 1009ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc = to_atl_desc(hw); 1019ac33b0cSPeter Ujfalusi 1029ac33b0cSPeter Ujfalusi if (!cdesc->probed) 1039ac33b0cSPeter Ujfalusi goto out; 1049ac33b0cSPeter Ujfalusi 1059ac33b0cSPeter Ujfalusi atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), 0); 1069ac33b0cSPeter Ujfalusi pm_runtime_put_sync(cdesc->cinfo->dev); 1079ac33b0cSPeter Ujfalusi 1089ac33b0cSPeter Ujfalusi out: 1099ac33b0cSPeter Ujfalusi cdesc->enabled = false; 1109ac33b0cSPeter Ujfalusi } 1119ac33b0cSPeter Ujfalusi 1129ac33b0cSPeter Ujfalusi static int atl_clk_is_enabled(struct clk_hw *hw) 1139ac33b0cSPeter Ujfalusi { 1149ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc = to_atl_desc(hw); 1159ac33b0cSPeter Ujfalusi 1169ac33b0cSPeter Ujfalusi return cdesc->enabled; 1179ac33b0cSPeter Ujfalusi } 1189ac33b0cSPeter Ujfalusi 1199ac33b0cSPeter Ujfalusi static unsigned long atl_clk_recalc_rate(struct clk_hw *hw, 1209ac33b0cSPeter Ujfalusi unsigned long parent_rate) 1219ac33b0cSPeter Ujfalusi { 1229ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc = to_atl_desc(hw); 1239ac33b0cSPeter Ujfalusi 1249ac33b0cSPeter Ujfalusi return parent_rate / cdesc->divider; 1259ac33b0cSPeter Ujfalusi } 1269ac33b0cSPeter Ujfalusi 1279ac33b0cSPeter Ujfalusi static long atl_clk_round_rate(struct clk_hw *hw, unsigned long rate, 1289ac33b0cSPeter Ujfalusi unsigned long *parent_rate) 1299ac33b0cSPeter Ujfalusi { 1309ac33b0cSPeter Ujfalusi unsigned divider; 1319ac33b0cSPeter Ujfalusi 1329ac33b0cSPeter Ujfalusi divider = (*parent_rate + rate / 2) / rate; 1339ac33b0cSPeter Ujfalusi if (divider > DRA7_ATL_DIVIDER_MASK + 1) 1349ac33b0cSPeter Ujfalusi divider = DRA7_ATL_DIVIDER_MASK + 1; 1359ac33b0cSPeter Ujfalusi 1369ac33b0cSPeter Ujfalusi return *parent_rate / divider; 1379ac33b0cSPeter Ujfalusi } 1389ac33b0cSPeter Ujfalusi 1399ac33b0cSPeter Ujfalusi static int atl_clk_set_rate(struct clk_hw *hw, unsigned long rate, 1409ac33b0cSPeter Ujfalusi unsigned long parent_rate) 1419ac33b0cSPeter Ujfalusi { 1429ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc = to_atl_desc(hw); 1439ac33b0cSPeter Ujfalusi u32 divider; 1449ac33b0cSPeter Ujfalusi 1459ac33b0cSPeter Ujfalusi divider = ((parent_rate + rate / 2) / rate) - 1; 1469ac33b0cSPeter Ujfalusi if (divider > DRA7_ATL_DIVIDER_MASK) 1479ac33b0cSPeter Ujfalusi divider = DRA7_ATL_DIVIDER_MASK; 1489ac33b0cSPeter Ujfalusi 1499ac33b0cSPeter Ujfalusi cdesc->divider = divider + 1; 1509ac33b0cSPeter Ujfalusi 1519ac33b0cSPeter Ujfalusi return 0; 1529ac33b0cSPeter Ujfalusi } 1539ac33b0cSPeter Ujfalusi 1549ac33b0cSPeter Ujfalusi const struct clk_ops atl_clk_ops = { 1559ac33b0cSPeter Ujfalusi .enable = atl_clk_enable, 1569ac33b0cSPeter Ujfalusi .disable = atl_clk_disable, 1579ac33b0cSPeter Ujfalusi .is_enabled = atl_clk_is_enabled, 1589ac33b0cSPeter Ujfalusi .recalc_rate = atl_clk_recalc_rate, 1599ac33b0cSPeter Ujfalusi .round_rate = atl_clk_round_rate, 1609ac33b0cSPeter Ujfalusi .set_rate = atl_clk_set_rate, 1619ac33b0cSPeter Ujfalusi }; 1629ac33b0cSPeter Ujfalusi 1639ac33b0cSPeter Ujfalusi static void __init of_dra7_atl_clock_setup(struct device_node *node) 1649ac33b0cSPeter Ujfalusi { 1659ac33b0cSPeter Ujfalusi struct dra7_atl_desc *clk_hw = NULL; 1669ac33b0cSPeter Ujfalusi struct clk_init_data init = { 0 }; 1679ac33b0cSPeter Ujfalusi const char **parent_names = NULL; 1689ac33b0cSPeter Ujfalusi struct clk *clk; 1699ac33b0cSPeter Ujfalusi 1709ac33b0cSPeter Ujfalusi clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL); 1719ac33b0cSPeter Ujfalusi if (!clk_hw) { 1729ac33b0cSPeter Ujfalusi pr_err("%s: could not allocate dra7_atl_desc\n", __func__); 1739ac33b0cSPeter Ujfalusi return; 1749ac33b0cSPeter Ujfalusi } 1759ac33b0cSPeter Ujfalusi 1769ac33b0cSPeter Ujfalusi clk_hw->hw.init = &init; 1779ac33b0cSPeter Ujfalusi clk_hw->divider = 1; 1789ac33b0cSPeter Ujfalusi init.name = node->name; 1799ac33b0cSPeter Ujfalusi init.ops = &atl_clk_ops; 1809ac33b0cSPeter Ujfalusi init.flags = CLK_IGNORE_UNUSED; 1819ac33b0cSPeter Ujfalusi init.num_parents = of_clk_get_parent_count(node); 1829ac33b0cSPeter Ujfalusi 1839ac33b0cSPeter Ujfalusi if (init.num_parents != 1) { 1849ac33b0cSPeter Ujfalusi pr_err("%s: atl clock %s must have 1 parent\n", __func__, 1859ac33b0cSPeter Ujfalusi node->name); 1869ac33b0cSPeter Ujfalusi goto cleanup; 1879ac33b0cSPeter Ujfalusi } 1889ac33b0cSPeter Ujfalusi 1899ac33b0cSPeter Ujfalusi parent_names = kzalloc(sizeof(char *), GFP_KERNEL); 1909ac33b0cSPeter Ujfalusi 1919ac33b0cSPeter Ujfalusi if (!parent_names) 1929ac33b0cSPeter Ujfalusi goto cleanup; 1939ac33b0cSPeter Ujfalusi 1949ac33b0cSPeter Ujfalusi parent_names[0] = of_clk_get_parent_name(node, 0); 1959ac33b0cSPeter Ujfalusi 1969ac33b0cSPeter Ujfalusi init.parent_names = parent_names; 1979ac33b0cSPeter Ujfalusi 1989ac33b0cSPeter Ujfalusi clk = clk_register(NULL, &clk_hw->hw); 1999ac33b0cSPeter Ujfalusi 2009ac33b0cSPeter Ujfalusi if (!IS_ERR(clk)) { 2019ac33b0cSPeter Ujfalusi of_clk_add_provider(node, of_clk_src_simple_get, clk); 20273b5d5f7STero Kristo kfree(parent_names); 2039ac33b0cSPeter Ujfalusi return; 2049ac33b0cSPeter Ujfalusi } 2059ac33b0cSPeter Ujfalusi cleanup: 2069ac33b0cSPeter Ujfalusi kfree(parent_names); 2079ac33b0cSPeter Ujfalusi kfree(clk_hw); 2089ac33b0cSPeter Ujfalusi } 2099ac33b0cSPeter Ujfalusi CLK_OF_DECLARE(dra7_atl_clock, "ti,dra7-atl-clock", of_dra7_atl_clock_setup); 2109ac33b0cSPeter Ujfalusi 2119ac33b0cSPeter Ujfalusi static int of_dra7_atl_clk_probe(struct platform_device *pdev) 2129ac33b0cSPeter Ujfalusi { 2139ac33b0cSPeter Ujfalusi struct device_node *node = pdev->dev.of_node; 2149ac33b0cSPeter Ujfalusi struct dra7_atl_clock_info *cinfo; 2159ac33b0cSPeter Ujfalusi int i; 2169ac33b0cSPeter Ujfalusi int ret = 0; 2179ac33b0cSPeter Ujfalusi 2189ac33b0cSPeter Ujfalusi if (!node) 2199ac33b0cSPeter Ujfalusi return -ENODEV; 2209ac33b0cSPeter Ujfalusi 2219ac33b0cSPeter Ujfalusi cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL); 2229ac33b0cSPeter Ujfalusi if (!cinfo) 2239ac33b0cSPeter Ujfalusi return -ENOMEM; 2249ac33b0cSPeter Ujfalusi 2259ac33b0cSPeter Ujfalusi cinfo->iobase = of_iomap(node, 0); 2269ac33b0cSPeter Ujfalusi cinfo->dev = &pdev->dev; 2279ac33b0cSPeter Ujfalusi pm_runtime_enable(cinfo->dev); 22804ed831fSPeter Ujfalusi pm_runtime_irq_safe(cinfo->dev); 2299ac33b0cSPeter Ujfalusi 2309ac33b0cSPeter Ujfalusi pm_runtime_get_sync(cinfo->dev); 2319ac33b0cSPeter Ujfalusi atl_write(cinfo, DRA7_ATL_PCLKMUX_REG(0), DRA7_ATL_PCLKMUX); 2329ac33b0cSPeter Ujfalusi 2339ac33b0cSPeter Ujfalusi for (i = 0; i < DRA7_ATL_INSTANCES; i++) { 2349ac33b0cSPeter Ujfalusi struct device_node *cfg_node; 2359ac33b0cSPeter Ujfalusi char prop[5]; 2369ac33b0cSPeter Ujfalusi struct dra7_atl_desc *cdesc; 2379ac33b0cSPeter Ujfalusi struct of_phandle_args clkspec; 2389ac33b0cSPeter Ujfalusi struct clk *clk; 2399ac33b0cSPeter Ujfalusi int rc; 2409ac33b0cSPeter Ujfalusi 2419ac33b0cSPeter Ujfalusi rc = of_parse_phandle_with_args(node, "ti,provided-clocks", 2429ac33b0cSPeter Ujfalusi NULL, i, &clkspec); 2439ac33b0cSPeter Ujfalusi 2449ac33b0cSPeter Ujfalusi if (rc) { 2459ac33b0cSPeter Ujfalusi pr_err("%s: failed to lookup atl clock %d\n", __func__, 2469ac33b0cSPeter Ujfalusi i); 2479ac33b0cSPeter Ujfalusi return -EINVAL; 2489ac33b0cSPeter Ujfalusi } 2499ac33b0cSPeter Ujfalusi 2509ac33b0cSPeter Ujfalusi clk = of_clk_get_from_provider(&clkspec); 2519ac33b0cSPeter Ujfalusi 2529ac33b0cSPeter Ujfalusi cdesc = to_atl_desc(__clk_get_hw(clk)); 2539ac33b0cSPeter Ujfalusi cdesc->cinfo = cinfo; 2549ac33b0cSPeter Ujfalusi cdesc->id = i; 2559ac33b0cSPeter Ujfalusi 2569ac33b0cSPeter Ujfalusi /* Get configuration for the ATL instances */ 2579ac33b0cSPeter Ujfalusi snprintf(prop, sizeof(prop), "atl%u", i); 2589ac33b0cSPeter Ujfalusi cfg_node = of_find_node_by_name(node, prop); 2599ac33b0cSPeter Ujfalusi if (cfg_node) { 2609ac33b0cSPeter Ujfalusi ret = of_property_read_u32(cfg_node, "bws", 2619ac33b0cSPeter Ujfalusi &cdesc->bws); 2629ac33b0cSPeter Ujfalusi ret |= of_property_read_u32(cfg_node, "aws", 2639ac33b0cSPeter Ujfalusi &cdesc->aws); 2649ac33b0cSPeter Ujfalusi if (!ret) { 2659ac33b0cSPeter Ujfalusi cdesc->valid = true; 2669ac33b0cSPeter Ujfalusi atl_write(cinfo, DRA7_ATL_BWSMUX_REG(i), 2679ac33b0cSPeter Ujfalusi cdesc->bws); 2689ac33b0cSPeter Ujfalusi atl_write(cinfo, DRA7_ATL_AWSMUX_REG(i), 2699ac33b0cSPeter Ujfalusi cdesc->aws); 2709ac33b0cSPeter Ujfalusi } 2719ac33b0cSPeter Ujfalusi } 2729ac33b0cSPeter Ujfalusi 2739ac33b0cSPeter Ujfalusi cdesc->probed = true; 2749ac33b0cSPeter Ujfalusi /* 2759ac33b0cSPeter Ujfalusi * Enable the clock if it has been asked prior to loading the 2769ac33b0cSPeter Ujfalusi * hw driver 2779ac33b0cSPeter Ujfalusi */ 2789ac33b0cSPeter Ujfalusi if (cdesc->enabled) 2799ac33b0cSPeter Ujfalusi atl_clk_enable(__clk_get_hw(clk)); 2809ac33b0cSPeter Ujfalusi } 2819ac33b0cSPeter Ujfalusi pm_runtime_put_sync(cinfo->dev); 2829ac33b0cSPeter Ujfalusi 2839ac33b0cSPeter Ujfalusi return ret; 2849ac33b0cSPeter Ujfalusi } 2859ac33b0cSPeter Ujfalusi 2869ac33b0cSPeter Ujfalusi static int of_dra7_atl_clk_remove(struct platform_device *pdev) 2879ac33b0cSPeter Ujfalusi { 2889ac33b0cSPeter Ujfalusi pm_runtime_disable(&pdev->dev); 2899ac33b0cSPeter Ujfalusi 2909ac33b0cSPeter Ujfalusi return 0; 2919ac33b0cSPeter Ujfalusi } 2929ac33b0cSPeter Ujfalusi 2939ac33b0cSPeter Ujfalusi static struct of_device_id of_dra7_atl_clk_match_tbl[] = { 2949ac33b0cSPeter Ujfalusi { .compatible = "ti,dra7-atl", }, 2959ac33b0cSPeter Ujfalusi {}, 2969ac33b0cSPeter Ujfalusi }; 2979ac33b0cSPeter Ujfalusi MODULE_DEVICE_TABLE(of, of_dra7_atl_clk_match_tbl); 2989ac33b0cSPeter Ujfalusi 2999ac33b0cSPeter Ujfalusi static struct platform_driver dra7_atl_clk_driver = { 3009ac33b0cSPeter Ujfalusi .driver = { 3019ac33b0cSPeter Ujfalusi .name = "dra7-atl", 3029ac33b0cSPeter Ujfalusi .owner = THIS_MODULE, 3039ac33b0cSPeter Ujfalusi .of_match_table = of_dra7_atl_clk_match_tbl, 3049ac33b0cSPeter Ujfalusi }, 3059ac33b0cSPeter Ujfalusi .probe = of_dra7_atl_clk_probe, 3069ac33b0cSPeter Ujfalusi .remove = of_dra7_atl_clk_remove, 3079ac33b0cSPeter Ujfalusi }; 3089ac33b0cSPeter Ujfalusi 3099ac33b0cSPeter Ujfalusi module_platform_driver(dra7_atl_clk_driver); 3109ac33b0cSPeter Ujfalusi 3119ac33b0cSPeter Ujfalusi MODULE_DESCRIPTION("Clock driver for DRA7 Audio Tracking Logic"); 3129ac33b0cSPeter Ujfalusi MODULE_ALIAS("platform:dra7-atl-clock"); 3139ac33b0cSPeter Ujfalusi MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); 3149ac33b0cSPeter Ujfalusi MODULE_LICENSE("GPL v2"); 315