Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Copyright (c) 2013 Heiko Stuebner <heiko@sntech.de>
0004  *
0005  * Common Clock Framework support for s3c24xx external clock output.
0006  */
0007 
0008 #include <linux/clkdev.h>
0009 #include <linux/slab.h>
0010 #include <linux/clk.h>
0011 #include <linux/clk-provider.h>
0012 #include <linux/io.h>
0013 #include <linux/platform_device.h>
0014 #include <linux/platform_data/clk-s3c2410.h>
0015 #include <linux/module.h>
0016 #include "clk.h"
0017 
0018 #define MUX_DCLK0   0
0019 #define MUX_DCLK1   1
0020 #define DIV_DCLK0   2
0021 #define DIV_DCLK1   3
0022 #define GATE_DCLK0  4
0023 #define GATE_DCLK1  5
0024 #define MUX_CLKOUT0 6
0025 #define MUX_CLKOUT1 7
0026 #define DCLK_MAX_CLKS   (MUX_CLKOUT1 + 1)
0027 
0028 enum supported_socs {
0029     S3C2410,
0030     S3C2412,
0031     S3C2440,
0032     S3C2443,
0033 };
0034 
0035 struct s3c24xx_dclk_drv_data {
0036     const char **clkout0_parent_names;
0037     int clkout0_num_parents;
0038     const char **clkout1_parent_names;
0039     int clkout1_num_parents;
0040     const char **mux_parent_names;
0041     int mux_num_parents;
0042 };
0043 
0044 /*
0045  * Clock for output-parent selection in misccr
0046  */
0047 
0048 struct s3c24xx_clkout {
0049     struct clk_hw       hw;
0050     u32         mask;
0051     u8          shift;
0052     unsigned int (*modify_misccr)(unsigned int clr, unsigned int chg);
0053 };
0054 
0055 #define to_s3c24xx_clkout(_hw) container_of(_hw, struct s3c24xx_clkout, hw)
0056 
0057 static u8 s3c24xx_clkout_get_parent(struct clk_hw *hw)
0058 {
0059     struct s3c24xx_clkout *clkout = to_s3c24xx_clkout(hw);
0060     int num_parents = clk_hw_get_num_parents(hw);
0061     u32 val;
0062 
0063     val = clkout->modify_misccr(0, 0) >> clkout->shift;
0064     val >>= clkout->shift;
0065     val &= clkout->mask;
0066 
0067     if (val >= num_parents)
0068         return -EINVAL;
0069 
0070     return val;
0071 }
0072 
0073 static int s3c24xx_clkout_set_parent(struct clk_hw *hw, u8 index)
0074 {
0075     struct s3c24xx_clkout *clkout = to_s3c24xx_clkout(hw);
0076 
0077     clkout->modify_misccr((clkout->mask << clkout->shift),
0078                   (index << clkout->shift));
0079 
0080     return 0;
0081 }
0082 
0083 static const struct clk_ops s3c24xx_clkout_ops = {
0084     .get_parent = s3c24xx_clkout_get_parent,
0085     .set_parent = s3c24xx_clkout_set_parent,
0086     .determine_rate = __clk_mux_determine_rate,
0087 };
0088 
0089 static struct clk_hw *s3c24xx_register_clkout(struct device *dev,
0090         const char *name, const char **parent_names, u8 num_parents,
0091         u8 shift, u32 mask)
0092 {
0093     struct s3c2410_clk_platform_data *pdata = dev_get_platdata(dev);
0094     struct s3c24xx_clkout *clkout;
0095     struct clk_init_data init;
0096     int ret;
0097 
0098     if (!pdata)
0099         return ERR_PTR(-EINVAL);
0100 
0101     /* allocate the clkout */
0102     clkout = kzalloc(sizeof(*clkout), GFP_KERNEL);
0103     if (!clkout)
0104         return ERR_PTR(-ENOMEM);
0105 
0106     init.name = name;
0107     init.ops = &s3c24xx_clkout_ops;
0108     init.flags = 0;
0109     init.parent_names = parent_names;
0110     init.num_parents = num_parents;
0111 
0112     clkout->shift = shift;
0113     clkout->mask = mask;
0114     clkout->hw.init = &init;
0115     clkout->modify_misccr = pdata->modify_misccr;
0116 
0117     ret = clk_hw_register(dev, &clkout->hw);
0118     if (ret)
0119         return ERR_PTR(ret);
0120 
0121     return &clkout->hw;
0122 }
0123 
0124 /*
0125  * dclk and clkout init
0126  */
0127 
0128 struct s3c24xx_dclk {
0129     struct device *dev;
0130     void __iomem *base;
0131     struct notifier_block dclk0_div_change_nb;
0132     struct notifier_block dclk1_div_change_nb;
0133     spinlock_t dclk_lock;
0134     unsigned long reg_save;
0135     /* clk_data must be the last entry in the structure */
0136     struct clk_hw_onecell_data clk_data;
0137 };
0138 
0139 #define to_s3c24xx_dclk0(x) \
0140         container_of(x, struct s3c24xx_dclk, dclk0_div_change_nb)
0141 
0142 #define to_s3c24xx_dclk1(x) \
0143         container_of(x, struct s3c24xx_dclk, dclk1_div_change_nb)
0144 
0145 static const char *dclk_s3c2410_p[] = { "pclk", "uclk" };
0146 static const char *clkout0_s3c2410_p[] = { "mpll", "upll", "fclk", "hclk", "pclk",
0147                  "gate_dclk0" };
0148 static const char *clkout1_s3c2410_p[] = { "mpll", "upll", "fclk", "hclk", "pclk",
0149                  "gate_dclk1" };
0150 
0151 static const char *clkout0_s3c2412_p[] = { "mpll", "upll", "rtc_clkout",
0152                  "hclk", "pclk", "gate_dclk0" };
0153 static const char *clkout1_s3c2412_p[] = { "xti", "upll", "fclk", "hclk", "pclk",
0154                  "gate_dclk1" };
0155 
0156 static const char *clkout0_s3c2440_p[] = { "xti", "upll", "fclk", "hclk", "pclk",
0157                  "gate_dclk0" };
0158 static const char *clkout1_s3c2440_p[] = { "mpll", "upll", "rtc_clkout",
0159                  "hclk", "pclk", "gate_dclk1" };
0160 
0161 static const char *dclk_s3c2443_p[] = { "pclk", "epll" };
0162 static const char *clkout0_s3c2443_p[] = { "xti", "epll", "armclk", "hclk", "pclk",
0163                  "gate_dclk0" };
0164 static const char *clkout1_s3c2443_p[] = { "dummy", "epll", "rtc_clkout",
0165                  "hclk", "pclk", "gate_dclk1" };
0166 
0167 #define DCLKCON_DCLK_DIV_MASK       0xf
0168 #define DCLKCON_DCLK0_DIV_SHIFT     4
0169 #define DCLKCON_DCLK0_CMP_SHIFT     8
0170 #define DCLKCON_DCLK1_DIV_SHIFT     20
0171 #define DCLKCON_DCLK1_CMP_SHIFT     24
0172 
0173 static void s3c24xx_dclk_update_cmp(struct s3c24xx_dclk *s3c24xx_dclk,
0174                     int div_shift, int cmp_shift)
0175 {
0176     unsigned long flags = 0;
0177     u32 dclk_con, div, cmp;
0178 
0179     spin_lock_irqsave(&s3c24xx_dclk->dclk_lock, flags);
0180 
0181     dclk_con = readl_relaxed(s3c24xx_dclk->base);
0182 
0183     div = ((dclk_con >> div_shift) & DCLKCON_DCLK_DIV_MASK) + 1;
0184     cmp = ((div + 1) / 2) - 1;
0185 
0186     dclk_con &= ~(DCLKCON_DCLK_DIV_MASK << cmp_shift);
0187     dclk_con |= (cmp << cmp_shift);
0188 
0189     writel_relaxed(dclk_con, s3c24xx_dclk->base);
0190 
0191     spin_unlock_irqrestore(&s3c24xx_dclk->dclk_lock, flags);
0192 }
0193 
0194 static int s3c24xx_dclk0_div_notify(struct notifier_block *nb,
0195                    unsigned long event, void *data)
0196 {
0197     struct s3c24xx_dclk *s3c24xx_dclk = to_s3c24xx_dclk0(nb);
0198 
0199     if (event == POST_RATE_CHANGE) {
0200         s3c24xx_dclk_update_cmp(s3c24xx_dclk,
0201             DCLKCON_DCLK0_DIV_SHIFT, DCLKCON_DCLK0_CMP_SHIFT);
0202     }
0203 
0204     return NOTIFY_DONE;
0205 }
0206 
0207 static int s3c24xx_dclk1_div_notify(struct notifier_block *nb,
0208                    unsigned long event, void *data)
0209 {
0210     struct s3c24xx_dclk *s3c24xx_dclk = to_s3c24xx_dclk1(nb);
0211 
0212     if (event == POST_RATE_CHANGE) {
0213         s3c24xx_dclk_update_cmp(s3c24xx_dclk,
0214             DCLKCON_DCLK1_DIV_SHIFT, DCLKCON_DCLK1_CMP_SHIFT);
0215     }
0216 
0217     return NOTIFY_DONE;
0218 }
0219 
0220 #ifdef CONFIG_PM_SLEEP
0221 static int s3c24xx_dclk_suspend(struct device *dev)
0222 {
0223     struct s3c24xx_dclk *s3c24xx_dclk = dev_get_drvdata(dev);
0224 
0225     s3c24xx_dclk->reg_save = readl_relaxed(s3c24xx_dclk->base);
0226     return 0;
0227 }
0228 
0229 static int s3c24xx_dclk_resume(struct device *dev)
0230 {
0231     struct s3c24xx_dclk *s3c24xx_dclk = dev_get_drvdata(dev);
0232 
0233     writel_relaxed(s3c24xx_dclk->reg_save, s3c24xx_dclk->base);
0234     return 0;
0235 }
0236 #endif
0237 
0238 static SIMPLE_DEV_PM_OPS(s3c24xx_dclk_pm_ops,
0239              s3c24xx_dclk_suspend, s3c24xx_dclk_resume);
0240 
0241 static int s3c24xx_dclk_probe(struct platform_device *pdev)
0242 {
0243     struct s3c24xx_dclk *s3c24xx_dclk;
0244     struct s3c24xx_dclk_drv_data *dclk_variant;
0245     struct clk_hw **clk_table;
0246     int ret, i;
0247 
0248     s3c24xx_dclk = devm_kzalloc(&pdev->dev,
0249                     struct_size(s3c24xx_dclk, clk_data.hws,
0250                         DCLK_MAX_CLKS),
0251                     GFP_KERNEL);
0252     if (!s3c24xx_dclk)
0253         return -ENOMEM;
0254 
0255     clk_table = s3c24xx_dclk->clk_data.hws;
0256 
0257     s3c24xx_dclk->dev = &pdev->dev;
0258     s3c24xx_dclk->clk_data.num = DCLK_MAX_CLKS;
0259     platform_set_drvdata(pdev, s3c24xx_dclk);
0260     spin_lock_init(&s3c24xx_dclk->dclk_lock);
0261 
0262     s3c24xx_dclk->base = devm_platform_ioremap_resource(pdev, 0);
0263     if (IS_ERR(s3c24xx_dclk->base))
0264         return PTR_ERR(s3c24xx_dclk->base);
0265 
0266     dclk_variant = (struct s3c24xx_dclk_drv_data *)
0267                 platform_get_device_id(pdev)->driver_data;
0268 
0269 
0270     clk_table[MUX_DCLK0] = clk_hw_register_mux(&pdev->dev, "mux_dclk0",
0271                 dclk_variant->mux_parent_names,
0272                 dclk_variant->mux_num_parents, 0,
0273                 s3c24xx_dclk->base, 1, 1, 0,
0274                 &s3c24xx_dclk->dclk_lock);
0275     clk_table[MUX_DCLK1] = clk_hw_register_mux(&pdev->dev, "mux_dclk1",
0276                 dclk_variant->mux_parent_names,
0277                 dclk_variant->mux_num_parents, 0,
0278                 s3c24xx_dclk->base, 17, 1, 0,
0279                 &s3c24xx_dclk->dclk_lock);
0280 
0281     clk_table[DIV_DCLK0] = clk_hw_register_divider(&pdev->dev, "div_dclk0",
0282                 "mux_dclk0", 0, s3c24xx_dclk->base,
0283                 4, 4, 0, &s3c24xx_dclk->dclk_lock);
0284     clk_table[DIV_DCLK1] = clk_hw_register_divider(&pdev->dev, "div_dclk1",
0285                 "mux_dclk1", 0, s3c24xx_dclk->base,
0286                 20, 4, 0, &s3c24xx_dclk->dclk_lock);
0287 
0288     clk_table[GATE_DCLK0] = clk_hw_register_gate(&pdev->dev, "gate_dclk0",
0289                 "div_dclk0", CLK_SET_RATE_PARENT,
0290                 s3c24xx_dclk->base, 0, 0,
0291                 &s3c24xx_dclk->dclk_lock);
0292     clk_table[GATE_DCLK1] = clk_hw_register_gate(&pdev->dev, "gate_dclk1",
0293                 "div_dclk1", CLK_SET_RATE_PARENT,
0294                 s3c24xx_dclk->base, 16, 0,
0295                 &s3c24xx_dclk->dclk_lock);
0296 
0297     clk_table[MUX_CLKOUT0] = s3c24xx_register_clkout(&pdev->dev,
0298                 "clkout0", dclk_variant->clkout0_parent_names,
0299                 dclk_variant->clkout0_num_parents, 4, 7);
0300     clk_table[MUX_CLKOUT1] = s3c24xx_register_clkout(&pdev->dev,
0301                 "clkout1", dclk_variant->clkout1_parent_names,
0302                 dclk_variant->clkout1_num_parents, 8, 7);
0303 
0304     for (i = 0; i < DCLK_MAX_CLKS; i++)
0305         if (IS_ERR(clk_table[i])) {
0306             dev_err(&pdev->dev, "clock %d failed to register\n", i);
0307             ret = PTR_ERR(clk_table[i]);
0308             goto err_clk_register;
0309         }
0310 
0311     ret = clk_hw_register_clkdev(clk_table[MUX_DCLK0], "dclk0", NULL);
0312     if (!ret)
0313         ret = clk_hw_register_clkdev(clk_table[MUX_DCLK1], "dclk1",
0314                          NULL);
0315     if (!ret)
0316         ret = clk_hw_register_clkdev(clk_table[MUX_CLKOUT0],
0317                          "clkout0", NULL);
0318     if (!ret)
0319         ret = clk_hw_register_clkdev(clk_table[MUX_CLKOUT1],
0320                          "clkout1", NULL);
0321     if (ret) {
0322         dev_err(&pdev->dev, "failed to register aliases, %d\n", ret);
0323         goto err_clk_register;
0324     }
0325 
0326     s3c24xx_dclk->dclk0_div_change_nb.notifier_call =
0327                         s3c24xx_dclk0_div_notify;
0328 
0329     s3c24xx_dclk->dclk1_div_change_nb.notifier_call =
0330                         s3c24xx_dclk1_div_notify;
0331 
0332     ret = clk_notifier_register(clk_table[DIV_DCLK0]->clk,
0333                     &s3c24xx_dclk->dclk0_div_change_nb);
0334     if (ret)
0335         goto err_clk_register;
0336 
0337     ret = clk_notifier_register(clk_table[DIV_DCLK1]->clk,
0338                     &s3c24xx_dclk->dclk1_div_change_nb);
0339     if (ret)
0340         goto err_dclk_notify;
0341 
0342     return 0;
0343 
0344 err_dclk_notify:
0345     clk_notifier_unregister(clk_table[DIV_DCLK0]->clk,
0346                 &s3c24xx_dclk->dclk0_div_change_nb);
0347 err_clk_register:
0348     for (i = 0; i < DCLK_MAX_CLKS; i++)
0349         if (clk_table[i] && !IS_ERR(clk_table[i]))
0350             clk_hw_unregister(clk_table[i]);
0351 
0352     return ret;
0353 }
0354 
0355 static int s3c24xx_dclk_remove(struct platform_device *pdev)
0356 {
0357     struct s3c24xx_dclk *s3c24xx_dclk = platform_get_drvdata(pdev);
0358     struct clk_hw **clk_table = s3c24xx_dclk->clk_data.hws;
0359     int i;
0360 
0361     clk_notifier_unregister(clk_table[DIV_DCLK1]->clk,
0362                 &s3c24xx_dclk->dclk1_div_change_nb);
0363     clk_notifier_unregister(clk_table[DIV_DCLK0]->clk,
0364                 &s3c24xx_dclk->dclk0_div_change_nb);
0365 
0366     for (i = 0; i < DCLK_MAX_CLKS; i++)
0367         clk_hw_unregister(clk_table[i]);
0368 
0369     return 0;
0370 }
0371 
0372 static struct s3c24xx_dclk_drv_data dclk_variants[] = {
0373     [S3C2410] = {
0374         .clkout0_parent_names = clkout0_s3c2410_p,
0375         .clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2410_p),
0376         .clkout1_parent_names = clkout1_s3c2410_p,
0377         .clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2410_p),
0378         .mux_parent_names = dclk_s3c2410_p,
0379         .mux_num_parents = ARRAY_SIZE(dclk_s3c2410_p),
0380     },
0381     [S3C2412] = {
0382         .clkout0_parent_names = clkout0_s3c2412_p,
0383         .clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2412_p),
0384         .clkout1_parent_names = clkout1_s3c2412_p,
0385         .clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2412_p),
0386         .mux_parent_names = dclk_s3c2410_p,
0387         .mux_num_parents = ARRAY_SIZE(dclk_s3c2410_p),
0388     },
0389     [S3C2440] = {
0390         .clkout0_parent_names = clkout0_s3c2440_p,
0391         .clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2440_p),
0392         .clkout1_parent_names = clkout1_s3c2440_p,
0393         .clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2440_p),
0394         .mux_parent_names = dclk_s3c2410_p,
0395         .mux_num_parents = ARRAY_SIZE(dclk_s3c2410_p),
0396     },
0397     [S3C2443] = {
0398         .clkout0_parent_names = clkout0_s3c2443_p,
0399         .clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2443_p),
0400         .clkout1_parent_names = clkout1_s3c2443_p,
0401         .clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2443_p),
0402         .mux_parent_names = dclk_s3c2443_p,
0403         .mux_num_parents = ARRAY_SIZE(dclk_s3c2443_p),
0404     },
0405 };
0406 
0407 static const struct platform_device_id s3c24xx_dclk_driver_ids[] = {
0408     {
0409         .name       = "s3c2410-dclk",
0410         .driver_data    = (kernel_ulong_t)&dclk_variants[S3C2410],
0411     }, {
0412         .name       = "s3c2412-dclk",
0413         .driver_data    = (kernel_ulong_t)&dclk_variants[S3C2412],
0414     }, {
0415         .name       = "s3c2440-dclk",
0416         .driver_data    = (kernel_ulong_t)&dclk_variants[S3C2440],
0417     }, {
0418         .name       = "s3c2443-dclk",
0419         .driver_data    = (kernel_ulong_t)&dclk_variants[S3C2443],
0420     },
0421     { }
0422 };
0423 
0424 MODULE_DEVICE_TABLE(platform, s3c24xx_dclk_driver_ids);
0425 
0426 static struct platform_driver s3c24xx_dclk_driver = {
0427     .driver = {
0428         .name           = "s3c24xx-dclk",
0429         .pm         = &s3c24xx_dclk_pm_ops,
0430         .suppress_bind_attrs    = true,
0431     },
0432     .probe = s3c24xx_dclk_probe,
0433     .remove = s3c24xx_dclk_remove,
0434     .id_table = s3c24xx_dclk_driver_ids,
0435 };
0436 module_platform_driver(s3c24xx_dclk_driver);
0437 
0438 MODULE_LICENSE("GPL v2");
0439 MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
0440 MODULE_DESCRIPTION("Driver for the S3C24XX external clock outputs");