[PATCH 1/4] clk/qcom: Print more details about stuck GDSC

Clearly say whether it's stuck during power on or power off.
Signed-off-by: Alexey Minnekhanov alexeymin@postmarketos.org --- drivers/clk/qcom/clock-qcom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/clk/qcom/clock-qcom.c b/drivers/clk/qcom/clock-qcom.c index 25ca67e537d1..1cfc430c14a5 100644 --- a/drivers/clk/qcom/clock-qcom.c +++ b/drivers/clk/qcom/clock-qcom.c @@ -496,8 +496,8 @@ static int qcom_power_set(struct power_domain *pwr, bool on) GDSC_STATUS_POLL_TIMEOUT_US);
if (ret == -ETIMEDOUT) - printf("WARNING: GDSC %lu is stuck during power on/off\n", - pwr->id); + printf("WARNING: GDSC %lu is stuck during power o%s\n", + pwr->id, on ? "n" : "ff"); return ret; }

Apparently not all GDSCs are the same. In Linux driver, depending on which GDSC flags are set, different status register is checked when powering it on/off. And on top of that, different bit inside that register is tested.
Port missing parts from Linux driver to U-Boot: - add GDSC flags; - adjust logic when checking GDSC status to match one in Linux driver.
This fixes e.g. SDM660's USB_30_GDSC to be forever stuck during power on, since unlike SDM845 one it doesn't have POLL_CFG_GDSCR flag.
Even though only POLL_CFG_GDSCR is the important one here, add all flags from Linux driver, to make it easier to compare which flags are needed for which GDSC, making porting process easier.
Signed-off-by: Alexey Minnekhanov alexeymin@postmarketos.org --- drivers/clk/qcom/clock-qcom.c | 45 ++++++++++++++++++++++++----------- drivers/clk/qcom/clock-qcom.h | 16 ++++++++++++- 2 files changed, 46 insertions(+), 15 deletions(-)
diff --git a/drivers/clk/qcom/clock-qcom.c b/drivers/clk/qcom/clock-qcom.c index 1cfc430c14a5..bf9249b55af3 100644 --- a/drivers/clk/qcom/clock-qcom.c +++ b/drivers/clk/qcom/clock-qcom.c @@ -461,7 +461,7 @@ static int qcom_power_set(struct power_domain *pwr, bool on) struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(pwr->dev); void __iomem *base = dev_get_priv(pwr->dev); const struct qcom_power_map *map; - u32 value; + u32 value, status_reg; int ret;
if (pwr->id >= data->num_power_domains) @@ -481,19 +481,36 @@ static int qcom_power_set(struct power_domain *pwr, bool on)
writel(value, base + map->reg);
- if (on) - ret = readl_poll_timeout(base + map->reg + CFG_GDSCR_OFFSET, - value, - (value & GDSC_POWER_UP_COMPLETE) || - (value & GDSC_PWR_ON_MASK), - GDSC_STATUS_POLL_TIMEOUT_US); - - else - ret = readl_poll_timeout(base + map->reg + CFG_GDSCR_OFFSET, - value, - (value & GDSC_POWER_DOWN_COMPLETE) || - !(value & GDSC_PWR_ON_MASK), - GDSC_STATUS_POLL_TIMEOUT_US); + /* depending on the type of gdsc the status register is different */ + /* and we need to check different status bit */ + if (map->flags & POLL_CFG_GDSCR) { + status_reg = map->reg + CFG_GDSCR_OFFSET; + + if (on) + ret = readl_poll_timeout(base + status_reg, + value, + (value & GDSC_POWER_UP_COMPLETE), + GDSC_STATUS_POLL_TIMEOUT_US); + else + ret = readl_poll_timeout(base + status_reg, + value, + (value & GDSC_POWER_DOWN_COMPLETE), + GDSC_STATUS_POLL_TIMEOUT_US); + } else { + status_reg = map->reg; + + if (on) + ret = readl_poll_timeout(base + status_reg, + value, + (value & GDSC_PWR_ON_MASK), + GDSC_STATUS_POLL_TIMEOUT_US); + + else + ret = readl_poll_timeout(base + status_reg, + value, + !(value & GDSC_PWR_ON_MASK), + GDSC_STATUS_POLL_TIMEOUT_US); + }
if (ret == -ETIMEDOUT) printf("WARNING: GDSC %lu is stuck during power o%s\n", diff --git a/drivers/clk/qcom/clock-qcom.h b/drivers/clk/qcom/clock-qcom.h index 78d9b1d81ece..84965b2555c7 100644 --- a/drivers/clk/qcom/clock-qcom.h +++ b/drivers/clk/qcom/clock-qcom.h @@ -63,8 +63,22 @@ struct qcom_reset_map { u8 bit; };
+enum qcom_gdsc_flags { + VOTABLE = BIT(0), + CLAMP_IO = BIT(1), + HW_CTRL = BIT(2), + SW_RESET = BIT(3), + AON_RESET = BIT(4), + POLL_CFG_GDSCR = BIT(5), + ALWAYS_ON = BIT(6), + RETAIN_FF_ENABLE = BIT(7), + NO_RET_PERIPH = BIT(8), + HW_CTRL_TRIGGER = BIT(9), +}; + struct qcom_power_map { - unsigned int reg; + unsigned int reg; /* GDSCR */ + unsigned int flags; };
struct clk;

Hi Alexey,
Thanks for the patch! Really happy to see support for this new platform.
On 08/01/2025 12:59, Alexey Minnekhanov wrote:
Apparently not all GDSCs are the same. In Linux driver, depending on which GDSC flags are set, different status register is checked when powering it on/off. And on top of that, different bit inside that register is tested.
Port missing parts from Linux driver to U-Boot:
- add GDSC flags;
- adjust logic when checking GDSC status to match one in Linux driver.
Sorry it's a bit of a pain, but could you split this into two patch and adjust the ordering so we don't risk breaking someones bisect if they land here. I'd propose:
Patch 1: as-is Patch 2: add the flags enum and adjust struct qcom_power_map Patch 3: would be the "port to new flags" patch Patch 4: adjust qcom_power_set() (the bulk of this patch) Patch 5: add sdm660 clock driver
does this make sense?
This fixes e.g. SDM660's USB_30_GDSC to be forever stuck during power on, since unlike SDM845 one it doesn't have POLL_CFG_GDSCR flag.
Even though only POLL_CFG_GDSCR is the important one here, add all flags from Linux driver, to make it easier to compare which flags are needed for which GDSC, making porting process easier.
This is great, thanks for taking the time to add all of these.
Kind regards,
Signed-off-by: Alexey Minnekhanov alexeymin@postmarketos.org
drivers/clk/qcom/clock-qcom.c | 45 ++++++++++++++++++++++++----------- drivers/clk/qcom/clock-qcom.h | 16 ++++++++++++- 2 files changed, 46 insertions(+), 15 deletions(-)
diff --git a/drivers/clk/qcom/clock-qcom.c b/drivers/clk/qcom/clock-qcom.c index 1cfc430c14a5..bf9249b55af3 100644 --- a/drivers/clk/qcom/clock-qcom.c +++ b/drivers/clk/qcom/clock-qcom.c @@ -461,7 +461,7 @@ static int qcom_power_set(struct power_domain *pwr, bool on) struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(pwr->dev); void __iomem *base = dev_get_priv(pwr->dev); const struct qcom_power_map *map;
- u32 value;
u32 value, status_reg; int ret;
if (pwr->id >= data->num_power_domains)
@@ -481,19 +481,36 @@ static int qcom_power_set(struct power_domain *pwr, bool on)
writel(value, base + map->reg);
- if (on)
ret = readl_poll_timeout(base + map->reg + CFG_GDSCR_OFFSET,
value,
(value & GDSC_POWER_UP_COMPLETE) ||
(value & GDSC_PWR_ON_MASK),
GDSC_STATUS_POLL_TIMEOUT_US);
- else
ret = readl_poll_timeout(base + map->reg + CFG_GDSCR_OFFSET,
value,
(value & GDSC_POWER_DOWN_COMPLETE) ||
!(value & GDSC_PWR_ON_MASK),
GDSC_STATUS_POLL_TIMEOUT_US);
/* depending on the type of gdsc the status register is different */
/* and we need to check different status bit */
if (map->flags & POLL_CFG_GDSCR) {
status_reg = map->reg + CFG_GDSCR_OFFSET;
if (on)
ret = readl_poll_timeout(base + status_reg,
value,
(value & GDSC_POWER_UP_COMPLETE),
GDSC_STATUS_POLL_TIMEOUT_US);
else
ret = readl_poll_timeout(base + status_reg,
value,
(value & GDSC_POWER_DOWN_COMPLETE),
GDSC_STATUS_POLL_TIMEOUT_US);
} else {
status_reg = map->reg;
if (on)
ret = readl_poll_timeout(base + status_reg,
value,
(value & GDSC_PWR_ON_MASK),
GDSC_STATUS_POLL_TIMEOUT_US);
else
ret = readl_poll_timeout(base + status_reg,
value,
!(value & GDSC_PWR_ON_MASK),
GDSC_STATUS_POLL_TIMEOUT_US);
}
if (ret == -ETIMEDOUT) printf("WARNING: GDSC %lu is stuck during power o%s\n",
diff --git a/drivers/clk/qcom/clock-qcom.h b/drivers/clk/qcom/clock-qcom.h index 78d9b1d81ece..84965b2555c7 100644 --- a/drivers/clk/qcom/clock-qcom.h +++ b/drivers/clk/qcom/clock-qcom.h @@ -63,8 +63,22 @@ struct qcom_reset_map { u8 bit; };
+enum qcom_gdsc_flags {
- VOTABLE = BIT(0),
- CLAMP_IO = BIT(1),
- HW_CTRL = BIT(2),
- SW_RESET = BIT(3),
- AON_RESET = BIT(4),
- POLL_CFG_GDSCR = BIT(5),
- ALWAYS_ON = BIT(6),
- RETAIN_FF_ENABLE = BIT(7),
- NO_RET_PERIPH = BIT(8),
- HW_CTRL_TRIGGER = BIT(9),
+};
struct qcom_power_map {
- unsigned int reg;
- unsigned int reg; /* GDSCR */
- unsigned int flags;
};
struct clk;

Now we need to specify flags for each GDSC for it to function properly. Copy .flags field from respective Linux GCC driver.
Turns out, even among SDM845 not all GDSCs are the same. Some of GDSCs do specify the POLL_CFG_GDSCR flag that changes the logic to check the ON/OFF status, other don't.
Some qcom clock drivers in U-Boot don't specify any GDSCs at all (apq8016, apq8096, ipq4019, qcs404), or GDSCs in their linux GCC driver don't specify any flags (qcm2290, sm6115) thus making them unaffected by this change.
Add flags to all other SoC's GDSCs currently present in U-Boot that do need this change.
Signed-off-by: Alexey Minnekhanov alexeymin@postmarketos.org --- drivers/clk/qcom/clock-sc7280.c | 4 ++-- drivers/clk/qcom/clock-sdm845.c | 26 +++++++++++++------------- drivers/clk/qcom/clock-sm8150.c | 14 +++++++------- drivers/clk/qcom/clock-sm8550.c | 16 ++++++++-------- drivers/clk/qcom/clock-sm8650.c | 16 ++++++++-------- 5 files changed, 38 insertions(+), 38 deletions(-)
diff --git a/drivers/clk/qcom/clock-sc7280.c b/drivers/clk/qcom/clock-sc7280.c index 5d343f120519..cacad0eda164 100644 --- a/drivers/clk/qcom/clock-sc7280.c +++ b/drivers/clk/qcom/clock-sc7280.c @@ -98,8 +98,8 @@ static const struct qcom_reset_map sc7280_gcc_resets[] = { };
static const struct qcom_power_map sc7280_gdscs[] = { - [GCC_UFS_PHY_GDSC] = { 0x77004 }, - [GCC_USB30_PRIM_GDSC] = { 0xf004 }, + [GCC_UFS_PHY_GDSC] = { 0x77004, .flags = VOTABLE }, + [GCC_USB30_PRIM_GDSC] = { 0xf004, .flags = VOTABLE }, };
static struct msm_clk_data qcs404_gcc_data = { diff --git a/drivers/clk/qcom/clock-sdm845.c b/drivers/clk/qcom/clock-sdm845.c index adffb0cb2402..53068bd3baa4 100644 --- a/drivers/clk/qcom/clock-sdm845.c +++ b/drivers/clk/qcom/clock-sdm845.c @@ -188,19 +188,19 @@ static const struct qcom_reset_map sdm845_gcc_resets[] = { };
static const struct qcom_power_map sdm845_gdscs[] = { - [PCIE_0_GDSC] = { 0x6b004 }, - [PCIE_1_GDSC] = { 0x8d004 }, - [UFS_CARD_GDSC] = { 0x75004 }, - [UFS_PHY_GDSC] = { 0x77004 }, - [USB30_PRIM_GDSC] = { 0xf004 }, - [USB30_SEC_GDSC] = { 0x10004 }, - [HLOS1_VOTE_AGGRE_NOC_MMU_AUDIO_TBU_GDSC] = { 0x7d030 }, - [HLOS1_VOTE_AGGRE_NOC_MMU_PCIE_TBU_GDSC] = { 0x7d03c }, - [HLOS1_VOTE_AGGRE_NOC_MMU_TBU1_GDSC] = { 0x7d034 }, - [HLOS1_VOTE_AGGRE_NOC_MMU_TBU2_GDSC] = { 0x7d038 }, - [HLOS1_VOTE_MMNOC_MMU_TBU_HF0_GDSC] = { 0x7d040 }, - [HLOS1_VOTE_MMNOC_MMU_TBU_HF1_GDSC] = { 0x7d048 }, - [HLOS1_VOTE_MMNOC_MMU_TBU_SF_GDSC] = { 0x7d044 }, + [PCIE_0_GDSC] = { 0x6b004, .flags = POLL_CFG_GDSCR }, + [PCIE_1_GDSC] = { 0x8d004, .flags = POLL_CFG_GDSCR }, + [UFS_CARD_GDSC] = { 0x75004, .flags = POLL_CFG_GDSCR }, + [UFS_PHY_GDSC] = { 0x77004, .flags = POLL_CFG_GDSCR }, + [USB30_PRIM_GDSC] = { 0xf004, .flags = POLL_CFG_GDSCR }, + [USB30_SEC_GDSC] = { 0x10004, .flags = POLL_CFG_GDSCR }, + [HLOS1_VOTE_AGGRE_NOC_MMU_AUDIO_TBU_GDSC] = { 0x7d030, .flags = VOTABLE }, + [HLOS1_VOTE_AGGRE_NOC_MMU_PCIE_TBU_GDSC] = { 0x7d03c, .flags = VOTABLE }, + [HLOS1_VOTE_AGGRE_NOC_MMU_TBU1_GDSC] = { 0x7d034, .flags = VOTABLE }, + [HLOS1_VOTE_AGGRE_NOC_MMU_TBU2_GDSC] = { 0x7d038, .flags = VOTABLE }, + [HLOS1_VOTE_MMNOC_MMU_TBU_HF0_GDSC] = { 0x7d040, .flags = VOTABLE }, + [HLOS1_VOTE_MMNOC_MMU_TBU_HF1_GDSC] = { 0x7d048, .flags = VOTABLE }, + [HLOS1_VOTE_MMNOC_MMU_TBU_SF_GDSC] = { 0x7d044, .flags = VOTABLE }, };
static const phys_addr_t sdm845_gpll_addrs[] = { diff --git a/drivers/clk/qcom/clock-sm8150.c b/drivers/clk/qcom/clock-sm8150.c index 88f2e678f43c..3bff73de0481 100644 --- a/drivers/clk/qcom/clock-sm8150.c +++ b/drivers/clk/qcom/clock-sm8150.c @@ -281,13 +281,13 @@ static const struct qcom_reset_map sm8150_gcc_resets[] = { };
static const struct qcom_power_map sm8150_gcc_power_domains[] = { - [EMAC_GDSC] = { 0x6004 }, - [PCIE_0_GDSC] = { 0x6b004 }, - [PCIE_1_GDSC] = { 0x8d004 }, - [UFS_CARD_GDSC] = { 0x75004 }, - [UFS_PHY_GDSC] = { 0x77004 }, - [USB30_PRIM_GDSC] = { 0xf004 }, - [USB30_SEC_GDSC] = { 0x10004 }, + [EMAC_GDSC] = { 0x6004, .flags = POLL_CFG_GDSCR }, + [PCIE_0_GDSC] = { 0x6b004, .flags = POLL_CFG_GDSCR }, + [PCIE_1_GDSC] = { 0x8d004, .flags = POLL_CFG_GDSCR }, + [UFS_CARD_GDSC] = { 0x75004, .flags = POLL_CFG_GDSCR }, + [UFS_PHY_GDSC] = { 0x77004, .flags = POLL_CFG_GDSCR }, + [USB30_PRIM_GDSC] = { 0xf004, .flags = POLL_CFG_GDSCR }, + [USB30_SEC_GDSC] = { 0x10004, .flags = POLL_CFG_GDSCR }, };
static struct msm_clk_data sm8150_clk_data = { diff --git a/drivers/clk/qcom/clock-sm8550.c b/drivers/clk/qcom/clock-sm8550.c index c0249925cc7e..d8368688afaa 100644 --- a/drivers/clk/qcom/clock-sm8550.c +++ b/drivers/clk/qcom/clock-sm8550.c @@ -229,14 +229,14 @@ static const struct qcom_reset_map sm8550_gcc_resets[] = { };
static const struct qcom_power_map sm8550_gdscs[] = { - [PCIE_0_GDSC] = { 0x6b004 }, - [PCIE_0_PHY_GDSC] = { 0x6c000 }, - [PCIE_1_GDSC] = { 0x8d004 }, - [PCIE_1_PHY_GDSC] = { 0x8e000 }, - [UFS_PHY_GDSC] = { 0x77004 }, - [UFS_MEM_PHY_GDSC] = { 0x9e000 }, - [USB30_PRIM_GDSC] = { 0x39004 }, - [USB3_PHY_GDSC] = { 0x50018 }, + [PCIE_0_GDSC] = { 0x6b004, .flags = VOTABLE | POLL_CFG_GDSCR | RETAIN_FF_ENABLE }, + [PCIE_0_PHY_GDSC] = { 0x6c000, .flags = VOTABLE | POLL_CFG_GDSCR | RETAIN_FF_ENABLE }, + [PCIE_1_GDSC] = { 0x8d004, .flags = VOTABLE | POLL_CFG_GDSCR | RETAIN_FF_ENABLE }, + [PCIE_1_PHY_GDSC] = { 0x8e000, .flags = VOTABLE | POLL_CFG_GDSCR | RETAIN_FF_ENABLE }, + [UFS_PHY_GDSC] = { 0x77004, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE }, + [UFS_MEM_PHY_GDSC] = { 0x9e000, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE }, + [USB30_PRIM_GDSC] = { 0x39004, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE }, + [USB3_PHY_GDSC] = { 0x50018, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE }, };
static struct msm_clk_data sm8550_gcc_data = { diff --git a/drivers/clk/qcom/clock-sm8650.c b/drivers/clk/qcom/clock-sm8650.c index 0ce83e9b2437..e5b8e6a10be3 100644 --- a/drivers/clk/qcom/clock-sm8650.c +++ b/drivers/clk/qcom/clock-sm8650.c @@ -226,14 +226,14 @@ static const struct qcom_reset_map sm8650_gcc_resets[] = { };
static const struct qcom_power_map sm8650_gdscs[] = { - [PCIE_0_GDSC] = { 0x6b004 }, - [PCIE_0_PHY_GDSC] = { 0x6c000 }, - [PCIE_1_GDSC] = { 0x8d004 }, - [PCIE_1_PHY_GDSC] = { 0x8e000 }, - [UFS_PHY_GDSC] = { 0x77004 }, - [UFS_MEM_PHY_GDSC] = { 0x9e000 }, - [USB30_PRIM_GDSC] = { 0x39004 }, - [USB3_PHY_GDSC] = { 0x50018 }, + [PCIE_0_GDSC] = { 0x6b004, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE | VOTABLE }, + [PCIE_0_PHY_GDSC] = { 0x6c000, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE | VOTABLE }, + [PCIE_1_GDSC] = { 0x8d004, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE | VOTABLE }, + [PCIE_1_PHY_GDSC] = { 0x8e000, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE | VOTABLE }, + [UFS_PHY_GDSC] = { 0x77004, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE }, + [UFS_MEM_PHY_GDSC] = { 0x9e000, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE }, + [USB30_PRIM_GDSC] = { 0x39004, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE }, + [USB3_PHY_GDSC] = { 0x50018, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE }, };
static struct msm_clk_data sm8650_gcc_data = {

Add driver for Global Clock Controller used in Qualcomm SDM630/660 SoCs.
Signed-off-by: Alexey Minnekhanov alexeymin@postmarketos.org --- drivers/clk/qcom/Kconfig | 8 ++ drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/clock-sdm660.c | 180 ++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 drivers/clk/qcom/clock-sdm660.c
diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index d76fca5dba41..29767ab256f3 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -47,6 +47,14 @@ config CLK_QCOM_QCS404 on the Snapdragon QCS404 SoC. This driver supports the clocks and resets exposed by the GCC hardware block.
+config CLK_QCOM_SDM660 + bool "Qualcomm SDM630/660 GCC" + select CLK_QCOM + help + Say Y here to enable support for the Global Clock Controller + on the Snapdragon 630/660 SoCs. This driver supports the clocks + and resets exposed by the GCC hardware block. + config CLK_QCOM_SDM845 bool "Qualcomm SDM845 GCC" select CLK_QCOM diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index ab33f1c5faf9..3065ba90cb7a 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -3,6 +3,7 @@ # (C) Copyright 2023 Linaro
obj-y += clock-qcom.o +obj-$(CONFIG_CLK_QCOM_SDM660) += clock-sdm660.o obj-$(CONFIG_CLK_QCOM_SDM845) += clock-sdm845.o obj-$(CONFIG_CLK_QCOM_APQ8016) += clock-apq8016.o obj-$(CONFIG_CLK_QCOM_APQ8096) += clock-apq8096.o diff --git a/drivers/clk/qcom/clock-sdm660.c b/drivers/clk/qcom/clock-sdm660.c new file mode 100644 index 000000000000..c4cd49f39a57 --- /dev/null +++ b/drivers/clk/qcom/clock-sdm660.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Clock drivers for Qualcomm sdm630/660 + */ + +#include <clk-uclass.h> +#include <dm.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <linux/bitops.h> +#include <linux/bug.h> +#include <dt-bindings/clock/qcom,gcc-sdm660.h> + +#include "clock-qcom.h" + +#define GCC_BLSP1_UART2_APPS_CLK_CMD_RCGR 0x1c00c +#define SDCC1_APPS_CLK_CMD_RCGR 0x1602c +#define SDCC2_APPS_CLK_CMD_RCGR 0x14010 + +/* blsp1_uart{1,2}_apps_clk_src share the same frequency table */ +static const struct freq_tbl ftbl_blsp1_uart1_apps_clk_src[] = { + F(3686400, CFG_CLK_SRC_GPLL0, 1, 96, 15625), + F(7372800, CFG_CLK_SRC_GPLL0, 1, 192, 15625), + F(14745600, CFG_CLK_SRC_GPLL0, 1, 384, 15625), + F(16000000, CFG_CLK_SRC_GPLL0, 5, 2, 15), + F(19200000, CFG_CLK_SRC_CXO, 1, 0, 0), + F(24000000, CFG_CLK_SRC_GPLL0, 5, 1, 5), + F(32000000, CFG_CLK_SRC_GPLL0, 1, 4, 75), + F(40000000, CFG_CLK_SRC_GPLL0, 15, 0, 0), + F(46400000, CFG_CLK_SRC_GPLL0, 1, 29, 375), + F(48000000, CFG_CLK_SRC_GPLL0, 12.5, 0, 0), + F(51200000, CFG_CLK_SRC_GPLL0, 1, 32, 375), + F(56000000, CFG_CLK_SRC_GPLL0, 1, 7, 75), + F(58982400, CFG_CLK_SRC_GPLL0, 1, 1536, 15625), + F(60000000, CFG_CLK_SRC_GPLL0, 10, 0, 0), + F(63157895, CFG_CLK_SRC_GPLL0, 9.5, 0, 0), + { } +}; + +/* + * In Linux's parent_map for this clock ( gcc_parent_map_xo_gpll0_gpll0_early_div_gpll4 ): + * { P_GPLL0_EARLY_DIV, 2 } + */ +#define CFG_CLK_SRC_GPLL0_EARLY_DIV_SDCC2 (2 << 8) + +static const struct freq_tbl ftbl_sdcc2_apps_clk_src[] = { + F(144000, CFG_CLK_SRC_CXO, 16, 3, 25), + F(400000, CFG_CLK_SRC_CXO, 12, 1, 4), + F(20000000, CFG_CLK_SRC_GPLL0_EARLY_DIV_SDCC2, 5, 1, 3), + F(25000000, CFG_CLK_SRC_GPLL0_EARLY_DIV_SDCC2, 6, 1, 2), + F(50000000, CFG_CLK_SRC_GPLL0_EARLY_DIV_SDCC2, 6, 0, 0), + F(100000000, CFG_CLK_SRC_GPLL0, 6, 0, 0), + F(192000000, CFG_CLK_SRC_GPLL4, 8, 0, 0), + F(200000000, CFG_CLK_SRC_GPLL0, 3, 0, 0), + { } +}; + +static const struct pll_vote_clk gpll0_clk = { + .status = 0, + .status_bit = BIT(31), + .ena_vote = 0x52000, + .vote_bit = BIT(0), +}; + +static const struct gate_clk sdm660_clks[] = { + GATE_CLK(GCC_BLSP1_AHB_CLK, 0x52004, BIT(17)), + GATE_CLK(GCC_SDCC1_AHB_CLK, 0x16008, BIT(0)), + GATE_CLK(GCC_SDCC1_APPS_CLK, 0x16004, BIT(0)), + GATE_CLK(GCC_SDCC1_ICE_CORE_CLK, 0x1600c, BIT(0)), + GATE_CLK(GCC_SDCC2_AHB_CLK, 0x14008, BIT(0)), + GATE_CLK(GCC_SDCC2_APPS_CLK, 0x14004, BIT(0)), +}; + +static ulong sdm660_gcc_set_rate(struct clk *clk, ulong rate) +{ + struct msm_clk_priv *priv = dev_get_priv(clk->dev); + const struct freq_tbl *ftbl_entry; + + debug("%s: clk %s rate %lu\n", __func__, sdm660_clks[clk->id].name, rate); + + switch (clk->id) { + case GCC_BLSP1_UART2_APPS_CLK: + ftbl_entry = qcom_find_freq(ftbl_blsp1_uart1_apps_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, GCC_BLSP1_UART2_APPS_CLK_CMD_RCGR, + ftbl_entry->pre_div, ftbl_entry->m, ftbl_entry->n, + ftbl_entry->src, 8); + return ftbl_entry->freq; + case GCC_SDCC2_APPS_CLK: + ftbl_entry = qcom_find_freq(ftbl_sdcc2_apps_clk_src, rate); + /* we probably should enable source PLL for the selected frequency */ + switch (ftbl_entry->src) { + case CFG_CLK_SRC_GPLL0: + /* enable gpll0 */ + clk_enable_gpll0(priv->base, &gpll0_clk); + break; + case CFG_CLK_SRC_GPLL4: + /* enable gpll4 */ + debug("can't use GPLL4 as src for SDCC2_APPS_CLK, req rate: %lu", rate); + break; + case CFG_CLK_SRC_GPLL0_EARLY_DIV_SDCC2: + /* enable gpll0_early */ + debug("can't use GPLL0_EARLY as src for SDCC2_APPS_CLK, req rate: %lu", + rate); + break; + } + clk_rcg_set_rate_mnd(priv->base, SDCC2_APPS_CLK_CMD_RCGR, + ftbl_entry->pre_div, ftbl_entry->m, ftbl_entry->n, + ftbl_entry->src, 8); + return ftbl_entry->freq; + case GCC_SDCC1_APPS_CLK: + /* The firmware turns this on for us and always sets it to this rate */ + return 384000000; + default: + debug("clock-sdm660: set_rate for some unknown clock %lu\n", clk->id); + return rate; + } +} + +static int sdm660_gcc_enable(struct clk *clk) +{ + struct msm_clk_priv *priv = dev_get_priv(clk->dev); + + if (priv->data->num_clks < clk->id) { + debug("unknown clk id %lu\n", clk->id); + return 0; + } + + debug("enable clk %s\n", sdm660_clks[clk->id].name); + qcom_gate_clk_en(priv, clk->id); + + return 0; +} + +static const struct qcom_reset_map sdm660_gcc_resets[] = { + [GCC_QUSB2PHY_PRIM_BCR] = { 0x12000 }, + [GCC_QUSB2PHY_SEC_BCR] = { 0x12004 }, + /* + * Linux dt-bindings (and sdm630 dtsi) don't have the defines for + * (or uses of) GCC_SDCCn_BCR resets, so this won't compile, + * but the hardware exists and their offsets are: + */ + /* [GCC_SDCC1_BCR] = { 0x16000 }, */ + /* [GCC_SDCC2_BCR] = { 0x14000 }, */ + [GCC_USB_20_BCR] = { 0x2f000 }, + [GCC_USB_30_BCR] = { 0xf000 }, + [GCC_USB3_PHY_BCR] = { 0x50020 }, + [GCC_USB3PHY_PHY_BCR] = { 0x50024 }, +}; + +static const struct qcom_power_map sdm660_gdscs[] = { + [USB_30_GDSC] = { .reg = 0xf004, .flags = VOTABLE }, +}; + +static struct msm_clk_data sdm660_gcc_data = { + .resets = sdm660_gcc_resets, + .num_resets = ARRAY_SIZE(sdm660_gcc_resets), + .clks = sdm660_clks, + .num_clks = ARRAY_SIZE(sdm660_clks), + .power_domains = sdm660_gdscs, + .num_power_domains = ARRAY_SIZE(sdm660_gdscs), + + .enable = sdm660_gcc_enable, + .set_rate = sdm660_gcc_set_rate, +}; + +static const struct udevice_id gcc_sdm660_of_match[] = { + { + .compatible = "qcom,gcc-sdm660", + .data = (ulong)&sdm660_gcc_data, + }, + {} +}; + +U_BOOT_DRIVER(gcc_sdm660) = { + .name = "gcc_sdm660", + .id = UCLASS_NOP, + .of_match = gcc_sdm660_of_match, + .bind = qcom_cc_bind, + .flags = DM_FLAG_PRE_RELOC, +};

Hi Alexey,
On 08/01/2025 12:59, Alexey Minnekhanov wrote:
Add driver for Global Clock Controller used in Qualcomm SDM630/660 SoCs.
Signed-off-by: Alexey Minnekhanov alexeymin@postmarketos.org
drivers/clk/qcom/Kconfig | 8 ++ drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/clock-sdm660.c | 180 ++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 drivers/clk/qcom/clock-sdm660.c
diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index d76fca5dba41..29767ab256f3 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -47,6 +47,14 @@ config CLK_QCOM_QCS404 on the Snapdragon QCS404 SoC. This driver supports the clocks and resets exposed by the GCC hardware block.
+config CLK_QCOM_SDM660
- bool "Qualcomm SDM630/660 GCC"
- select CLK_QCOM
- help
Say Y here to enable support for the Global Clock Controller
on the Snapdragon 630/660 SoCs. This driver supports the clocks
and resets exposed by the GCC hardware block.
config CLK_QCOM_SDM845 bool "Qualcomm SDM845 GCC" select CLK_QCOM diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index ab33f1c5faf9..3065ba90cb7a 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -3,6 +3,7 @@ # (C) Copyright 2023 Linaro
obj-y += clock-qcom.o +obj-$(CONFIG_CLK_QCOM_SDM660) += clock-sdm660.o obj-$(CONFIG_CLK_QCOM_SDM845) += clock-sdm845.o obj-$(CONFIG_CLK_QCOM_APQ8016) += clock-apq8016.o obj-$(CONFIG_CLK_QCOM_APQ8096) += clock-apq8096.o diff --git a/drivers/clk/qcom/clock-sdm660.c b/drivers/clk/qcom/clock-sdm660.c new file mode 100644 index 000000000000..c4cd49f39a57 --- /dev/null +++ b/drivers/clk/qcom/clock-sdm660.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: BSD-3-Clause +/*
- Clock drivers for Qualcomm sdm630/660
- */
+#include <clk-uclass.h> +#include <dm.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <linux/bitops.h> +#include <linux/bug.h> +#include <dt-bindings/clock/qcom,gcc-sdm660.h>
+#include "clock-qcom.h"
+#define GCC_BLSP1_UART2_APPS_CLK_CMD_RCGR 0x1c00c +#define SDCC1_APPS_CLK_CMD_RCGR 0x1602c +#define SDCC2_APPS_CLK_CMD_RCGR 0x14010
+/* blsp1_uart{1,2}_apps_clk_src share the same frequency table */ +static const struct freq_tbl ftbl_blsp1_uart1_apps_clk_src[] = {
- F(3686400, CFG_CLK_SRC_GPLL0, 1, 96, 15625),
- F(7372800, CFG_CLK_SRC_GPLL0, 1, 192, 15625),
- F(14745600, CFG_CLK_SRC_GPLL0, 1, 384, 15625),
- F(16000000, CFG_CLK_SRC_GPLL0, 5, 2, 15),
- F(19200000, CFG_CLK_SRC_CXO, 1, 0, 0),
- F(24000000, CFG_CLK_SRC_GPLL0, 5, 1, 5),
- F(32000000, CFG_CLK_SRC_GPLL0, 1, 4, 75),
- F(40000000, CFG_CLK_SRC_GPLL0, 15, 0, 0),
- F(46400000, CFG_CLK_SRC_GPLL0, 1, 29, 375),
- F(48000000, CFG_CLK_SRC_GPLL0, 12.5, 0, 0),
- F(51200000, CFG_CLK_SRC_GPLL0, 1, 32, 375),
- F(56000000, CFG_CLK_SRC_GPLL0, 1, 7, 75),
- F(58982400, CFG_CLK_SRC_GPLL0, 1, 1536, 15625),
- F(60000000, CFG_CLK_SRC_GPLL0, 10, 0, 0),
- F(63157895, CFG_CLK_SRC_GPLL0, 9.5, 0, 0),
- { }
+};
+/*
- In Linux's parent_map for this clock ( gcc_parent_map_xo_gpll0_gpll0_early_div_gpll4 ):
- { P_GPLL0_EARLY_DIV, 2 }
- */
+#define CFG_CLK_SRC_GPLL0_EARLY_DIV_SDCC2 (2 << 8)
This can go into clock-qcom.h with the others.
With that, please add
Reviewed-by: Caleb Connolly caleb.connolly@linaro.org
Kind regards,
+static const struct freq_tbl ftbl_sdcc2_apps_clk_src[] = {
- F(144000, CFG_CLK_SRC_CXO, 16, 3, 25),
- F(400000, CFG_CLK_SRC_CXO, 12, 1, 4),
- F(20000000, CFG_CLK_SRC_GPLL0_EARLY_DIV_SDCC2, 5, 1, 3),
- F(25000000, CFG_CLK_SRC_GPLL0_EARLY_DIV_SDCC2, 6, 1, 2),
- F(50000000, CFG_CLK_SRC_GPLL0_EARLY_DIV_SDCC2, 6, 0, 0),
- F(100000000, CFG_CLK_SRC_GPLL0, 6, 0, 0),
- F(192000000, CFG_CLK_SRC_GPLL4, 8, 0, 0),
- F(200000000, CFG_CLK_SRC_GPLL0, 3, 0, 0),
- { }
+};
+static const struct pll_vote_clk gpll0_clk = {
- .status = 0,
- .status_bit = BIT(31),
- .ena_vote = 0x52000,
- .vote_bit = BIT(0),
+};
+static const struct gate_clk sdm660_clks[] = {
- GATE_CLK(GCC_BLSP1_AHB_CLK, 0x52004, BIT(17)),
- GATE_CLK(GCC_SDCC1_AHB_CLK, 0x16008, BIT(0)),
- GATE_CLK(GCC_SDCC1_APPS_CLK, 0x16004, BIT(0)),
- GATE_CLK(GCC_SDCC1_ICE_CORE_CLK, 0x1600c, BIT(0)),
- GATE_CLK(GCC_SDCC2_AHB_CLK, 0x14008, BIT(0)),
- GATE_CLK(GCC_SDCC2_APPS_CLK, 0x14004, BIT(0)),
+};
+static ulong sdm660_gcc_set_rate(struct clk *clk, ulong rate) +{
- struct msm_clk_priv *priv = dev_get_priv(clk->dev);
- const struct freq_tbl *ftbl_entry;
- debug("%s: clk %s rate %lu\n", __func__, sdm660_clks[clk->id].name, rate);
- switch (clk->id) {
- case GCC_BLSP1_UART2_APPS_CLK:
ftbl_entry = qcom_find_freq(ftbl_blsp1_uart1_apps_clk_src, rate);
clk_rcg_set_rate_mnd(priv->base, GCC_BLSP1_UART2_APPS_CLK_CMD_RCGR,
ftbl_entry->pre_div, ftbl_entry->m, ftbl_entry->n,
ftbl_entry->src, 8);
return ftbl_entry->freq;
- case GCC_SDCC2_APPS_CLK:
ftbl_entry = qcom_find_freq(ftbl_sdcc2_apps_clk_src, rate);
/* we probably should enable source PLL for the selected frequency */
switch (ftbl_entry->src) {
case CFG_CLK_SRC_GPLL0:
/* enable gpll0 */
clk_enable_gpll0(priv->base, &gpll0_clk);
break;
case CFG_CLK_SRC_GPLL4:
/* enable gpll4 */
debug("can't use GPLL4 as src for SDCC2_APPS_CLK, req rate: %lu", rate);
break;
case CFG_CLK_SRC_GPLL0_EARLY_DIV_SDCC2:
/* enable gpll0_early */
debug("can't use GPLL0_EARLY as src for SDCC2_APPS_CLK, req rate: %lu",
rate);
break;
}
clk_rcg_set_rate_mnd(priv->base, SDCC2_APPS_CLK_CMD_RCGR,
ftbl_entry->pre_div, ftbl_entry->m, ftbl_entry->n,
ftbl_entry->src, 8);
return ftbl_entry->freq;
- case GCC_SDCC1_APPS_CLK:
/* The firmware turns this on for us and always sets it to this rate */
return 384000000;
- default:
debug("clock-sdm660: set_rate for some unknown clock %lu\n", clk->id);
return rate;
- }
+}
+static int sdm660_gcc_enable(struct clk *clk) +{
- struct msm_clk_priv *priv = dev_get_priv(clk->dev);
- if (priv->data->num_clks < clk->id) {
debug("unknown clk id %lu\n", clk->id);
return 0;
- }
- debug("enable clk %s\n", sdm660_clks[clk->id].name);
- qcom_gate_clk_en(priv, clk->id);
- return 0;
+}
+static const struct qcom_reset_map sdm660_gcc_resets[] = {
- [GCC_QUSB2PHY_PRIM_BCR] = { 0x12000 },
- [GCC_QUSB2PHY_SEC_BCR] = { 0x12004 },
- /*
* Linux dt-bindings (and sdm630 dtsi) don't have the defines for
* (or uses of) GCC_SDCCn_BCR resets, so this won't compile,
* but the hardware exists and their offsets are:
*/
- /* [GCC_SDCC1_BCR] = { 0x16000 }, */
- /* [GCC_SDCC2_BCR] = { 0x14000 }, */
- [GCC_USB_20_BCR] = { 0x2f000 },
- [GCC_USB_30_BCR] = { 0xf000 },
- [GCC_USB3_PHY_BCR] = { 0x50020 },
- [GCC_USB3PHY_PHY_BCR] = { 0x50024 },
+};
+static const struct qcom_power_map sdm660_gdscs[] = {
- [USB_30_GDSC] = { .reg = 0xf004, .flags = VOTABLE },
+};
+static struct msm_clk_data sdm660_gcc_data = {
- .resets = sdm660_gcc_resets,
- .num_resets = ARRAY_SIZE(sdm660_gcc_resets),
- .clks = sdm660_clks,
- .num_clks = ARRAY_SIZE(sdm660_clks),
- .power_domains = sdm660_gdscs,
- .num_power_domains = ARRAY_SIZE(sdm660_gdscs),
- .enable = sdm660_gcc_enable,
- .set_rate = sdm660_gcc_set_rate,
+};
+static const struct udevice_id gcc_sdm660_of_match[] = {
- {
.compatible = "qcom,gcc-sdm660",
.data = (ulong)&sdm660_gcc_data,
- },
- {}
+};
+U_BOOT_DRIVER(gcc_sdm660) = {
- .name = "gcc_sdm660",
- .id = UCLASS_NOP,
- .of_match = gcc_sdm660_of_match,
- .bind = qcom_cc_bind,
- .flags = DM_FLAG_PRE_RELOC,
+};

On 08/01/2025 12:59, Alexey Minnekhanov via groups.io wrote:
Clearly say whether it's stuck during power on or power off.
Signed-off-by: Alexey Minnekhanov alexeymin@postmarketos.org
Reviewed-by: Caleb Connolly caleb.connolly@linaro.org
Thanks
drivers/clk/qcom/clock-qcom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/clk/qcom/clock-qcom.c b/drivers/clk/qcom/clock-qcom.c index 25ca67e537d1..1cfc430c14a5 100644 --- a/drivers/clk/qcom/clock-qcom.c +++ b/drivers/clk/qcom/clock-qcom.c @@ -496,8 +496,8 @@ static int qcom_power_set(struct power_domain *pwr, bool on) GDSC_STATUS_POLL_TIMEOUT_US);
if (ret == -ETIMEDOUT)
printf("WARNING: GDSC %lu is stuck during power on/off\n",
pwr->id);
printf("WARNING: GDSC %lu is stuck during power o%s\n",
return ret;pwr->id, on ? "n" : "ff");
}
participants (2)
-
Alexey Minnekhanov
-
Caleb Connolly