linux/drivers/clk/samsung/clk-exynos-audss.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2013 Samsung Electronics Co., Ltd.
   3 * Author: Padmavathi Venna <padma.v@samsung.com>
   4 *
   5 * This program is free software; you can redistribute it and/or modify
   6 * it under the terms of the GNU General Public License version 2 as
   7 * published by the Free Software Foundation.
   8 *
   9 * Common Clock Framework support for Audio Subsystem Clock Controller.
  10*/
  11
  12#include <linux/clkdev.h>
  13#include <linux/io.h>
  14#include <linux/clk-provider.h>
  15#include <linux/of_address.h>
  16#include <linux/syscore_ops.h>
  17#include <linux/module.h>
  18#include <linux/platform_device.h>
  19
  20#include <dt-bindings/clock/exynos-audss-clk.h>
  21
  22enum exynos_audss_clk_type {
  23        TYPE_EXYNOS4210,
  24        TYPE_EXYNOS5250,
  25        TYPE_EXYNOS5420,
  26};
  27
  28static DEFINE_SPINLOCK(lock);
  29static struct clk **clk_table;
  30static void __iomem *reg_base;
  31static struct clk_onecell_data clk_data;
  32/*
  33 * On Exynos5420 this will be a clock which has to be enabled before any
  34 * access to audss registers. Typically a child of EPLL.
  35 *
  36 * On other platforms this will be -ENODEV.
  37 */
  38static struct clk *epll;
  39
  40#define ASS_CLK_SRC 0x0
  41#define ASS_CLK_DIV 0x4
  42#define ASS_CLK_GATE 0x8
  43
  44#ifdef CONFIG_PM_SLEEP
  45static unsigned long reg_save[][2] = {
  46        {ASS_CLK_SRC,  0},
  47        {ASS_CLK_DIV,  0},
  48        {ASS_CLK_GATE, 0},
  49};
  50
  51static int exynos_audss_clk_suspend(void)
  52{
  53        int i;
  54
  55        for (i = 0; i < ARRAY_SIZE(reg_save); i++)
  56                reg_save[i][1] = readl(reg_base + reg_save[i][0]);
  57
  58        return 0;
  59}
  60
  61static void exynos_audss_clk_resume(void)
  62{
  63        int i;
  64
  65        for (i = 0; i < ARRAY_SIZE(reg_save); i++)
  66                writel(reg_save[i][1], reg_base + reg_save[i][0]);
  67}
  68
  69static struct syscore_ops exynos_audss_clk_syscore_ops = {
  70        .suspend        = exynos_audss_clk_suspend,
  71        .resume         = exynos_audss_clk_resume,
  72};
  73#endif /* CONFIG_PM_SLEEP */
  74
  75static const struct of_device_id exynos_audss_clk_of_match[] = {
  76        { .compatible = "samsung,exynos4210-audss-clock",
  77          .data = (void *)TYPE_EXYNOS4210, },
  78        { .compatible = "samsung,exynos5250-audss-clock",
  79          .data = (void *)TYPE_EXYNOS5250, },
  80        { .compatible = "samsung,exynos5420-audss-clock",
  81          .data = (void *)TYPE_EXYNOS5420, },
  82        {},
  83};
  84
  85/* register exynos_audss clocks */
  86static int exynos_audss_clk_probe(struct platform_device *pdev)
  87{
  88        int i, ret = 0;
  89        struct resource *res;
  90        const char *mout_audss_p[] = {"fin_pll", "fout_epll"};
  91        const char *mout_i2s_p[] = {"mout_audss", "cdclk0", "sclk_audio0"};
  92        const char *sclk_pcm_p = "sclk_pcm0";
  93        struct clk *pll_ref, *pll_in, *cdclk, *sclk_audio, *sclk_pcm_in;
  94        const struct of_device_id *match;
  95        enum exynos_audss_clk_type variant;
  96
  97        match = of_match_node(exynos_audss_clk_of_match, pdev->dev.of_node);
  98        if (!match)
  99                return -EINVAL;
 100        variant = (enum exynos_audss_clk_type)match->data;
 101
 102        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 103        reg_base = devm_ioremap_resource(&pdev->dev, res);
 104        if (IS_ERR(reg_base)) {
 105                dev_err(&pdev->dev, "failed to map audss registers\n");
 106                return PTR_ERR(reg_base);
 107        }
 108        /* EPLL don't have to be enabled for boards other than Exynos5420 */
 109        epll = ERR_PTR(-ENODEV);
 110
 111        clk_table = devm_kzalloc(&pdev->dev,
 112                                sizeof(struct clk *) * EXYNOS_AUDSS_MAX_CLKS,
 113                                GFP_KERNEL);
 114        if (!clk_table)
 115                return -ENOMEM;
 116
 117        clk_data.clks = clk_table;
 118        if (variant == TYPE_EXYNOS5420)
 119                clk_data.clk_num = EXYNOS_AUDSS_MAX_CLKS;
 120        else
 121                clk_data.clk_num = EXYNOS_AUDSS_MAX_CLKS - 1;
 122
 123        pll_ref = devm_clk_get(&pdev->dev, "pll_ref");
 124        pll_in = devm_clk_get(&pdev->dev, "pll_in");
 125        if (!IS_ERR(pll_ref))
 126                mout_audss_p[0] = __clk_get_name(pll_ref);
 127        if (!IS_ERR(pll_in)) {
 128                mout_audss_p[1] = __clk_get_name(pll_in);
 129
 130                if (variant == TYPE_EXYNOS5420) {
 131                        epll = pll_in;
 132
 133                        ret = clk_prepare_enable(epll);
 134                        if (ret) {
 135                                dev_err(&pdev->dev,
 136                                                "failed to prepare the epll clock\n");
 137                                return ret;
 138                        }
 139                }
 140        }
 141        clk_table[EXYNOS_MOUT_AUDSS] = clk_register_mux(NULL, "mout_audss",
 142                                mout_audss_p, ARRAY_SIZE(mout_audss_p),
 143                                CLK_SET_RATE_NO_REPARENT,
 144                                reg_base + ASS_CLK_SRC, 0, 1, 0, &lock);
 145
 146        cdclk = devm_clk_get(&pdev->dev, "cdclk");
 147        sclk_audio = devm_clk_get(&pdev->dev, "sclk_audio");
 148        if (!IS_ERR(cdclk))
 149                mout_i2s_p[1] = __clk_get_name(cdclk);
 150        if (!IS_ERR(sclk_audio))
 151                mout_i2s_p[2] = __clk_get_name(sclk_audio);
 152        clk_table[EXYNOS_MOUT_I2S] = clk_register_mux(NULL, "mout_i2s",
 153                                mout_i2s_p, ARRAY_SIZE(mout_i2s_p),
 154                                CLK_SET_RATE_NO_REPARENT,
 155                                reg_base + ASS_CLK_SRC, 2, 2, 0, &lock);
 156
 157        clk_table[EXYNOS_DOUT_SRP] = clk_register_divider(NULL, "dout_srp",
 158                                "mout_audss", 0, reg_base + ASS_CLK_DIV, 0, 4,
 159                                0, &lock);
 160
 161        clk_table[EXYNOS_DOUT_AUD_BUS] = clk_register_divider(NULL,
 162                                "dout_aud_bus", "dout_srp", 0,
 163                                reg_base + ASS_CLK_DIV, 4, 4, 0, &lock);
 164
 165        clk_table[EXYNOS_DOUT_I2S] = clk_register_divider(NULL, "dout_i2s",
 166                                "mout_i2s", 0, reg_base + ASS_CLK_DIV, 8, 4, 0,
 167                                &lock);
 168
 169        clk_table[EXYNOS_SRP_CLK] = clk_register_gate(NULL, "srp_clk",
 170                                "dout_srp", CLK_SET_RATE_PARENT,
 171                                reg_base + ASS_CLK_GATE, 0, 0, &lock);
 172
 173        clk_table[EXYNOS_I2S_BUS] = clk_register_gate(NULL, "i2s_bus",
 174                                "dout_aud_bus", CLK_SET_RATE_PARENT,
 175                                reg_base + ASS_CLK_GATE, 2, 0, &lock);
 176
 177        clk_table[EXYNOS_SCLK_I2S] = clk_register_gate(NULL, "sclk_i2s",
 178                                "dout_i2s", CLK_SET_RATE_PARENT,
 179                                reg_base + ASS_CLK_GATE, 3, 0, &lock);
 180
 181        clk_table[EXYNOS_PCM_BUS] = clk_register_gate(NULL, "pcm_bus",
 182                                 "sclk_pcm", CLK_SET_RATE_PARENT,
 183                                reg_base + ASS_CLK_GATE, 4, 0, &lock);
 184
 185        sclk_pcm_in = devm_clk_get(&pdev->dev, "sclk_pcm_in");
 186        if (!IS_ERR(sclk_pcm_in))
 187                sclk_pcm_p = __clk_get_name(sclk_pcm_in);
 188        clk_table[EXYNOS_SCLK_PCM] = clk_register_gate(NULL, "sclk_pcm",
 189                                sclk_pcm_p, CLK_SET_RATE_PARENT,
 190                                reg_base + ASS_CLK_GATE, 5, 0, &lock);
 191
 192        if (variant == TYPE_EXYNOS5420) {
 193                clk_table[EXYNOS_ADMA] = clk_register_gate(NULL, "adma",
 194                                "dout_srp", CLK_SET_RATE_PARENT,
 195                                reg_base + ASS_CLK_GATE, 9, 0, &lock);
 196        }
 197
 198        for (i = 0; i < clk_data.clk_num; i++) {
 199                if (IS_ERR(clk_table[i])) {
 200                        dev_err(&pdev->dev, "failed to register clock %d\n", i);
 201                        ret = PTR_ERR(clk_table[i]);
 202                        goto unregister;
 203                }
 204        }
 205
 206        ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get,
 207                                        &clk_data);
 208        if (ret) {
 209                dev_err(&pdev->dev, "failed to add clock provider\n");
 210                goto unregister;
 211        }
 212
 213#ifdef CONFIG_PM_SLEEP
 214        register_syscore_ops(&exynos_audss_clk_syscore_ops);
 215#endif
 216
 217        dev_info(&pdev->dev, "setup completed\n");
 218
 219        return 0;
 220
 221unregister:
 222        for (i = 0; i < clk_data.clk_num; i++) {
 223                if (!IS_ERR(clk_table[i]))
 224                        clk_unregister(clk_table[i]);
 225        }
 226
 227        if (!IS_ERR(epll))
 228                clk_disable_unprepare(epll);
 229
 230        return ret;
 231}
 232
 233static int exynos_audss_clk_remove(struct platform_device *pdev)
 234{
 235        int i;
 236
 237#ifdef CONFIG_PM_SLEEP
 238        unregister_syscore_ops(&exynos_audss_clk_syscore_ops);
 239#endif
 240
 241        of_clk_del_provider(pdev->dev.of_node);
 242
 243        for (i = 0; i < clk_data.clk_num; i++) {
 244                if (!IS_ERR(clk_table[i]))
 245                        clk_unregister(clk_table[i]);
 246        }
 247
 248        if (!IS_ERR(epll))
 249                clk_disable_unprepare(epll);
 250
 251        return 0;
 252}
 253
 254static struct platform_driver exynos_audss_clk_driver = {
 255        .driver = {
 256                .name = "exynos-audss-clk",
 257                .of_match_table = exynos_audss_clk_of_match,
 258        },
 259        .probe = exynos_audss_clk_probe,
 260        .remove = exynos_audss_clk_remove,
 261};
 262
 263static int __init exynos_audss_clk_init(void)
 264{
 265        return platform_driver_register(&exynos_audss_clk_driver);
 266}
 267core_initcall(exynos_audss_clk_init);
 268
 269static void __exit exynos_audss_clk_exit(void)
 270{
 271        platform_driver_unregister(&exynos_audss_clk_driver);
 272}
 273module_exit(exynos_audss_clk_exit);
 274
 275MODULE_AUTHOR("Padmavathi Venna <padma.v@samsung.com>");
 276MODULE_DESCRIPTION("Exynos Audio Subsystem Clock Controller");
 277MODULE_LICENSE("GPL v2");
 278MODULE_ALIAS("platform:exynos-audss-clk");
 279
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.