1 /* 2 * Intel Atom platform clocks driver for BayTrail and CherryTrail SoCs 3 * 4 * Copyright (C) 2016, Intel Corporation 5 * Author: Irina Tirdea <irina.tirdea@intel.com> 6 * 7 * This program is free software; you can redistribute it and/or modify it 8 * under the terms and conditions of the GNU General Public License, 9 * version 2, as published by the Free Software Foundation. 10 * 11 * This program is distributed in the hope it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 * more details. 15 */ 16 17 #include <linux/clk-provider.h> 18 #include <linux/clkdev.h> 19 #include <linux/err.h> 20 #include <linux/io.h> 21 #include <linux/platform_data/x86/clk-pmc-atom.h> 22 #include <linux/platform_device.h> 23 #include <linux/slab.h> 24 25 #define PLT_CLK_NAME_BASE "pmc_plt_clk" 26 27 #define PMC_CLK_CTL_OFFSET 0x60 28 #define PMC_CLK_CTL_SIZE 4 29 #define PMC_CLK_NUM 6 30 #define PMC_CLK_CTL_GATED_ON_D3 0x0 31 #define PMC_CLK_CTL_FORCE_ON 0x1 32 #define PMC_CLK_CTL_FORCE_OFF 0x2 33 #define PMC_CLK_CTL_RESERVED 0x3 34 #define PMC_MASK_CLK_CTL GENMASK(1, 0) 35 #define PMC_MASK_CLK_FREQ BIT(2) 36 #define PMC_CLK_FREQ_XTAL (0 << 2) /* 25 MHz */ 37 #define PMC_CLK_FREQ_PLL (1 << 2) /* 19.2 MHz */ 38 39 struct clk_plt_fixed { 40 struct clk_hw *clk; 41 struct clk_lookup *lookup; 42 }; 43 44 struct clk_plt { 45 struct clk_hw hw; 46 void __iomem *reg; 47 struct clk_lookup *lookup; 48 /* protect access to PMC registers */ 49 spinlock_t lock; 50 }; 51 52 #define to_clk_plt(_hw) container_of(_hw, struct clk_plt, hw) 53 54 struct clk_plt_data { 55 struct clk_plt_fixed **parents; 56 u8 nparents; 57 struct clk_plt *clks[PMC_CLK_NUM]; 58 struct clk_lookup *mclk_lookup; 59 struct clk_lookup *ether_clk_lookup; 60 }; 61 62 /* Return an index in parent table */ 63 static inline int plt_reg_to_parent(int reg) 64 { 65 switch (reg & PMC_MASK_CLK_FREQ) { 66 default: 67 case PMC_CLK_FREQ_XTAL: 68 return 0; 69 case PMC_CLK_FREQ_PLL: 70 return 1; 71 } 72 } 73 74 /* Return clk index of parent */ 75 static inline int plt_parent_to_reg(int index) 76 { 77 switch (index) { 78 default: 79 case 0: 80 return PMC_CLK_FREQ_XTAL; 81 case 1: 82 return PMC_CLK_FREQ_PLL; 83 } 84 } 85 86 /* Abstract status in simpler enabled/disabled value */ 87 static inline int plt_reg_to_enabled(int reg) 88 { 89 switch (reg & PMC_MASK_CLK_CTL) { 90 case PMC_CLK_CTL_GATED_ON_D3: 91 case PMC_CLK_CTL_FORCE_ON: 92 return 1; /* enabled */ 93 case PMC_CLK_CTL_FORCE_OFF: 94 case PMC_CLK_CTL_RESERVED: 95 default: 96 return 0; /* disabled */ 97 } 98 } 99 100 static void plt_clk_reg_update(struct clk_plt *clk, u32 mask, u32 val) 101 { 102 u32 tmp; 103 unsigned long flags; 104 105 spin_lock_irqsave(&clk->lock, flags); 106 107 tmp = readl(clk->reg); 108 tmp = (tmp & ~mask) | (val & mask); 109 writel(tmp, clk->reg); 110 111 spin_unlock_irqrestore(&clk->lock, flags); 112 } 113 114 static int plt_clk_set_parent(struct clk_hw *hw, u8 index) 115 { 116 struct clk_plt *clk = to_clk_plt(hw); 117 118 plt_clk_reg_update(clk, PMC_MASK_CLK_FREQ, plt_parent_to_reg(index)); 119 120 return 0; 121 } 122 123 static u8 plt_clk_get_parent(struct clk_hw *hw) 124 { 125 struct clk_plt *clk = to_clk_plt(hw); 126 u32 value; 127 128 value = readl(clk->reg); 129 130 return plt_reg_to_parent(value); 131 } 132 133 static int plt_clk_enable(struct clk_hw *hw) 134 { 135 struct clk_plt *clk = to_clk_plt(hw); 136 137 plt_clk_reg_update(clk, PMC_MASK_CLK_CTL, PMC_CLK_CTL_FORCE_ON); 138 139 return 0; 140 } 141 142 static void plt_clk_disable(struct clk_hw *hw) 143 { 144 struct clk_plt *clk = to_clk_plt(hw); 145 146 plt_clk_reg_update(clk, PMC_MASK_CLK_CTL, PMC_CLK_CTL_FORCE_OFF); 147 } 148 149 static int plt_clk_is_enabled(struct clk_hw *hw) 150 { 151 struct clk_plt *clk = to_clk_plt(hw); 152 u32 value; 153 154 value = readl(clk->reg); 155 156 return plt_reg_to_enabled(value); 157 } 158 159 static const struct clk_ops plt_clk_ops = { 160 .enable = plt_clk_enable, 161 .disable = plt_clk_disable, 162 .is_enabled = plt_clk_is_enabled, 163 .get_parent = plt_clk_get_parent, 164 .set_parent = plt_clk_set_parent, 165 .determine_rate = __clk_mux_determine_rate, 166 }; 167 168 static struct clk_plt *plt_clk_register(struct platform_device *pdev, int id, 169 const struct pmc_clk_data *pmc_data, 170 const char **parent_names, 171 int num_parents) 172 { 173 struct clk_plt *pclk; 174 struct clk_init_data init; 175 int ret; 176 177 pclk = devm_kzalloc(&pdev->dev, sizeof(*pclk), GFP_KERNEL); 178 if (!pclk) 179 return ERR_PTR(-ENOMEM); 180 181 init.name = kasprintf(GFP_KERNEL, "%s_%d", PLT_CLK_NAME_BASE, id); 182 init.ops = &plt_clk_ops; 183 init.flags = 0; 184 init.parent_names = parent_names; 185 init.num_parents = num_parents; 186 187 pclk->hw.init = &init; 188 pclk->reg = pmc_data->base + PMC_CLK_CTL_OFFSET + id * PMC_CLK_CTL_SIZE; 189 spin_lock_init(&pclk->lock); 190 191 /* 192 * On some systems, the pmc_plt_clocks already enabled by the 193 * firmware are being marked as critical to avoid them being 194 * gated by the clock framework. 195 */ 196 if (pmc_data->critical && plt_clk_is_enabled(&pclk->hw)) 197 init.flags |= CLK_IS_CRITICAL; 198 199 ret = devm_clk_hw_register(&pdev->dev, &pclk->hw); 200 if (ret) { 201 pclk = ERR_PTR(ret); 202 goto err_free_init; 203 } 204 205 pclk->lookup = clkdev_hw_create(&pclk->hw, init.name, NULL); 206 if (!pclk->lookup) { 207 pclk = ERR_PTR(-ENOMEM); 208 goto err_free_init; 209 } 210 211 err_free_init: 212 kfree(init.name); 213 return pclk; 214 } 215 216 static void plt_clk_unregister(struct clk_plt *pclk) 217 { 218 clkdev_drop(pclk->lookup); 219 } 220 221 static struct clk_plt_fixed *plt_clk_register_fixed_rate(struct platform_device *pdev, 222 const char *name, 223 const char *parent_name, 224 unsigned long fixed_rate) 225 { 226 struct clk_plt_fixed *pclk; 227 228 pclk = devm_kzalloc(&pdev->dev, sizeof(*pclk), GFP_KERNEL); 229 if (!pclk) 230 return ERR_PTR(-ENOMEM); 231 232 pclk->clk = clk_hw_register_fixed_rate(&pdev->dev, name, parent_name, 233 0, fixed_rate); 234 if (IS_ERR(pclk->clk)) 235 return ERR_CAST(pclk->clk); 236 237 pclk->lookup = clkdev_hw_create(pclk->clk, name, NULL); 238 if (!pclk->lookup) { 239 clk_hw_unregister_fixed_rate(pclk->clk); 240 return ERR_PTR(-ENOMEM); 241 } 242 243 return pclk; 244 } 245 246 static void plt_clk_unregister_fixed_rate(struct clk_plt_fixed *pclk) 247 { 248 clkdev_drop(pclk->lookup); 249 clk_hw_unregister_fixed_rate(pclk->clk); 250 } 251 252 static void plt_clk_unregister_fixed_rate_loop(struct clk_plt_data *data, 253 unsigned int i) 254 { 255 while (i--) 256 plt_clk_unregister_fixed_rate(data->parents[i]); 257 } 258 259 static void plt_clk_free_parent_names_loop(const char **parent_names, 260 unsigned int i) 261 { 262 while (i--) 263 kfree_const(parent_names[i]); 264 kfree(parent_names); 265 } 266 267 static void plt_clk_unregister_loop(struct clk_plt_data *data, 268 unsigned int i) 269 { 270 while (i--) 271 plt_clk_unregister(data->clks[i]); 272 } 273 274 static const char **plt_clk_register_parents(struct platform_device *pdev, 275 struct clk_plt_data *data, 276 const struct pmc_clk *clks) 277 { 278 const char **parent_names; 279 unsigned int i; 280 int err; 281 int nparents = 0; 282 283 data->nparents = 0; 284 while (clks[nparents].name) 285 nparents++; 286 287 data->parents = devm_kcalloc(&pdev->dev, nparents, 288 sizeof(*data->parents), GFP_KERNEL); 289 if (!data->parents) 290 return ERR_PTR(-ENOMEM); 291 292 parent_names = kcalloc(nparents, sizeof(*parent_names), 293 GFP_KERNEL); 294 if (!parent_names) 295 return ERR_PTR(-ENOMEM); 296 297 for (i = 0; i < nparents; i++) { 298 data->parents[i] = 299 plt_clk_register_fixed_rate(pdev, clks[i].name, 300 clks[i].parent_name, 301 clks[i].freq); 302 if (IS_ERR(data->parents[i])) { 303 err = PTR_ERR(data->parents[i]); 304 goto err_unreg; 305 } 306 parent_names[i] = kstrdup_const(clks[i].name, GFP_KERNEL); 307 } 308 309 data->nparents = nparents; 310 return parent_names; 311 312 err_unreg: 313 plt_clk_unregister_fixed_rate_loop(data, i); 314 plt_clk_free_parent_names_loop(parent_names, i); 315 return ERR_PTR(err); 316 } 317 318 static void plt_clk_unregister_parents(struct clk_plt_data *data) 319 { 320 plt_clk_unregister_fixed_rate_loop(data, data->nparents); 321 } 322 323 static int plt_clk_probe(struct platform_device *pdev) 324 { 325 const struct pmc_clk_data *pmc_data; 326 const char **parent_names; 327 struct clk_plt_data *data; 328 unsigned int i; 329 int err; 330 331 pmc_data = dev_get_platdata(&pdev->dev); 332 if (!pmc_data || !pmc_data->clks) 333 return -EINVAL; 334 335 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 336 if (!data) 337 return -ENOMEM; 338 339 parent_names = plt_clk_register_parents(pdev, data, pmc_data->clks); 340 if (IS_ERR(parent_names)) 341 return PTR_ERR(parent_names); 342 343 for (i = 0; i < PMC_CLK_NUM; i++) { 344 data->clks[i] = plt_clk_register(pdev, i, pmc_data, 345 parent_names, data->nparents); 346 if (IS_ERR(data->clks[i])) { 347 err = PTR_ERR(data->clks[i]); 348 goto err_unreg_clk_plt; 349 } 350 } 351 data->mclk_lookup = clkdev_hw_create(&data->clks[3]->hw, "mclk", NULL); 352 if (!data->mclk_lookup) { 353 err = -ENOMEM; 354 goto err_unreg_clk_plt; 355 } 356 357 data->ether_clk_lookup = clkdev_hw_create(&data->clks[4]->hw, 358 "ether_clk", NULL); 359 if (!data->ether_clk_lookup) { 360 err = -ENOMEM; 361 goto err_drop_mclk; 362 } 363 364 plt_clk_free_parent_names_loop(parent_names, data->nparents); 365 366 platform_set_drvdata(pdev, data); 367 return 0; 368 369 err_drop_mclk: 370 clkdev_drop(data->mclk_lookup); 371 err_unreg_clk_plt: 372 plt_clk_unregister_loop(data, i); 373 plt_clk_unregister_parents(data); 374 plt_clk_free_parent_names_loop(parent_names, data->nparents); 375 return err; 376 } 377 378 static int plt_clk_remove(struct platform_device *pdev) 379 { 380 struct clk_plt_data *data; 381 382 data = platform_get_drvdata(pdev); 383 384 clkdev_drop(data->ether_clk_lookup); 385 clkdev_drop(data->mclk_lookup); 386 plt_clk_unregister_loop(data, PMC_CLK_NUM); 387 plt_clk_unregister_parents(data); 388 return 0; 389 } 390 391 static struct platform_driver plt_clk_driver = { 392 .driver = { 393 .name = "clk-pmc-atom", 394 }, 395 .probe = plt_clk_probe, 396 .remove = plt_clk_remove, 397 }; 398 builtin_platform_driver(plt_clk_driver); 399