186 lines · c
1// SPDX-License-Identifier: GPL-2.02// Copyright (c) 2020, Linaro Limited3 4#include <linux/err.h>5#include <linux/init.h>6#include <linux/clk-provider.h>7#include <linux/module.h>8#include <linux/device.h>9#include <linux/platform_device.h>10#include <linux/of.h>11#include <linux/slab.h>12#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h>13#include "q6dsp-lpass-clocks.h"14 15#define Q6DSP_MAX_CLK_ID 10416#define Q6DSP_LPASS_CLK_ROOT_DEFAULT 017 18 19struct q6dsp_clk {20 struct device *dev;21 int q6dsp_clk_id;22 int attributes;23 int rate;24 uint32_t handle;25 struct clk_hw hw;26};27 28#define to_q6dsp_clk(_hw) container_of(_hw, struct q6dsp_clk, hw)29 30struct q6dsp_cc {31 struct device *dev;32 struct q6dsp_clk *clks[Q6DSP_MAX_CLK_ID];33 const struct q6dsp_clk_desc *desc;34};35 36static int clk_q6dsp_prepare(struct clk_hw *hw)37{38 struct q6dsp_clk *clk = to_q6dsp_clk(hw);39 struct q6dsp_cc *cc = dev_get_drvdata(clk->dev);40 41 return cc->desc->lpass_set_clk(clk->dev, clk->q6dsp_clk_id, clk->attributes,42 Q6DSP_LPASS_CLK_ROOT_DEFAULT, clk->rate);43}44 45static void clk_q6dsp_unprepare(struct clk_hw *hw)46{47 struct q6dsp_clk *clk = to_q6dsp_clk(hw);48 struct q6dsp_cc *cc = dev_get_drvdata(clk->dev);49 50 cc->desc->lpass_set_clk(clk->dev, clk->q6dsp_clk_id, clk->attributes,51 Q6DSP_LPASS_CLK_ROOT_DEFAULT, 0);52}53 54static int clk_q6dsp_set_rate(struct clk_hw *hw, unsigned long rate,55 unsigned long parent_rate)56{57 struct q6dsp_clk *clk = to_q6dsp_clk(hw);58 59 clk->rate = rate;60 61 return 0;62}63 64static unsigned long clk_q6dsp_recalc_rate(struct clk_hw *hw,65 unsigned long parent_rate)66{67 struct q6dsp_clk *clk = to_q6dsp_clk(hw);68 69 return clk->rate;70}71 72static long clk_q6dsp_round_rate(struct clk_hw *hw, unsigned long rate,73 unsigned long *parent_rate)74{75 return rate;76}77 78static const struct clk_ops clk_q6dsp_ops = {79 .prepare = clk_q6dsp_prepare,80 .unprepare = clk_q6dsp_unprepare,81 .set_rate = clk_q6dsp_set_rate,82 .round_rate = clk_q6dsp_round_rate,83 .recalc_rate = clk_q6dsp_recalc_rate,84};85 86static int clk_vote_q6dsp_block(struct clk_hw *hw)87{88 struct q6dsp_clk *clk = to_q6dsp_clk(hw);89 struct q6dsp_cc *cc = dev_get_drvdata(clk->dev);90 91 return cc->desc->lpass_vote_clk(clk->dev, clk->q6dsp_clk_id,92 clk_hw_get_name(&clk->hw), &clk->handle);93}94 95static void clk_unvote_q6dsp_block(struct clk_hw *hw)96{97 struct q6dsp_clk *clk = to_q6dsp_clk(hw);98 struct q6dsp_cc *cc = dev_get_drvdata(clk->dev);99 100 cc->desc->lpass_unvote_clk(clk->dev, clk->q6dsp_clk_id, clk->handle);101}102 103static const struct clk_ops clk_vote_q6dsp_ops = {104 .prepare = clk_vote_q6dsp_block,105 .unprepare = clk_unvote_q6dsp_block,106};107 108 109static struct clk_hw *q6dsp_of_clk_hw_get(struct of_phandle_args *clkspec,110 void *data)111{112 struct q6dsp_cc *cc = data;113 unsigned int idx = clkspec->args[0];114 unsigned int attr = clkspec->args[1];115 116 if (idx >= Q6DSP_MAX_CLK_ID || attr > LPASS_CLK_ATTRIBUTE_COUPLE_DIVISOR) {117 dev_err(cc->dev, "Invalid clk specifier (%d, %d)\n", idx, attr);118 return ERR_PTR(-EINVAL);119 }120 121 if (cc->clks[idx]) {122 cc->clks[idx]->attributes = attr;123 return &cc->clks[idx]->hw;124 }125 126 return ERR_PTR(-ENOENT);127}128 129int q6dsp_clock_dev_probe(struct platform_device *pdev)130{131 struct q6dsp_cc *cc;132 struct device *dev = &pdev->dev;133 const struct q6dsp_clk_init *q6dsp_clks;134 const struct q6dsp_clk_desc *desc;135 int i, ret;136 137 cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL);138 if (!cc)139 return -ENOMEM;140 141 desc = of_device_get_match_data(&pdev->dev);142 if (!desc)143 return -EINVAL;144 145 cc->desc = desc;146 cc->dev = dev;147 q6dsp_clks = desc->clks;148 149 for (i = 0; i < desc->num_clks; i++) {150 unsigned int id = q6dsp_clks[i].clk_id;151 struct clk_init_data init = {152 .name = q6dsp_clks[i].name,153 };154 struct q6dsp_clk *clk;155 156 clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL);157 if (!clk)158 return -ENOMEM;159 160 clk->dev = dev;161 clk->q6dsp_clk_id = q6dsp_clks[i].q6dsp_clk_id;162 clk->rate = q6dsp_clks[i].rate;163 clk->hw.init = &init;164 165 if (clk->rate)166 init.ops = &clk_q6dsp_ops;167 else168 init.ops = &clk_vote_q6dsp_ops;169 170 cc->clks[id] = clk;171 172 ret = devm_clk_hw_register(dev, &clk->hw);173 if (ret)174 return ret;175 }176 177 ret = devm_of_clk_add_hw_provider(dev, q6dsp_of_clk_hw_get, cc);178 if (ret)179 return ret;180 181 dev_set_drvdata(dev, cc);182 183 return 0;184}185EXPORT_SYMBOL_GPL(q6dsp_clock_dev_probe);186