clk-dra7-atl.c 7.71 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 * DRA7 ATL (Audio Tracking Logic) clock driver
 *
 * Copyright (C) 2013 Texas Instruments, Inc.
 *
 * Peter Ujfalusi <peter.ujfalusi@ti.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
 * kind, whether express or implied; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/module.h>
Stephen Boyd's avatar
Stephen Boyd committed
19
#include <linux/clk.h>
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
#include <linux/clk-provider.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>

#define DRA7_ATL_INSTANCES	4

#define DRA7_ATL_PPMR_REG(id)		(0x200 + (id * 0x80))
#define DRA7_ATL_BBSR_REG(id)		(0x204 + (id * 0x80))
#define DRA7_ATL_ATLCR_REG(id)		(0x208 + (id * 0x80))
#define DRA7_ATL_SWEN_REG(id)		(0x210 + (id * 0x80))
#define DRA7_ATL_BWSMUX_REG(id)		(0x214 + (id * 0x80))
#define DRA7_ATL_AWSMUX_REG(id)		(0x218 + (id * 0x80))
#define DRA7_ATL_PCLKMUX_REG(id)	(0x21c + (id * 0x80))

#define DRA7_ATL_SWEN			BIT(0)
#define DRA7_ATL_DIVIDER_MASK		(0x1f)
#define DRA7_ATL_PCLKMUX		BIT(0)
struct dra7_atl_clock_info;

struct dra7_atl_desc {
	struct clk *clk;
	struct clk_hw hw;
	struct dra7_atl_clock_info *cinfo;
	int id;

	bool probed;		/* the driver for the IP has been loaded */
	bool valid;		/* configured */
	bool enabled;
	u32 bws;		/* Baseband Word Select Mux */
	u32 aws;		/* Audio Word Select Mux */
	u32 divider;		/* Cached divider value */
};

struct dra7_atl_clock_info {
	struct device *dev;
	void __iomem *iobase;

	struct dra7_atl_desc *cdesc;
};

#define to_atl_desc(_hw)	container_of(_hw, struct dra7_atl_desc, hw)

static inline void atl_write(struct dra7_atl_clock_info *cinfo, u32 reg,
			     u32 val)
{
	__raw_writel(val, cinfo->iobase + reg);
}

static inline int atl_read(struct dra7_atl_clock_info *cinfo, u32 reg)
{
	return __raw_readl(cinfo->iobase + reg);
}

static int atl_clk_enable(struct clk_hw *hw)
{
	struct dra7_atl_desc *cdesc = to_atl_desc(hw);

	if (!cdesc->probed)
		goto out;

	if (unlikely(!cdesc->valid))
		dev_warn(cdesc->cinfo->dev, "atl%d has not been configured\n",
			 cdesc->id);
	pm_runtime_get_sync(cdesc->cinfo->dev);

	atl_write(cdesc->cinfo, DRA7_ATL_ATLCR_REG(cdesc->id),
		  cdesc->divider - 1);
	atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), DRA7_ATL_SWEN);

out:
	cdesc->enabled = true;

	return 0;
}

static void atl_clk_disable(struct clk_hw *hw)
{
	struct dra7_atl_desc *cdesc = to_atl_desc(hw);

	if (!cdesc->probed)
		goto out;

	atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), 0);
	pm_runtime_put_sync(cdesc->cinfo->dev);

out:
	cdesc->enabled = false;
}

static int atl_clk_is_enabled(struct clk_hw *hw)
{
	struct dra7_atl_desc *cdesc = to_atl_desc(hw);

	return cdesc->enabled;
}

static unsigned long atl_clk_recalc_rate(struct clk_hw *hw,
					 unsigned long parent_rate)
{
	struct dra7_atl_desc *cdesc = to_atl_desc(hw);

	return parent_rate / cdesc->divider;
}

static long atl_clk_round_rate(struct clk_hw *hw, unsigned long rate,
			       unsigned long *parent_rate)
{
	unsigned divider;

	divider = (*parent_rate + rate / 2) / rate;
	if (divider > DRA7_ATL_DIVIDER_MASK + 1)
		divider = DRA7_ATL_DIVIDER_MASK + 1;

	return *parent_rate / divider;
}

static int atl_clk_set_rate(struct clk_hw *hw, unsigned long rate,
			    unsigned long parent_rate)
{
143
	struct dra7_atl_desc *cdesc;
144 145
	u32 divider;

146 147 148 149
	if (!hw || !rate)
		return -EINVAL;

	cdesc = to_atl_desc(hw);
150 151 152 153 154 155 156 157 158
	divider = ((parent_rate + rate / 2) / rate) - 1;
	if (divider > DRA7_ATL_DIVIDER_MASK)
		divider = DRA7_ATL_DIVIDER_MASK;

	cdesc->divider = divider + 1;

	return 0;
}

159
static const struct clk_ops atl_clk_ops = {
160 161 162 163 164 165 166 167 168 169 170
	.enable		= atl_clk_enable,
	.disable	= atl_clk_disable,
	.is_enabled	= atl_clk_is_enabled,
	.recalc_rate	= atl_clk_recalc_rate,
	.round_rate	= atl_clk_round_rate,
	.set_rate	= atl_clk_set_rate,
};

static void __init of_dra7_atl_clock_setup(struct device_node *node)
{
	struct dra7_atl_desc *clk_hw = NULL;
171
	struct clk_init_data init = { NULL };
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
	const char **parent_names = NULL;
	struct clk *clk;

	clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL);
	if (!clk_hw) {
		pr_err("%s: could not allocate dra7_atl_desc\n", __func__);
		return;
	}

	clk_hw->hw.init = &init;
	clk_hw->divider = 1;
	init.name = node->name;
	init.ops = &atl_clk_ops;
	init.flags = CLK_IGNORE_UNUSED;
	init.num_parents = of_clk_get_parent_count(node);

	if (init.num_parents != 1) {
		pr_err("%s: atl clock %s must have 1 parent\n", __func__,
		       node->name);
		goto cleanup;
	}

	parent_names = kzalloc(sizeof(char *), GFP_KERNEL);

	if (!parent_names)
		goto cleanup;

	parent_names[0] = of_clk_get_parent_name(node, 0);

	init.parent_names = parent_names;

	clk = clk_register(NULL, &clk_hw->hw);

	if (!IS_ERR(clk)) {
		of_clk_add_provider(node, of_clk_src_simple_get, clk);
207
		kfree(parent_names);
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
		return;
	}
cleanup:
	kfree(parent_names);
	kfree(clk_hw);
}
CLK_OF_DECLARE(dra7_atl_clock, "ti,dra7-atl-clock", of_dra7_atl_clock_setup);

static int of_dra7_atl_clk_probe(struct platform_device *pdev)
{
	struct device_node *node = pdev->dev.of_node;
	struct dra7_atl_clock_info *cinfo;
	int i;
	int ret = 0;

	if (!node)
		return -ENODEV;

	cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL);
	if (!cinfo)
		return -ENOMEM;

	cinfo->iobase = of_iomap(node, 0);
	cinfo->dev = &pdev->dev;
	pm_runtime_enable(cinfo->dev);
233
	pm_runtime_irq_safe(cinfo->dev);
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255

	pm_runtime_get_sync(cinfo->dev);
	atl_write(cinfo, DRA7_ATL_PCLKMUX_REG(0), DRA7_ATL_PCLKMUX);

	for (i = 0; i < DRA7_ATL_INSTANCES; i++) {
		struct device_node *cfg_node;
		char prop[5];
		struct dra7_atl_desc *cdesc;
		struct of_phandle_args clkspec;
		struct clk *clk;
		int rc;

		rc = of_parse_phandle_with_args(node, "ti,provided-clocks",
						NULL, i, &clkspec);

		if (rc) {
			pr_err("%s: failed to lookup atl clock %d\n", __func__,
			       i);
			return -EINVAL;
		}

		clk = of_clk_get_from_provider(&clkspec);
256 257 258 259 260
		if (IS_ERR(clk)) {
			pr_err("%s: failed to get atl clock %d from provider\n",
			       __func__, i);
			return PTR_ERR(clk);
		}
261 262 263 264 265 266 267

		cdesc = to_atl_desc(__clk_get_hw(clk));
		cdesc->cinfo = cinfo;
		cdesc->id = i;

		/* Get configuration for the ATL instances */
		snprintf(prop, sizeof(prop), "atl%u", i);
268
		of_node_get(node);
269 270 271 272 273 274 275 276 277 278 279 280 281
		cfg_node = of_find_node_by_name(node, prop);
		if (cfg_node) {
			ret = of_property_read_u32(cfg_node, "bws",
						   &cdesc->bws);
			ret |= of_property_read_u32(cfg_node, "aws",
						    &cdesc->aws);
			if (!ret) {
				cdesc->valid = true;
				atl_write(cinfo, DRA7_ATL_BWSMUX_REG(i),
					  cdesc->bws);
				atl_write(cinfo, DRA7_ATL_AWSMUX_REG(i),
					  cdesc->aws);
			}
282
			of_node_put(cfg_node);
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
		}

		cdesc->probed = true;
		/*
		 * Enable the clock if it has been asked prior to loading the
		 * hw driver
		 */
		if (cdesc->enabled)
			atl_clk_enable(__clk_get_hw(clk));
	}
	pm_runtime_put_sync(cinfo->dev);

	return ret;
}

static int of_dra7_atl_clk_remove(struct platform_device *pdev)
{
	pm_runtime_disable(&pdev->dev);

	return 0;
}

305
static const struct of_device_id of_dra7_atl_clk_match_tbl[] = {
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
	{ .compatible = "ti,dra7-atl", },
	{},
};
MODULE_DEVICE_TABLE(of, of_dra7_atl_clk_match_tbl);

static struct platform_driver dra7_atl_clk_driver = {
	.driver = {
		.name = "dra7-atl",
		.of_match_table = of_dra7_atl_clk_match_tbl,
	},
	.probe = of_dra7_atl_clk_probe,
	.remove = of_dra7_atl_clk_remove,
};

module_platform_driver(dra7_atl_clk_driver);

MODULE_DESCRIPTION("Clock driver for DRA7 Audio Tracking Logic");
MODULE_ALIAS("platform:dra7-atl-clock");
MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
MODULE_LICENSE("GPL v2");