root/drivers/clk/samsung/clk-s3c2410-dclk.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. s3c24xx_clkout_get_parent
  2. s3c24xx_clkout_set_parent
  3. s3c24xx_register_clkout
  4. s3c24xx_dclk_update_cmp
  5. s3c24xx_dclk0_div_notify
  6. s3c24xx_dclk1_div_notify
  7. s3c24xx_dclk_suspend
  8. s3c24xx_dclk_resume
  9. s3c24xx_dclk_probe
  10. s3c24xx_dclk_remove

   1 // SPDX-License-Identifier: GPL-2.0-only
   2 /*
   3  * Copyright (c) 2013 Heiko Stuebner <heiko@sntech.de>
   4  *
   5  * Common Clock Framework support for s3c24xx external clock output.
   6  */
   7 
   8 #include <linux/clkdev.h>
   9 #include <linux/slab.h>
  10 #include <linux/clk.h>
  11 #include <linux/clk-provider.h>
  12 #include <linux/io.h>
  13 #include <linux/platform_device.h>
  14 #include <linux/module.h>
  15 #include "clk.h"
  16 
  17 /* legacy access to misccr, until dt conversion is finished */
  18 #include <mach/hardware.h>
  19 #include <mach/regs-gpio.h>
  20 
  21 #define MUX_DCLK0       0
  22 #define MUX_DCLK1       1
  23 #define DIV_DCLK0       2
  24 #define DIV_DCLK1       3
  25 #define GATE_DCLK0      4
  26 #define GATE_DCLK1      5
  27 #define MUX_CLKOUT0     6
  28 #define MUX_CLKOUT1     7
  29 #define DCLK_MAX_CLKS   (MUX_CLKOUT1 + 1)
  30 
  31 enum supported_socs {
  32         S3C2410,
  33         S3C2412,
  34         S3C2440,
  35         S3C2443,
  36 };
  37 
  38 struct s3c24xx_dclk_drv_data {
  39         const char **clkout0_parent_names;
  40         int clkout0_num_parents;
  41         const char **clkout1_parent_names;
  42         int clkout1_num_parents;
  43         const char **mux_parent_names;
  44         int mux_num_parents;
  45 };
  46 
  47 /*
  48  * Clock for output-parent selection in misccr
  49  */
  50 
  51 struct s3c24xx_clkout {
  52         struct clk_hw           hw;
  53         u32                     mask;
  54         u8                      shift;
  55 };
  56 
  57 #define to_s3c24xx_clkout(_hw) container_of(_hw, struct s3c24xx_clkout, hw)
  58 
  59 static u8 s3c24xx_clkout_get_parent(struct clk_hw *hw)
  60 {
  61         struct s3c24xx_clkout *clkout = to_s3c24xx_clkout(hw);
  62         int num_parents = clk_hw_get_num_parents(hw);
  63         u32 val;
  64 
  65         val = readl_relaxed(S3C24XX_MISCCR) >> clkout->shift;
  66         val >>= clkout->shift;
  67         val &= clkout->mask;
  68 
  69         if (val >= num_parents)
  70                 return -EINVAL;
  71 
  72         return val;
  73 }
  74 
  75 static int s3c24xx_clkout_set_parent(struct clk_hw *hw, u8 index)
  76 {
  77         struct s3c24xx_clkout *clkout = to_s3c24xx_clkout(hw);
  78 
  79         s3c2410_modify_misccr((clkout->mask << clkout->shift),
  80                               (index << clkout->shift));
  81 
  82         return 0;
  83 }
  84 
  85 static const struct clk_ops s3c24xx_clkout_ops = {
  86         .get_parent = s3c24xx_clkout_get_parent,
  87         .set_parent = s3c24xx_clkout_set_parent,
  88         .determine_rate = __clk_mux_determine_rate,
  89 };
  90 
  91 static struct clk_hw *s3c24xx_register_clkout(struct device *dev,
  92                 const char *name, const char **parent_names, u8 num_parents,
  93                 u8 shift, u32 mask)
  94 {
  95         struct s3c24xx_clkout *clkout;
  96         struct clk_init_data init;
  97         int ret;
  98 
  99         /* allocate the clkout */
 100         clkout = kzalloc(sizeof(*clkout), GFP_KERNEL);
 101         if (!clkout)
 102                 return ERR_PTR(-ENOMEM);
 103 
 104         init.name = name;
 105         init.ops = &s3c24xx_clkout_ops;
 106         init.flags = 0;
 107         init.parent_names = parent_names;
 108         init.num_parents = num_parents;
 109 
 110         clkout->shift = shift;
 111         clkout->mask = mask;
 112         clkout->hw.init = &init;
 113 
 114         ret = clk_hw_register(dev, &clkout->hw);
 115         if (ret)
 116                 return ERR_PTR(ret);
 117 
 118         return &clkout->hw;
 119 }
 120 
 121 /*
 122  * dclk and clkout init
 123  */
 124 
 125 struct s3c24xx_dclk {
 126         struct device *dev;
 127         void __iomem *base;
 128         struct notifier_block dclk0_div_change_nb;
 129         struct notifier_block dclk1_div_change_nb;
 130         spinlock_t dclk_lock;
 131         unsigned long reg_save;
 132         /* clk_data must be the last entry in the structure */
 133         struct clk_hw_onecell_data clk_data;
 134 };
 135 
 136 #define to_s3c24xx_dclk0(x) \
 137                 container_of(x, struct s3c24xx_dclk, dclk0_div_change_nb)
 138 
 139 #define to_s3c24xx_dclk1(x) \
 140                 container_of(x, struct s3c24xx_dclk, dclk1_div_change_nb)
 141 
 142 static const char *dclk_s3c2410_p[] = { "pclk", "uclk" };
 143 static const char *clkout0_s3c2410_p[] = { "mpll", "upll", "fclk", "hclk", "pclk",
 144                              "gate_dclk0" };
 145 static const char *clkout1_s3c2410_p[] = { "mpll", "upll", "fclk", "hclk", "pclk",
 146                              "gate_dclk1" };
 147 
 148 static const char *clkout0_s3c2412_p[] = { "mpll", "upll", "rtc_clkout",
 149                              "hclk", "pclk", "gate_dclk0" };
 150 static const char *clkout1_s3c2412_p[] = { "xti", "upll", "fclk", "hclk", "pclk",
 151                              "gate_dclk1" };
 152 
 153 static const char *clkout0_s3c2440_p[] = { "xti", "upll", "fclk", "hclk", "pclk",
 154                              "gate_dclk0" };
 155 static const char *clkout1_s3c2440_p[] = { "mpll", "upll", "rtc_clkout",
 156                              "hclk", "pclk", "gate_dclk1" };
 157 
 158 static const char *dclk_s3c2443_p[] = { "pclk", "epll" };
 159 static const char *clkout0_s3c2443_p[] = { "xti", "epll", "armclk", "hclk", "pclk",
 160                              "gate_dclk0" };
 161 static const char *clkout1_s3c2443_p[] = { "dummy", "epll", "rtc_clkout",
 162                              "hclk", "pclk", "gate_dclk1" };
 163 
 164 #define DCLKCON_DCLK_DIV_MASK           0xf
 165 #define DCLKCON_DCLK0_DIV_SHIFT         4
 166 #define DCLKCON_DCLK0_CMP_SHIFT         8
 167 #define DCLKCON_DCLK1_DIV_SHIFT         20
 168 #define DCLKCON_DCLK1_CMP_SHIFT         24
 169 
 170 static void s3c24xx_dclk_update_cmp(struct s3c24xx_dclk *s3c24xx_dclk,
 171                                     int div_shift, int cmp_shift)
 172 {
 173         unsigned long flags = 0;
 174         u32 dclk_con, div, cmp;
 175 
 176         spin_lock_irqsave(&s3c24xx_dclk->dclk_lock, flags);
 177 
 178         dclk_con = readl_relaxed(s3c24xx_dclk->base);
 179 
 180         div = ((dclk_con >> div_shift) & DCLKCON_DCLK_DIV_MASK) + 1;
 181         cmp = ((div + 1) / 2) - 1;
 182 
 183         dclk_con &= ~(DCLKCON_DCLK_DIV_MASK << cmp_shift);
 184         dclk_con |= (cmp << cmp_shift);
 185 
 186         writel_relaxed(dclk_con, s3c24xx_dclk->base);
 187 
 188         spin_unlock_irqrestore(&s3c24xx_dclk->dclk_lock, flags);
 189 }
 190 
 191 static int s3c24xx_dclk0_div_notify(struct notifier_block *nb,
 192                                unsigned long event, void *data)
 193 {
 194         struct s3c24xx_dclk *s3c24xx_dclk = to_s3c24xx_dclk0(nb);
 195 
 196         if (event == POST_RATE_CHANGE) {
 197                 s3c24xx_dclk_update_cmp(s3c24xx_dclk,
 198                         DCLKCON_DCLK0_DIV_SHIFT, DCLKCON_DCLK0_CMP_SHIFT);
 199         }
 200 
 201         return NOTIFY_DONE;
 202 }
 203 
 204 static int s3c24xx_dclk1_div_notify(struct notifier_block *nb,
 205                                unsigned long event, void *data)
 206 {
 207         struct s3c24xx_dclk *s3c24xx_dclk = to_s3c24xx_dclk1(nb);
 208 
 209         if (event == POST_RATE_CHANGE) {
 210                 s3c24xx_dclk_update_cmp(s3c24xx_dclk,
 211                         DCLKCON_DCLK1_DIV_SHIFT, DCLKCON_DCLK1_CMP_SHIFT);
 212         }
 213 
 214         return NOTIFY_DONE;
 215 }
 216 
 217 #ifdef CONFIG_PM_SLEEP
 218 static int s3c24xx_dclk_suspend(struct device *dev)
 219 {
 220         struct s3c24xx_dclk *s3c24xx_dclk = dev_get_drvdata(dev);
 221 
 222         s3c24xx_dclk->reg_save = readl_relaxed(s3c24xx_dclk->base);
 223         return 0;
 224 }
 225 
 226 static int s3c24xx_dclk_resume(struct device *dev)
 227 {
 228         struct s3c24xx_dclk *s3c24xx_dclk = dev_get_drvdata(dev);
 229 
 230         writel_relaxed(s3c24xx_dclk->reg_save, s3c24xx_dclk->base);
 231         return 0;
 232 }
 233 #endif
 234 
 235 static SIMPLE_DEV_PM_OPS(s3c24xx_dclk_pm_ops,
 236                          s3c24xx_dclk_suspend, s3c24xx_dclk_resume);
 237 
 238 static int s3c24xx_dclk_probe(struct platform_device *pdev)
 239 {
 240         struct s3c24xx_dclk *s3c24xx_dclk;
 241         struct resource *mem;
 242         struct s3c24xx_dclk_drv_data *dclk_variant;
 243         struct clk_hw **clk_table;
 244         int ret, i;
 245 
 246         s3c24xx_dclk = devm_kzalloc(&pdev->dev,
 247                                     struct_size(s3c24xx_dclk, clk_data.hws,
 248                                                 DCLK_MAX_CLKS),
 249                                     GFP_KERNEL);
 250         if (!s3c24xx_dclk)
 251                 return -ENOMEM;
 252 
 253         clk_table = s3c24xx_dclk->clk_data.hws;
 254 
 255         s3c24xx_dclk->dev = &pdev->dev;
 256         s3c24xx_dclk->clk_data.num = DCLK_MAX_CLKS;
 257         platform_set_drvdata(pdev, s3c24xx_dclk);
 258         spin_lock_init(&s3c24xx_dclk->dclk_lock);
 259 
 260         mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 261         s3c24xx_dclk->base = devm_ioremap_resource(&pdev->dev, mem);
 262         if (IS_ERR(s3c24xx_dclk->base))
 263                 return PTR_ERR(s3c24xx_dclk->base);
 264 
 265         dclk_variant = (struct s3c24xx_dclk_drv_data *)
 266                                 platform_get_device_id(pdev)->driver_data;
 267 
 268 
 269         clk_table[MUX_DCLK0] = clk_hw_register_mux(&pdev->dev, "mux_dclk0",
 270                                 dclk_variant->mux_parent_names,
 271                                 dclk_variant->mux_num_parents, 0,
 272                                 s3c24xx_dclk->base, 1, 1, 0,
 273                                 &s3c24xx_dclk->dclk_lock);
 274         clk_table[MUX_DCLK1] = clk_hw_register_mux(&pdev->dev, "mux_dclk1",
 275                                 dclk_variant->mux_parent_names,
 276                                 dclk_variant->mux_num_parents, 0,
 277                                 s3c24xx_dclk->base, 17, 1, 0,
 278                                 &s3c24xx_dclk->dclk_lock);
 279 
 280         clk_table[DIV_DCLK0] = clk_hw_register_divider(&pdev->dev, "div_dclk0",
 281                                 "mux_dclk0", 0, s3c24xx_dclk->base,
 282                                 4, 4, 0, &s3c24xx_dclk->dclk_lock);
 283         clk_table[DIV_DCLK1] = clk_hw_register_divider(&pdev->dev, "div_dclk1",
 284                                 "mux_dclk1", 0, s3c24xx_dclk->base,
 285                                 20, 4, 0, &s3c24xx_dclk->dclk_lock);
 286 
 287         clk_table[GATE_DCLK0] = clk_hw_register_gate(&pdev->dev, "gate_dclk0",
 288                                 "div_dclk0", CLK_SET_RATE_PARENT,
 289                                 s3c24xx_dclk->base, 0, 0,
 290                                 &s3c24xx_dclk->dclk_lock);
 291         clk_table[GATE_DCLK1] = clk_hw_register_gate(&pdev->dev, "gate_dclk1",
 292                                 "div_dclk1", CLK_SET_RATE_PARENT,
 293                                 s3c24xx_dclk->base, 16, 0,
 294                                 &s3c24xx_dclk->dclk_lock);
 295 
 296         clk_table[MUX_CLKOUT0] = s3c24xx_register_clkout(&pdev->dev,
 297                                 "clkout0", dclk_variant->clkout0_parent_names,
 298                                 dclk_variant->clkout0_num_parents, 4, 7);
 299         clk_table[MUX_CLKOUT1] = s3c24xx_register_clkout(&pdev->dev,
 300                                 "clkout1", dclk_variant->clkout1_parent_names,
 301                                 dclk_variant->clkout1_num_parents, 8, 7);
 302 
 303         for (i = 0; i < DCLK_MAX_CLKS; i++)
 304                 if (IS_ERR(clk_table[i])) {
 305                         dev_err(&pdev->dev, "clock %d failed to register\n", i);
 306                         ret = PTR_ERR(clk_table[i]);
 307                         goto err_clk_register;
 308                 }
 309 
 310         ret = clk_hw_register_clkdev(clk_table[MUX_DCLK0], "dclk0", NULL);
 311         if (!ret)
 312                 ret = clk_hw_register_clkdev(clk_table[MUX_DCLK1], "dclk1",
 313                                              NULL);
 314         if (!ret)
 315                 ret = clk_hw_register_clkdev(clk_table[MUX_CLKOUT0],
 316                                              "clkout0", NULL);
 317         if (!ret)
 318                 ret = clk_hw_register_clkdev(clk_table[MUX_CLKOUT1],
 319                                              "clkout1", NULL);
 320         if (ret) {
 321                 dev_err(&pdev->dev, "failed to register aliases, %d\n", ret);
 322                 goto err_clk_register;
 323         }
 324 
 325         s3c24xx_dclk->dclk0_div_change_nb.notifier_call =
 326                                                 s3c24xx_dclk0_div_notify;
 327 
 328         s3c24xx_dclk->dclk1_div_change_nb.notifier_call =
 329                                                 s3c24xx_dclk1_div_notify;
 330 
 331         ret = clk_notifier_register(clk_table[DIV_DCLK0]->clk,
 332                                     &s3c24xx_dclk->dclk0_div_change_nb);
 333         if (ret)
 334                 goto err_clk_register;
 335 
 336         ret = clk_notifier_register(clk_table[DIV_DCLK1]->clk,
 337                                     &s3c24xx_dclk->dclk1_div_change_nb);
 338         if (ret)
 339                 goto err_dclk_notify;
 340 
 341         return 0;
 342 
 343 err_dclk_notify:
 344         clk_notifier_unregister(clk_table[DIV_DCLK0]->clk,
 345                                 &s3c24xx_dclk->dclk0_div_change_nb);
 346 err_clk_register:
 347         for (i = 0; i < DCLK_MAX_CLKS; i++)
 348                 if (clk_table[i] && !IS_ERR(clk_table[i]))
 349                         clk_hw_unregister(clk_table[i]);
 350 
 351         return ret;
 352 }
 353 
 354 static int s3c24xx_dclk_remove(struct platform_device *pdev)
 355 {
 356         struct s3c24xx_dclk *s3c24xx_dclk = platform_get_drvdata(pdev);
 357         struct clk_hw **clk_table = s3c24xx_dclk->clk_data.hws;
 358         int i;
 359 
 360         clk_notifier_unregister(clk_table[DIV_DCLK1]->clk,
 361                                 &s3c24xx_dclk->dclk1_div_change_nb);
 362         clk_notifier_unregister(clk_table[DIV_DCLK0]->clk,
 363                                 &s3c24xx_dclk->dclk0_div_change_nb);
 364 
 365         for (i = 0; i < DCLK_MAX_CLKS; i++)
 366                 clk_hw_unregister(clk_table[i]);
 367 
 368         return 0;
 369 }
 370 
 371 static struct s3c24xx_dclk_drv_data dclk_variants[] = {
 372         [S3C2410] = {
 373                 .clkout0_parent_names = clkout0_s3c2410_p,
 374                 .clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2410_p),
 375                 .clkout1_parent_names = clkout1_s3c2410_p,
 376                 .clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2410_p),
 377                 .mux_parent_names = dclk_s3c2410_p,
 378                 .mux_num_parents = ARRAY_SIZE(dclk_s3c2410_p),
 379         },
 380         [S3C2412] = {
 381                 .clkout0_parent_names = clkout0_s3c2412_p,
 382                 .clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2412_p),
 383                 .clkout1_parent_names = clkout1_s3c2412_p,
 384                 .clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2412_p),
 385                 .mux_parent_names = dclk_s3c2410_p,
 386                 .mux_num_parents = ARRAY_SIZE(dclk_s3c2410_p),
 387         },
 388         [S3C2440] = {
 389                 .clkout0_parent_names = clkout0_s3c2440_p,
 390                 .clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2440_p),
 391                 .clkout1_parent_names = clkout1_s3c2440_p,
 392                 .clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2440_p),
 393                 .mux_parent_names = dclk_s3c2410_p,
 394                 .mux_num_parents = ARRAY_SIZE(dclk_s3c2410_p),
 395         },
 396         [S3C2443] = {
 397                 .clkout0_parent_names = clkout0_s3c2443_p,
 398                 .clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2443_p),
 399                 .clkout1_parent_names = clkout1_s3c2443_p,
 400                 .clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2443_p),
 401                 .mux_parent_names = dclk_s3c2443_p,
 402                 .mux_num_parents = ARRAY_SIZE(dclk_s3c2443_p),
 403         },
 404 };
 405 
 406 static const struct platform_device_id s3c24xx_dclk_driver_ids[] = {
 407         {
 408                 .name           = "s3c2410-dclk",
 409                 .driver_data    = (kernel_ulong_t)&dclk_variants[S3C2410],
 410         }, {
 411                 .name           = "s3c2412-dclk",
 412                 .driver_data    = (kernel_ulong_t)&dclk_variants[S3C2412],
 413         }, {
 414                 .name           = "s3c2440-dclk",
 415                 .driver_data    = (kernel_ulong_t)&dclk_variants[S3C2440],
 416         }, {
 417                 .name           = "s3c2443-dclk",
 418                 .driver_data    = (kernel_ulong_t)&dclk_variants[S3C2443],
 419         },
 420         { }
 421 };
 422 
 423 MODULE_DEVICE_TABLE(platform, s3c24xx_dclk_driver_ids);
 424 
 425 static struct platform_driver s3c24xx_dclk_driver = {
 426         .driver = {
 427                 .name                   = "s3c24xx-dclk",
 428                 .pm                     = &s3c24xx_dclk_pm_ops,
 429                 .suppress_bind_attrs    = true,
 430         },
 431         .probe = s3c24xx_dclk_probe,
 432         .remove = s3c24xx_dclk_remove,
 433         .id_table = s3c24xx_dclk_driver_ids,
 434 };
 435 module_platform_driver(s3c24xx_dclk_driver);
 436 
 437 MODULE_LICENSE("GPL v2");
 438 MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
 439 MODULE_DESCRIPTION("Driver for the S3C24XX external clock outputs");

/* [<][>][^][v][top][bottom][index][help] */