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