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