Files
2026-08-24 10:03:24 +08:00

514 lines
14 KiB
C

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2025 Amlogic, Inc. All rights reserved.
*/
#include <command.h>
#include <clk.h>
#include <dm.h>
#include <dm/device.h>
#include <dm/root.h>
#include <dm/device-internal.h>
#include <linux/arm-smccc.h>
#include <linux/clk-provider.h>
#include <linux/math64.h>
#include <asm/amlogic/arch/io.h>
#include <asm/amlogic/arch/secure_apb.h>
#include <amlogic/clk-core.h>
#define MAX_CLK_MEASURE_TOL 1000000ul
static unsigned long clk_util_clk_msr(unsigned long clk_mux)
{
unsigned int regval = 0;
writel(0, MSR_CLK_REG0);
/* Set the measurement gate to 640us */
setbits_le32(MSR_CLK_REG0, 640 - 1);
/* Disable continuous measurement */
/* Disable interrupts */
clrbits_le32(MSR_CLK_REG0, (1 << 17) | (1 << 18));
clrbits_le32(MSR_CLK_REG0, 0x7f << 20);
setbits_le32(MSR_CLK_REG0, (clk_mux) << 20 | (1 << 19) | (1 << 16));
/* Wait for the measurement to be done */
regval = readl(MSR_CLK_REG0);
do {
regval = readl(MSR_CLK_REG0);
} while (regval & (1 << 31));
/* Disable measuring */
clrbits_le32(MSR_CLK_REG0, 1 << 16);
regval = readl(MSR_CLK_REG2) & 0x000fffff;
return DIV_ROUND_UP_ULL(regval * 1000000ull, 640);
}
static struct clk *aml_clk_get_by_name(const char *name)
{
struct udevice *dev;
struct uclass *uc;
struct clk *clkp;
int ret;
ret = uclass_get(UCLASS_CLK, &uc);
if (ret)
return NULL;
uclass_foreach_dev(dev, uc) {
clkp = dev_get_clk_ptr(dev);
if (device_get_uclass_id(dev) == UCLASS_CLK && clkp &&
(strcmp(name, clkp->dev->name) == 0))
return clkp;
}
return NULL;
}
static int aml_clk_msr_check(const char *msr_name, unsigned long msr_rate,
unsigned long req_rate)
{
if (abs(req_rate - msr_rate) > MAX_CLK_MEASURE_TOL) {
log_err("%s: measure check failed, req_rate = %ld, msr_rate = %ld\n",
msr_name, req_rate, msr_rate);
return -EIO;
}
return 0;
}
static int aml_clk_mux_test(const char *clk_name, struct clk_mux *data,
const char *msr_name, int msr_id)
{
struct clk *clk = aml_clk_get_by_name(clk_name);
struct clk *msr_clk = aml_clk_get_by_name(msr_name);
struct clk *pclk;
int i, pnum = data->num_parents;
unsigned long last_msr_clk_rate = 0, msr_clk_rate, msr_rate;
if (!clk || !msr_clk || !data)
return -EINVAL;
for (i = 0; i < pnum; i++) {
pclk = aml_clk_get_by_name(data->parent_names[i]);
if (!pclk) {
log_notice("%s: can't find parent of %s \n", clk_name,
data->parent_names[i]);
continue;
}
clk_set_parent(clk, pclk);
msr_clk_rate = clk_get_rate(msr_clk);
if (msr_clk_rate == last_msr_clk_rate)
log_warning("%s: no change after switching parent\n",
clk_name);
clk_enable(msr_clk);
msr_rate = clk_util_clk_msr(msr_id);
clk_disable(msr_clk);
if (aml_clk_msr_check(msr_name, msr_rate, msr_clk_rate)) {
log_err("%s: test failed after switching parent to %s\n",
clk_name, data->parent_names[i]);
return -EIO;
}
last_msr_clk_rate = msr_clk_rate;
}
return 0;
}
static unsigned int clk_div_get_max_table_div(const struct clk_div_table *table)
{
const struct clk_div_table *clkt;
unsigned int max_div = 0;
for (clkt = table; clkt->div; clkt++)
if (clkt->div > max_div)
max_div = clkt->div;
return max_div;
}
static unsigned int _get_max_div(const struct clk_div_table *table,
unsigned long flags, u8 width)
{
int div_mask = clk_div_mask(width);
if (flags & CLK_DIVIDER_ONE_BASED)
return div_mask;
if (flags & CLK_DIVIDER_POWER_OF_TWO)
return 1 << div_mask;
if (flags & CLK_DIVIDER_MAX_AT_ZERO)
return div_mask + 1;
if (table)
return clk_div_get_max_table_div(table);
return div_mask + 1;
}
static int aml_clk_div_test(const char *clk_name, struct clk_divider *data,
const char *msr_name, int msr_id)
{
struct clk *clk = aml_clk_get_by_name(clk_name);
struct clk *msr_clk = aml_clk_get_by_name(msr_name);
struct clk *pclk;
unsigned long p_rate, last_msr_clk_rate = 0, msr_clk_rate, msr_rate;
int div;
if (!clk || !msr_clk || !data)
return -EINVAL;
pclk = clk_get_parent(clk);
p_rate = clk_get_rate(pclk);
// set min div
clk_set_rate(clk, p_rate);
msr_clk_rate = clk_get_rate(msr_clk);
clk_enable(msr_clk);
msr_rate = clk_util_clk_msr(msr_id);
clk_disable(msr_clk);
if (aml_clk_msr_check(msr_name, msr_rate, msr_clk_rate)) {
log_err("%s: test min division parameter failed\n",clk_name);
return -EIO;
}
last_msr_clk_rate = msr_clk_rate;
// set max div
div = _get_max_div(data->table, data->flags, data->width);
clk_set_rate(clk, p_rate / div);
msr_clk_rate = clk_get_rate(msr_clk);
if (msr_clk_rate == last_msr_clk_rate)
log_warning("%s: no change after setting rate?\n", clk_name);
clk_enable(msr_clk);
msr_rate = clk_util_clk_msr(msr_id);
clk_disable(msr_clk);
if (aml_clk_msr_check(msr_name, msr_rate, msr_clk_rate)) {
log_err("%s: test max division parameter failed\n",clk_name);
return -EIO;
}
return 0;
}
static int aml_clk_gate_test(const char *clk_name, const char *msr_name, int msr_id)
{
struct clk *clk = aml_clk_get_by_name(clk_name);
struct clk *msr_clk = aml_clk_get_by_name(msr_name);
unsigned long msr_clk_rate, msr_rate;
if (!clk || !msr_clk)
return -EINVAL;
msr_clk_rate = clk_get_rate(msr_clk);
clk_enable(msr_clk);
msr_rate = clk_util_clk_msr(msr_id);
if (aml_clk_msr_check(msr_name, msr_rate, msr_clk_rate)) {
log_err("%s: test gate enable failed\n",clk_name);
return -EIO;
}
clk_disable(msr_clk);
msr_rate = clk_util_clk_msr(msr_id);
if (aml_clk_msr_check(msr_name, msr_rate, 0)) {
log_err("%s: test gate disable failed\n",clk_name);
return -EIO;
}
return 0;
}
static int aml_clk_pll_test(const char *clk_name, struct aml_clk_pll *data,
const char *msr_name, int msr_id)
{
struct clk *clk = aml_clk_get_by_name(clk_name);
struct clk *msr_clk = aml_clk_get_by_name(msr_name);
unsigned long last_msr_clk_rate = 0, msr_clk_rate, msr_rate;
int i;
if (!clk || !msr_clk || !data)
return -EINVAL;
for (i = 0; i < data->table_count; i++) {
clk_set_rate(clk, data->table[i].rate);
msr_clk_rate = clk_get_rate(msr_clk);
if (msr_clk_rate == last_msr_clk_rate)
log_warning("%s: no change after setting rate?\n",
clk_name);
clk_enable(msr_clk);
msr_rate = clk_util_clk_msr(msr_id);
clk_disable(msr_clk);
if (aml_clk_msr_check(msr_name, msr_rate, msr_clk_rate)) {
log_err("%s: test failed after setting rate to %ld\n",
clk_name, data->table[i].rate);
return -EIO;
}
last_msr_clk_rate = msr_clk_rate;
}
return 0;
}
static int aml_clk_msr_test(const char *clk_name, const char *msr_name, int msr_id)
{
struct clk *clk = aml_clk_get_by_name(clk_name);
struct clk *msr_clk = aml_clk_get_by_name(msr_name);
unsigned long msr_clk_rate, msr_rate;
if (!clk || !msr_clk)
return -EINVAL;
msr_clk_rate = clk_get_rate(msr_clk);
msr_rate = clk_util_clk_msr(msr_id);
if (aml_clk_msr_check(msr_name, msr_rate, msr_clk_rate)) {
log_err("%s: msr test failed\n", clk_name);
return -EIO;
}
return 0;
}
struct aml_clk_test_data {
struct aml_clk_data *clk_data;
const char * msr_name;
int msr_id;
};
void aml_clk_test(const struct aml_clk_test_data *test_data, int num)
{
int i, ret;
struct aml_clk_data *clk_data;
struct clk *clk;
if (!test_data || !num)
return;
for (i = 0; i < num; i++) {
clk_data = test_data[i].clk_data;
clk = aml_clk_get_by_name(clk_data->name);
if (!clk) {
log_notice("can't find %s, skip testing it\n",
clk_data->name);
continue;
}
if (clk && clk->enable_count) {
log_notice("%s has been enabled, skip testing it\n",
clk_data->name);
continue;
}
switch (clk_data->type) {
case MUX:
ret = aml_clk_mux_test(clk_data->name,
&clk_data->data.mux,
test_data[i].msr_name,
test_data[i].msr_id);
break;
case DIVIDER:
ret = aml_clk_div_test(clk_data->name,
&clk_data->data.divider,
test_data[i].msr_name,
test_data[i].msr_id);
break;
case GATE:
ret = aml_clk_gate_test(clk_data->name,
test_data[i].msr_name,
test_data[i].msr_id);
break;
case PLL:
ret = aml_clk_pll_test(clk_data->name,
&clk_data->data.pll,
test_data[i].msr_name,
test_data[i].msr_id);
break;
default:
ret = aml_clk_msr_test(clk_data->name,
test_data[i].msr_name,
test_data[i].msr_id);
break;
}
if (ret)
log_err("%s: clock function test failed, ret = %d\n",
clk_data->name, ret);
}
}
#ifdef CONFIG_CLK_MESON_C4
#include <dt-bindings/amlogic/clock/c4-clkc.h>
extern struct aml_clk_data c4_clks[];
struct aml_clk_test_data c4_clk_test_data[] = {
{ &c4_clks[CLKID_FCLK_DIV2], "fclk_div2", 8 },
{ &c4_clks[CLKID_FCLK_DIV2P5], "fclk_div2p5", 9 },
{ &c4_clks[CLKID_FCLK_DIV3], "fclk_div3", 10 },
{ &c4_clks[CLKID_FCLK_DIV4], "fclk_div4", 11 },
{ &c4_clks[CLKID_FCLK_DIV5], "fclk_div5", 12 },
{ &c4_clks[CLKID_FCLK_DIV7], "fclk_div7", 13 },
{ &c4_clks[CLKID_SYS_CLK], "sys_clk", 0 },
{ &c4_clks[CLKID_GP0_PLL], "gp0_pll", 20 },
{ &c4_clks[CLKID_HIFI_PLL], "hifi_pll", 19 },
{ &c4_clks[CLKID_SAR_ADC_SEL], "sar_adc", 111 },
{ &c4_clks[CLKID_SAR_ADC_DIV], "sar_adc", 111 },
{ &c4_clks[CLKID_SAR_ADC], "sar_adc", 111 },
{ &c4_clks[CLKID_SPICC_0_SEL], "spicc_0", 118 },
{ &c4_clks[CLKID_SPICC_0_DIV], "spicc_0", 118 },
{ &c4_clks[CLKID_SPICC_0], "spicc_0", 118 },
{ &c4_clks[CLKID_SPICC_1_SEL], "spicc_1", 117 },
{ &c4_clks[CLKID_SPICC_1_DIV], "spicc_1", 117 },
{ &c4_clks[CLKID_SPICC_1], "spicc_1", 117 },
{ &c4_clks[CLKID_SD_EMMC_A_SEL], "sd_emmc_a", 115 },
{ &c4_clks[CLKID_SD_EMMC_A_DIV], "sd_emmc_a", 115 },
{ &c4_clks[CLKID_SD_EMMC_A], "sd_emmc_a", 115 },
{ &c4_clks[CLKID_SD_EMMC_B_SEL], "sd_emmc_b", 114 },
{ &c4_clks[CLKID_SD_EMMC_B_DIV], "sd_emmc_b", 114 },
{ &c4_clks[CLKID_SD_EMMC_B], "sd_emmc_b", 114 },
{ &c4_clks[CLKID_SD_EMMC_C_SEL], "sd_emmc_c", 113 },
{ &c4_clks[CLKID_SD_EMMC_C_DIV], "sd_emmc_c", 113 },
{ &c4_clks[CLKID_SD_EMMC_C], "sd_emmc_c", 113 },
{ &c4_clks[CLKID_ETH_RMII_DIV], "eth_rmii", 33 },
{ &c4_clks[CLKID_ETH_RMII], "eth_rmii", 33 },
{ &c4_clks[CLKID_TS_DIV], "ts", 112 },
{ &c4_clks[CLKID_TS], "ts", 112 },
};
#endif /* CONFIG_CLK_MESON_C4 */
#ifdef CONFIG_CLK_MESON_A9
#include <dt-bindings/amlogic/clock/a9-clkc.h>
extern struct aml_clk_data a9_clks[];
struct aml_clk_test_data a9_clk_test_data[] = {
{ &a9_clks[CLKID_FCLK_DIV5], "fclk_div5", 11 },
{ &a9_clks[CLKID_SYS_CLK], "sys_clk", 0 },
{ &a9_clks[CLKID_GP0_PLL], "gp0_pll", 20 },
{ &a9_clks[CLKID_HIFI_PLL], "hifi_pll", 19 },
{ &a9_clks[CLKID_SAR_ADC_SEL], "sar_adc", 111 },
{ &a9_clks[CLKID_SAR_ADC_DIV], "sar_adc", 111 },
{ &a9_clks[CLKID_SAR_ADC], "sar_adc", 111 },
{ &a9_clks[CLKID_SPISG_0_SEL], "spisg_0", 118 },
{ &a9_clks[CLKID_SPISG_0_DIV], "spisg_0", 118 },
{ &a9_clks[CLKID_SPISG_0], "spisg_0", 118 },
{ &a9_clks[CLKID_SPISG_1_SEL], "spisg_1", 105 },
{ &a9_clks[CLKID_SPISG_1_DIV], "spisg_1", 105 },
{ &a9_clks[CLKID_SPISG_1], "spisg_1", 105 },
{ &a9_clks[CLKID_SPISG_2_SEL], "spisg_2", 104 },
{ &a9_clks[CLKID_SPISG_2_DIV], "spisg_2", 104 },
{ &a9_clks[CLKID_SPISG_2], "spisg_2", 104 },
{ &a9_clks[CLKID_SD_EMMC_A_SEL], "sd_emmc_a", 115 },
{ &a9_clks[CLKID_SD_EMMC_A_DIV], "sd_emmc_a", 115 },
{ &a9_clks[CLKID_SD_EMMC_A], "sd_emmc_a", 115 },
{ &a9_clks[CLKID_SD_EMMC_B_SEL], "sd_emmc_b", 114 },
{ &a9_clks[CLKID_SD_EMMC_B_DIV], "sd_emmc_b", 114 },
{ &a9_clks[CLKID_SD_EMMC_B], "sd_emmc_b", 114 },
{ &a9_clks[CLKID_SD_EMMC_C_SEL], "sd_emmc_c", 113 },
{ &a9_clks[CLKID_SD_EMMC_C_DIV], "sd_emmc_c", 113 },
{ &a9_clks[CLKID_SD_EMMC_C], "sd_emmc_c", 113 },
{ &a9_clks[CLKID_ETH_RMII_DIV], "eth_rmii", 125 },
{ &a9_clks[CLKID_ETH_RMII], "eth_rmii", 125 },
{ &a9_clks[CLKID_TS_DIV], "ts", 121 },
{ &a9_clks[CLKID_TS], "ts", 121 },
{ &a9_clks[CLKID_AMFC_SEL], "amfc", 89 },
{ &a9_clks[CLKID_AMFC_DIV], "amfc", 89 },
{ &a9_clks[CLKID_AMFC], "amfc", 89 },
{ &a9_clks[CLKID_VPU_0_SEL], "vpu", 61 },
{ &a9_clks[CLKID_VPU_0_DIV], "vpu", 61 },
{ &a9_clks[CLKID_VPU_0], "vpu", 61 },
{ &a9_clks[CLKID_VPU_1_SEL], "vpu", 61 },
{ &a9_clks[CLKID_VPU_1_DIV], "vpu", 61 },
{ &a9_clks[CLKID_VPU_1], "vpu", 61 },
{ &a9_clks[CLKID_VPU], "vpu", 61 },
{ &a9_clks[CLKID_VAPB_0_SEL], "vapb", 66 },
{ &a9_clks[CLKID_VAPB_0_DIV], "vapb", 66 },
{ &a9_clks[CLKID_VAPB_0], "vapb", 66 },
{ &a9_clks[CLKID_VAPB_1_SEL], "vapb", 66 },
{ &a9_clks[CLKID_VAPB_1_DIV], "vapb", 66 },
{ &a9_clks[CLKID_VAPB_1], "vapb", 66 },
{ &a9_clks[CLKID_VAPB], "vapb", 66 },
{ &a9_clks[CLKID_GE2D], "ge2d", 67 },
};
#endif /* CONFIG_CLK_MESON_A9 */
struct aml_clk_test_data_info {
const struct aml_clk_test_data *data;
unsigned int num;
};
struct aml_clk_test_data_info aml_clk_test_data_info = {
#ifdef CONFIG_CLK_MESON_C4
.data = c4_clk_test_data,
.num = ARRAY_SIZE(c4_clk_test_data)
#ifdef CONFIG_CLK_MESON_A9
.data = a9_clk_test_data,
.num = ARRAY_SIZE(a9_clk_test_data)
#endif
};
#define SIP_SMC_SCMI_CMD 0x820000C1
#define SCMI_SUBID_TEST_SCMI_CLK 0x14
static int do_clktest_bl31(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct arm_smccc_res res;
arm_smccc_smc(SIP_SMC_SCMI_CMD, SCMI_SUBID_TEST_SCMI_CLK,
0, 0, 0, 0, 0, 0, &res);
return (int)res.a0;
}
static int do_clktest_bl33(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
aml_clk_test(aml_clk_test_data_info.data, aml_clk_test_data_info.num);
return 0;
}
static struct cmd_tbl cmd_clktest_sub[] = {
U_BOOT_CMD_MKENT(bl31, 1, 1, do_clktest_bl31, "", ""),
U_BOOT_CMD_MKENT(bl33, 1, 1, do_clktest_bl33, "", ""),
};
static int do_clktest(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
struct cmd_tbl *c;
if (argc < 2)
return CMD_RET_USAGE;
/* Strip off leading 'clktest' command argument */
argc--;
argv++;
c = find_cmd_tbl(argv[0], cmd_clktest_sub, ARRAY_SIZE(cmd_clktest_sub));
if (c)
return c->cmd(cmdtp, flag, argc, argv);
else
return CMD_RET_USAGE;
}
U_BOOT_CMD(clktest, 2, 1, do_clktest,
"Amlogic test clock",
" - Test all supported clocks.\n"
"bl31 - Test the clock driving function of BL31 (or BL32)\n"
"bl33 - Test the clock driving function of BL33\n"
);