Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

clk: x86: Add Atom PMC platform clocks

The BayTrail and CherryTrail platforms provide platform clocks
through their Power Management Controller (PMC).

The SoC supports up to 6 clocks (PMC_PLT_CLK[0..5]) with a
frequency of either 19.2 MHz (PLL) or 25 MHz (XTAL) for BayTrail
and a frequency of 19.2 MHz (XTAL) for CherryTrail. These clocks
are available for general system use, where appropriate, and each
have Control & Frequency register fields associated with them.

Port from legacy by Pierre Bossart, integration in clock framework
by Irina Tirdea

Signed-off-by: Irina Tirdea <irina.tirdea@intel.com>
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>

authored by

Irina Tirdea and committed by
Stephen Boyd
1141d9d0 f35b6542

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