brintos

brintos / linux-shallow public Read only

0
0
Text · 7.9 KiB · abd49ed Raw
304 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (c) 2013 Samsung Electronics Co., Ltd.4 * Author: Padmavathi Venna <padma.v@samsung.com>5 *6 * Common Clock Framework support for Audio Subsystem Clock Controller.7*/8 9#include <linux/slab.h>10#include <linux/io.h>11#include <linux/clk.h>12#include <linux/clk-provider.h>13#include <linux/of.h>14#include <linux/module.h>15#include <linux/platform_device.h>16#include <linux/pm_runtime.h>17 18#include <dt-bindings/clock/exynos-audss-clk.h>19 20static DEFINE_SPINLOCK(lock);21static void __iomem *reg_base;22static struct clk_hw_onecell_data *clk_data;23/*24 * On Exynos5420 this will be a clock which has to be enabled before any25 * access to audss registers. Typically a child of EPLL.26 *27 * On other platforms this will be -ENODEV.28 */29static struct clk *epll;30 31#define ASS_CLK_SRC 0x032#define ASS_CLK_DIV 0x433#define ASS_CLK_GATE 0x834 35static unsigned long reg_save[][2] = {36	{ ASS_CLK_SRC,  0 },37	{ ASS_CLK_DIV,  0 },38	{ ASS_CLK_GATE, 0 },39};40 41static int __maybe_unused exynos_audss_clk_suspend(struct device *dev)42{43	int i;44 45	for (i = 0; i < ARRAY_SIZE(reg_save); i++)46		reg_save[i][1] = readl(reg_base + reg_save[i][0]);47 48	return 0;49}50 51static int __maybe_unused exynos_audss_clk_resume(struct device *dev)52{53	int i;54 55	for (i = 0; i < ARRAY_SIZE(reg_save); i++)56		writel(reg_save[i][1], reg_base + reg_save[i][0]);57 58	return 0;59}60 61struct exynos_audss_clk_drvdata {62	unsigned int has_adma_clk:1;63	unsigned int has_mst_clk:1;64	unsigned int enable_epll:1;65	unsigned int num_clks;66};67 68static const struct exynos_audss_clk_drvdata exynos4210_drvdata = {69	.num_clks	= EXYNOS_AUDSS_MAX_CLKS - 1,70	.enable_epll	= 1,71};72 73static const struct exynos_audss_clk_drvdata exynos5410_drvdata = {74	.num_clks	= EXYNOS_AUDSS_MAX_CLKS - 1,75	.has_mst_clk	= 1,76};77 78static const struct exynos_audss_clk_drvdata exynos5420_drvdata = {79	.num_clks	= EXYNOS_AUDSS_MAX_CLKS,80	.has_adma_clk	= 1,81	.enable_epll	= 1,82};83 84static const struct of_device_id exynos_audss_clk_of_match[] = {85	{86		.compatible	= "samsung,exynos4210-audss-clock",87		.data		= &exynos4210_drvdata,88	}, {89		.compatible	= "samsung,exynos5250-audss-clock",90		.data		= &exynos4210_drvdata,91	}, {92		.compatible	= "samsung,exynos5410-audss-clock",93		.data		= &exynos5410_drvdata,94	}, {95		.compatible	= "samsung,exynos5420-audss-clock",96		.data		= &exynos5420_drvdata,97	},98	{ },99};100MODULE_DEVICE_TABLE(of, exynos_audss_clk_of_match);101 102static void exynos_audss_clk_teardown(void)103{104	int i;105 106	for (i = EXYNOS_MOUT_AUDSS; i < EXYNOS_DOUT_SRP; i++) {107		if (!IS_ERR(clk_data->hws[i]))108			clk_hw_unregister_mux(clk_data->hws[i]);109	}110 111	for (; i < EXYNOS_SRP_CLK; i++) {112		if (!IS_ERR(clk_data->hws[i]))113			clk_hw_unregister_divider(clk_data->hws[i]);114	}115 116	for (; i < clk_data->num; i++) {117		if (!IS_ERR(clk_data->hws[i]))118			clk_hw_unregister_gate(clk_data->hws[i]);119	}120}121 122/* register exynos_audss clocks */123static int exynos_audss_clk_probe(struct platform_device *pdev)124{125	const char *mout_audss_p[] = {"fin_pll", "fout_epll"};126	const char *mout_i2s_p[] = {"mout_audss", "cdclk0", "sclk_audio0"};127	const char *sclk_pcm_p = "sclk_pcm0";128	struct clk *pll_ref, *pll_in, *cdclk, *sclk_audio, *sclk_pcm_in;129	const struct exynos_audss_clk_drvdata *variant;130	struct clk_hw **clk_table;131	struct device *dev = &pdev->dev;132	int i, ret = 0;133 134	variant = of_device_get_match_data(&pdev->dev);135	if (!variant)136		return -EINVAL;137 138	reg_base = devm_platform_ioremap_resource(pdev, 0);139	if (IS_ERR(reg_base))140		return PTR_ERR(reg_base);141 142	epll = ERR_PTR(-ENODEV);143 144	clk_data = devm_kzalloc(dev,145				struct_size(clk_data, hws,146					    EXYNOS_AUDSS_MAX_CLKS),147				GFP_KERNEL);148	if (!clk_data)149		return -ENOMEM;150 151	clk_data->num = variant->num_clks;152	clk_table = clk_data->hws;153 154	pll_ref = devm_clk_get(dev, "pll_ref");155	pll_in = devm_clk_get(dev, "pll_in");156	if (!IS_ERR(pll_ref))157		mout_audss_p[0] = __clk_get_name(pll_ref);158	if (!IS_ERR(pll_in)) {159		mout_audss_p[1] = __clk_get_name(pll_in);160 161		if (variant->enable_epll) {162			epll = pll_in;163 164			ret = clk_prepare_enable(epll);165			if (ret) {166				dev_err(dev,167					"failed to prepare the epll clock\n");168				return ret;169			}170		}171	}172 173	/*174	 * Enable runtime PM here to allow the clock core using runtime PM175	 * for the registered clocks. Additionally, we increase the runtime176	 * PM usage count before registering the clocks, to prevent the177	 * clock core from runtime suspending the device.178	 */179	pm_runtime_get_noresume(dev);180	pm_runtime_set_active(dev);181	pm_runtime_enable(dev);182 183	clk_table[EXYNOS_MOUT_AUDSS] = clk_hw_register_mux(dev, "mout_audss",184				mout_audss_p, ARRAY_SIZE(mout_audss_p),185				CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,186				reg_base + ASS_CLK_SRC, 0, 1, 0, &lock);187 188	cdclk = devm_clk_get(dev, "cdclk");189	sclk_audio = devm_clk_get(dev, "sclk_audio");190	if (!IS_ERR(cdclk))191		mout_i2s_p[1] = __clk_get_name(cdclk);192	if (!IS_ERR(sclk_audio))193		mout_i2s_p[2] = __clk_get_name(sclk_audio);194	clk_table[EXYNOS_MOUT_I2S] = clk_hw_register_mux(dev, "mout_i2s",195				mout_i2s_p, ARRAY_SIZE(mout_i2s_p),196				CLK_SET_RATE_NO_REPARENT,197				reg_base + ASS_CLK_SRC, 2, 2, 0, &lock);198 199	clk_table[EXYNOS_DOUT_SRP] = clk_hw_register_divider(dev, "dout_srp",200				"mout_audss", CLK_SET_RATE_PARENT,201				reg_base + ASS_CLK_DIV, 0, 4, 0, &lock);202 203	clk_table[EXYNOS_DOUT_AUD_BUS] = clk_hw_register_divider(dev,204				"dout_aud_bus", "dout_srp", CLK_SET_RATE_PARENT,205				reg_base + ASS_CLK_DIV, 4, 4, 0, &lock);206 207	clk_table[EXYNOS_DOUT_I2S] = clk_hw_register_divider(dev, "dout_i2s",208				"mout_i2s", 0, reg_base + ASS_CLK_DIV, 8, 4, 0,209				&lock);210 211	clk_table[EXYNOS_SRP_CLK] = clk_hw_register_gate(dev, "srp_clk",212				"dout_srp", CLK_SET_RATE_PARENT,213				reg_base + ASS_CLK_GATE, 0, 0, &lock);214 215	clk_table[EXYNOS_I2S_BUS] = clk_hw_register_gate(dev, "i2s_bus",216				"dout_aud_bus", CLK_SET_RATE_PARENT,217				reg_base + ASS_CLK_GATE, 2, 0, &lock);218 219	clk_table[EXYNOS_SCLK_I2S] = clk_hw_register_gate(dev, "sclk_i2s",220				"dout_i2s", CLK_SET_RATE_PARENT,221				reg_base + ASS_CLK_GATE, 3, 0, &lock);222 223	clk_table[EXYNOS_PCM_BUS] = clk_hw_register_gate(dev, "pcm_bus",224				 "sclk_pcm", CLK_SET_RATE_PARENT,225				reg_base + ASS_CLK_GATE, 4, 0, &lock);226 227	sclk_pcm_in = devm_clk_get(dev, "sclk_pcm_in");228	if (!IS_ERR(sclk_pcm_in))229		sclk_pcm_p = __clk_get_name(sclk_pcm_in);230	clk_table[EXYNOS_SCLK_PCM] = clk_hw_register_gate(dev, "sclk_pcm",231				sclk_pcm_p, CLK_SET_RATE_PARENT,232				reg_base + ASS_CLK_GATE, 5, 0, &lock);233 234	if (variant->has_adma_clk) {235		clk_table[EXYNOS_ADMA] = clk_hw_register_gate(dev, "adma",236				"dout_srp", CLK_SET_RATE_PARENT,237				reg_base + ASS_CLK_GATE, 9, 0, &lock);238	}239 240	for (i = 0; i < clk_data->num; i++) {241		if (IS_ERR(clk_table[i])) {242			dev_err(dev, "failed to register clock %d\n", i);243			ret = PTR_ERR(clk_table[i]);244			goto unregister;245		}246	}247 248	ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,249				     clk_data);250	if (ret) {251		dev_err(dev, "failed to add clock provider\n");252		goto unregister;253	}254 255	pm_runtime_put_sync(dev);256 257	return 0;258 259unregister:260	exynos_audss_clk_teardown();261	pm_runtime_put_sync(dev);262	pm_runtime_disable(dev);263 264	if (!IS_ERR(epll))265		clk_disable_unprepare(epll);266 267	return ret;268}269 270static void exynos_audss_clk_remove(struct platform_device *pdev)271{272	of_clk_del_provider(pdev->dev.of_node);273 274	exynos_audss_clk_teardown();275	pm_runtime_disable(&pdev->dev);276 277	if (!IS_ERR(epll))278		clk_disable_unprepare(epll);279}280 281static const struct dev_pm_ops exynos_audss_clk_pm_ops = {282	SET_RUNTIME_PM_OPS(exynos_audss_clk_suspend, exynos_audss_clk_resume,283			   NULL)284	SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,285				     pm_runtime_force_resume)286};287 288static struct platform_driver exynos_audss_clk_driver = {289	.driver	= {290		.name = "exynos-audss-clk",291		.of_match_table = exynos_audss_clk_of_match,292		.pm = &exynos_audss_clk_pm_ops,293	},294	.probe = exynos_audss_clk_probe,295	.remove = exynos_audss_clk_remove,296};297 298module_platform_driver(exynos_audss_clk_driver);299 300MODULE_AUTHOR("Padmavathi Venna <padma.v@samsung.com>");301MODULE_DESCRIPTION("Exynos Audio Subsystem Clock Controller");302MODULE_LICENSE("GPL v2");303MODULE_ALIAS("platform:exynos-audss-clk");304