brintos

brintos / linux-shallow public Read only

0
0
Text · 12.8 KiB · 88d798d Raw
446 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * MMP Audio Clock Controller driver4 *5 * Copyright (C) 2020 Lubomir Rintel <lkundrak@v3.sk>6 */7 8#include <linux/clk-provider.h>9#include <linux/io.h>10#include <linux/module.h>11#include <linux/platform_device.h>12#include <linux/pm_clock.h>13#include <linux/pm_runtime.h>14#include <linux/slab.h>15#include <dt-bindings/clock/marvell,mmp2-audio.h>16 17/* Audio Controller Registers */18#define SSPA_AUD_CTRL				0x0419#define SSPA_AUD_PLL_CTRL0			0x0820#define SSPA_AUD_PLL_CTRL1			0x0c21 22/* SSPA Audio Control Register */23#define SSPA_AUD_CTRL_SYSCLK_SHIFT		024#define SSPA_AUD_CTRL_SYSCLK_DIV_SHIFT		125#define SSPA_AUD_CTRL_SSPA0_MUX_SHIFT		726#define SSPA_AUD_CTRL_SSPA0_SHIFT		827#define SSPA_AUD_CTRL_SSPA0_DIV_SHIFT		928#define SSPA_AUD_CTRL_SSPA1_SHIFT		1629#define SSPA_AUD_CTRL_SSPA1_DIV_SHIFT		1730#define SSPA_AUD_CTRL_SSPA1_MUX_SHIFT		2331#define SSPA_AUD_CTRL_DIV_MASK			0x7e32 33/* SSPA Audio PLL Control 0 Register */34#define SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO_MASK (0x7 << 28)35#define SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO(x)	((x) << 28)36#define SSPA_AUD_PLL_CTRL0_FRACT_MASK		(0xfffff << 8)37#define SSPA_AUD_PLL_CTRL0_FRACT(x)		((x) << 8)38#define SSPA_AUD_PLL_CTRL0_ENA_DITHER		(1 << 7)39#define SSPA_AUD_PLL_CTRL0_ICP_2UA		(0 << 5)40#define SSPA_AUD_PLL_CTRL0_ICP_5UA		(1 << 5)41#define SSPA_AUD_PLL_CTRL0_ICP_7UA		(2 << 5)42#define SSPA_AUD_PLL_CTRL0_ICP_10UA		(3 << 5)43#define SSPA_AUD_PLL_CTRL0_DIV_FBCCLK_MASK	(0x3 << 3)44#define SSPA_AUD_PLL_CTRL0_DIV_FBCCLK(x)	((x) << 3)45#define SSPA_AUD_PLL_CTRL0_DIV_MCLK_MASK	(0x1 << 2)46#define SSPA_AUD_PLL_CTRL0_DIV_MCLK(x)		((x) << 2)47#define SSPA_AUD_PLL_CTRL0_PD_OVPROT_DIS	(1 << 1)48#define SSPA_AUD_PLL_CTRL0_PU			(1 << 0)49 50/* SSPA Audio PLL Control 1 Register */51#define SSPA_AUD_PLL_CTRL1_SEL_FAST_CLK		(1 << 24)52#define SSPA_AUD_PLL_CTRL1_CLK_SEL_MASK		(1 << 11)53#define SSPA_AUD_PLL_CTRL1_CLK_SEL_AUDIO_PLL	(1 << 11)54#define SSPA_AUD_PLL_CTRL1_CLK_SEL_VCXO		(0 << 11)55#define SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN_MASK (0x7ff << 0)56#define SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN(x)	((x) << 0)57 58#define CLK_AUDIO_NR_CLKS			359 60struct mmp2_audio_clk {61	void __iomem *mmio_base;62 63	struct clk_hw audio_pll_hw;64	struct clk_mux sspa_mux;65	struct clk_mux sspa1_mux;66	struct clk_divider sysclk_div;67	struct clk_divider sspa0_div;68	struct clk_divider sspa1_div;69	struct clk_gate sysclk_gate;70	struct clk_gate sspa0_gate;71	struct clk_gate sspa1_gate;72 73	u32 aud_ctrl;74	u32 aud_pll_ctrl0;75	u32 aud_pll_ctrl1;76 77	spinlock_t lock;78 79	/* Must be last */80	struct clk_hw_onecell_data clk_data;81};82 83static const struct {84	unsigned long parent_rate;85	unsigned long freq_vco;86	unsigned char mclk;87	unsigned char fbcclk;88	unsigned short fract;89} predivs[] = {90	{ 26000000, 135475200, 0, 0, 0x8a18 },91	{ 26000000, 147456000, 0, 1, 0x0da1 },92	{ 38400000, 135475200, 1, 2, 0x8208 },93	{ 38400000, 147456000, 1, 3, 0xaaaa },94};95 96static const struct {97	unsigned char divisor;98	unsigned char modulo;99	unsigned char pattern;100} postdivs[] = {101	{   1,	3,  0, },102	{   2,	5,  0, },103	{   4,	0,  0, },104	{   6,	1,  1, },105	{   8,	1,  0, },106	{   9,	1,  2, },107	{  12,	2,  1, },108	{  16,	2,  0, },109	{  18,	2,  2, },110	{  24,	4,  1, },111	{  36,	4,  2, },112	{  48,	6,  1, },113	{  72,	6,  2, },114};115 116static unsigned long audio_pll_recalc_rate(struct clk_hw *hw,117					   unsigned long parent_rate)118{119	struct mmp2_audio_clk *priv = container_of(hw, struct mmp2_audio_clk, audio_pll_hw);120	unsigned int prediv;121	unsigned int postdiv;122	u32 aud_pll_ctrl0;123	u32 aud_pll_ctrl1;124 125	aud_pll_ctrl0 = readl(priv->mmio_base + SSPA_AUD_PLL_CTRL0);126	aud_pll_ctrl0 &= SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO_MASK |127			 SSPA_AUD_PLL_CTRL0_FRACT_MASK |128			 SSPA_AUD_PLL_CTRL0_ENA_DITHER |129			 SSPA_AUD_PLL_CTRL0_DIV_FBCCLK_MASK |130			 SSPA_AUD_PLL_CTRL0_DIV_MCLK_MASK |131			 SSPA_AUD_PLL_CTRL0_PU;132 133	aud_pll_ctrl1 = readl(priv->mmio_base + SSPA_AUD_PLL_CTRL1);134	aud_pll_ctrl1 &= SSPA_AUD_PLL_CTRL1_CLK_SEL_MASK |135			 SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN_MASK;136 137	for (prediv = 0; prediv < ARRAY_SIZE(predivs); prediv++) {138		if (predivs[prediv].parent_rate != parent_rate)139			continue;140		for (postdiv = 0; postdiv < ARRAY_SIZE(postdivs); postdiv++) {141			unsigned long freq;142			u32 val;143 144			val = SSPA_AUD_PLL_CTRL0_ENA_DITHER;145			val |= SSPA_AUD_PLL_CTRL0_PU;146			val |= SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO(postdivs[postdiv].modulo);147			val |= SSPA_AUD_PLL_CTRL0_FRACT(predivs[prediv].fract);148			val |= SSPA_AUD_PLL_CTRL0_DIV_FBCCLK(predivs[prediv].fbcclk);149			val |= SSPA_AUD_PLL_CTRL0_DIV_MCLK(predivs[prediv].mclk);150			if (val != aud_pll_ctrl0)151				continue;152 153			val = SSPA_AUD_PLL_CTRL1_CLK_SEL_AUDIO_PLL;154			val |= SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN(postdivs[postdiv].pattern);155			if (val != aud_pll_ctrl1)156				continue;157 158			freq = predivs[prediv].freq_vco;159			freq /= postdivs[postdiv].divisor;160			return freq;161		}162	}163 164	return 0;165}166 167static long audio_pll_round_rate(struct clk_hw *hw, unsigned long rate,168				 unsigned long *parent_rate)169{170	unsigned int prediv;171	unsigned int postdiv;172	long rounded = 0;173 174	for (prediv = 0; prediv < ARRAY_SIZE(predivs); prediv++) {175		if (predivs[prediv].parent_rate != *parent_rate)176			continue;177		for (postdiv = 0; postdiv < ARRAY_SIZE(postdivs); postdiv++) {178			long freq = predivs[prediv].freq_vco;179 180			freq /= postdivs[postdiv].divisor;181			if (freq == rate)182				return rate;183			if (freq < rate)184				continue;185			if (rounded && freq > rounded)186				continue;187			rounded = freq;188		}189	}190 191	return rounded;192}193 194static int audio_pll_set_rate(struct clk_hw *hw, unsigned long rate,195			      unsigned long parent_rate)196{197	struct mmp2_audio_clk *priv = container_of(hw, struct mmp2_audio_clk, audio_pll_hw);198	unsigned int prediv;199	unsigned int postdiv;200	unsigned long val;201 202	for (prediv = 0; prediv < ARRAY_SIZE(predivs); prediv++) {203		if (predivs[prediv].parent_rate != parent_rate)204			continue;205 206		for (postdiv = 0; postdiv < ARRAY_SIZE(postdivs); postdiv++) {207			if (rate * postdivs[postdiv].divisor != predivs[prediv].freq_vco)208				continue;209 210			val = SSPA_AUD_PLL_CTRL0_ENA_DITHER;211			val |= SSPA_AUD_PLL_CTRL0_PU;212			val |= SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO(postdivs[postdiv].modulo);213			val |= SSPA_AUD_PLL_CTRL0_FRACT(predivs[prediv].fract);214			val |= SSPA_AUD_PLL_CTRL0_DIV_FBCCLK(predivs[prediv].fbcclk);215			val |= SSPA_AUD_PLL_CTRL0_DIV_MCLK(predivs[prediv].mclk);216			writel(val, priv->mmio_base + SSPA_AUD_PLL_CTRL0);217 218			val = SSPA_AUD_PLL_CTRL1_CLK_SEL_AUDIO_PLL;219			val |= SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN(postdivs[postdiv].pattern);220			writel(val, priv->mmio_base + SSPA_AUD_PLL_CTRL1);221 222			return 0;223		}224	}225 226	return -ERANGE;227}228 229static const struct clk_ops audio_pll_ops = {230	.recalc_rate = audio_pll_recalc_rate,231	.round_rate = audio_pll_round_rate,232	.set_rate = audio_pll_set_rate,233};234 235static int register_clocks(struct mmp2_audio_clk *priv, struct device *dev)236{237	const struct clk_parent_data sspa_mux_parents[] = {238		{ .hw = &priv->audio_pll_hw },239		{ .fw_name = "i2s0" },240	};241	const struct clk_parent_data sspa1_mux_parents[] = {242		{ .hw = &priv->audio_pll_hw },243		{ .fw_name = "i2s1" },244	};245	int ret;246 247	priv->audio_pll_hw.init = CLK_HW_INIT_FW_NAME("audio_pll",248				"vctcxo", &audio_pll_ops,249				CLK_SET_RATE_PARENT);250	ret = devm_clk_hw_register(dev, &priv->audio_pll_hw);251	if (ret)252		return ret;253 254	priv->sspa_mux.hw.init = CLK_HW_INIT_PARENTS_DATA("sspa_mux",255				sspa_mux_parents, &clk_mux_ops,256				CLK_SET_RATE_PARENT);257	priv->sspa_mux.reg = priv->mmio_base + SSPA_AUD_CTRL;258	priv->sspa_mux.mask = 1;259	priv->sspa_mux.shift = SSPA_AUD_CTRL_SSPA0_MUX_SHIFT;260	ret = devm_clk_hw_register(dev, &priv->sspa_mux.hw);261	if (ret)262		return ret;263 264	priv->sysclk_div.hw.init = CLK_HW_INIT_HW("sys_div",265				&priv->sspa_mux.hw, &clk_divider_ops,266				CLK_SET_RATE_PARENT);267	priv->sysclk_div.reg = priv->mmio_base + SSPA_AUD_CTRL;268	priv->sysclk_div.shift = SSPA_AUD_CTRL_SYSCLK_DIV_SHIFT;269	priv->sysclk_div.width = 6;270	priv->sysclk_div.flags = CLK_DIVIDER_ONE_BASED;271	priv->sysclk_div.flags |= CLK_DIVIDER_ROUND_CLOSEST;272	priv->sysclk_div.flags |= CLK_DIVIDER_ALLOW_ZERO;273	ret = devm_clk_hw_register(dev, &priv->sysclk_div.hw);274	if (ret)275		return ret;276 277	priv->sysclk_gate.hw.init = CLK_HW_INIT_HW("sys_clk",278				&priv->sysclk_div.hw, &clk_gate_ops,279				CLK_SET_RATE_PARENT);280	priv->sysclk_gate.reg = priv->mmio_base + SSPA_AUD_CTRL;281	priv->sysclk_gate.bit_idx = SSPA_AUD_CTRL_SYSCLK_SHIFT;282	ret = devm_clk_hw_register(dev, &priv->sysclk_gate.hw);283	if (ret)284		return ret;285 286	priv->sspa0_div.hw.init = CLK_HW_INIT_HW("sspa0_div",287				&priv->sspa_mux.hw, &clk_divider_ops, 0);288	priv->sspa0_div.reg = priv->mmio_base + SSPA_AUD_CTRL;289	priv->sspa0_div.shift = SSPA_AUD_CTRL_SSPA0_DIV_SHIFT;290	priv->sspa0_div.width = 6;291	priv->sspa0_div.flags = CLK_DIVIDER_ONE_BASED;292	priv->sspa0_div.flags |= CLK_DIVIDER_ROUND_CLOSEST;293	priv->sspa0_div.flags |= CLK_DIVIDER_ALLOW_ZERO;294	ret = devm_clk_hw_register(dev, &priv->sspa0_div.hw);295	if (ret)296		return ret;297 298	priv->sspa0_gate.hw.init = CLK_HW_INIT_HW("sspa0_clk",299				&priv->sspa0_div.hw, &clk_gate_ops,300				CLK_SET_RATE_PARENT);301	priv->sspa0_gate.reg = priv->mmio_base + SSPA_AUD_CTRL;302	priv->sspa0_gate.bit_idx = SSPA_AUD_CTRL_SSPA0_SHIFT;303	ret = devm_clk_hw_register(dev, &priv->sspa0_gate.hw);304	if (ret)305		return ret;306 307	priv->sspa1_mux.hw.init = CLK_HW_INIT_PARENTS_DATA("sspa1_mux",308				sspa1_mux_parents, &clk_mux_ops,309				CLK_SET_RATE_PARENT);310	priv->sspa1_mux.reg = priv->mmio_base + SSPA_AUD_CTRL;311	priv->sspa1_mux.mask = 1;312	priv->sspa1_mux.shift = SSPA_AUD_CTRL_SSPA1_MUX_SHIFT;313	ret = devm_clk_hw_register(dev, &priv->sspa1_mux.hw);314	if (ret)315		return ret;316 317	priv->sspa1_div.hw.init = CLK_HW_INIT_HW("sspa1_div",318				&priv->sspa1_mux.hw, &clk_divider_ops, 0);319	priv->sspa1_div.reg = priv->mmio_base + SSPA_AUD_CTRL;320	priv->sspa1_div.shift = SSPA_AUD_CTRL_SSPA1_DIV_SHIFT;321	priv->sspa1_div.width = 6;322	priv->sspa1_div.flags = CLK_DIVIDER_ONE_BASED;323	priv->sspa1_div.flags |= CLK_DIVIDER_ROUND_CLOSEST;324	priv->sspa1_div.flags |= CLK_DIVIDER_ALLOW_ZERO;325	ret = devm_clk_hw_register(dev, &priv->sspa1_div.hw);326	if (ret)327		return ret;328 329	priv->sspa1_gate.hw.init = CLK_HW_INIT_HW("sspa1_clk",330				&priv->sspa1_div.hw, &clk_gate_ops,331				CLK_SET_RATE_PARENT);332	priv->sspa1_gate.reg = priv->mmio_base + SSPA_AUD_CTRL;333	priv->sspa1_gate.bit_idx = SSPA_AUD_CTRL_SSPA1_SHIFT;334	ret = devm_clk_hw_register(dev, &priv->sspa1_gate.hw);335	if (ret)336		return ret;337 338	priv->clk_data.hws[MMP2_CLK_AUDIO_SYSCLK] = &priv->sysclk_gate.hw;339	priv->clk_data.hws[MMP2_CLK_AUDIO_SSPA0] = &priv->sspa0_gate.hw;340	priv->clk_data.hws[MMP2_CLK_AUDIO_SSPA1] = &priv->sspa1_gate.hw;341	priv->clk_data.num = CLK_AUDIO_NR_CLKS;342 343	return of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,344				      &priv->clk_data);345}346 347static int mmp2_audio_clk_probe(struct platform_device *pdev)348{349	struct mmp2_audio_clk *priv;350	int ret;351 352	priv = devm_kzalloc(&pdev->dev,353			    struct_size(priv, clk_data.hws,354					CLK_AUDIO_NR_CLKS),355			    GFP_KERNEL);356	if (!priv)357		return -ENOMEM;358 359	spin_lock_init(&priv->lock);360	platform_set_drvdata(pdev, priv);361 362	priv->mmio_base = devm_platform_ioremap_resource(pdev, 0);363	if (IS_ERR(priv->mmio_base))364		return PTR_ERR(priv->mmio_base);365 366	pm_runtime_enable(&pdev->dev);367	ret = pm_clk_create(&pdev->dev);368	if (ret)369		goto disable_pm_runtime;370 371	ret = pm_clk_add(&pdev->dev, "audio");372	if (ret)373		goto destroy_pm_clk;374 375	ret = register_clocks(priv, &pdev->dev);376	if (ret)377		goto destroy_pm_clk;378 379	return 0;380 381destroy_pm_clk:382	pm_clk_destroy(&pdev->dev);383disable_pm_runtime:384	pm_runtime_disable(&pdev->dev);385 386	return ret;387}388 389static void mmp2_audio_clk_remove(struct platform_device *pdev)390{391	pm_clk_destroy(&pdev->dev);392	pm_runtime_disable(&pdev->dev);393}394 395#ifdef CONFIG_PM396static int mmp2_audio_clk_suspend(struct device *dev)397{398	struct mmp2_audio_clk *priv = dev_get_drvdata(dev);399 400	priv->aud_ctrl = readl(priv->mmio_base + SSPA_AUD_CTRL);401	priv->aud_pll_ctrl0 = readl(priv->mmio_base + SSPA_AUD_PLL_CTRL0);402	priv->aud_pll_ctrl1 = readl(priv->mmio_base + SSPA_AUD_PLL_CTRL1);403	pm_clk_suspend(dev);404 405	return 0;406}407 408static int mmp2_audio_clk_resume(struct device *dev)409{410	struct mmp2_audio_clk *priv = dev_get_drvdata(dev);411 412	pm_clk_resume(dev);413	writel(priv->aud_ctrl, priv->mmio_base + SSPA_AUD_CTRL);414	writel(priv->aud_pll_ctrl0, priv->mmio_base + SSPA_AUD_PLL_CTRL0);415	writel(priv->aud_pll_ctrl1, priv->mmio_base + SSPA_AUD_PLL_CTRL1);416 417	return 0;418}419#endif420 421static const struct dev_pm_ops mmp2_audio_clk_pm_ops = {422	SET_RUNTIME_PM_OPS(mmp2_audio_clk_suspend, mmp2_audio_clk_resume, NULL)423};424 425static const struct of_device_id mmp2_audio_clk_of_match[] = {426	{ .compatible = "marvell,mmp2-audio-clock" },427	{}428};429 430MODULE_DEVICE_TABLE(of, mmp2_audio_clk_of_match);431 432static struct platform_driver mmp2_audio_clk_driver = {433	.driver = {434		.name = "mmp2-audio-clock",435		.of_match_table = of_match_ptr(mmp2_audio_clk_of_match),436		.pm = &mmp2_audio_clk_pm_ops,437	},438	.probe = mmp2_audio_clk_probe,439	.remove = mmp2_audio_clk_remove,440};441module_platform_driver(mmp2_audio_clk_driver);442 443MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>");444MODULE_DESCRIPTION("Clock driver for MMP2 Audio subsystem");445MODULE_LICENSE("GPL");446