// 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #include 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 GFM_MASK BIT(1) #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) & GFM_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 |= GFM_MASK; else val &= ~GFM_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); pm_runtime_enable(dev); err = pm_clk_create(dev); if (err) goto pm_clk_err; err = of_pm_clk_add_clks(dev); if (err < 0) { dev_dbg(dev, "Failed to get lpass core voting clocks\n"); goto clk_reg_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) goto clk_reg_err; } err = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, data->onecell_data); if (err) goto clk_reg_err; return 0; clk_reg_err: pm_clk_destroy(dev); pm_clk_err: pm_runtime_disable(dev); return err; } 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");