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