blob: 5669ca9954dc0e868ff18fe19dbb154dd7b1fa8f [file] [edit]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024 Google LLC
* Author: Chen-Yu Tsai <wenst@chromium.org>
*
* Based on driver in downstream ChromeOS v5.15 kernel.
*
* Copyright (c) 2014 MediaTek Inc.
* Author: Chiawen Lee <chiawen.lee@mediatek.com>
*/
#include <dt-bindings/clock/mt8173-clk.h>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include "clk-gate.h"
#include "clk-mtk.h"
static const struct mtk_gate_regs mfg_cg_regs = {
.sta_ofs = 0x0000,
.clr_ofs = 0x0008,
.set_ofs = 0x0004,
};
#define GATE_MFG(_id, _name, _parent, _shift, _flags) \
GATE_MTK_FLAGS(_id, _name, _parent, &mfg_cg_regs, _shift, &mtk_clk_gate_ops_setclr, _flags)
/* TODO: The block actually has dividers for the core and mem clocks. */
static const struct mtk_gate mfg_clks[] = {
GATE_MFG(CLK_MFG_AXI, "mfg_axi", "axi_mfg_in_sel", 0, CLK_SET_RATE_PARENT),
GATE_MFG(CLK_MFG_MEM, "mfg_mem", "mem_mfg_in_sel", 1, CLK_SET_RATE_PARENT),
GATE_MFG(CLK_MFG_G3D, "mfg_g3d", "mfg_sel", 2, CLK_SET_RATE_PARENT),
GATE_MFG(CLK_MFG_26M, "mfg_26m", "clk26m", 3, 0),
};
struct mt8173_mfgtop_data {
struct clk_hw_onecell_data *clk_data;
struct regmap *regmap;
struct generic_pm_domain genpd;
struct of_phandle_args parent_pd, child_pd;
struct clk *clk_26m;
};
/* Delay count in clock cycles */
#define MFG_ACTIVE_POWER_CON0 0x24
#define RST_B_DELAY_CNT GENMASK(7, 0) /* pwr_rst_b de-assert delay during power-up */
#define CLK_EN_DELAY_CNT GENMASK(15, 8) /* CLK_DIS deassert delay during power-up */
#define CLK_DIS_DELAY_CNT GENMASK(23, 16) /* CLK_DIS assert delay during power-down */
#define FORCE_ABORT BIT(30) /* write 1 to force abort a power event */
#define ACTIVE_PWRCTL_EN BIT(31) /* enable ACTIVE_POWER */
#define MFG_ACTIVE_POWER_CON1 0x28
#define PWR_ON_S_DELAY_CNT GENMASK(7, 0) /* pwr_on_s assert delay during power-up */
#define ISO_DELAY_CNT GENMASK(15, 8) /* ISO assert delay during power-down */
#define ISOOFF_DELAY_CNT GENMASK(23, 16) /* ISO de-assert delay during power-up */
#define RST_DELAY_CNT GENMASK(31, 24) /* pwr_rsb_b assert delay during power-down */
static int clk_mt8173_mfgtop_power_on(struct generic_pm_domain *domain)
{
struct mt8173_mfgtop_data *data = container_of(domain, struct mt8173_mfgtop_data, genpd);
int ret;
/* drives internal power management */
ret = clk_prepare_enable(data->clk_26m);
if (ret)
return ret;
/* Power on/off delays for various signals */
regmap_write(data->regmap, MFG_ACTIVE_POWER_CON0,
FIELD_PREP(RST_B_DELAY_CNT, 77) |
FIELD_PREP(CLK_EN_DELAY_CNT, 61) |
FIELD_PREP(CLK_DIS_DELAY_CNT, 60) |
FIELD_PREP(ACTIVE_PWRCTL_EN, 0));
regmap_write(data->regmap, MFG_ACTIVE_POWER_CON1,
FIELD_PREP(PWR_ON_S_DELAY_CNT, 11) |
FIELD_PREP(ISO_DELAY_CNT, 68) |
FIELD_PREP(ISOOFF_DELAY_CNT, 69) |
FIELD_PREP(RST_DELAY_CNT, 77));
/* Magic numbers related to core switch sequence and delays */
regmap_write(data->regmap, 0xe0, 0x7a710184);
regmap_write(data->regmap, 0xe4, 0x835f6856);
regmap_write(data->regmap, 0xe8, 0x002b0234);
regmap_write(data->regmap, 0xec, 0x80000000);
regmap_write(data->regmap, 0xa0, 0x08000000);
return 0;
}
static int clk_mt8173_mfgtop_power_off(struct generic_pm_domain *domain)
{
struct mt8173_mfgtop_data *data = container_of(domain, struct mt8173_mfgtop_data, genpd);
/* Magic numbers related to core switch sequence and delays */
regmap_write(data->regmap, 0xec, 0);
/* drives internal power management */
clk_disable_unprepare(data->clk_26m);
return 0;
}
static int clk_mt8173_mfgtop_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct mt8173_mfgtop_data *data;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
data->clk_data = mtk_devm_alloc_clk_data(dev, ARRAY_SIZE(mfg_clks));
if (!data->clk_data)
return -ENOMEM;
/* MTK clock gates also uses regmap */
data->regmap = device_node_to_regmap(node);
if (IS_ERR(data->regmap))
return dev_err_probe(dev, PTR_ERR(data->regmap), "Failed to get regmap\n");
data->child_pd.np = node;
data->child_pd.args_count = 0;
ret = of_parse_phandle_with_args(node, "power-domains", "#power-domain-cells", 0,
&data->parent_pd);
if (ret)
return dev_err_probe(dev, ret, "Failed to parse power domain\n");
devm_pm_runtime_enable(dev);
/*
* Do a pm_runtime_resume_and_get() to workaround a possible
* deadlock between clk_register() and the genpd framework.
*/
ret = pm_runtime_resume_and_get(dev);
if (ret) {
dev_err_probe(dev, ret, "Failed to runtime resume device\n");
goto put_of_node;
}
ret = mtk_clk_register_gates(dev, node, mfg_clks, ARRAY_SIZE(mfg_clks),
data->clk_data);
if (ret) {
dev_err_probe(dev, ret, "Failed to register clock gates\n");
goto put_pm_runtime;
}
data->clk_26m = clk_hw_get_clk(data->clk_data->hws[CLK_MFG_26M], "26m");
if (IS_ERR(data->clk_26m)) {
ret = dev_err_probe(dev, PTR_ERR(data->clk_26m), "Failed to get 26 MHz clock\n");
goto unregister_clks;
}
ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get, data->clk_data);
if (ret) {
dev_err_probe(dev, ret, "Failed to add clk OF provider\n");
goto put_26m_clk;
}
data->genpd.name = "mfg-top";
data->genpd.power_on = clk_mt8173_mfgtop_power_on;
data->genpd.power_off = clk_mt8173_mfgtop_power_off;
ret = pm_genpd_init(&data->genpd, NULL, true);
if (ret) {
dev_err_probe(dev, ret, "Failed to add power domain\n");
goto del_clk_provider;
}
ret = of_genpd_add_provider_simple(node, &data->genpd);
if (ret) {
dev_err_probe(dev, ret, "Failed to add power domain OF provider\n");
goto remove_pd;
}
ret = of_genpd_add_subdomain(&data->parent_pd, &data->child_pd);
if (ret) {
dev_err_probe(dev, ret, "Failed to link PM domains\n");
goto del_pd_provider;
}
pm_runtime_put(dev);
return 0;
del_pd_provider:
of_genpd_del_provider(node);
remove_pd:
pm_genpd_remove(&data->genpd);
del_clk_provider:
of_clk_del_provider(node);
put_26m_clk:
clk_put(data->clk_26m);
unregister_clks:
mtk_clk_unregister_gates(mfg_clks, ARRAY_SIZE(mfg_clks), data->clk_data);
put_pm_runtime:
pm_runtime_put_sync(dev);
put_of_node:
of_node_put(data->parent_pd.np);
return ret;
}
static void clk_mt8173_mfgtop_remove(struct platform_device *pdev)
{
struct mt8173_mfgtop_data *data = platform_get_drvdata(pdev);
struct device_node *node = pdev->dev.of_node;
of_genpd_remove_subdomain(&data->parent_pd, &data->child_pd);
of_genpd_del_provider(node);
pm_genpd_remove(&data->genpd);
of_clk_del_provider(node);
clk_put(data->clk_26m);
mtk_clk_unregister_gates(mfg_clks, ARRAY_SIZE(mfg_clks), data->clk_data);
of_node_put(data->parent_pd.np);
}
static const struct of_device_id of_match_clk_mt8173_mfgtop[] = {
{ .compatible = "mediatek,mt8173-mfgtop" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, of_match_clk_mt8173_mfgtop);
static struct platform_driver clk_mt8173_mfgtop_drv = {
.probe = clk_mt8173_mfgtop_probe,
.remove = clk_mt8173_mfgtop_remove,
.driver = {
.name = "clk-mt8173-mfgtop",
.of_match_table = of_match_clk_mt8173_mfgtop,
},
};
module_platform_driver(clk_mt8173_mfgtop_drv);
MODULE_DESCRIPTION("MediaTek MT8173 mfgtop clock driver");
MODULE_LICENSE("GPL");