forked from luck/tmp_suning_uos_patched
clk: qcom: Add spmi_pmic clock divider support
Clkdiv module provides a clock output on the PMIC with CXO as the source. This clock can be routed through PMIC GPIOs. Add a device driver to configure this clkdiv module. Signed-off-by: Tirupathi Reddy <tirupath@codeaurora.org> [sboyd: Simplified code and moved to devm clk provider APIs] Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
This commit is contained in:
parent
e3447a67c9
commit
4cfaa55f42
|
@ -196,3 +196,12 @@ config MSM_MMCC_8996
|
|||
Support for the multimedia clock controller on msm8996 devices.
|
||||
Say Y if you want to support multimedia devices such as display,
|
||||
graphics, video encode/decode, camera, etc.
|
||||
|
||||
config SPMI_PMIC_CLKDIV
|
||||
tristate "SPMI PMIC clkdiv Support"
|
||||
depends on (COMMON_CLK_QCOM && SPMI) || COMPILE_TEST
|
||||
help
|
||||
This driver supports the clkdiv functionality on the Qualcomm
|
||||
Technologies, Inc. SPMI PMIC. It configures the frequency of
|
||||
clkdiv outputs of the PMIC. These clocks are typically wired
|
||||
through alternate functions on GPIO pins.
|
||||
|
|
|
@ -34,3 +34,4 @@ obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
|
|||
obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o
|
||||
obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o
|
||||
obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o
|
||||
obj-$(CONFIG_SPMI_PMIC_CLKDIV) += clk-spmi-pmic-div.o
|
||||
|
|
302
drivers/clk/qcom/clk-spmi-pmic-div.c
Normal file
302
drivers/clk/qcom/clk-spmi-pmic-div.c
Normal file
|
@ -0,0 +1,302 @@
|
|||
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define REG_DIV_CTL1 0x43
|
||||
#define DIV_CTL1_DIV_FACTOR_MASK GENMASK(2, 0)
|
||||
|
||||
#define REG_EN_CTL 0x46
|
||||
#define REG_EN_MASK BIT(7)
|
||||
|
||||
struct clkdiv {
|
||||
struct regmap *regmap;
|
||||
u16 base;
|
||||
spinlock_t lock;
|
||||
|
||||
struct clk_hw hw;
|
||||
unsigned int cxo_period_ns;
|
||||
};
|
||||
|
||||
static inline struct clkdiv *to_clkdiv(struct clk_hw *hw)
|
||||
{
|
||||
return container_of(hw, struct clkdiv, hw);
|
||||
}
|
||||
|
||||
static inline unsigned int div_factor_to_div(unsigned int div_factor)
|
||||
{
|
||||
if (!div_factor)
|
||||
div_factor = 1;
|
||||
|
||||
return 1 << (div_factor - 1);
|
||||
}
|
||||
|
||||
static inline unsigned int div_to_div_factor(unsigned int div)
|
||||
{
|
||||
return min(ilog2(div) + 1, 7);
|
||||
}
|
||||
|
||||
static bool is_spmi_pmic_clkdiv_enabled(struct clkdiv *clkdiv)
|
||||
{
|
||||
unsigned int val = 0;
|
||||
|
||||
regmap_read(clkdiv->regmap, clkdiv->base + REG_EN_CTL, &val);
|
||||
|
||||
return val & REG_EN_MASK;
|
||||
}
|
||||
|
||||
static int
|
||||
__spmi_pmic_clkdiv_set_enable_state(struct clkdiv *clkdiv, bool enable,
|
||||
unsigned int div_factor)
|
||||
{
|
||||
int ret;
|
||||
unsigned int ns = clkdiv->cxo_period_ns;
|
||||
unsigned int div = div_factor_to_div(div_factor);
|
||||
|
||||
ret = regmap_update_bits(clkdiv->regmap, clkdiv->base + REG_EN_CTL,
|
||||
REG_EN_MASK, enable ? REG_EN_MASK : 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (enable)
|
||||
ndelay((2 + 3 * div) * ns);
|
||||
else
|
||||
ndelay(3 * div * ns);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spmi_pmic_clkdiv_set_enable_state(struct clkdiv *clkdiv, bool enable)
|
||||
{
|
||||
unsigned int div_factor;
|
||||
|
||||
regmap_read(clkdiv->regmap, clkdiv->base + REG_DIV_CTL1, &div_factor);
|
||||
div_factor &= DIV_CTL1_DIV_FACTOR_MASK;
|
||||
|
||||
return __spmi_pmic_clkdiv_set_enable_state(clkdiv, enable, div_factor);
|
||||
}
|
||||
|
||||
static int clk_spmi_pmic_div_enable(struct clk_hw *hw)
|
||||
{
|
||||
struct clkdiv *clkdiv = to_clkdiv(hw);
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&clkdiv->lock, flags);
|
||||
ret = spmi_pmic_clkdiv_set_enable_state(clkdiv, true);
|
||||
spin_unlock_irqrestore(&clkdiv->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void clk_spmi_pmic_div_disable(struct clk_hw *hw)
|
||||
{
|
||||
struct clkdiv *clkdiv = to_clkdiv(hw);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&clkdiv->lock, flags);
|
||||
spmi_pmic_clkdiv_set_enable_state(clkdiv, false);
|
||||
spin_unlock_irqrestore(&clkdiv->lock, flags);
|
||||
}
|
||||
|
||||
static long clk_spmi_pmic_div_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long *parent_rate)
|
||||
{
|
||||
unsigned int div, div_factor;
|
||||
|
||||
div = DIV_ROUND_UP(*parent_rate, rate);
|
||||
div_factor = div_to_div_factor(div);
|
||||
div = div_factor_to_div(div_factor);
|
||||
|
||||
return *parent_rate / div;
|
||||
}
|
||||
|
||||
static unsigned long
|
||||
clk_spmi_pmic_div_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
||||
{
|
||||
struct clkdiv *clkdiv = to_clkdiv(hw);
|
||||
unsigned int div_factor;
|
||||
|
||||
regmap_read(clkdiv->regmap, clkdiv->base + REG_DIV_CTL1, &div_factor);
|
||||
div_factor &= DIV_CTL1_DIV_FACTOR_MASK;
|
||||
|
||||
return parent_rate / div_factor_to_div(div_factor);
|
||||
}
|
||||
|
||||
static int clk_spmi_pmic_div_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct clkdiv *clkdiv = to_clkdiv(hw);
|
||||
unsigned int div_factor = div_to_div_factor(parent_rate / rate);
|
||||
unsigned long flags;
|
||||
bool enabled;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&clkdiv->lock, flags);
|
||||
enabled = is_spmi_pmic_clkdiv_enabled(clkdiv);
|
||||
if (enabled) {
|
||||
ret = spmi_pmic_clkdiv_set_enable_state(clkdiv, false);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(clkdiv->regmap, clkdiv->base + REG_DIV_CTL1,
|
||||
DIV_CTL1_DIV_FACTOR_MASK, div_factor);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
if (enabled)
|
||||
ret = __spmi_pmic_clkdiv_set_enable_state(clkdiv, true,
|
||||
div_factor);
|
||||
|
||||
unlock:
|
||||
spin_unlock_irqrestore(&clkdiv->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct clk_ops clk_spmi_pmic_div_ops = {
|
||||
.enable = clk_spmi_pmic_div_enable,
|
||||
.disable = clk_spmi_pmic_div_disable,
|
||||
.set_rate = clk_spmi_pmic_div_set_rate,
|
||||
.recalc_rate = clk_spmi_pmic_div_recalc_rate,
|
||||
.round_rate = clk_spmi_pmic_div_round_rate,
|
||||
};
|
||||
|
||||
struct spmi_pmic_div_clk_cc {
|
||||
int nclks;
|
||||
struct clkdiv clks[];
|
||||
};
|
||||
|
||||
static struct clk_hw *
|
||||
spmi_pmic_div_clk_hw_get(struct of_phandle_args *clkspec, void *data)
|
||||
{
|
||||
struct spmi_pmic_div_clk_cc *cc = data;
|
||||
int idx = clkspec->args[0] - 1; /* Start at 1 instead of 0 */
|
||||
|
||||
if (idx < 0 || idx >= cc->nclks) {
|
||||
pr_err("%s: index value %u is invalid; allowed range [1, %d]\n",
|
||||
__func__, clkspec->args[0], cc->nclks);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
return &cc->clks[idx].hw;
|
||||
}
|
||||
|
||||
static int spmi_pmic_clkdiv_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct spmi_pmic_div_clk_cc *cc;
|
||||
struct clk_init_data init = {};
|
||||
struct clkdiv *clkdiv;
|
||||
struct clk *cxo;
|
||||
struct regmap *regmap;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *of_node = dev->of_node;
|
||||
const char *parent_name;
|
||||
int nclks, i, ret, cxo_hz;
|
||||
char name[20];
|
||||
u32 start;
|
||||
|
||||
ret = of_property_read_u32(of_node, "reg", &start);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "reg property reading failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!regmap) {
|
||||
dev_err(dev, "Couldn't get parent's regmap\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(of_node, "qcom,num-clkdivs", &nclks);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "qcom,num-clkdivs property reading failed, ret=%d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!nclks)
|
||||
return -EINVAL;
|
||||
|
||||
cc = devm_kzalloc(dev, sizeof(*cc) + sizeof(*cc->clks) * nclks,
|
||||
GFP_KERNEL);
|
||||
if (!cc)
|
||||
return -ENOMEM;
|
||||
cc->nclks = nclks;
|
||||
|
||||
cxo = clk_get(dev, "xo");
|
||||
if (IS_ERR(cxo)) {
|
||||
ret = PTR_ERR(cxo);
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(dev, "failed to get xo clock\n");
|
||||
return ret;
|
||||
}
|
||||
cxo_hz = clk_get_rate(cxo);
|
||||
clk_put(cxo);
|
||||
|
||||
parent_name = of_clk_get_parent_name(of_node, 0);
|
||||
if (!parent_name) {
|
||||
dev_err(dev, "missing parent clock\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
init.name = name;
|
||||
init.parent_names = &parent_name;
|
||||
init.num_parents = 1;
|
||||
init.ops = &clk_spmi_pmic_div_ops;
|
||||
|
||||
for (i = 0, clkdiv = cc->clks; i < nclks; i++) {
|
||||
snprintf(name, sizeof(name), "div_clk%d", i + 1);
|
||||
|
||||
spin_lock_init(&clkdiv[i].lock);
|
||||
clkdiv[i].base = start + i * 0x100;
|
||||
clkdiv[i].regmap = regmap;
|
||||
clkdiv[i].cxo_period_ns = NSEC_PER_SEC / cxo_hz;
|
||||
clkdiv[i].hw.init = &init;
|
||||
|
||||
ret = devm_clk_hw_register(dev, &clkdiv[i].hw);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return devm_of_clk_add_hw_provider(dev, spmi_pmic_div_clk_hw_get, cc);
|
||||
}
|
||||
|
||||
static const struct of_device_id spmi_pmic_clkdiv_match_table[] = {
|
||||
{ .compatible = "qcom,spmi-clkdiv" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, spmi_pmic_clkdiv_match_table);
|
||||
|
||||
static struct platform_driver spmi_pmic_clkdiv_driver = {
|
||||
.driver = {
|
||||
.name = "qcom,spmi-pmic-clkdiv",
|
||||
.of_match_table = spmi_pmic_clkdiv_match_table,
|
||||
},
|
||||
.probe = spmi_pmic_clkdiv_probe,
|
||||
};
|
||||
module_platform_driver(spmi_pmic_clkdiv_driver);
|
||||
|
||||
MODULE_DESCRIPTION("QCOM SPMI PMIC clkdiv driver");
|
||||
MODULE_LICENSE("GPL v2");
|
Loading…
Reference in New Issue
Block a user