a96cbb146a
The DT of_device.h and of_platform.h date back to the separate of_platform_bus_type before it as merged into the regular platform bus. As part of that merge prepping Arm DT support 13 years ago, they "temporarily" include each other. They also include platform_device.h and of.h. As a result, there's a pretty much random mix of those include files used throughout the tree. In order to detangle these headers and replace the implicit includes with struct declarations, users need to explicitly include the correct includes. Acked-by: Dinh Nguyen <dinguyen@kernel.org> Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> # samsung Acked-by: Heiko Stuebner <heiko@sntech.de> #rockchip Acked-by: Chanwoo Choi <cw00.choi@samsung.com> Acked-by: Geert Uytterhoeven <geert+renesas@glider.be> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com> # versaclock5 Signed-off-by: Rob Herring <robh@kernel.org> Link: https://lore.kernel.org/r/20230718143156.1066339-1-robh@kernel.org Acked-by: Abel Vesa <abel.vesa@linaro.org> #imx Signed-off-by: Stephen Boyd <sboyd@kernel.org>
318 lines
7.0 KiB
C
318 lines
7.0 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* LPASS Audio CC and Always ON CC Glitch Free Mux clock driver
|
|
*
|
|
* Copyright (c) 2020 Linaro Ltd.
|
|
* Author: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/io.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/err.h>
|
|
#include <linux/pm_clock.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <dt-bindings/clock/qcom,sm8250-lpass-audiocc.h>
|
|
#include <dt-bindings/clock/qcom,sm8250-lpass-aoncc.h>
|
|
|
|
struct lpass_gfm {
|
|
struct device *dev;
|
|
void __iomem *base;
|
|
};
|
|
|
|
struct clk_gfm {
|
|
unsigned int mux_reg;
|
|
unsigned int mux_mask;
|
|
struct clk_hw hw;
|
|
struct lpass_gfm *priv;
|
|
void __iomem *gfm_mux;
|
|
};
|
|
|
|
#define to_clk_gfm(_hw) container_of(_hw, struct clk_gfm, hw)
|
|
|
|
static u8 clk_gfm_get_parent(struct clk_hw *hw)
|
|
{
|
|
struct clk_gfm *clk = to_clk_gfm(hw);
|
|
|
|
return readl(clk->gfm_mux) & clk->mux_mask;
|
|
}
|
|
|
|
static int clk_gfm_set_parent(struct clk_hw *hw, u8 index)
|
|
{
|
|
struct clk_gfm *clk = to_clk_gfm(hw);
|
|
unsigned int val;
|
|
|
|
val = readl(clk->gfm_mux);
|
|
|
|
if (index)
|
|
val |= clk->mux_mask;
|
|
else
|
|
val &= ~clk->mux_mask;
|
|
|
|
|
|
writel(val, clk->gfm_mux);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct clk_ops clk_gfm_ops = {
|
|
.get_parent = clk_gfm_get_parent,
|
|
.set_parent = clk_gfm_set_parent,
|
|
.determine_rate = __clk_mux_determine_rate,
|
|
};
|
|
|
|
static struct clk_gfm lpass_gfm_va_mclk = {
|
|
.mux_reg = 0x20000,
|
|
.mux_mask = BIT(0),
|
|
.hw.init = &(struct clk_init_data) {
|
|
.name = "VA_MCLK",
|
|
.ops = &clk_gfm_ops,
|
|
.flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE,
|
|
.num_parents = 2,
|
|
.parent_data = (const struct clk_parent_data[]){
|
|
{
|
|
.index = 0,
|
|
.fw_name = "LPASS_CLK_ID_TX_CORE_MCLK",
|
|
}, {
|
|
.index = 1,
|
|
.fw_name = "LPASS_CLK_ID_VA_CORE_MCLK",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
static struct clk_gfm lpass_gfm_tx_npl = {
|
|
.mux_reg = 0x20000,
|
|
.mux_mask = BIT(0),
|
|
.hw.init = &(struct clk_init_data) {
|
|
.name = "TX_NPL",
|
|
.ops = &clk_gfm_ops,
|
|
.flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE,
|
|
.parent_data = (const struct clk_parent_data[]){
|
|
{
|
|
.index = 0,
|
|
.fw_name = "LPASS_CLK_ID_TX_CORE_NPL_MCLK",
|
|
}, {
|
|
.index = 1,
|
|
.fw_name = "LPASS_CLK_ID_VA_CORE_2X_MCLK",
|
|
},
|
|
},
|
|
.num_parents = 2,
|
|
},
|
|
};
|
|
|
|
static struct clk_gfm lpass_gfm_wsa_mclk = {
|
|
.mux_reg = 0x220d8,
|
|
.mux_mask = BIT(0),
|
|
.hw.init = &(struct clk_init_data) {
|
|
.name = "WSA_MCLK",
|
|
.ops = &clk_gfm_ops,
|
|
.flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE,
|
|
.parent_data = (const struct clk_parent_data[]){
|
|
{
|
|
.index = 0,
|
|
.fw_name = "LPASS_CLK_ID_TX_CORE_MCLK",
|
|
}, {
|
|
.index = 1,
|
|
.fw_name = "LPASS_CLK_ID_WSA_CORE_MCLK",
|
|
},
|
|
},
|
|
.num_parents = 2,
|
|
},
|
|
};
|
|
|
|
static struct clk_gfm lpass_gfm_wsa_npl = {
|
|
.mux_reg = 0x220d8,
|
|
.mux_mask = BIT(0),
|
|
.hw.init = &(struct clk_init_data) {
|
|
.name = "WSA_NPL",
|
|
.ops = &clk_gfm_ops,
|
|
.flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE,
|
|
.parent_data = (const struct clk_parent_data[]){
|
|
{
|
|
.index = 0,
|
|
.fw_name = "LPASS_CLK_ID_TX_CORE_NPL_MCLK",
|
|
}, {
|
|
.index = 1,
|
|
.fw_name = "LPASS_CLK_ID_WSA_CORE_NPL_MCLK",
|
|
},
|
|
},
|
|
.num_parents = 2,
|
|
},
|
|
};
|
|
|
|
static struct clk_gfm lpass_gfm_rx_mclk_mclk2 = {
|
|
.mux_reg = 0x240d8,
|
|
.mux_mask = BIT(0),
|
|
.hw.init = &(struct clk_init_data) {
|
|
.name = "RX_MCLK_MCLK2",
|
|
.ops = &clk_gfm_ops,
|
|
.flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE,
|
|
.parent_data = (const struct clk_parent_data[]){
|
|
{
|
|
.index = 0,
|
|
.fw_name = "LPASS_CLK_ID_TX_CORE_MCLK",
|
|
}, {
|
|
.index = 1,
|
|
.fw_name = "LPASS_CLK_ID_RX_CORE_MCLK",
|
|
},
|
|
},
|
|
.num_parents = 2,
|
|
},
|
|
};
|
|
|
|
static struct clk_gfm lpass_gfm_rx_npl = {
|
|
.mux_reg = 0x240d8,
|
|
.mux_mask = BIT(0),
|
|
.hw.init = &(struct clk_init_data) {
|
|
.name = "RX_NPL",
|
|
.ops = &clk_gfm_ops,
|
|
.flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE,
|
|
.parent_data = (const struct clk_parent_data[]){
|
|
{
|
|
.index = 0,
|
|
.fw_name = "LPASS_CLK_ID_TX_CORE_NPL_MCLK",
|
|
}, {
|
|
.index = 1,
|
|
.fw_name = "LPASS_CLK_ID_RX_CORE_NPL_MCLK",
|
|
},
|
|
},
|
|
.num_parents = 2,
|
|
},
|
|
};
|
|
|
|
static struct clk_gfm *aoncc_gfm_clks[] = {
|
|
[LPASS_CDC_VA_MCLK] = &lpass_gfm_va_mclk,
|
|
[LPASS_CDC_TX_NPL] = &lpass_gfm_tx_npl,
|
|
};
|
|
|
|
static struct clk_hw_onecell_data aoncc_hw_onecell_data = {
|
|
.hws = {
|
|
[LPASS_CDC_VA_MCLK] = &lpass_gfm_va_mclk.hw,
|
|
[LPASS_CDC_TX_NPL] = &lpass_gfm_tx_npl.hw,
|
|
},
|
|
.num = ARRAY_SIZE(aoncc_gfm_clks),
|
|
};
|
|
|
|
static struct clk_gfm *audiocc_gfm_clks[] = {
|
|
[LPASS_CDC_WSA_NPL] = &lpass_gfm_wsa_npl,
|
|
[LPASS_CDC_WSA_MCLK] = &lpass_gfm_wsa_mclk,
|
|
[LPASS_CDC_RX_NPL] = &lpass_gfm_rx_npl,
|
|
[LPASS_CDC_RX_MCLK_MCLK2] = &lpass_gfm_rx_mclk_mclk2,
|
|
};
|
|
|
|
static struct clk_hw_onecell_data audiocc_hw_onecell_data = {
|
|
.hws = {
|
|
[LPASS_CDC_WSA_NPL] = &lpass_gfm_wsa_npl.hw,
|
|
[LPASS_CDC_WSA_MCLK] = &lpass_gfm_wsa_mclk.hw,
|
|
[LPASS_CDC_RX_NPL] = &lpass_gfm_rx_npl.hw,
|
|
[LPASS_CDC_RX_MCLK_MCLK2] = &lpass_gfm_rx_mclk_mclk2.hw,
|
|
},
|
|
.num = ARRAY_SIZE(audiocc_gfm_clks),
|
|
};
|
|
|
|
struct lpass_gfm_data {
|
|
struct clk_hw_onecell_data *onecell_data;
|
|
struct clk_gfm **gfm_clks;
|
|
};
|
|
|
|
static struct lpass_gfm_data audiocc_data = {
|
|
.onecell_data = &audiocc_hw_onecell_data,
|
|
.gfm_clks = audiocc_gfm_clks,
|
|
};
|
|
|
|
static struct lpass_gfm_data aoncc_data = {
|
|
.onecell_data = &aoncc_hw_onecell_data,
|
|
.gfm_clks = aoncc_gfm_clks,
|
|
};
|
|
|
|
static int lpass_gfm_clk_driver_probe(struct platform_device *pdev)
|
|
{
|
|
const struct lpass_gfm_data *data;
|
|
struct device *dev = &pdev->dev;
|
|
struct clk_gfm *gfm;
|
|
struct lpass_gfm *cc;
|
|
int err, i;
|
|
|
|
data = of_device_get_match_data(dev);
|
|
if (!data)
|
|
return -EINVAL;
|
|
|
|
cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL);
|
|
if (!cc)
|
|
return -ENOMEM;
|
|
|
|
cc->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(cc->base))
|
|
return PTR_ERR(cc->base);
|
|
|
|
err = devm_pm_runtime_enable(dev);
|
|
if (err)
|
|
return err;
|
|
|
|
err = devm_pm_clk_create(dev);
|
|
if (err)
|
|
return err;
|
|
|
|
err = of_pm_clk_add_clks(dev);
|
|
if (err < 0) {
|
|
dev_dbg(dev, "Failed to get lpass core voting clocks\n");
|
|
return err;
|
|
}
|
|
|
|
for (i = 0; i < data->onecell_data->num; i++) {
|
|
if (!data->gfm_clks[i])
|
|
continue;
|
|
|
|
gfm = data->gfm_clks[i];
|
|
gfm->priv = cc;
|
|
gfm->gfm_mux = cc->base;
|
|
gfm->gfm_mux = gfm->gfm_mux + data->gfm_clks[i]->mux_reg;
|
|
|
|
err = devm_clk_hw_register(dev, &data->gfm_clks[i]->hw);
|
|
if (err)
|
|
return err;
|
|
|
|
}
|
|
|
|
err = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
|
|
data->onecell_data);
|
|
if (err)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id lpass_gfm_clk_match_table[] = {
|
|
{
|
|
.compatible = "qcom,sm8250-lpass-aoncc",
|
|
.data = &aoncc_data,
|
|
},
|
|
{
|
|
.compatible = "qcom,sm8250-lpass-audiocc",
|
|
.data = &audiocc_data,
|
|
},
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, lpass_gfm_clk_match_table);
|
|
|
|
static const struct dev_pm_ops lpass_gfm_pm_ops = {
|
|
SET_RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL)
|
|
};
|
|
|
|
static struct platform_driver lpass_gfm_clk_driver = {
|
|
.probe = lpass_gfm_clk_driver_probe,
|
|
.driver = {
|
|
.name = "lpass-gfm-clk",
|
|
.of_match_table = lpass_gfm_clk_match_table,
|
|
.pm = &lpass_gfm_pm_ops,
|
|
},
|
|
};
|
|
module_platform_driver(lpass_gfm_clk_driver);
|
|
MODULE_LICENSE("GPL v2");
|