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