0001
0002
0003
0004
0005
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
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
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
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
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");