brintos

brintos / linux-shallow public Read only

0
0
Text · 12.2 KiB · b2cb157 Raw
470 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Driver for i.MX8M Plus Audio BLK_CTRL4 *5 * Copyright (C) 2022 Marek Vasut <marex@denx.de>6 */7 8#include <linux/auxiliary_bus.h>9#include <linux/clk-provider.h>10#include <linux/device.h>11#include <linux/io.h>12#include <linux/mod_devicetable.h>13#include <linux/module.h>14#include <linux/of.h>15#include <linux/platform_device.h>16#include <linux/pm_runtime.h>17#include <linux/slab.h>18 19#include <dt-bindings/clock/imx8mp-clock.h>20 21#include "clk.h"22 23#define CLKEN0			0x00024#define CLKEN1			0x00425#define EARC			0x20026#define SAI1_MCLK_SEL		0x30027#define SAI2_MCLK_SEL		0x30428#define SAI3_MCLK_SEL		0x30829#define SAI5_MCLK_SEL		0x30C30#define SAI6_MCLK_SEL		0x31031#define SAI7_MCLK_SEL		0x31432#define PDM_SEL			0x31833#define SAI_PLL_GNRL_CTL	0x40034#define SAI_PLL_FDIVL_CTL0	0x40435#define SAI_PLL_FDIVL_CTL1	0x40836#define SAI_PLL_SSCG_CTL	0x40C37#define SAI_PLL_MNIT_CTL	0x41038#define IPG_LP_CTRL		0x50439 40#define SAIn_MCLK1_PARENT(n)						\41static const struct clk_parent_data					\42clk_imx8mp_audiomix_sai##n##_mclk1_parents[] = {			\43	{								\44		.fw_name = "sai"__stringify(n),				\45		.name = "sai"__stringify(n)				\46	}, {								\47		.fw_name = "sai"__stringify(n)"_mclk",			\48		.name = "sai"__stringify(n)"_mclk"			\49	},								\50}51 52SAIn_MCLK1_PARENT(1);53SAIn_MCLK1_PARENT(2);54SAIn_MCLK1_PARENT(3);55SAIn_MCLK1_PARENT(5);56SAIn_MCLK1_PARENT(6);57SAIn_MCLK1_PARENT(7);58 59static const struct clk_parent_data clk_imx8mp_audiomix_sai_mclk2_parents[] = {60	{ .fw_name = "sai1", .name = "sai1" },61	{ .fw_name = "sai2", .name = "sai2" },62	{ .fw_name = "sai3", .name = "sai3" },63	{ .name = "dummy" },64	{ .fw_name = "sai5", .name = "sai5" },65	{ .fw_name = "sai6", .name = "sai6" },66	{ .fw_name = "sai7", .name = "sai7" },67	{ .fw_name = "sai1_mclk", .name = "sai1_mclk" },68	{ .fw_name = "sai2_mclk", .name = "sai2_mclk" },69	{ .fw_name = "sai3_mclk", .name = "sai3_mclk" },70	{ .name = "dummy" },71	{ .fw_name = "sai5_mclk", .name = "sai5_mclk" },72	{ .fw_name = "sai6_mclk", .name = "sai6_mclk" },73	{ .fw_name = "sai7_mclk", .name = "sai7_mclk" },74	{ .fw_name = "spdif_extclk", .name = "spdif_extclk" },75	{ .name = "dummy" },76};77 78static const struct clk_parent_data clk_imx8mp_audiomix_pdm_parents[] = {79	{ .fw_name = "pdm", .name = "pdm" },80	{ .name = "sai_pll_out_div2" },81	{ .fw_name = "sai1_mclk", .name = "sai1_mclk" },82	{ .name = "dummy" },83};84 85 86static const struct clk_parent_data clk_imx8mp_audiomix_pll_parents[] = {87	{ .fw_name = "osc_24m", .name = "osc_24m" },88	{ .name = "dummy" },89	{ .name = "dummy" },90	{ .name = "dummy" },91};92 93static const struct clk_parent_data clk_imx8mp_audiomix_pll_bypass_sels[] = {94	{ .fw_name = "sai_pll", .name = "sai_pll" },95	{ .fw_name = "sai_pll_ref_sel", .name = "sai_pll_ref_sel" },96};97 98#define CLK_GATE(gname, cname)						\99	{								\100		gname"_cg",						\101		IMX8MP_CLK_AUDIOMIX_##cname,				\102		{ .fw_name = "ahb", .name = "ahb" }, NULL, 1,		\103		CLKEN0 + 4 * !!(IMX8MP_CLK_AUDIOMIX_##cname / 32),	\104		1, IMX8MP_CLK_AUDIOMIX_##cname % 32			\105	}106 107#define CLK_SAIn(n)							\108	{								\109		"sai"__stringify(n)"_mclk1_sel",			\110		IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK1_SEL, {},		\111		clk_imx8mp_audiomix_sai##n##_mclk1_parents,		\112		ARRAY_SIZE(clk_imx8mp_audiomix_sai##n##_mclk1_parents), \113		SAI##n##_MCLK_SEL, 1, 0					\114	}, {								\115		"sai"__stringify(n)"_mclk2_sel",			\116		IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK2_SEL, {},		\117		clk_imx8mp_audiomix_sai_mclk2_parents,			\118		ARRAY_SIZE(clk_imx8mp_audiomix_sai_mclk2_parents),	\119		SAI##n##_MCLK_SEL, 4, 1					\120	}, {								\121		"sai"__stringify(n)"_ipg_cg",				\122		IMX8MP_CLK_AUDIOMIX_SAI##n##_IPG,			\123		{ .fw_name = "ahb", .name = "ahb" }, NULL, 1,		\124		CLKEN0, 1, IMX8MP_CLK_AUDIOMIX_SAI##n##_IPG		\125	}, {								\126		"sai"__stringify(n)"_mclk1_cg",				\127		IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK1,			\128		{							\129			.fw_name = "sai"__stringify(n)"_mclk1_sel",	\130			.name = "sai"__stringify(n)"_mclk1_sel"		\131		}, NULL, 1,						\132		CLKEN0, 1, IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK1		\133	}, {								\134		"sai"__stringify(n)"_mclk2_cg",				\135		IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK2,			\136		{							\137			.fw_name = "sai"__stringify(n)"_mclk2_sel",	\138			.name = "sai"__stringify(n)"_mclk2_sel"		\139		}, NULL, 1,						\140		CLKEN0, 1, IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK2		\141	}, {								\142		"sai"__stringify(n)"_mclk3_cg",				\143		IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK3,			\144		{							\145			.fw_name = "sai_pll_out_div2",			\146			.name = "sai_pll_out_div2"			\147		}, NULL, 1,						\148		CLKEN0, 1, IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK3		\149	}150 151#define CLK_PDM								\152	{								\153		"pdm_sel", IMX8MP_CLK_AUDIOMIX_PDM_SEL, {},		\154		clk_imx8mp_audiomix_pdm_parents,			\155		ARRAY_SIZE(clk_imx8mp_audiomix_pdm_parents),		\156		PDM_SEL, 2, 0						\157	}158 159#define CLK_GATE_PARENT(gname, cname, pname)						\160	{								\161		gname"_cg",						\162		IMX8MP_CLK_AUDIOMIX_##cname,				\163		{ .fw_name = pname, .name = pname }, NULL, 1,		\164		CLKEN0 + 4 * !!(IMX8MP_CLK_AUDIOMIX_##cname / 32),	\165		1, IMX8MP_CLK_AUDIOMIX_##cname % 32			\166	}167 168struct clk_imx8mp_audiomix_sel {169	const char			*name;170	int				clkid;171	const struct clk_parent_data	parent;		/* For gate */172	const struct clk_parent_data	*parents;	/* For mux */173	int				num_parents;174	u16				reg;175	u8				width;176	u8				shift;177};178 179static struct clk_imx8mp_audiomix_sel sels[] = {180	CLK_GATE("asrc", ASRC_IPG),181	CLK_GATE("pdm", PDM_IPG),182	CLK_GATE("earc", EARC_IPG),183	CLK_GATE("ocrama", OCRAMA_IPG),184	CLK_GATE("aud2htx", AUD2HTX_IPG),185	CLK_GATE_PARENT("earc_phy", EARC_PHY, "sai_pll_out_div2"),186	CLK_GATE("sdma2", SDMA2_ROOT),187	CLK_GATE("sdma3", SDMA3_ROOT),188	CLK_GATE("spba2", SPBA2_ROOT),189	CLK_GATE("dsp", DSP_ROOT),190	CLK_GATE("dspdbg", DSPDBG_ROOT),191	CLK_GATE("edma", EDMA_ROOT),192	CLK_GATE_PARENT("audpll", AUDPLL_ROOT, "osc_24m"),193	CLK_GATE("mu2", MU2_ROOT),194	CLK_GATE("mu3", MU3_ROOT),195	CLK_PDM,196	CLK_SAIn(1),197	CLK_SAIn(2),198	CLK_SAIn(3),199	CLK_SAIn(5),200	CLK_SAIn(6),201	CLK_SAIn(7)202};203 204static const u16 audiomix_regs[] = {205	CLKEN0,206	CLKEN1,207	EARC,208	SAI1_MCLK_SEL,209	SAI2_MCLK_SEL,210	SAI3_MCLK_SEL,211	SAI5_MCLK_SEL,212	SAI6_MCLK_SEL,213	SAI7_MCLK_SEL,214	PDM_SEL,215	SAI_PLL_GNRL_CTL,216	SAI_PLL_FDIVL_CTL0,217	SAI_PLL_FDIVL_CTL1,218	SAI_PLL_SSCG_CTL,219	SAI_PLL_MNIT_CTL,220	IPG_LP_CTRL,221};222 223struct clk_imx8mp_audiomix_priv {224	void __iomem *base;225	u32 regs_save[ARRAY_SIZE(audiomix_regs)];226 227	/* Must be last */228	struct clk_hw_onecell_data clk_data;229};230 231#if IS_ENABLED(CONFIG_RESET_CONTROLLER)232 233static void clk_imx8mp_audiomix_reset_unregister_adev(void *_adev)234{235	struct auxiliary_device *adev = _adev;236 237	auxiliary_device_delete(adev);238	auxiliary_device_uninit(adev);239}240 241static void clk_imx8mp_audiomix_reset_adev_release(struct device *dev)242{243	struct auxiliary_device *adev = to_auxiliary_dev(dev);244 245	kfree(adev);246}247 248static int clk_imx8mp_audiomix_reset_controller_register(struct device *dev,249							 struct clk_imx8mp_audiomix_priv *priv)250{251	struct auxiliary_device *adev __free(kfree) = NULL;252	int ret;253 254	if (!of_property_present(dev->of_node, "#reset-cells"))255		return 0;256 257	adev = kzalloc(sizeof(*adev), GFP_KERNEL);258	if (!adev)259		return -ENOMEM;260 261	adev->name = "reset";262	adev->dev.parent = dev;263	adev->dev.release = clk_imx8mp_audiomix_reset_adev_release;264 265	ret = auxiliary_device_init(adev);266	if (ret)267		return ret;268 269	ret = auxiliary_device_add(adev);270	if (ret) {271		auxiliary_device_uninit(adev);272		return ret;273	}274 275	return devm_add_action_or_reset(dev, clk_imx8mp_audiomix_reset_unregister_adev,276					no_free_ptr(adev));277}278 279#else /* !CONFIG_RESET_CONTROLLER */280 281static int clk_imx8mp_audiomix_reset_controller_register(struct clk_imx8mp_audiomix_priv *priv)282{283	return 0;284}285 286#endif /* !CONFIG_RESET_CONTROLLER */287 288static void clk_imx8mp_audiomix_save_restore(struct device *dev, bool save)289{290	struct clk_imx8mp_audiomix_priv *priv = dev_get_drvdata(dev);291	void __iomem *base = priv->base;292	int i;293 294	if (save) {295		for (i = 0; i < ARRAY_SIZE(audiomix_regs); i++)296			priv->regs_save[i] = readl(base + audiomix_regs[i]);297	} else {298		for (i = 0; i < ARRAY_SIZE(audiomix_regs); i++)299			writel(priv->regs_save[i], base + audiomix_regs[i]);300	}301}302 303static int clk_imx8mp_audiomix_probe(struct platform_device *pdev)304{305	struct clk_imx8mp_audiomix_priv *priv;306	struct clk_hw_onecell_data *clk_hw_data;307	struct device *dev = &pdev->dev;308	void __iomem *base;309	struct clk_hw *hw;310	int i, ret;311 312	priv = devm_kzalloc(dev,313			    struct_size(priv, clk_data.hws, IMX8MP_CLK_AUDIOMIX_END),314			    GFP_KERNEL);315	if (!priv)316		return -ENOMEM;317 318	clk_hw_data = &priv->clk_data;319	clk_hw_data->num = IMX8MP_CLK_AUDIOMIX_END;320 321	base = devm_platform_ioremap_resource(pdev, 0);322	if (IS_ERR(base))323		return PTR_ERR(base);324 325	priv->base = base;326	dev_set_drvdata(dev, priv);327 328	/*329	 * pm_runtime_enable needs to be called before clk register.330	 * That is to make core->rpm_enabled to be true for clock331	 * usage.332	 */333	pm_runtime_get_noresume(dev);334	pm_runtime_set_active(dev);335	pm_runtime_enable(dev);336 337	for (i = 0; i < ARRAY_SIZE(sels); i++) {338		if (sels[i].num_parents == 1) {339			hw = devm_clk_hw_register_gate_parent_data(dev,340				sels[i].name, &sels[i].parent, CLK_SET_RATE_PARENT,341				base + sels[i].reg, sels[i].shift, 0, NULL);342		} else {343			hw = devm_clk_hw_register_mux_parent_data_table(dev,344				sels[i].name, sels[i].parents,345				sels[i].num_parents, CLK_SET_RATE_PARENT,346				base + sels[i].reg,347				sels[i].shift, sels[i].width,348				0, NULL, NULL);349		}350 351		if (IS_ERR(hw)) {352			ret = PTR_ERR(hw);353			goto err_clk_register;354		}355 356		clk_hw_data->hws[sels[i].clkid] = hw;357	}358 359	/* SAI PLL */360	hw = devm_clk_hw_register_mux_parent_data_table(dev,361		"sai_pll_ref_sel", clk_imx8mp_audiomix_pll_parents,362		ARRAY_SIZE(clk_imx8mp_audiomix_pll_parents),363		CLK_SET_RATE_NO_REPARENT, base + SAI_PLL_GNRL_CTL,364		0, 2, 0, NULL, NULL);365	clk_hw_data->hws[IMX8MP_CLK_AUDIOMIX_SAI_PLL_REF_SEL] = hw;366 367	hw = imx_dev_clk_hw_pll14xx(dev, "sai_pll", "sai_pll_ref_sel",368				    base + 0x400, &imx_1443x_pll);369	if (IS_ERR(hw)) {370		ret = PTR_ERR(hw);371		goto err_clk_register;372	}373	clk_hw_data->hws[IMX8MP_CLK_AUDIOMIX_SAI_PLL] = hw;374 375	hw = devm_clk_hw_register_mux_parent_data_table(dev,376		"sai_pll_bypass", clk_imx8mp_audiomix_pll_bypass_sels,377		ARRAY_SIZE(clk_imx8mp_audiomix_pll_bypass_sels),378		CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,379		base + SAI_PLL_GNRL_CTL, 16, 1, 0, NULL, NULL);380	if (IS_ERR(hw)) {381		ret = PTR_ERR(hw);382		goto err_clk_register;383	}384 385	clk_hw_data->hws[IMX8MP_CLK_AUDIOMIX_SAI_PLL_BYPASS] = hw;386 387	hw = devm_clk_hw_register_gate(dev, "sai_pll_out", "sai_pll_bypass",388				       CLK_SET_RATE_PARENT,389				       base + SAI_PLL_GNRL_CTL, 13,390				       0, NULL);391	if (IS_ERR(hw)) {392		ret = PTR_ERR(hw);393		goto err_clk_register;394	}395	clk_hw_data->hws[IMX8MP_CLK_AUDIOMIX_SAI_PLL_OUT] = hw;396 397	hw = devm_clk_hw_register_fixed_factor(dev, "sai_pll_out_div2",398					       "sai_pll_out",399					       CLK_SET_RATE_PARENT, 1, 2);400	if (IS_ERR(hw)) {401		ret = PTR_ERR(hw);402		goto err_clk_register;403	}404 405	ret = devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_onecell_get,406					  clk_hw_data);407	if (ret)408		goto err_clk_register;409 410	ret = clk_imx8mp_audiomix_reset_controller_register(dev, priv);411	if (ret)412		goto err_clk_register;413 414	pm_runtime_put_sync(dev);415	return 0;416 417err_clk_register:418	pm_runtime_put_sync(dev);419	pm_runtime_disable(dev);420	return ret;421}422 423static void clk_imx8mp_audiomix_remove(struct platform_device *pdev)424{425	pm_runtime_disable(&pdev->dev);426}427 428static int clk_imx8mp_audiomix_runtime_suspend(struct device *dev)429{430	clk_imx8mp_audiomix_save_restore(dev, true);431 432	return 0;433}434 435static int clk_imx8mp_audiomix_runtime_resume(struct device *dev)436{437	clk_imx8mp_audiomix_save_restore(dev, false);438 439	return 0;440}441 442static const struct dev_pm_ops clk_imx8mp_audiomix_pm_ops = {443	RUNTIME_PM_OPS(clk_imx8mp_audiomix_runtime_suspend,444		       clk_imx8mp_audiomix_runtime_resume, NULL)445	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,446				      pm_runtime_force_resume)447};448 449static const struct of_device_id clk_imx8mp_audiomix_of_match[] = {450	{ .compatible = "fsl,imx8mp-audio-blk-ctrl" },451	{ /* sentinel */ }452};453MODULE_DEVICE_TABLE(of, clk_imx8mp_audiomix_of_match);454 455static struct platform_driver clk_imx8mp_audiomix_driver = {456	.probe	= clk_imx8mp_audiomix_probe,457	.remove = clk_imx8mp_audiomix_remove,458	.driver = {459		.name = "imx8mp-audio-blk-ctrl",460		.of_match_table = clk_imx8mp_audiomix_of_match,461		.pm = pm_ptr(&clk_imx8mp_audiomix_pm_ops),462	},463};464 465module_platform_driver(clk_imx8mp_audiomix_driver);466 467MODULE_AUTHOR("Marek Vasut <marex@denx.de>");468MODULE_DESCRIPTION("Freescale i.MX8MP Audio Block Controller driver");469MODULE_LICENSE("GPL");470