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