630 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (c) 2020, The Linux Foundation. All rights reserved.4 */5 6/*7 * Each of the CPU clusters (Power and Perf) on msm8996 are8 * clocked via 2 PLLs, a primary and alternate. There are also9 * 2 Mux'es, a primary and secondary all connected together10 * as shown below11 *12 * +-------+13 * XO | |14 * +------------------>0 |15 * SYS_APCS_AUX | |16 * +------------------>3 |17 * | |18 * PLL/2 | SMUX +----+19 * +------->1 | |20 * | | | |21 * | +-------+ | +-------+22 * | +---->0 |23 * | | |24 * +---------------+ | +----------->1 | CPU clk25 * |Primary PLL +----+ PLL_EARLY | | +------>26 * | +------+-----------+ +------>2 PMUX |27 * +---------------+ | | | |28 * | +------+ | +-->3 |29 * +--^+ ACD +-----+ | +-------+30 * +---------------+ +------+ |31 * |Alt PLL | |32 * | +---------------------------+33 * +---------------+ PLL_EARLY34 *35 * The primary PLL is what drives the CPU clk, except for times36 * when we are reprogramming the PLL itself (for rate changes) when37 * we temporarily switch to an alternate PLL.38 *39 * The primary PLL operates on a single VCO range, between 600MHz40 * and 3GHz. However the CPUs do support OPPs with frequencies41 * between 300MHz and 600MHz. In order to support running the CPUs42 * at those frequencies we end up having to lock the PLL at twice43 * the rate and drive the CPU clk via the PLL/2 output and SMUX.44 *45 * So for frequencies above 600MHz we follow the following path46 * Primary PLL --> PLL_EARLY --> PMUX(1) --> CPU clk47 * and for frequencies between 300MHz and 600MHz we follow48 * Primary PLL --> PLL/2 --> SMUX(1) --> PMUX(0) --> CPU clk49 *50 * ACD stands for Adaptive Clock Distribution and is used to51 * detect voltage droops.52 */53 54#include <linux/bitfield.h>55#include <linux/clk.h>56#include <linux/clk-provider.h>57#include <linux/io.h>58#include <linux/module.h>59#include <linux/platform_device.h>60#include <linux/regmap.h>61#include <soc/qcom/kryo-l2-accessors.h>62 63#include <asm/cputype.h>64 65#include "clk-alpha-pll.h"66#include "clk-regmap.h"67#include "clk-regmap-mux.h"68 69enum _pmux_input {70 SMUX_INDEX = 0,71 PLL_INDEX,72 ACD_INDEX,73 ALT_INDEX,74 NUM_OF_PMUX_INPUTS75};76 77#define DIV_2_THRESHOLD 60000000078#define PWRCL_REG_OFFSET 0x079#define PERFCL_REG_OFFSET 0x8000080#define MUX_OFFSET 0x4081#define CLK_CTL_OFFSET 0x4482#define CLK_CTL_AUTO_CLK_SEL BIT(8)83#define ALT_PLL_OFFSET 0x10084#define SSSCTL_OFFSET 0x16085#define PSCTL_OFFSET 0x16486 87#define PMUX_MASK 0x388#define MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK GENMASK(5, 4)89#define MUX_AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL \90 FIELD_PREP(MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK, 0x03)91 92static const u8 prim_pll_regs[PLL_OFF_MAX_REGS] = {93 [PLL_OFF_L_VAL] = 0x04,94 [PLL_OFF_ALPHA_VAL] = 0x08,95 [PLL_OFF_USER_CTL] = 0x10,96 [PLL_OFF_CONFIG_CTL] = 0x18,97 [PLL_OFF_CONFIG_CTL_U] = 0x1c,98 [PLL_OFF_TEST_CTL] = 0x20,99 [PLL_OFF_TEST_CTL_U] = 0x24,100 [PLL_OFF_STATUS] = 0x28,101};102 103static const u8 alt_pll_regs[PLL_OFF_MAX_REGS] = {104 [PLL_OFF_L_VAL] = 0x04,105 [PLL_OFF_ALPHA_VAL] = 0x08,106 [PLL_OFF_USER_CTL] = 0x10,107 [PLL_OFF_CONFIG_CTL] = 0x18,108 [PLL_OFF_TEST_CTL] = 0x20,109 [PLL_OFF_STATUS] = 0x28,110};111 112/* PLLs */113 114static const struct alpha_pll_config hfpll_config = {115 .l = 54,116 .config_ctl_val = 0x200d4828,117 .config_ctl_hi_val = 0x006,118 .test_ctl_val = 0x1c000000,119 .test_ctl_hi_val = 0x00004000,120 .pre_div_mask = BIT(12),121 .post_div_mask = 0x3 << 8,122 .post_div_val = 0x1 << 8,123 .main_output_mask = BIT(0),124 .early_output_mask = BIT(3),125};126 127static const struct clk_parent_data pll_parent[] = {128 { .fw_name = "xo" },129};130 131static struct clk_alpha_pll pwrcl_pll = {132 .offset = PWRCL_REG_OFFSET,133 .regs = prim_pll_regs,134 .flags = SUPPORTS_DYNAMIC_UPDATE | SUPPORTS_FSM_MODE,135 .clkr.hw.init = &(struct clk_init_data){136 .name = "pwrcl_pll",137 .parent_data = pll_parent,138 .num_parents = ARRAY_SIZE(pll_parent),139 .ops = &clk_alpha_pll_hwfsm_ops,140 },141};142 143static struct clk_alpha_pll perfcl_pll = {144 .offset = PERFCL_REG_OFFSET,145 .regs = prim_pll_regs,146 .flags = SUPPORTS_DYNAMIC_UPDATE | SUPPORTS_FSM_MODE,147 .clkr.hw.init = &(struct clk_init_data){148 .name = "perfcl_pll",149 .parent_data = pll_parent,150 .num_parents = ARRAY_SIZE(pll_parent),151 .ops = &clk_alpha_pll_hwfsm_ops,152 },153};154 155static struct clk_fixed_factor pwrcl_pll_postdiv = {156 .mult = 1,157 .div = 2,158 .hw.init = &(struct clk_init_data){159 .name = "pwrcl_pll_postdiv",160 .parent_data = &(const struct clk_parent_data){161 .hw = &pwrcl_pll.clkr.hw162 },163 .num_parents = 1,164 .ops = &clk_fixed_factor_ops,165 .flags = CLK_SET_RATE_PARENT,166 },167};168 169static struct clk_fixed_factor perfcl_pll_postdiv = {170 .mult = 1,171 .div = 2,172 .hw.init = &(struct clk_init_data){173 .name = "perfcl_pll_postdiv",174 .parent_data = &(const struct clk_parent_data){175 .hw = &perfcl_pll.clkr.hw176 },177 .num_parents = 1,178 .ops = &clk_fixed_factor_ops,179 .flags = CLK_SET_RATE_PARENT,180 },181};182 183static struct clk_fixed_factor perfcl_pll_acd = {184 .mult = 1,185 .div = 1,186 .hw.init = &(struct clk_init_data){187 .name = "perfcl_pll_acd",188 .parent_data = &(const struct clk_parent_data){189 .hw = &perfcl_pll.clkr.hw190 },191 .num_parents = 1,192 .ops = &clk_fixed_factor_ops,193 .flags = CLK_SET_RATE_PARENT,194 },195};196 197static struct clk_fixed_factor pwrcl_pll_acd = {198 .mult = 1,199 .div = 1,200 .hw.init = &(struct clk_init_data){201 .name = "pwrcl_pll_acd",202 .parent_data = &(const struct clk_parent_data){203 .hw = &pwrcl_pll.clkr.hw204 },205 .num_parents = 1,206 .ops = &clk_fixed_factor_ops,207 .flags = CLK_SET_RATE_PARENT,208 },209};210 211static const struct pll_vco alt_pll_vco_modes[] = {212 VCO(3, 250000000, 500000000),213 VCO(2, 500000000, 750000000),214 VCO(1, 750000000, 1000000000),215 VCO(0, 1000000000, 2150400000),216};217 218static const struct alpha_pll_config altpll_config = {219 .l = 16,220 .vco_val = 0x3 << 20,221 .vco_mask = 0x3 << 20,222 .config_ctl_val = 0x4001051b,223 .post_div_mask = 0x3 << 8,224 .post_div_val = 0x1 << 8,225 .main_output_mask = BIT(0),226 .early_output_mask = BIT(3),227};228 229static struct clk_alpha_pll pwrcl_alt_pll = {230 .offset = PWRCL_REG_OFFSET + ALT_PLL_OFFSET,231 .regs = alt_pll_regs,232 .vco_table = alt_pll_vco_modes,233 .num_vco = ARRAY_SIZE(alt_pll_vco_modes),234 .flags = SUPPORTS_OFFLINE_REQ | SUPPORTS_FSM_MODE,235 .clkr.hw.init = &(struct clk_init_data) {236 .name = "pwrcl_alt_pll",237 .parent_data = pll_parent,238 .num_parents = ARRAY_SIZE(pll_parent),239 .ops = &clk_alpha_pll_hwfsm_ops,240 },241};242 243static struct clk_alpha_pll perfcl_alt_pll = {244 .offset = PERFCL_REG_OFFSET + ALT_PLL_OFFSET,245 .regs = alt_pll_regs,246 .vco_table = alt_pll_vco_modes,247 .num_vco = ARRAY_SIZE(alt_pll_vco_modes),248 .flags = SUPPORTS_OFFLINE_REQ | SUPPORTS_FSM_MODE,249 .clkr.hw.init = &(struct clk_init_data) {250 .name = "perfcl_alt_pll",251 .parent_data = pll_parent,252 .num_parents = ARRAY_SIZE(pll_parent),253 .ops = &clk_alpha_pll_hwfsm_ops,254 },255};256 257struct clk_cpu_8996_pmux {258 u32 reg;259 struct notifier_block nb;260 struct clk_regmap clkr;261};262 263static int cpu_clk_notifier_cb(struct notifier_block *nb, unsigned long event,264 void *data);265 266#define to_clk_cpu_8996_pmux_nb(_nb) \267 container_of(_nb, struct clk_cpu_8996_pmux, nb)268 269static inline struct clk_cpu_8996_pmux *to_clk_cpu_8996_pmux_hw(struct clk_hw *hw)270{271 return container_of(to_clk_regmap(hw), struct clk_cpu_8996_pmux, clkr);272}273 274static u8 clk_cpu_8996_pmux_get_parent(struct clk_hw *hw)275{276 struct clk_regmap *clkr = to_clk_regmap(hw);277 struct clk_cpu_8996_pmux *cpuclk = to_clk_cpu_8996_pmux_hw(hw);278 u32 val;279 280 regmap_read(clkr->regmap, cpuclk->reg, &val);281 282 return FIELD_GET(PMUX_MASK, val);283}284 285static int clk_cpu_8996_pmux_set_parent(struct clk_hw *hw, u8 index)286{287 struct clk_regmap *clkr = to_clk_regmap(hw);288 struct clk_cpu_8996_pmux *cpuclk = to_clk_cpu_8996_pmux_hw(hw);289 u32 val;290 291 val = FIELD_PREP(PMUX_MASK, index);292 293 return regmap_update_bits(clkr->regmap, cpuclk->reg, PMUX_MASK, val);294}295 296static int clk_cpu_8996_pmux_determine_rate(struct clk_hw *hw,297 struct clk_rate_request *req)298{299 struct clk_hw *parent;300 301 if (req->rate < (DIV_2_THRESHOLD / 2))302 return -EINVAL;303 304 if (req->rate < DIV_2_THRESHOLD)305 parent = clk_hw_get_parent_by_index(hw, SMUX_INDEX);306 else307 parent = clk_hw_get_parent_by_index(hw, ACD_INDEX);308 if (!parent)309 return -EINVAL;310 311 req->best_parent_rate = clk_hw_round_rate(parent, req->rate);312 req->best_parent_hw = parent;313 314 return 0;315}316 317static const struct clk_ops clk_cpu_8996_pmux_ops = {318 .set_parent = clk_cpu_8996_pmux_set_parent,319 .get_parent = clk_cpu_8996_pmux_get_parent,320 .determine_rate = clk_cpu_8996_pmux_determine_rate,321};322 323static const struct parent_map smux_parent_map[] = {324 { .cfg = 0, }, /* xo */325 { .cfg = 1, }, /* pll */326 { .cfg = 3, }, /* sys_apcs_aux */327};328 329static const struct clk_parent_data pwrcl_smux_parents[] = {330 { .fw_name = "xo" },331 { .hw = &pwrcl_pll_postdiv.hw },332 { .fw_name = "sys_apcs_aux" },333};334 335static const struct clk_parent_data perfcl_smux_parents[] = {336 { .fw_name = "xo" },337 { .hw = &perfcl_pll_postdiv.hw },338 { .fw_name = "sys_apcs_aux" },339};340 341static struct clk_regmap_mux pwrcl_smux = {342 .reg = PWRCL_REG_OFFSET + MUX_OFFSET,343 .shift = 2,344 .width = 2,345 .parent_map = smux_parent_map,346 .clkr.hw.init = &(struct clk_init_data) {347 .name = "pwrcl_smux",348 .parent_data = pwrcl_smux_parents,349 .num_parents = ARRAY_SIZE(pwrcl_smux_parents),350 .ops = &clk_regmap_mux_closest_ops,351 .flags = CLK_SET_RATE_PARENT,352 },353};354 355static struct clk_regmap_mux perfcl_smux = {356 .reg = PERFCL_REG_OFFSET + MUX_OFFSET,357 .shift = 2,358 .width = 2,359 .parent_map = smux_parent_map,360 .clkr.hw.init = &(struct clk_init_data) {361 .name = "perfcl_smux",362 .parent_data = perfcl_smux_parents,363 .num_parents = ARRAY_SIZE(perfcl_smux_parents),364 .ops = &clk_regmap_mux_closest_ops,365 .flags = CLK_SET_RATE_PARENT,366 },367};368 369static const struct clk_hw *pwrcl_pmux_parents[] = {370 [SMUX_INDEX] = &pwrcl_smux.clkr.hw,371 [PLL_INDEX] = &pwrcl_pll.clkr.hw,372 [ACD_INDEX] = &pwrcl_pll_acd.hw,373 [ALT_INDEX] = &pwrcl_alt_pll.clkr.hw,374};375 376static const struct clk_hw *perfcl_pmux_parents[] = {377 [SMUX_INDEX] = &perfcl_smux.clkr.hw,378 [PLL_INDEX] = &perfcl_pll.clkr.hw,379 [ACD_INDEX] = &perfcl_pll_acd.hw,380 [ALT_INDEX] = &perfcl_alt_pll.clkr.hw,381};382 383static struct clk_cpu_8996_pmux pwrcl_pmux = {384 .reg = PWRCL_REG_OFFSET + MUX_OFFSET,385 .nb.notifier_call = cpu_clk_notifier_cb,386 .clkr.hw.init = &(struct clk_init_data) {387 .name = "pwrcl_pmux",388 .parent_hws = pwrcl_pmux_parents,389 .num_parents = ARRAY_SIZE(pwrcl_pmux_parents),390 .ops = &clk_cpu_8996_pmux_ops,391 /* CPU clock is critical and should never be gated */392 .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,393 },394};395 396static struct clk_cpu_8996_pmux perfcl_pmux = {397 .reg = PERFCL_REG_OFFSET + MUX_OFFSET,398 .nb.notifier_call = cpu_clk_notifier_cb,399 .clkr.hw.init = &(struct clk_init_data) {400 .name = "perfcl_pmux",401 .parent_hws = perfcl_pmux_parents,402 .num_parents = ARRAY_SIZE(perfcl_pmux_parents),403 .ops = &clk_cpu_8996_pmux_ops,404 /* CPU clock is critical and should never be gated */405 .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,406 },407};408 409static const struct regmap_config cpu_msm8996_regmap_config = {410 .reg_bits = 32,411 .reg_stride = 4,412 .val_bits = 32,413 .max_register = 0x80210,414 .fast_io = true,415 .val_format_endian = REGMAP_ENDIAN_LITTLE,416};417 418static struct clk_hw *cpu_msm8996_hw_clks[] = {419 &pwrcl_pll_postdiv.hw,420 &perfcl_pll_postdiv.hw,421 &pwrcl_pll_acd.hw,422 &perfcl_pll_acd.hw,423};424 425static struct clk_regmap *cpu_msm8996_clks[] = {426 &pwrcl_pll.clkr,427 &perfcl_pll.clkr,428 &pwrcl_alt_pll.clkr,429 &perfcl_alt_pll.clkr,430 &pwrcl_smux.clkr,431 &perfcl_smux.clkr,432 &pwrcl_pmux.clkr,433 &perfcl_pmux.clkr,434};435 436static void qcom_cpu_clk_msm8996_acd_init(struct regmap *regmap);437 438static int qcom_cpu_clk_msm8996_register_clks(struct device *dev,439 struct regmap *regmap)440{441 int i, ret;442 443 /* Select GPLL0 for 300MHz for both clusters */444 regmap_write(regmap, PERFCL_REG_OFFSET + MUX_OFFSET, 0xc);445 regmap_write(regmap, PWRCL_REG_OFFSET + MUX_OFFSET, 0xc);446 447 /* Ensure write goes through before PLLs are reconfigured */448 udelay(5);449 450 /* Set the auto clock sel always-on source to GPLL0/2 (300MHz) */451 regmap_update_bits(regmap, PWRCL_REG_OFFSET + MUX_OFFSET,452 MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK,453 MUX_AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL);454 regmap_update_bits(regmap, PERFCL_REG_OFFSET + MUX_OFFSET,455 MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK,456 MUX_AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL);457 458 clk_alpha_pll_configure(&pwrcl_pll, regmap, &hfpll_config);459 clk_alpha_pll_configure(&perfcl_pll, regmap, &hfpll_config);460 clk_alpha_pll_configure(&pwrcl_alt_pll, regmap, &altpll_config);461 clk_alpha_pll_configure(&perfcl_alt_pll, regmap, &altpll_config);462 463 /* Wait for PLL(s) to lock */464 udelay(50);465 466 /* Enable auto clock selection for both clusters */467 regmap_update_bits(regmap, PWRCL_REG_OFFSET + CLK_CTL_OFFSET,468 CLK_CTL_AUTO_CLK_SEL, CLK_CTL_AUTO_CLK_SEL);469 regmap_update_bits(regmap, PERFCL_REG_OFFSET + CLK_CTL_OFFSET,470 CLK_CTL_AUTO_CLK_SEL, CLK_CTL_AUTO_CLK_SEL);471 472 /* Ensure write goes through before muxes are switched */473 udelay(5);474 475 qcom_cpu_clk_msm8996_acd_init(regmap);476 477 /* Pulse swallower and soft-start settings */478 regmap_write(regmap, PWRCL_REG_OFFSET + PSCTL_OFFSET, 0x00030005);479 regmap_write(regmap, PERFCL_REG_OFFSET + PSCTL_OFFSET, 0x00030005);480 481 /* Switch clusters to use the ACD leg */482 regmap_write(regmap, PWRCL_REG_OFFSET + MUX_OFFSET, 0x32);483 regmap_write(regmap, PERFCL_REG_OFFSET + MUX_OFFSET, 0x32);484 485 for (i = 0; i < ARRAY_SIZE(cpu_msm8996_hw_clks); i++) {486 ret = devm_clk_hw_register(dev, cpu_msm8996_hw_clks[i]);487 if (ret)488 return ret;489 }490 491 for (i = 0; i < ARRAY_SIZE(cpu_msm8996_clks); i++) {492 ret = devm_clk_register_regmap(dev, cpu_msm8996_clks[i]);493 if (ret)494 return ret;495 }496 497 /* Enable alt PLLs */498 clk_prepare_enable(pwrcl_alt_pll.clkr.hw.clk);499 clk_prepare_enable(perfcl_alt_pll.clkr.hw.clk);500 501 devm_clk_notifier_register(dev, pwrcl_pmux.clkr.hw.clk, &pwrcl_pmux.nb);502 devm_clk_notifier_register(dev, perfcl_pmux.clkr.hw.clk, &perfcl_pmux.nb);503 504 return ret;505}506 507#define CPU_CLUSTER_AFFINITY_MASK 0xf00508#define PWRCL_AFFINITY_MASK 0x000509#define PERFCL_AFFINITY_MASK 0x100510 511#define L2ACDCR_REG 0x580ULL512#define L2ACDTD_REG 0x581ULL513#define L2ACDDVMRC_REG 0x584ULL514#define L2ACDSSCR_REG 0x589ULL515 516static DEFINE_SPINLOCK(qcom_clk_acd_lock);517 518static void qcom_cpu_clk_msm8996_acd_init(struct regmap *regmap)519{520 u64 hwid;521 u32 val;522 unsigned long flags;523 524 spin_lock_irqsave(&qcom_clk_acd_lock, flags);525 526 val = kryo_l2_get_indirect_reg(L2ACDTD_REG);527 if (val == 0x00006a11)528 goto out;529 530 kryo_l2_set_indirect_reg(L2ACDTD_REG, 0x00006a11);531 kryo_l2_set_indirect_reg(L2ACDDVMRC_REG, 0x000e0f0f);532 kryo_l2_set_indirect_reg(L2ACDSSCR_REG, 0x00000601);533 534 kryo_l2_set_indirect_reg(L2ACDCR_REG, 0x002c5ffd);535 536 hwid = read_cpuid_mpidr();537 if ((hwid & CPU_CLUSTER_AFFINITY_MASK) == PWRCL_AFFINITY_MASK)538 regmap_write(regmap, PWRCL_REG_OFFSET + SSSCTL_OFFSET, 0xf);539 else540 regmap_write(regmap, PERFCL_REG_OFFSET + SSSCTL_OFFSET, 0xf);541 542out:543 spin_unlock_irqrestore(&qcom_clk_acd_lock, flags);544}545 546static int cpu_clk_notifier_cb(struct notifier_block *nb, unsigned long event,547 void *data)548{549 struct clk_cpu_8996_pmux *cpuclk = to_clk_cpu_8996_pmux_nb(nb);550 struct clk_notifier_data *cnd = data;551 552 switch (event) {553 case PRE_RATE_CHANGE:554 qcom_cpu_clk_msm8996_acd_init(cpuclk->clkr.regmap);555 556 /*557 * Avoid overvolting. clk_core_set_rate_nolock() walks from top558 * to bottom, so it will change the rate of the PLL before559 * chaging the parent of PMUX. This can result in pmux getting560 * clocked twice the expected rate.561 *562 * Manually switch to PLL/2 here.563 */564 if (cnd->new_rate < DIV_2_THRESHOLD &&565 cnd->old_rate > DIV_2_THRESHOLD)566 clk_cpu_8996_pmux_set_parent(&cpuclk->clkr.hw, SMUX_INDEX);567 568 break;569 case ABORT_RATE_CHANGE:570 /* Revert manual change */571 if (cnd->new_rate < DIV_2_THRESHOLD &&572 cnd->old_rate > DIV_2_THRESHOLD)573 clk_cpu_8996_pmux_set_parent(&cpuclk->clkr.hw, ACD_INDEX);574 break;575 default:576 break;577 }578 579 return NOTIFY_OK;580};581 582static int qcom_cpu_clk_msm8996_driver_probe(struct platform_device *pdev)583{584 static void __iomem *base;585 struct regmap *regmap;586 struct clk_hw_onecell_data *data;587 struct device *dev = &pdev->dev;588 int ret;589 590 data = devm_kzalloc(dev, struct_size(data, hws, 2), GFP_KERNEL);591 if (!data)592 return -ENOMEM;593 data->num = 2;594 595 base = devm_platform_ioremap_resource(pdev, 0);596 if (IS_ERR(base))597 return PTR_ERR(base);598 599 regmap = devm_regmap_init_mmio(dev, base, &cpu_msm8996_regmap_config);600 if (IS_ERR(regmap))601 return PTR_ERR(regmap);602 603 ret = qcom_cpu_clk_msm8996_register_clks(dev, regmap);604 if (ret)605 return ret;606 607 data->hws[0] = &pwrcl_pmux.clkr.hw;608 data->hws[1] = &perfcl_pmux.clkr.hw;609 610 return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, data);611}612 613static const struct of_device_id qcom_cpu_clk_msm8996_match_table[] = {614 { .compatible = "qcom,msm8996-apcc" },615 {}616};617MODULE_DEVICE_TABLE(of, qcom_cpu_clk_msm8996_match_table);618 619static struct platform_driver qcom_cpu_clk_msm8996_driver = {620 .probe = qcom_cpu_clk_msm8996_driver_probe,621 .driver = {622 .name = "qcom-msm8996-apcc",623 .of_match_table = qcom_cpu_clk_msm8996_match_table,624 },625};626module_platform_driver(qcom_cpu_clk_msm8996_driver);627 628MODULE_DESCRIPTION("QCOM MSM8996 CPU Clock Driver");629MODULE_LICENSE("GPL v2");630