From c388cc804016cf0f65afdc2362b120aa594ff3e6 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 30 Sep 2022 01:53:55 +0300 Subject: [PATCH 01/20] clk: vc5: Fix 5P49V6901 outputs disabling when enabling FOD We have discovered random glitches during the system boot up procedure. The problem investigation led us to the weird outcomes: when none of the Renesas 5P49V6901 ports are explicitly enabled by the kernel driver, the glitches disappeared. It was a mystery since the SoC external clock domains were fed with different 5P49V6901 outputs. The driver code didn't seem like bogus either. We almost despaired to find out a root cause when the solution has been found for a more modern revision of the chip. It turned out the 5P49V6901 clock generator stopped its output for a short period of time during the VC5_OUT_DIV_CONTROL register writing. The same problem was found for the 5P49V6965 revision of the chip and was successfully fixed in commit fc336ae622df ("clk: vc5: fix output disabling when enabling a FOD") by enabling the "bypass_sync" flag hidden inside "Unused Factory Reserved Register". Even though the 5P49V6901 registers description and programming guide doesn't provide any intel regarding that flag, setting it up anyway in the officially unused register completely eliminated the denoted glitches. Thus let's activate the functionality submitted in commit fc336ae622df ("clk: vc5: fix output disabling when enabling a FOD") for the Renesas 5P49V6901 chip too in order to remove the ports implicit inter-dependency. Fixes: dbf6b16f5683 ("clk: vc5: Add support for IDT VersaClock 5P49V6901") Signed-off-by: Serge Semin Reviewed-by: Luca Ceresoli Link: https://lore.kernel.org/r/20220929225402.9696-2-Sergey.Semin@baikalelectronics.ru Signed-off-by: Stephen Boyd --- drivers/clk/clk-versaclock5.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/clk-versaclock5.c b/drivers/clk/clk-versaclock5.c index e7be3e54b9be..03cfef494b49 100644 --- a/drivers/clk/clk-versaclock5.c +++ b/drivers/clk/clk-versaclock5.c @@ -1204,7 +1204,7 @@ static const struct vc5_chip_info idt_5p49v6901_info = { .model = IDT_VC6_5P49V6901, .clk_fod_cnt = 4, .clk_out_cnt = 5, - .flags = VC5_HAS_PFD_FREQ_DBL, + .flags = VC5_HAS_PFD_FREQ_DBL | VC5_HAS_BYPASS_SYNC_BIT, }; static const struct vc5_chip_info idt_5p49v6965_info = { From 3c742088686ce922704aec5b11d09bcc5a396589 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 30 Sep 2022 01:53:56 +0300 Subject: [PATCH 02/20] clk: baikal-t1: Fix invalid xGMAC PTP clock divider Most likely due to copy-paste mistake the divider has been set to 10 while according to the SoC reference manual it's supposed to be 8 thus having PTP clock frequency of 156.25 MHz. Fixes: 353afa3a8d2e ("clk: Add Baikal-T1 CCU Dividers driver") Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20220929225402.9696-3-Sergey.Semin@baikalelectronics.ru Signed-off-by: Stephen Boyd --- drivers/clk/baikal-t1/clk-ccu-div.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/baikal-t1/clk-ccu-div.c b/drivers/clk/baikal-t1/clk-ccu-div.c index f141fda12b09..ea77eec40ddd 100644 --- a/drivers/clk/baikal-t1/clk-ccu-div.c +++ b/drivers/clk/baikal-t1/clk-ccu-div.c @@ -207,7 +207,7 @@ static const struct ccu_div_info sys_info[] = { CCU_DIV_GATE_INFO(CCU_SYS_XGMAC_REF_CLK, "sys_xgmac_ref_clk", "eth_clk", CCU_SYS_XGMAC_BASE, 8), CCU_DIV_FIXED_INFO(CCU_SYS_XGMAC_PTP_CLK, "sys_xgmac_ptp_clk", - "eth_clk", 10), + "eth_clk", 8), CCU_DIV_GATE_INFO(CCU_SYS_USB_CLK, "sys_usb_clk", "eth_clk", CCU_SYS_USB_BASE, 10), CCU_DIV_VAR_INFO(CCU_SYS_PVT_CLK, "sys_pvt_clk", From e2eef312762e0b5a5a70d29fe59a245c0a3cffa0 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 30 Sep 2022 01:53:57 +0300 Subject: [PATCH 03/20] clk: baikal-t1: Add shared xGMAC ref/ptp clocks internal parent Baikal-T1 CCU reference manual says that both xGMAC reference and xGMAC PTP clocks are generated by two different wrappers with the same constant divider thus each producing a 156.25 MHz signal. But for some reason both of these clock sources are gated by a single switch-flag in the CCU registers space - CCU_SYS_XGMAC_BASE.BIT(0). In order to make the clocks handled independently we need to define a shared parental gate so the base clock signal would be switched off only if both of the child-clocks are disabled. Note the ID is intentionally set to -2 since we are going to add a one more internal clock identifier in the next commit. Fixes: 353afa3a8d2e ("clk: Add Baikal-T1 CCU Dividers driver") Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20220929225402.9696-4-Sergey.Semin@baikalelectronics.ru Signed-off-by: Stephen Boyd --- drivers/clk/baikal-t1/ccu-div.c | 1 + drivers/clk/baikal-t1/ccu-div.h | 6 ++++++ drivers/clk/baikal-t1/clk-ccu-div.c | 8 +++++--- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/clk/baikal-t1/ccu-div.c b/drivers/clk/baikal-t1/ccu-div.c index 4062092d67f9..bbfa3526ee10 100644 --- a/drivers/clk/baikal-t1/ccu-div.c +++ b/drivers/clk/baikal-t1/ccu-div.c @@ -579,6 +579,7 @@ struct ccu_div *ccu_div_hw_register(const struct ccu_div_init_data *div_init) goto err_free_div; } parent_data.fw_name = div_init->parent_name; + parent_data.name = div_init->parent_name; hw_init.parent_data = &parent_data; hw_init.num_parents = 1; diff --git a/drivers/clk/baikal-t1/ccu-div.h b/drivers/clk/baikal-t1/ccu-div.h index 795665caefbd..b6a9c8e45318 100644 --- a/drivers/clk/baikal-t1/ccu-div.h +++ b/drivers/clk/baikal-t1/ccu-div.h @@ -13,6 +13,12 @@ #include #include +/* + * CCU Divider private clock IDs + * @CCU_SYS_XGMAC_CLK: CCU XGMAC internal clock + */ +#define CCU_SYS_XGMAC_CLK -2 + /* * CCU Divider private flags * @CCU_DIV_SKIP_ONE: Due to some reason divider can't be set to 1. diff --git a/drivers/clk/baikal-t1/clk-ccu-div.c b/drivers/clk/baikal-t1/clk-ccu-div.c index ea77eec40ddd..3953ae5664be 100644 --- a/drivers/clk/baikal-t1/clk-ccu-div.c +++ b/drivers/clk/baikal-t1/clk-ccu-div.c @@ -204,10 +204,12 @@ static const struct ccu_div_info sys_info[] = { "eth_clk", CCU_SYS_GMAC1_BASE, 5), CCU_DIV_FIXED_INFO(CCU_SYS_GMAC1_PTP_CLK, "sys_gmac1_ptp_clk", "eth_clk", 10), - CCU_DIV_GATE_INFO(CCU_SYS_XGMAC_REF_CLK, "sys_xgmac_ref_clk", - "eth_clk", CCU_SYS_XGMAC_BASE, 8), + CCU_DIV_GATE_INFO(CCU_SYS_XGMAC_CLK, "sys_xgmac_clk", + "eth_clk", CCU_SYS_XGMAC_BASE, 1), + CCU_DIV_FIXED_INFO(CCU_SYS_XGMAC_REF_CLK, "sys_xgmac_ref_clk", + "sys_xgmac_clk", 8), CCU_DIV_FIXED_INFO(CCU_SYS_XGMAC_PTP_CLK, "sys_xgmac_ptp_clk", - "eth_clk", 8), + "sys_xgmac_clk", 8), CCU_DIV_GATE_INFO(CCU_SYS_USB_CLK, "sys_usb_clk", "eth_clk", CCU_SYS_USB_BASE, 10), CCU_DIV_VAR_INFO(CCU_SYS_PVT_CLK, "sys_pvt_clk", From 081a9b7c74eae4e12b2cb1b86720f836a8f29247 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 30 Sep 2022 01:53:58 +0300 Subject: [PATCH 04/20] clk: baikal-t1: Add SATA internal ref clock buffer It turns out the internal SATA reference clock signal will stay unavailable for the SATA interface consumer until the buffer on it's way is ungated. So aside with having the actual clock divider enabled we need to ungate a buffer placed on the signal way to the SATA controller (most likely some rudiment from the initial SoC release). Seeing the switch flag is placed in the same register as the SATA-ref clock divider at a non-standard ffset, let's implement it as a separate clock controller with the set-rate propagation to the parental clock divider wrapper. As such we'll be able to disable/enable and still change the original clock source rate. Fixes: 353afa3a8d2e ("clk: Add Baikal-T1 CCU Dividers driver") Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20220929225402.9696-5-Sergey.Semin@baikalelectronics.ru Signed-off-by: Stephen Boyd --- drivers/clk/baikal-t1/ccu-div.c | 64 +++++++++++++++++++++++++++++ drivers/clk/baikal-t1/ccu-div.h | 4 ++ drivers/clk/baikal-t1/clk-ccu-div.c | 18 +++++++- 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/drivers/clk/baikal-t1/ccu-div.c b/drivers/clk/baikal-t1/ccu-div.c index bbfa3526ee10..a6642f3d33d4 100644 --- a/drivers/clk/baikal-t1/ccu-div.c +++ b/drivers/clk/baikal-t1/ccu-div.c @@ -34,6 +34,7 @@ #define CCU_DIV_CTL_CLKDIV_MASK(_width) \ GENMASK((_width) + CCU_DIV_CTL_CLKDIV_FLD - 1, CCU_DIV_CTL_CLKDIV_FLD) #define CCU_DIV_CTL_LOCK_SHIFTED BIT(27) +#define CCU_DIV_CTL_GATE_REF_BUF BIT(28) #define CCU_DIV_CTL_LOCK_NORMAL BIT(31) #define CCU_DIV_RST_DELAY_US 1 @@ -170,6 +171,40 @@ static int ccu_div_gate_is_enabled(struct clk_hw *hw) return !!(val & CCU_DIV_CTL_EN); } +static int ccu_div_buf_enable(struct clk_hw *hw) +{ + struct ccu_div *div = to_ccu_div(hw); + unsigned long flags; + + spin_lock_irqsave(&div->lock, flags); + regmap_update_bits(div->sys_regs, div->reg_ctl, + CCU_DIV_CTL_GATE_REF_BUF, 0); + spin_unlock_irqrestore(&div->lock, flags); + + return 0; +} + +static void ccu_div_buf_disable(struct clk_hw *hw) +{ + struct ccu_div *div = to_ccu_div(hw); + unsigned long flags; + + spin_lock_irqsave(&div->lock, flags); + regmap_update_bits(div->sys_regs, div->reg_ctl, + CCU_DIV_CTL_GATE_REF_BUF, CCU_DIV_CTL_GATE_REF_BUF); + spin_unlock_irqrestore(&div->lock, flags); +} + +static int ccu_div_buf_is_enabled(struct clk_hw *hw) +{ + struct ccu_div *div = to_ccu_div(hw); + u32 val = 0; + + regmap_read(div->sys_regs, div->reg_ctl, &val); + + return !(val & CCU_DIV_CTL_GATE_REF_BUF); +} + static unsigned long ccu_div_var_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { @@ -323,6 +358,7 @@ static const struct ccu_div_dbgfs_bit ccu_div_bits[] = { CCU_DIV_DBGFS_BIT_ATTR("div_en", CCU_DIV_CTL_EN), CCU_DIV_DBGFS_BIT_ATTR("div_rst", CCU_DIV_CTL_RST), CCU_DIV_DBGFS_BIT_ATTR("div_bypass", CCU_DIV_CTL_SET_CLKDIV), + CCU_DIV_DBGFS_BIT_ATTR("div_buf", CCU_DIV_CTL_GATE_REF_BUF), CCU_DIV_DBGFS_BIT_ATTR("div_lock", CCU_DIV_CTL_LOCK_NORMAL) }; @@ -441,6 +477,9 @@ static void ccu_div_var_debug_init(struct clk_hw *hw, struct dentry *dentry) continue; } + if (!strcmp("div_buf", name)) + continue; + bits[didx] = ccu_div_bits[bidx]; bits[didx].div = div; @@ -477,6 +516,21 @@ static void ccu_div_gate_debug_init(struct clk_hw *hw, struct dentry *dentry) &ccu_div_dbgfs_fixed_clkdiv_fops); } +static void ccu_div_buf_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + struct ccu_div *div = to_ccu_div(hw); + struct ccu_div_dbgfs_bit *bit; + + bit = kmalloc(sizeof(*bit), GFP_KERNEL); + if (!bit) + return; + + *bit = ccu_div_bits[3]; + bit->div = div; + debugfs_create_file_unsafe(bit->name, ccu_div_dbgfs_mode, dentry, bit, + &ccu_div_dbgfs_bit_fops); +} + static void ccu_div_fixed_debug_init(struct clk_hw *hw, struct dentry *dentry) { struct ccu_div *div = to_ccu_div(hw); @@ -489,6 +543,7 @@ static void ccu_div_fixed_debug_init(struct clk_hw *hw, struct dentry *dentry) #define ccu_div_var_debug_init NULL #define ccu_div_gate_debug_init NULL +#define ccu_div_buf_debug_init NULL #define ccu_div_fixed_debug_init NULL #endif /* !CONFIG_DEBUG_FS */ @@ -520,6 +575,13 @@ static const struct clk_ops ccu_div_gate_ops = { .debug_init = ccu_div_gate_debug_init }; +static const struct clk_ops ccu_div_buf_ops = { + .enable = ccu_div_buf_enable, + .disable = ccu_div_buf_disable, + .is_enabled = ccu_div_buf_is_enabled, + .debug_init = ccu_div_buf_debug_init +}; + static const struct clk_ops ccu_div_fixed_ops = { .recalc_rate = ccu_div_fixed_recalc_rate, .round_rate = ccu_div_fixed_round_rate, @@ -566,6 +628,8 @@ struct ccu_div *ccu_div_hw_register(const struct ccu_div_init_data *div_init) } else if (div_init->type == CCU_DIV_GATE) { hw_init.ops = &ccu_div_gate_ops; div->divider = div_init->divider; + } else if (div_init->type == CCU_DIV_BUF) { + hw_init.ops = &ccu_div_buf_ops; } else if (div_init->type == CCU_DIV_FIXED) { hw_init.ops = &ccu_div_fixed_ops; div->divider = div_init->divider; diff --git a/drivers/clk/baikal-t1/ccu-div.h b/drivers/clk/baikal-t1/ccu-div.h index b6a9c8e45318..4eb49ff4803c 100644 --- a/drivers/clk/baikal-t1/ccu-div.h +++ b/drivers/clk/baikal-t1/ccu-div.h @@ -15,8 +15,10 @@ /* * CCU Divider private clock IDs + * @CCU_SYS_SATA_CLK: CCU SATA internal clock * @CCU_SYS_XGMAC_CLK: CCU XGMAC internal clock */ +#define CCU_SYS_SATA_CLK -1 #define CCU_SYS_XGMAC_CLK -2 /* @@ -37,11 +39,13 @@ * enum ccu_div_type - CCU Divider types * @CCU_DIV_VAR: Clocks gate with variable divider. * @CCU_DIV_GATE: Clocks gate with fixed divider. + * @CCU_DIV_BUF: Clock gate with no divider. * @CCU_DIV_FIXED: Ungateable clock with fixed divider. */ enum ccu_div_type { CCU_DIV_VAR, CCU_DIV_GATE, + CCU_DIV_BUF, CCU_DIV_FIXED }; diff --git a/drivers/clk/baikal-t1/clk-ccu-div.c b/drivers/clk/baikal-t1/clk-ccu-div.c index 3953ae5664be..90f4fda406ee 100644 --- a/drivers/clk/baikal-t1/clk-ccu-div.c +++ b/drivers/clk/baikal-t1/clk-ccu-div.c @@ -76,6 +76,16 @@ .divider = _divider \ } +#define CCU_DIV_BUF_INFO(_id, _name, _pname, _base, _flags) \ + { \ + .id = _id, \ + .name = _name, \ + .parent_name = _pname, \ + .base = _base, \ + .type = CCU_DIV_BUF, \ + .flags = _flags \ + } + #define CCU_DIV_FIXED_INFO(_id, _name, _pname, _divider) \ { \ .id = _id, \ @@ -188,11 +198,14 @@ static const struct ccu_div_rst_map axi_rst_map[] = { * for the SoC devices registers IO-operations. */ static const struct ccu_div_info sys_info[] = { - CCU_DIV_VAR_INFO(CCU_SYS_SATA_REF_CLK, "sys_sata_ref_clk", + CCU_DIV_VAR_INFO(CCU_SYS_SATA_CLK, "sys_sata_clk", "sata_clk", CCU_SYS_SATA_REF_BASE, 4, CLK_SET_RATE_GATE, CCU_DIV_SKIP_ONE | CCU_DIV_LOCK_SHIFTED | CCU_DIV_RESET_DOMAIN), + CCU_DIV_BUF_INFO(CCU_SYS_SATA_REF_CLK, "sys_sata_ref_clk", + "sys_sata_clk", CCU_SYS_SATA_REF_BASE, + CLK_SET_RATE_PARENT), CCU_DIV_VAR_INFO(CCU_SYS_APB_CLK, "sys_apb_clk", "pcie_clk", CCU_SYS_APB_BASE, 5, CLK_IS_CRITICAL, CCU_DIV_RESET_DOMAIN), @@ -398,6 +411,9 @@ static int ccu_div_clk_register(struct ccu_div_data *data) init.base = info->base; init.sys_regs = data->sys_regs; init.divider = info->divider; + } else if (init.type == CCU_DIV_BUF) { + init.base = info->base; + init.sys_regs = data->sys_regs; } else { init.divider = info->divider; } From 70fa895488a4ebdae8b2d08b4c84e164ef14696e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 30 Sep 2022 01:53:59 +0300 Subject: [PATCH 05/20] clk: baikal-t1: Move reset-controls code into a dedicated module Before adding the directly controlled resets support it's reasonable to move the existing resets control functionality into a dedicated object for the sake of the CCU dividers clock driver simplification. After the new functionality was added clk-ccu-div.c would have got to a mixture of the weakly dependent clocks and resets methods. Splitting the methods up into the two objects will make the code easier to read and maintain. It shall also improve the code scalability (though hopefully we won't need this part that much in the future). The reset control functionality is now implemented in the framework of a single unit since splitting it up doesn't make much sense due to relatively simple reset operations. The ccu-rst.c has been designed to be looking like ccu-div.c or ccu-pll.c with two globally available methods for the sake of the code unification and better code readability. This commit doesn't provide any change in the CCU reset implementation semantics. As before the driver will support the trigger-like CCU resets only, which are responsible for the AXI-bus, APB-bus and SATA-ref blocks reset. The assert/de-assert-capable reset controls support will be added in the next commit. Note the CCU Clock dividers and resets functionality split up was possible due to not having any side-effects (at least we didn't found ones) of the regmap-based concurrent access of the common CCU dividers/reset CSRs. Signed-off-by: Serge Semin Reviewed-by: Philipp Zabel Link: https://lore.kernel.org/r/20220929225402.9696-6-Sergey.Semin@baikalelectronics.ru Signed-off-by: Stephen Boyd --- drivers/clk/baikal-t1/Kconfig | 12 ++- drivers/clk/baikal-t1/Makefile | 1 + drivers/clk/baikal-t1/ccu-div.c | 19 ---- drivers/clk/baikal-t1/ccu-div.h | 4 +- drivers/clk/baikal-t1/ccu-rst.c | 151 ++++++++++++++++++++++++++++ drivers/clk/baikal-t1/ccu-rst.h | 57 +++++++++++ drivers/clk/baikal-t1/clk-ccu-div.c | 92 ++--------------- 7 files changed, 231 insertions(+), 105 deletions(-) create mode 100644 drivers/clk/baikal-t1/ccu-rst.c create mode 100644 drivers/clk/baikal-t1/ccu-rst.h diff --git a/drivers/clk/baikal-t1/Kconfig b/drivers/clk/baikal-t1/Kconfig index 03102f1094bc..f0b186830324 100644 --- a/drivers/clk/baikal-t1/Kconfig +++ b/drivers/clk/baikal-t1/Kconfig @@ -29,7 +29,6 @@ config CLK_BT1_CCU_PLL config CLK_BT1_CCU_DIV bool "Baikal-T1 CCU Dividers support" - select RESET_CONTROLLER select MFD_SYSCON default MIPS_BAIKAL_T1 help @@ -39,4 +38,15 @@ config CLK_BT1_CCU_DIV either gateable or ungateable. Some of the CCU dividers can be as well used to reset the domains they're supplying clock to. +config CLK_BT1_CCU_RST + bool "Baikal-T1 CCU Resets support" + select RESET_CONTROLLER + select MFD_SYSCON + default MIPS_BAIKAL_T1 + help + Enable this to support the CCU reset blocks responsible for the + AXI-bus and some subsystems reset. These are mainly the + self-deasserted reset controls but there are several lines which + can be directly asserted/de-asserted (PCIe and DDR sub-domains). + endif diff --git a/drivers/clk/baikal-t1/Makefile b/drivers/clk/baikal-t1/Makefile index b3b9590b95ed..9c3637de9407 100644 --- a/drivers/clk/baikal-t1/Makefile +++ b/drivers/clk/baikal-t1/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_CLK_BT1_CCU_PLL) += ccu-pll.o clk-ccu-pll.o obj-$(CONFIG_CLK_BT1_CCU_DIV) += ccu-div.o clk-ccu-div.o +obj-$(CONFIG_CLK_BT1_CCU_RST) += ccu-rst.o diff --git a/drivers/clk/baikal-t1/ccu-div.c b/drivers/clk/baikal-t1/ccu-div.c index a6642f3d33d4..8d5fc7158f33 100644 --- a/drivers/clk/baikal-t1/ccu-div.c +++ b/drivers/clk/baikal-t1/ccu-div.c @@ -37,7 +37,6 @@ #define CCU_DIV_CTL_GATE_REF_BUF BIT(28) #define CCU_DIV_CTL_LOCK_NORMAL BIT(31) -#define CCU_DIV_RST_DELAY_US 1 #define CCU_DIV_LOCK_CHECK_RETRIES 50 #define CCU_DIV_CLKDIV_MIN 0 @@ -323,24 +322,6 @@ static int ccu_div_fixed_set_rate(struct clk_hw *hw, unsigned long rate, return 0; } -int ccu_div_reset_domain(struct ccu_div *div) -{ - unsigned long flags; - - if (!div || !(div->features & CCU_DIV_RESET_DOMAIN)) - return -EINVAL; - - spin_lock_irqsave(&div->lock, flags); - regmap_update_bits(div->sys_regs, div->reg_ctl, - CCU_DIV_CTL_RST, CCU_DIV_CTL_RST); - spin_unlock_irqrestore(&div->lock, flags); - - /* The next delay must be enough to cover all the resets. */ - udelay(CCU_DIV_RST_DELAY_US); - - return 0; -} - #ifdef CONFIG_DEBUG_FS struct ccu_div_dbgfs_bit { diff --git a/drivers/clk/baikal-t1/ccu-div.h b/drivers/clk/baikal-t1/ccu-div.h index 4eb49ff4803c..ff97bb30fcc3 100644 --- a/drivers/clk/baikal-t1/ccu-div.h +++ b/drivers/clk/baikal-t1/ccu-div.h @@ -28,7 +28,7 @@ * @CCU_DIV_SKIP_ONE_TO_THREE: For some reason divider can't be within [1,3]. * It can be either 0 or greater than 3. * @CCU_DIV_LOCK_SHIFTED: Find lock-bit at non-standard position. - * @CCU_DIV_RESET_DOMAIN: Provide reset clock domain method. + * @CCU_DIV_RESET_DOMAIN: There is a clock domain reset handle. */ #define CCU_DIV_SKIP_ONE BIT(1) #define CCU_DIV_SKIP_ONE_TO_THREE BIT(2) @@ -115,6 +115,4 @@ struct ccu_div *ccu_div_hw_register(const struct ccu_div_init_data *init); void ccu_div_hw_unregister(struct ccu_div *div); -int ccu_div_reset_domain(struct ccu_div *div); - #endif /* __CLK_BT1_CCU_DIV_H__ */ diff --git a/drivers/clk/baikal-t1/ccu-rst.c b/drivers/clk/baikal-t1/ccu-rst.c new file mode 100644 index 000000000000..7db52633270f --- /dev/null +++ b/drivers/clk/baikal-t1/ccu-rst.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 BAIKAL ELECTRONICS, JSC + * + * Authors: + * Serge Semin + * + * Baikal-T1 CCU Resets interface driver + */ + +#define pr_fmt(fmt) "bt1-ccu-rst: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ccu-rst.h" + +#define CCU_AXI_MAIN_BASE 0x030 +#define CCU_AXI_DDR_BASE 0x034 +#define CCU_AXI_SATA_BASE 0x038 +#define CCU_AXI_GMAC0_BASE 0x03C +#define CCU_AXI_GMAC1_BASE 0x040 +#define CCU_AXI_XGMAC_BASE 0x044 +#define CCU_AXI_PCIE_M_BASE 0x048 +#define CCU_AXI_PCIE_S_BASE 0x04C +#define CCU_AXI_USB_BASE 0x050 +#define CCU_AXI_HWA_BASE 0x054 +#define CCU_AXI_SRAM_BASE 0x058 + +#define CCU_SYS_SATA_REF_BASE 0x060 +#define CCU_SYS_APB_BASE 0x064 + +#define CCU_RST_DELAY_US 1 + +#define CCU_RST_TRIG(_base, _ofs) \ + { \ + .base = _base, \ + .mask = BIT(_ofs), \ + } + +struct ccu_rst_info { + unsigned int base; + unsigned int mask; +}; + +/* + * Each AXI-bus clock divider is equipped with the corresponding clock-consumer + * domain reset (it's self-deasserted reset control). + */ +static const struct ccu_rst_info axi_rst_info[] = { + [CCU_AXI_MAIN_RST] = CCU_RST_TRIG(CCU_AXI_MAIN_BASE, 1), + [CCU_AXI_DDR_RST] = CCU_RST_TRIG(CCU_AXI_DDR_BASE, 1), + [CCU_AXI_SATA_RST] = CCU_RST_TRIG(CCU_AXI_SATA_BASE, 1), + [CCU_AXI_GMAC0_RST] = CCU_RST_TRIG(CCU_AXI_GMAC0_BASE, 1), + [CCU_AXI_GMAC1_RST] = CCU_RST_TRIG(CCU_AXI_GMAC1_BASE, 1), + [CCU_AXI_XGMAC_RST] = CCU_RST_TRIG(CCU_AXI_XGMAC_BASE, 1), + [CCU_AXI_PCIE_M_RST] = CCU_RST_TRIG(CCU_AXI_PCIE_M_BASE, 1), + [CCU_AXI_PCIE_S_RST] = CCU_RST_TRIG(CCU_AXI_PCIE_S_BASE, 1), + [CCU_AXI_USB_RST] = CCU_RST_TRIG(CCU_AXI_USB_BASE, 1), + [CCU_AXI_HWA_RST] = CCU_RST_TRIG(CCU_AXI_HWA_BASE, 1), + [CCU_AXI_SRAM_RST] = CCU_RST_TRIG(CCU_AXI_SRAM_BASE, 1), +}; + +/* + * SATA reference clock domain and APB-bus domain are connected with the + * sefl-deasserted reset control, which can be activated via the corresponding + * clock divider register. DDR and PCIe sub-domains can be reset with directly + * controlled reset signals. Resetting the DDR controller though won't end up + * well while the Linux kernel is working. + */ +static const struct ccu_rst_info sys_rst_info[] = { + [CCU_SYS_SATA_REF_RST] = CCU_RST_TRIG(CCU_SYS_SATA_REF_BASE, 1), + [CCU_SYS_APB_RST] = CCU_RST_TRIG(CCU_SYS_APB_BASE, 1), +}; + +static int ccu_rst_reset(struct reset_controller_dev *rcdev, unsigned long idx) +{ + struct ccu_rst *rst = to_ccu_rst(rcdev); + const struct ccu_rst_info *info = &rst->rsts_info[idx]; + + regmap_update_bits(rst->sys_regs, info->base, info->mask, info->mask); + + /* The next delay must be enough to cover all the resets. */ + udelay(CCU_RST_DELAY_US); + + return 0; +} + +static const struct reset_control_ops ccu_rst_ops = { + .reset = ccu_rst_reset, +}; + +struct ccu_rst *ccu_rst_hw_register(const struct ccu_rst_init_data *rst_init) +{ + struct ccu_rst *rst; + int ret; + + if (!rst_init) + return ERR_PTR(-EINVAL); + + rst = kzalloc(sizeof(*rst), GFP_KERNEL); + if (!rst) + return ERR_PTR(-ENOMEM); + + rst->sys_regs = rst_init->sys_regs; + if (of_device_is_compatible(rst_init->np, "baikal,bt1-ccu-axi")) { + rst->rcdev.nr_resets = ARRAY_SIZE(axi_rst_info); + rst->rsts_info = axi_rst_info; + } else if (of_device_is_compatible(rst_init->np, "baikal,bt1-ccu-sys")) { + rst->rcdev.nr_resets = ARRAY_SIZE(sys_rst_info); + rst->rsts_info = sys_rst_info; + } else { + pr_err("Incompatible DT node '%s' specified\n", + of_node_full_name(rst_init->np)); + ret = -EINVAL; + goto err_kfree_rst; + } + + rst->rcdev.owner = THIS_MODULE; + rst->rcdev.ops = &ccu_rst_ops; + rst->rcdev.of_node = rst_init->np; + + ret = reset_controller_register(&rst->rcdev); + if (ret) { + pr_err("Couldn't register '%s' reset controller\n", + of_node_full_name(rst_init->np)); + goto err_kfree_rst; + } + + return rst; + +err_kfree_rst: + kfree(rst); + + return ERR_PTR(ret); +} + +void ccu_rst_hw_unregister(struct ccu_rst *rst) +{ + reset_controller_unregister(&rst->rcdev); + + kfree(rst); +} diff --git a/drivers/clk/baikal-t1/ccu-rst.h b/drivers/clk/baikal-t1/ccu-rst.h new file mode 100644 index 000000000000..68214d777465 --- /dev/null +++ b/drivers/clk/baikal-t1/ccu-rst.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 CCU Resets interface driver + */ +#ifndef __CLK_BT1_CCU_RST_H__ +#define __CLK_BT1_CCU_RST_H__ + +#include +#include +#include + +struct ccu_rst_info; + +/* + * struct ccu_rst_init_data - CCU Resets initialization data + * @sys_regs: Baikal-T1 System Controller registers map. + * @np: Pointer to the node with the System CCU block. + */ +struct ccu_rst_init_data { + struct regmap *sys_regs; + struct device_node *np; +}; + +/* + * struct ccu_rst - CCU Reset descriptor + * @rcdev: Reset controller descriptor. + * @sys_regs: Baikal-T1 System Controller registers map. + * @rsts_info: Reset flag info (base address and mask). + */ +struct ccu_rst { + struct reset_controller_dev rcdev; + struct regmap *sys_regs; + const struct ccu_rst_info *rsts_info; +}; +#define to_ccu_rst(_rcdev) container_of(_rcdev, struct ccu_rst, rcdev) + +#ifdef CONFIG_CLK_BT1_CCU_RST + +struct ccu_rst *ccu_rst_hw_register(const struct ccu_rst_init_data *init); + +void ccu_rst_hw_unregister(struct ccu_rst *rst); + +#else + +static inline +struct ccu_rst *ccu_rst_hw_register(const struct ccu_rst_init_data *init) +{ + return NULL; +} + +static inline void ccu_rst_hw_unregister(struct ccu_rst *rst) {} + +#endif + +#endif /* __CLK_BT1_CCU_RST_H__ */ diff --git a/drivers/clk/baikal-t1/clk-ccu-div.c b/drivers/clk/baikal-t1/clk-ccu-div.c index 90f4fda406ee..278aa38d767e 100644 --- a/drivers/clk/baikal-t1/clk-ccu-div.c +++ b/drivers/clk/baikal-t1/clk-ccu-div.c @@ -24,9 +24,9 @@ #include #include -#include #include "ccu-div.h" +#include "ccu-rst.h" #define CCU_AXI_MAIN_BASE 0x030 #define CCU_AXI_DDR_BASE 0x034 @@ -95,12 +95,6 @@ .divider = _divider \ } -#define CCU_DIV_RST_MAP(_rst_id, _clk_id) \ - { \ - .rst_id = _rst_id, \ - .clk_id = _clk_id \ - } - struct ccu_div_info { unsigned int id; const char *name; @@ -115,11 +109,6 @@ struct ccu_div_info { unsigned long features; }; -struct ccu_div_rst_map { - unsigned int rst_id; - unsigned int clk_id; -}; - struct ccu_div_data { struct device_node *np; struct regmap *sys_regs; @@ -128,11 +117,8 @@ struct ccu_div_data { const struct ccu_div_info *divs_info; struct ccu_div **divs; - unsigned int rst_num; - const struct ccu_div_rst_map *rst_map; - struct reset_controller_dev rcdev; + struct ccu_rst *rsts; }; -#define to_ccu_div_data(_rcdev) container_of(_rcdev, struct ccu_div_data, rcdev) /* * AXI Main Interconnect (axi_main_clk) and DDR AXI-bus (axi_ddr_clk) clocks @@ -179,20 +165,6 @@ static const struct ccu_div_info axi_info[] = { CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN) }; -static const struct ccu_div_rst_map axi_rst_map[] = { - CCU_DIV_RST_MAP(CCU_AXI_MAIN_RST, CCU_AXI_MAIN_CLK), - CCU_DIV_RST_MAP(CCU_AXI_DDR_RST, CCU_AXI_DDR_CLK), - CCU_DIV_RST_MAP(CCU_AXI_SATA_RST, CCU_AXI_SATA_CLK), - CCU_DIV_RST_MAP(CCU_AXI_GMAC0_RST, CCU_AXI_GMAC0_CLK), - CCU_DIV_RST_MAP(CCU_AXI_GMAC1_RST, CCU_AXI_GMAC1_CLK), - CCU_DIV_RST_MAP(CCU_AXI_XGMAC_RST, CCU_AXI_XGMAC_CLK), - CCU_DIV_RST_MAP(CCU_AXI_PCIE_M_RST, CCU_AXI_PCIE_M_CLK), - CCU_DIV_RST_MAP(CCU_AXI_PCIE_S_RST, CCU_AXI_PCIE_S_CLK), - CCU_DIV_RST_MAP(CCU_AXI_USB_RST, CCU_AXI_USB_CLK), - CCU_DIV_RST_MAP(CCU_AXI_HWA_RST, CCU_AXI_HWA_CLK), - CCU_DIV_RST_MAP(CCU_AXI_SRAM_RST, CCU_AXI_SRAM_CLK) -}; - /* * APB-bus clock is marked as critical since it's a main communication bus * for the SoC devices registers IO-operations. @@ -254,11 +226,6 @@ static const struct ccu_div_info sys_info[] = { CLK_SET_RATE_GATE, CCU_DIV_SKIP_ONE_TO_THREE) }; -static const struct ccu_div_rst_map sys_rst_map[] = { - CCU_DIV_RST_MAP(CCU_SYS_SATA_REF_RST, CCU_SYS_SATA_REF_CLK), - CCU_DIV_RST_MAP(CCU_SYS_APB_RST, CCU_SYS_APB_CLK), -}; - static struct ccu_div *ccu_div_find_desc(struct ccu_div_data *data, unsigned int clk_id) { @@ -274,42 +241,6 @@ static struct ccu_div *ccu_div_find_desc(struct ccu_div_data *data, return ERR_PTR(-EINVAL); } -static int ccu_div_reset(struct reset_controller_dev *rcdev, - unsigned long rst_id) -{ - struct ccu_div_data *data = to_ccu_div_data(rcdev); - const struct ccu_div_rst_map *map; - struct ccu_div *div; - int idx, ret; - - for (idx = 0, map = data->rst_map; idx < data->rst_num; ++idx, ++map) { - if (map->rst_id == rst_id) - break; - } - if (idx == data->rst_num) { - pr_err("Invalid reset ID %lu specified\n", rst_id); - return -EINVAL; - } - - div = ccu_div_find_desc(data, map->clk_id); - if (IS_ERR(div)) { - pr_err("Invalid clock ID %d in mapping\n", map->clk_id); - return PTR_ERR(div); - } - - ret = ccu_div_reset_domain(div); - if (ret) { - pr_err("Reset isn't supported by divider %s\n", - clk_hw_get_name(ccu_div_get_clk_hw(div))); - } - - return ret; -} - -static const struct reset_control_ops ccu_div_rst_ops = { - .reset = ccu_div_reset, -}; - static struct ccu_div_data *ccu_div_create_data(struct device_node *np) { struct ccu_div_data *data; @@ -323,13 +254,9 @@ static struct ccu_div_data *ccu_div_create_data(struct device_node *np) if (of_device_is_compatible(np, "baikal,bt1-ccu-axi")) { data->divs_num = ARRAY_SIZE(axi_info); data->divs_info = axi_info; - data->rst_num = ARRAY_SIZE(axi_rst_map); - data->rst_map = axi_rst_map; } else if (of_device_is_compatible(np, "baikal,bt1-ccu-sys")) { data->divs_num = ARRAY_SIZE(sys_info); data->divs_info = sys_info; - data->rst_num = ARRAY_SIZE(sys_rst_map); - data->rst_map = sys_rst_map; } else { pr_err("Incompatible DT node '%s' specified\n", of_node_full_name(np)); @@ -455,18 +382,19 @@ static void ccu_div_clk_unregister(struct ccu_div_data *data) static int ccu_div_rst_register(struct ccu_div_data *data) { - int ret; + struct ccu_rst_init_data init = {0}; - data->rcdev.ops = &ccu_div_rst_ops; - data->rcdev.of_node = data->np; - data->rcdev.nr_resets = data->rst_num; + init.sys_regs = data->sys_regs; + init.np = data->np; - ret = reset_controller_register(&data->rcdev); - if (ret) + data->rsts = ccu_rst_hw_register(&init); + if (IS_ERR(data->rsts)) { pr_err("Couldn't register divider '%s' reset controller\n", of_node_full_name(data->np)); + return PTR_ERR(data->rsts); + } - return ret; + return 0; } static void ccu_div_init(struct device_node *np) From c0cd3b1790df76049d1806100d6c744b757fe004 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 30 Sep 2022 01:54:00 +0300 Subject: [PATCH 06/20] dt-bindings: clk: baikal-t1: Add DDR/PCIe reset IDs Aside with a set of the trigger-like resets Baikal-T1 CCU provides additional directly controlled reset signals for the DDR and PCIe controllers. As a preparation before adding these resets support to the kernel let's extent the Baikal-T1 CCU IDs list with the new IDs, which will be used to access the corresponding reset controls. Signed-off-by: Serge Semin Reviewed-by: Philipp Zabel Acked-by: Rob Herring Link: https://lore.kernel.org/r/20220929225402.9696-7-Sergey.Semin@baikalelectronics.ru Signed-off-by: Stephen Boyd --- include/dt-bindings/reset/bt1-ccu.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/dt-bindings/reset/bt1-ccu.h b/include/dt-bindings/reset/bt1-ccu.h index 3578e83026bc..c691efaa678f 100644 --- a/include/dt-bindings/reset/bt1-ccu.h +++ b/include/dt-bindings/reset/bt1-ccu.h @@ -21,5 +21,14 @@ #define CCU_SYS_SATA_REF_RST 0 #define CCU_SYS_APB_RST 1 +#define CCU_SYS_DDR_FULL_RST 2 +#define CCU_SYS_DDR_INIT_RST 3 +#define CCU_SYS_PCIE_PCS_PHY_RST 4 +#define CCU_SYS_PCIE_PIPE0_RST 5 +#define CCU_SYS_PCIE_CORE_RST 6 +#define CCU_SYS_PCIE_PWR_RST 7 +#define CCU_SYS_PCIE_STICKY_RST 8 +#define CCU_SYS_PCIE_NSTICKY_RST 9 +#define CCU_SYS_PCIE_HOT_RST 10 #endif /* __DT_BINDINGS_RESET_BT1_CCU_H */ From fa6bd541d786aa6db93bcddf23cea26fcfa859a3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 30 Sep 2022 01:54:01 +0300 Subject: [PATCH 07/20] clk: baikal-t1: Add DDR/PCIe directly controlled resets support Aside with a set of the trigger-like resets Baikal-T1 CCU provides two additional blocks with directly controlled reset signals. In particular it concerns DDR full and initial resets and various PCIe sub-domains resets. Let's add the direct reset assertion/de-assertion of the corresponding flags support into the Baikal-T1 CCU driver then. It will be required at least for the PCIe platform driver. Obviously the DDR controller isn't supposed to be fully reset in the kernel, so the corresponding controls are added just for the sake of the interface implementation completeness. Signed-off-by: Serge Semin Reviewed-by: Philipp Zabel Link: https://lore.kernel.org/r/20220929225402.9696-8-Sergey.Semin@baikalelectronics.ru Signed-off-by: Stephen Boyd --- drivers/clk/baikal-t1/ccu-rst.c | 66 +++++++++++++++++++++++++++++++++ drivers/clk/baikal-t1/ccu-rst.h | 10 +++++ 2 files changed, 76 insertions(+) diff --git a/drivers/clk/baikal-t1/ccu-rst.c b/drivers/clk/baikal-t1/ccu-rst.c index 7db52633270f..40023ea67463 100644 --- a/drivers/clk/baikal-t1/ccu-rst.c +++ b/drivers/clk/baikal-t1/ccu-rst.c @@ -35,18 +35,29 @@ #define CCU_AXI_HWA_BASE 0x054 #define CCU_AXI_SRAM_BASE 0x058 +#define CCU_SYS_DDR_BASE 0x02c #define CCU_SYS_SATA_REF_BASE 0x060 #define CCU_SYS_APB_BASE 0x064 +#define CCU_SYS_PCIE_BASE 0x144 #define CCU_RST_DELAY_US 1 #define CCU_RST_TRIG(_base, _ofs) \ { \ + .type = CCU_RST_TRIG, \ + .base = _base, \ + .mask = BIT(_ofs), \ + } + +#define CCU_RST_DIR(_base, _ofs) \ + { \ + .type = CCU_RST_DIR, \ .base = _base, \ .mask = BIT(_ofs), \ } struct ccu_rst_info { + enum ccu_rst_type type; unsigned int base; unsigned int mask; }; @@ -79,6 +90,15 @@ static const struct ccu_rst_info axi_rst_info[] = { static const struct ccu_rst_info sys_rst_info[] = { [CCU_SYS_SATA_REF_RST] = CCU_RST_TRIG(CCU_SYS_SATA_REF_BASE, 1), [CCU_SYS_APB_RST] = CCU_RST_TRIG(CCU_SYS_APB_BASE, 1), + [CCU_SYS_DDR_FULL_RST] = CCU_RST_DIR(CCU_SYS_DDR_BASE, 1), + [CCU_SYS_DDR_INIT_RST] = CCU_RST_DIR(CCU_SYS_DDR_BASE, 2), + [CCU_SYS_PCIE_PCS_PHY_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 0), + [CCU_SYS_PCIE_PIPE0_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 4), + [CCU_SYS_PCIE_CORE_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 8), + [CCU_SYS_PCIE_PWR_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 9), + [CCU_SYS_PCIE_STICKY_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 10), + [CCU_SYS_PCIE_NSTICKY_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 11), + [CCU_SYS_PCIE_HOT_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 12), }; static int ccu_rst_reset(struct reset_controller_dev *rcdev, unsigned long idx) @@ -86,6 +106,9 @@ static int ccu_rst_reset(struct reset_controller_dev *rcdev, unsigned long idx) struct ccu_rst *rst = to_ccu_rst(rcdev); const struct ccu_rst_info *info = &rst->rsts_info[idx]; + if (info->type != CCU_RST_TRIG) + return -EOPNOTSUPP; + regmap_update_bits(rst->sys_regs, info->base, info->mask, info->mask); /* The next delay must be enough to cover all the resets. */ @@ -94,8 +117,51 @@ static int ccu_rst_reset(struct reset_controller_dev *rcdev, unsigned long idx) return 0; } +static int ccu_rst_set(struct reset_controller_dev *rcdev, + unsigned long idx, bool high) +{ + struct ccu_rst *rst = to_ccu_rst(rcdev); + const struct ccu_rst_info *info = &rst->rsts_info[idx]; + + if (info->type != CCU_RST_DIR) + return high ? -EOPNOTSUPP : 0; + + return regmap_update_bits(rst->sys_regs, info->base, + info->mask, high ? info->mask : 0); +} + +static int ccu_rst_assert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + return ccu_rst_set(rcdev, idx, true); +} + +static int ccu_rst_deassert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + return ccu_rst_set(rcdev, idx, false); +} + +static int ccu_rst_status(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + struct ccu_rst *rst = to_ccu_rst(rcdev); + const struct ccu_rst_info *info = &rst->rsts_info[idx]; + u32 val; + + if (info->type != CCU_RST_DIR) + return -EOPNOTSUPP; + + regmap_read(rst->sys_regs, info->base, &val); + + return !!(val & info->mask); +} + static const struct reset_control_ops ccu_rst_ops = { .reset = ccu_rst_reset, + .assert = ccu_rst_assert, + .deassert = ccu_rst_deassert, + .status = ccu_rst_status, }; struct ccu_rst *ccu_rst_hw_register(const struct ccu_rst_init_data *rst_init) diff --git a/drivers/clk/baikal-t1/ccu-rst.h b/drivers/clk/baikal-t1/ccu-rst.h index 68214d777465..d6e8b2f671f4 100644 --- a/drivers/clk/baikal-t1/ccu-rst.h +++ b/drivers/clk/baikal-t1/ccu-rst.h @@ -13,6 +13,16 @@ struct ccu_rst_info; +/* + * enum ccu_rst_type - CCU Reset types + * @CCU_RST_TRIG: Self-deasserted reset signal. + * @CCU_RST_DIR: Directly controlled reset signal. + */ +enum ccu_rst_type { + CCU_RST_TRIG, + CCU_RST_DIR, +}; + /* * struct ccu_rst_init_data - CCU Resets initialization data * @sys_regs: Baikal-T1 System Controller registers map. From c4e05443b6d400ed026cef9107188899a6d5e632 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 30 Sep 2022 01:54:02 +0300 Subject: [PATCH 08/20] clk: baikal-t1: Convert to platform device driver In accordance with the way the MIPS platform is normally design there are only six clock sources which need to be available on the kernel start in order to one end up booting correctly: + CPU PLL: needed by the r4k and MIPS GIC timer drivers. The former one is initialized by the arch code, while the later one is implemented in the mips-gic-timer.c driver as the OF-declared timer. + PCIe PLL: required as a parental clock source for the APB/timer domains. + APB clock: needed in order to access all the SoC CSRs at least for the timer OF-declared drivers. + APB Timer{0-2} clocks: these are the DW APB timers which drivers dw_apb_timer_of.c are implemented as the OF-declared timers. So as long as the clocks above are available early the kernel will normally work. Let's convert the Baikal-T1 CCU drivers to the platform device drivers keeping that in mind. Generally speaking the conversion isn't that complicated since the driver infrastructure has been designed as flexible enough for that. First we need to add a new PLL/Divider clock features flag which indicates the corresponding clock source as a basic one and that clock sources will be available on the kernel early boot stages. Second the internal PLL/Divider descriptors need to be initialized with -EPROBE_DEFER value as the corresponding clock source is unavailable at the early stages. They will be allocated and initialized on the Baikal-T1 clock platform driver probe procedure. Finally the already available PLL/Divider init functions need to be split up into two ones: init procedure performed in the framework of the OF-declared clock initialization (of_clk_init()), and the probe procedure called by the platform devices bus driver. Note the later method will just continue the system clocks initialization started in the former one. Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20220929225402.9696-9-Sergey.Semin@baikalelectronics.ru [sboyd@kernel.org: Remove module things because the Kconfig is still bool] Signed-off-by: Stephen Boyd --- drivers/clk/baikal-t1/ccu-div.h | 3 + drivers/clk/baikal-t1/ccu-pll.h | 8 ++ drivers/clk/baikal-t1/clk-ccu-div.c | 146 ++++++++++++++++++++++------ drivers/clk/baikal-t1/clk-ccu-pll.c | 125 +++++++++++++++++++----- 4 files changed, 227 insertions(+), 55 deletions(-) diff --git a/drivers/clk/baikal-t1/ccu-div.h b/drivers/clk/baikal-t1/ccu-div.h index ff97bb30fcc3..76d8ee44d415 100644 --- a/drivers/clk/baikal-t1/ccu-div.h +++ b/drivers/clk/baikal-t1/ccu-div.h @@ -23,6 +23,8 @@ /* * CCU Divider private flags + * @CCU_DIV_BASIC: Basic divider clock required by the kernel as early as + * possible. * @CCU_DIV_SKIP_ONE: Due to some reason divider can't be set to 1. * It can be 0 though, which is functionally the same. * @CCU_DIV_SKIP_ONE_TO_THREE: For some reason divider can't be within [1,3]. @@ -30,6 +32,7 @@ * @CCU_DIV_LOCK_SHIFTED: Find lock-bit at non-standard position. * @CCU_DIV_RESET_DOMAIN: There is a clock domain reset handle. */ +#define CCU_DIV_BASIC BIT(0) #define CCU_DIV_SKIP_ONE BIT(1) #define CCU_DIV_SKIP_ONE_TO_THREE BIT(2) #define CCU_DIV_LOCK_SHIFTED BIT(3) diff --git a/drivers/clk/baikal-t1/ccu-pll.h b/drivers/clk/baikal-t1/ccu-pll.h index 76cd9132a219..a71bfd7b90ec 100644 --- a/drivers/clk/baikal-t1/ccu-pll.h +++ b/drivers/clk/baikal-t1/ccu-pll.h @@ -13,6 +13,12 @@ #include #include +/* + * CCU PLL private flags + * @CCU_PLL_BASIC: Basic PLL required by the kernel as early as possible. + */ +#define CCU_PLL_BASIC BIT(0) + /* * struct ccu_pll_init_data - CCU PLL initialization data * @id: Clock private identifier. @@ -22,6 +28,7 @@ * @sys_regs: Baikal-T1 System Controller registers map. * @np: Pointer to the node describing the CCU PLLs. * @flags: PLL clock flags. + * @features: PLL private features. */ struct ccu_pll_init_data { unsigned int id; @@ -31,6 +38,7 @@ struct ccu_pll_init_data { struct regmap *sys_regs; struct device_node *np; unsigned long flags; + unsigned long features; }; /* diff --git a/drivers/clk/baikal-t1/clk-ccu-div.c b/drivers/clk/baikal-t1/clk-ccu-div.c index 278aa38d767e..0e772e034812 100644 --- a/drivers/clk/baikal-t1/clk-ccu-div.c +++ b/drivers/clk/baikal-t1/clk-ccu-div.c @@ -12,6 +12,7 @@ #define pr_fmt(fmt) "bt1-ccu-div: " fmt #include +#include #include #include #include @@ -180,7 +181,7 @@ static const struct ccu_div_info sys_info[] = { CLK_SET_RATE_PARENT), CCU_DIV_VAR_INFO(CCU_SYS_APB_CLK, "sys_apb_clk", "pcie_clk", CCU_SYS_APB_BASE, 5, - CLK_IS_CRITICAL, CCU_DIV_RESET_DOMAIN), + CLK_IS_CRITICAL, CCU_DIV_BASIC | CCU_DIV_RESET_DOMAIN), CCU_DIV_GATE_INFO(CCU_SYS_GMAC0_TX_CLK, "sys_gmac0_tx_clk", "eth_clk", CCU_SYS_GMAC0_BASE, 5), CCU_DIV_FIXED_INFO(CCU_SYS_GMAC0_PTP_CLK, "sys_gmac0_ptp_clk", @@ -214,28 +215,53 @@ static const struct ccu_div_info sys_info[] = { "ref_clk", 25), CCU_DIV_VAR_INFO(CCU_SYS_TIMER0_CLK, "sys_timer0_clk", "ref_clk", CCU_SYS_TIMER0_BASE, 17, - CLK_SET_RATE_GATE, 0), + CLK_SET_RATE_GATE, CCU_DIV_BASIC), CCU_DIV_VAR_INFO(CCU_SYS_TIMER1_CLK, "sys_timer1_clk", "ref_clk", CCU_SYS_TIMER1_BASE, 17, - CLK_SET_RATE_GATE, 0), + CLK_SET_RATE_GATE, CCU_DIV_BASIC), CCU_DIV_VAR_INFO(CCU_SYS_TIMER2_CLK, "sys_timer2_clk", "ref_clk", CCU_SYS_TIMER2_BASE, 17, - CLK_SET_RATE_GATE, 0), + CLK_SET_RATE_GATE, CCU_DIV_BASIC), CCU_DIV_VAR_INFO(CCU_SYS_WDT_CLK, "sys_wdt_clk", "eth_clk", CCU_SYS_WDT_BASE, 17, CLK_SET_RATE_GATE, CCU_DIV_SKIP_ONE_TO_THREE) }; +static struct ccu_div_data *axi_data; +static struct ccu_div_data *sys_data; + +static void ccu_div_set_data(struct ccu_div_data *data) +{ + struct device_node *np = data->np; + + if (of_device_is_compatible(np, "baikal,bt1-ccu-axi")) + axi_data = data; + else if (of_device_is_compatible(np, "baikal,bt1-ccu-sys")) + sys_data = data; + else + pr_err("Invalid DT node '%s' specified\n", of_node_full_name(np)); +} + +static struct ccu_div_data *ccu_div_get_data(struct device_node *np) +{ + if (of_device_is_compatible(np, "baikal,bt1-ccu-axi")) + return axi_data; + else if (of_device_is_compatible(np, "baikal,bt1-ccu-sys")) + return sys_data; + + pr_err("Invalid DT node '%s' specified\n", of_node_full_name(np)); + + return NULL; +} + static struct ccu_div *ccu_div_find_desc(struct ccu_div_data *data, unsigned int clk_id) { - struct ccu_div *div; int idx; for (idx = 0; idx < data->divs_num; ++idx) { - div = data->divs[idx]; - if (div && div->id == clk_id) - return div; + if (data->divs_info[idx].id == clk_id) + return data->divs[idx]; } return ERR_PTR(-EINVAL); @@ -307,14 +333,16 @@ static struct clk_hw *ccu_div_of_clk_hw_get(struct of_phandle_args *clkspec, clk_id = clkspec->args[0]; div = ccu_div_find_desc(data, clk_id); if (IS_ERR(div)) { - pr_info("Invalid clock ID %d specified\n", clk_id); + if (div != ERR_PTR(-EPROBE_DEFER)) + pr_info("Invalid clock ID %d specified\n", clk_id); + return ERR_CAST(div); } return ccu_div_get_clk_hw(div); } -static int ccu_div_clk_register(struct ccu_div_data *data) +static int ccu_div_clk_register(struct ccu_div_data *data, bool defer) { int idx, ret; @@ -322,6 +350,13 @@ static int ccu_div_clk_register(struct ccu_div_data *data) const struct ccu_div_info *info = &data->divs_info[idx]; struct ccu_div_init_data init = {0}; + if (!!(info->features & CCU_DIV_BASIC) ^ defer) { + if (!data->divs[idx]) + data->divs[idx] = ERR_PTR(-EPROBE_DEFER); + + continue; + } + init.id = info->id; init.name = info->name; init.parent_name = info->parent_name; @@ -354,30 +389,43 @@ static int ccu_div_clk_register(struct ccu_div_data *data) } } - ret = of_clk_add_hw_provider(data->np, ccu_div_of_clk_hw_get, data); - if (ret) { - pr_err("Couldn't register dividers '%s' clock provider\n", - of_node_full_name(data->np)); - goto err_hw_unregister; - } - return 0; err_hw_unregister: - for (--idx; idx >= 0; --idx) + for (--idx; idx >= 0; --idx) { + if (!!(data->divs_info[idx].features & CCU_DIV_BASIC) ^ defer) + continue; + ccu_div_hw_unregister(data->divs[idx]); + } return ret; } -static void ccu_div_clk_unregister(struct ccu_div_data *data) +static void ccu_div_clk_unregister(struct ccu_div_data *data, bool defer) { int idx; - of_clk_del_provider(data->np); + /* Uninstall only the clocks registered on the specfied stage */ + for (idx = 0; idx < data->divs_num; ++idx) { + if (!!(data->divs_info[idx].features & CCU_DIV_BASIC) ^ defer) + continue; - for (idx = 0; idx < data->divs_num; ++idx) ccu_div_hw_unregister(data->divs[idx]); + } +} + +static int ccu_div_of_register(struct ccu_div_data *data) +{ + int ret; + + ret = of_clk_add_hw_provider(data->np, ccu_div_of_clk_hw_get, data); + if (ret) { + pr_err("Couldn't register dividers '%s' clock provider\n", + of_node_full_name(data->np)); + } + + return ret; } static int ccu_div_rst_register(struct ccu_div_data *data) @@ -397,7 +445,48 @@ static int ccu_div_rst_register(struct ccu_div_data *data) return 0; } -static void ccu_div_init(struct device_node *np) +static int ccu_div_probe(struct platform_device *pdev) +{ + struct ccu_div_data *data; + int ret; + + data = ccu_div_get_data(dev_of_node(&pdev->dev)); + if (!data) + return -EINVAL; + + ret = ccu_div_clk_register(data, false); + if (ret) + return ret; + + ret = ccu_div_rst_register(data); + if (ret) + goto err_clk_unregister; + + return 0; + +err_clk_unregister: + ccu_div_clk_unregister(data, false); + + return ret; +} + +static const struct of_device_id ccu_div_of_match[] = { + { .compatible = "baikal,bt1-ccu-axi" }, + { .compatible = "baikal,bt1-ccu-sys" }, + { } +}; + +static struct platform_driver ccu_div_driver = { + .probe = ccu_div_probe, + .driver = { + .name = "clk-ccu-div", + .of_match_table = ccu_div_of_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(ccu_div_driver); + +static __init void ccu_div_init(struct device_node *np) { struct ccu_div_data *data; int ret; @@ -410,22 +499,23 @@ static void ccu_div_init(struct device_node *np) if (ret) goto err_free_data; - ret = ccu_div_clk_register(data); + ret = ccu_div_clk_register(data, true); if (ret) goto err_free_data; - ret = ccu_div_rst_register(data); + ret = ccu_div_of_register(data); if (ret) goto err_clk_unregister; + ccu_div_set_data(data); + return; err_clk_unregister: - ccu_div_clk_unregister(data); + ccu_div_clk_unregister(data, true); err_free_data: ccu_div_free_data(data); } - -CLK_OF_DECLARE(ccu_axi, "baikal,bt1-ccu-axi", ccu_div_init); -CLK_OF_DECLARE(ccu_sys, "baikal,bt1-ccu-sys", ccu_div_init); +CLK_OF_DECLARE_DRIVER(ccu_axi, "baikal,bt1-ccu-axi", ccu_div_init); +CLK_OF_DECLARE_DRIVER(ccu_sys, "baikal,bt1-ccu-sys", ccu_div_init); diff --git a/drivers/clk/baikal-t1/clk-ccu-pll.c b/drivers/clk/baikal-t1/clk-ccu-pll.c index 2445d4b12baf..fce02ce77347 100644 --- a/drivers/clk/baikal-t1/clk-ccu-pll.c +++ b/drivers/clk/baikal-t1/clk-ccu-pll.c @@ -12,6 +12,7 @@ #define pr_fmt(fmt) "bt1-ccu-pll: " fmt #include +#include #include #include #include @@ -31,13 +32,14 @@ #define CCU_PCIE_PLL_BASE 0x018 #define CCU_ETH_PLL_BASE 0x020 -#define CCU_PLL_INFO(_id, _name, _pname, _base, _flags) \ - { \ - .id = _id, \ - .name = _name, \ - .parent_name = _pname, \ - .base = _base, \ - .flags = _flags \ +#define CCU_PLL_INFO(_id, _name, _pname, _base, _flags, _features) \ + { \ + .id = _id, \ + .name = _name, \ + .parent_name = _pname, \ + .base = _base, \ + .flags = _flags, \ + .features = _features, \ } #define CCU_PLL_NUM ARRAY_SIZE(pll_info) @@ -48,6 +50,7 @@ struct ccu_pll_info { const char *parent_name; unsigned int base; unsigned long flags; + unsigned long features; }; /* @@ -61,15 +64,15 @@ struct ccu_pll_info { */ static const struct ccu_pll_info pll_info[] = { CCU_PLL_INFO(CCU_CPU_PLL, "cpu_pll", "ref_clk", CCU_CPU_PLL_BASE, - CLK_IS_CRITICAL), + CLK_IS_CRITICAL, CCU_PLL_BASIC), CCU_PLL_INFO(CCU_SATA_PLL, "sata_pll", "ref_clk", CCU_SATA_PLL_BASE, - CLK_IS_CRITICAL | CLK_SET_RATE_GATE), + CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0), CCU_PLL_INFO(CCU_DDR_PLL, "ddr_pll", "ref_clk", CCU_DDR_PLL_BASE, - CLK_IS_CRITICAL | CLK_SET_RATE_GATE), + CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0), CCU_PLL_INFO(CCU_PCIE_PLL, "pcie_pll", "ref_clk", CCU_PCIE_PLL_BASE, - CLK_IS_CRITICAL), + CLK_IS_CRITICAL, CCU_PLL_BASIC), CCU_PLL_INFO(CCU_ETH_PLL, "eth_pll", "ref_clk", CCU_ETH_PLL_BASE, - CLK_IS_CRITICAL | CLK_SET_RATE_GATE) + CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0) }; struct ccu_pll_data { @@ -78,16 +81,16 @@ struct ccu_pll_data { struct ccu_pll *plls[CCU_PLL_NUM]; }; +static struct ccu_pll_data *pll_data; + static struct ccu_pll *ccu_pll_find_desc(struct ccu_pll_data *data, unsigned int clk_id) { - struct ccu_pll *pll; int idx; for (idx = 0; idx < CCU_PLL_NUM; ++idx) { - pll = data->plls[idx]; - if (pll && pll->id == clk_id) - return pll; + if (pll_info[idx].id == clk_id) + return data->plls[idx]; } return ERR_PTR(-EINVAL); @@ -133,14 +136,16 @@ static struct clk_hw *ccu_pll_of_clk_hw_get(struct of_phandle_args *clkspec, clk_id = clkspec->args[0]; pll = ccu_pll_find_desc(data, clk_id); if (IS_ERR(pll)) { - pr_info("Invalid PLL clock ID %d specified\n", clk_id); + if (pll != ERR_PTR(-EPROBE_DEFER)) + pr_info("Invalid PLL clock ID %d specified\n", clk_id); + return ERR_CAST(pll); } return ccu_pll_get_clk_hw(pll); } -static int ccu_pll_clk_register(struct ccu_pll_data *data) +static int ccu_pll_clk_register(struct ccu_pll_data *data, bool defer) { int idx, ret; @@ -148,6 +153,14 @@ static int ccu_pll_clk_register(struct ccu_pll_data *data) const struct ccu_pll_info *info = &pll_info[idx]; struct ccu_pll_init_data init = {0}; + /* Defer non-basic PLLs allocation for the probe stage */ + if (!!(info->features & CCU_PLL_BASIC) ^ defer) { + if (!data->plls[idx]) + data->plls[idx] = ERR_PTR(-EPROBE_DEFER); + + continue; + } + init.id = info->id; init.name = info->name; init.parent_name = info->parent_name; @@ -155,6 +168,7 @@ static int ccu_pll_clk_register(struct ccu_pll_data *data) init.sys_regs = data->sys_regs; init.np = data->np; init.flags = info->flags; + init.features = info->features; data->plls[idx] = ccu_pll_hw_register(&init); if (IS_ERR(data->plls[idx])) { @@ -165,22 +179,70 @@ static int ccu_pll_clk_register(struct ccu_pll_data *data) } } + return 0; + +err_hw_unregister: + for (--idx; idx >= 0; --idx) { + if (!!(pll_info[idx].features & CCU_PLL_BASIC) ^ defer) + continue; + + ccu_pll_hw_unregister(data->plls[idx]); + } + + return ret; +} + +static void ccu_pll_clk_unregister(struct ccu_pll_data *data, bool defer) +{ + int idx; + + /* Uninstall only the clocks registered on the specfied stage */ + for (idx = 0; idx < CCU_PLL_NUM; ++idx) { + if (!!(pll_info[idx].features & CCU_PLL_BASIC) ^ defer) + continue; + + ccu_pll_hw_unregister(data->plls[idx]); + } +} + +static int ccu_pll_of_register(struct ccu_pll_data *data) +{ + int ret; + ret = of_clk_add_hw_provider(data->np, ccu_pll_of_clk_hw_get, data); if (ret) { pr_err("Couldn't register PLL provider of '%s'\n", of_node_full_name(data->np)); - goto err_hw_unregister; } - return 0; - -err_hw_unregister: - for (--idx; idx >= 0; --idx) - ccu_pll_hw_unregister(data->plls[idx]); - return ret; } +static int ccu_pll_probe(struct platform_device *pdev) +{ + struct ccu_pll_data *data = pll_data; + + if (!data) + return -EINVAL; + + return ccu_pll_clk_register(data, false); +} + +static const struct of_device_id ccu_pll_of_match[] = { + { .compatible = "baikal,bt1-ccu-pll" }, + { } +}; + +static struct platform_driver ccu_pll_driver = { + .probe = ccu_pll_probe, + .driver = { + .name = "clk-ccu-pll", + .of_match_table = ccu_pll_of_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(ccu_pll_driver); + static __init void ccu_pll_init(struct device_node *np) { struct ccu_pll_data *data; @@ -194,13 +256,22 @@ static __init void ccu_pll_init(struct device_node *np) if (ret) goto err_free_data; - ret = ccu_pll_clk_register(data); + ret = ccu_pll_clk_register(data, true); if (ret) goto err_free_data; + ret = ccu_pll_of_register(data); + if (ret) + goto err_clk_unregister; + + pll_data = data; + return; +err_clk_unregister: + ccu_pll_clk_unregister(data, true); + err_free_data: ccu_pll_free_data(data); } -CLK_OF_DECLARE(ccu_pll, "baikal,bt1-ccu-pll", ccu_pll_init); +CLK_OF_DECLARE_DRIVER(ccu_pll, "baikal,bt1-ccu-pll", ccu_pll_init); From 6c5422851d8be8c7451e968fd2e6da41b6109e17 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 26 Sep 2022 10:45:09 +0200 Subject: [PATCH 09/20] clk: bcm2835: Make peripheral PLLC critical MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When testing for a series affecting the VEC, it was discovered that turning off and on the VEC clock is crashing the system. It turns out that, when disabling the VEC clock, it's the only child of the PLLC-per clock which will also get disabled. The source of the crash is PLLC-per being disabled. It's likely that some other device might not take a clock reference that it actually needs, but it's unclear which at this point. Let's make PLLC-per critical so that we don't have that crash. Reported-by: Noralf Trønnes Signed-off-by: Maxime Ripard Link: https://lore.kernel.org/r/20220926084509.12233-1-maxime@cerno.tech Reviewed-by: Stefan Wahren Acked-by: Noralf Trønnes Signed-off-by: Stephen Boyd --- drivers/clk/bcm/clk-bcm2835.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/bcm/clk-bcm2835.c b/drivers/clk/bcm/clk-bcm2835.c index 48a1eb9f2d55..19de0e83b65d 100644 --- a/drivers/clk/bcm/clk-bcm2835.c +++ b/drivers/clk/bcm/clk-bcm2835.c @@ -1784,7 +1784,7 @@ static const struct bcm2835_clk_desc clk_desc_array[] = { .load_mask = CM_PLLC_LOADPER, .hold_mask = CM_PLLC_HOLDPER, .fixed_divider = 1, - .flags = CLK_SET_RATE_PARENT), + .flags = CLK_IS_CRITICAL | CLK_SET_RATE_PARENT), /* * PLLD is the display PLL, used to drive DSI display panels. From f690a4d7a8f66430662975511c86819dc9965bcc Mon Sep 17 00:00:00 2001 From: "Ivan T. Ivanov" Date: Mon, 12 Sep 2022 11:13:04 +0300 Subject: [PATCH 10/20] clk: bcm2835: Round UART input clock up It was reported that RPi3[1] and RPi Zero 2W boards have issues with the Bluetooth. It turns out that when switching from initial to operation speed host and device no longer can talk each other because host uses incorrect UART baud rate. The UART driver used in this case is amba-pl011. Original fix, see below Github link[2], was inside pl011 module, but somehow it didn't look as the right place to fix. Beside that this original rounding function is not exactly perfect for all possible clock values. So I deiced to move the hack to the platform which actually need it. The UART clock is initialised to be as close to the requested frequency as possible without exceeding it. Now that there is a clock manager that returns the actual frequencies, an expected 48MHz clock is reported as 47999625. If the requested baud rate == requested clock/16, there is no headroom and the slight reduction in actual clock rate results in failure. If increasing a clock by less than 0.1% changes it from ..999.. to ..000.., round it up. [1] https://bugzilla.suse.com/show_bug.cgi?id=1188238 [2] https://github.com/raspberrypi/linux/commit/ab3f1b39537f6d3825b8873006fbe2fc5ff057b7 Cc: Phil Elwell Signed-off-by: Ivan T. Ivanov Reviewed-by: Stefan Wahren Link: https://lore.kernel.org/r/20220912081306.24662-1-iivanov@suse.de Signed-off-by: Stephen Boyd --- drivers/clk/bcm/clk-bcm2835.c | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/drivers/clk/bcm/clk-bcm2835.c b/drivers/clk/bcm/clk-bcm2835.c index 19de0e83b65d..3f2ce20d27ec 100644 --- a/drivers/clk/bcm/clk-bcm2835.c +++ b/drivers/clk/bcm/clk-bcm2835.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -502,6 +503,8 @@ struct bcm2835_clock_data { bool low_jitter; u32 tcnt_mux; + + bool round_up; }; struct bcm2835_gate_data { @@ -993,12 +996,34 @@ static long bcm2835_clock_rate_from_divisor(struct bcm2835_clock *clock, return temp; } +static unsigned long bcm2835_round_rate(unsigned long rate) +{ + unsigned long scaler; + unsigned long limit; + + limit = rate / 100000; + + scaler = 1; + while (scaler < limit) + scaler *= 10; + + /* + * If increasing a clock by less than 0.1% changes it + * from ..999.. to ..000.., round up. + */ + if ((rate + scaler - 1) / scaler % 1000 == 0) + rate = roundup(rate, scaler); + + return rate; +} + static unsigned long bcm2835_clock_get_rate(struct clk_hw *hw, unsigned long parent_rate) { struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw); struct bcm2835_cprman *cprman = clock->cprman; const struct bcm2835_clock_data *data = clock->data; + unsigned long rate; u32 div; if (data->int_bits == 0 && data->frac_bits == 0) @@ -1006,7 +1031,12 @@ static unsigned long bcm2835_clock_get_rate(struct clk_hw *hw, div = cprman_read(cprman, data->div_reg); - return bcm2835_clock_rate_from_divisor(clock, parent_rate, div); + rate = bcm2835_clock_rate_from_divisor(clock, parent_rate, div); + + if (data->round_up) + rate = bcm2835_round_rate(rate); + + return rate; } static void bcm2835_clock_wait_busy(struct bcm2835_clock *clock) @@ -2143,7 +2173,8 @@ static const struct bcm2835_clk_desc clk_desc_array[] = { .div_reg = CM_UARTDIV, .int_bits = 10, .frac_bits = 12, - .tcnt_mux = 28), + .tcnt_mux = 28, + .round_up = true), /* TV encoder clock. Only operating frequency is 108Mhz. */ [BCM2835_CLOCK_VEC] = REGISTER_PER_CLK( From 0b919a3728691c172312dee99ba654055ccd8c84 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Sun, 4 Sep 2022 16:10:37 +0200 Subject: [PATCH 11/20] clk: bcm2835: fix bcm2835_clock_rate_from_divisor declaration The return value of bcm2835_clock_rate_from_divisor is always unsigned and also all caller expect this. So fix the declaration accordingly. Fixes: 41691b8862e2 ("clk: bcm2835: Add support for programming the audio domain clocks") Signed-off-by: Stefan Wahren Link: https://lore.kernel.org/r/20220904141037.38816-1-stefan.wahren@i2se.com Reviewed-by: Ivan T. Ivanov Reviewed-by: Florian Fainelli Signed-off-by: Stephen Boyd --- drivers/clk/bcm/clk-bcm2835.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/clk/bcm/clk-bcm2835.c b/drivers/clk/bcm/clk-bcm2835.c index 3f2ce20d27ec..e74fe6219d14 100644 --- a/drivers/clk/bcm/clk-bcm2835.c +++ b/drivers/clk/bcm/clk-bcm2835.c @@ -969,9 +969,9 @@ static u32 bcm2835_clock_choose_div(struct clk_hw *hw, return div; } -static long bcm2835_clock_rate_from_divisor(struct bcm2835_clock *clock, - unsigned long parent_rate, - u32 div) +static unsigned long bcm2835_clock_rate_from_divisor(struct bcm2835_clock *clock, + unsigned long parent_rate, + u32 div) { const struct bcm2835_clock_data *data = clock->data; u64 temp; From 4c68a345c157db23a59edb8e3227932c00de83a1 Mon Sep 17 00:00:00 2001 From: "Ivan T. Ivanov" Date: Mon, 29 Aug 2022 18:21:52 +0300 Subject: [PATCH 12/20] clk: bcm: rpi: Add support HEVC clock Export clock required for RPiVid video decoder hardware. Cc: Dom Cobley Acked-by: Maxime Ripard Signed-off-by: Ivan T. Ivanov Link: https://lore.kernel.org/r/20220829152154.147250-2-iivanov@suse.de Signed-off-by: Stephen Boyd --- drivers/clk/bcm/clk-raspberrypi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/clk/bcm/clk-raspberrypi.c b/drivers/clk/bcm/clk-raspberrypi.c index 73518009a0f2..8f560c6b602d 100644 --- a/drivers/clk/bcm/clk-raspberrypi.c +++ b/drivers/clk/bcm/clk-raspberrypi.c @@ -129,6 +129,9 @@ raspberrypi_clk_variants[RPI_FIRMWARE_NUM_CLK_ID] = { [RPI_FIRMWARE_V3D_CLK_ID] = { .export = true, }, + [RPI_FIRMWARE_HEVC_CLK_ID] = { + .export = true, + }, [RPI_FIRMWARE_PIXEL_BVB_CLK_ID] = { .export = true, }, From 16baa8c1c1ba00506d367d00856c58f5985f9b2e Mon Sep 17 00:00:00 2001 From: "Ivan T. Ivanov" Date: Mon, 29 Aug 2022 18:21:53 +0300 Subject: [PATCH 13/20] clk: bcm: rpi: Handle pixel clock in firmware The clk-bcm2835 handling of the pixel clock does not function correctly when the HDMI power domain is disabled. The firmware supports it correctly, so add it to the firmware clock driver. Acked-by: Maxime Ripard Acked-by: Dave Stevenson Signed-off-by: Ivan T. Ivanov Link: https://lore.kernel.org/r/20220829152154.147250-3-iivanov@suse.de Signed-off-by: Stephen Boyd --- drivers/clk/bcm/clk-raspberrypi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/clk/bcm/clk-raspberrypi.c b/drivers/clk/bcm/clk-raspberrypi.c index 8f560c6b602d..1248c0d64eae 100644 --- a/drivers/clk/bcm/clk-raspberrypi.c +++ b/drivers/clk/bcm/clk-raspberrypi.c @@ -129,6 +129,9 @@ raspberrypi_clk_variants[RPI_FIRMWARE_NUM_CLK_ID] = { [RPI_FIRMWARE_V3D_CLK_ID] = { .export = true, }, + [RPI_FIRMWARE_PIXEL_CLK_ID] = { + .export = true, + }, [RPI_FIRMWARE_HEVC_CLK_ID] = { .export = true, }, From 1777cb60f7df80d9b4ca3385eca1d2c0bed61cb6 Mon Sep 17 00:00:00 2001 From: Dom Cobley Date: Mon, 29 Aug 2022 18:21:54 +0300 Subject: [PATCH 14/20] clk: bcm: rpi: Add support for VEC clock Platform driver clk-bcm2835 gets an inaccurate clock for VEC (107MHz). Export VEC clock trough clk-raspberrypi which uses the right PLL to get an accurate 108MHz. Signed-off-by: Dom Cobley [iivanov: Adapted on top of v5.17-rc6] Signed-off-by: Ivan T. Ivanov Link: https://lore.kernel.org/r/20220829152154.147250-4-iivanov@suse.de Signed-off-by: Stephen Boyd --- drivers/clk/bcm/clk-raspberrypi.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/clk/bcm/clk-raspberrypi.c b/drivers/clk/bcm/clk-raspberrypi.c index 1248c0d64eae..d70bb9283af7 100644 --- a/drivers/clk/bcm/clk-raspberrypi.c +++ b/drivers/clk/bcm/clk-raspberrypi.c @@ -33,6 +33,7 @@ enum rpi_firmware_clk_id { RPI_FIRMWARE_EMMC2_CLK_ID, RPI_FIRMWARE_M2MC_CLK_ID, RPI_FIRMWARE_PIXEL_BVB_CLK_ID, + RPI_FIRMWARE_VEC_CLK_ID, RPI_FIRMWARE_NUM_CLK_ID, }; @@ -51,6 +52,7 @@ static char *rpi_firmware_clk_names[] = { [RPI_FIRMWARE_EMMC2_CLK_ID] = "emmc2", [RPI_FIRMWARE_M2MC_CLK_ID] = "m2mc", [RPI_FIRMWARE_PIXEL_BVB_CLK_ID] = "pixel-bvb", + [RPI_FIRMWARE_VEC_CLK_ID] = "vec", }; #define RPI_FIRMWARE_STATE_ENABLE_BIT BIT(0) @@ -138,6 +140,9 @@ raspberrypi_clk_variants[RPI_FIRMWARE_NUM_CLK_ID] = { [RPI_FIRMWARE_PIXEL_BVB_CLK_ID] = { .export = true, }, + [RPI_FIRMWARE_VEC_CLK_ID] = { + .export = true, + }, }; /* From cc3237827a21ca6fe29085a78f96b0509cf6d095 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Tue, 19 Jul 2022 11:46:36 +0200 Subject: [PATCH 15/20] clk: vc5: Check IO access results The devices of the versaclk clock generator family use an I2C control bus. IO access on an I2C bus can fail for various reasons. The driver currently ignores the return value of most IO operations. This results in silent failure. To avoid this check the return value and in case of an error abort the operation and propagate the error code to the caller. Signed-off-by: Lars-Peter Clausen Link: https://lore.kernel.org/r/20220719094637.844946-1-lars@metafoo.de Reviewed-by: Luca Ceresoli Signed-off-by: Stephen Boyd --- drivers/clk/clk-versaclock5.c | 139 ++++++++++++++++++++++------------ 1 file changed, 90 insertions(+), 49 deletions(-) diff --git a/drivers/clk/clk-versaclock5.c b/drivers/clk/clk-versaclock5.c index e7be3e54b9be..e83148eb2c24 100644 --- a/drivers/clk/clk-versaclock5.c +++ b/drivers/clk/clk-versaclock5.c @@ -230,8 +230,12 @@ static unsigned char vc5_mux_get_parent(struct clk_hw *hw) container_of(hw, struct vc5_driver_data, clk_mux); const u8 mask = VC5_PRIM_SRC_SHDN_EN_XTAL | VC5_PRIM_SRC_SHDN_EN_CLKIN; unsigned int src; + int ret; + + ret = regmap_read(vc5->regmap, VC5_PRIM_SRC_SHDN, &src); + if (ret) + return 0; - regmap_read(vc5->regmap, VC5_PRIM_SRC_SHDN, &src); src &= mask; if (src == VC5_PRIM_SRC_SHDN_EN_XTAL) @@ -286,8 +290,12 @@ static unsigned long vc5_dbl_recalc_rate(struct clk_hw *hw, struct vc5_driver_data *vc5 = container_of(hw, struct vc5_driver_data, clk_mul); unsigned int premul; + int ret; + + ret = regmap_read(vc5->regmap, VC5_PRIM_SRC_SHDN, &premul); + if (ret) + return 0; - regmap_read(vc5->regmap, VC5_PRIM_SRC_SHDN, &premul); if (premul & VC5_PRIM_SRC_SHDN_EN_DOUBLE_XTAL_FREQ) parent_rate *= 2; @@ -315,11 +323,9 @@ static int vc5_dbl_set_rate(struct clk_hw *hw, unsigned long rate, else mask = 0; - regmap_update_bits(vc5->regmap, VC5_PRIM_SRC_SHDN, - VC5_PRIM_SRC_SHDN_EN_DOUBLE_XTAL_FREQ, - mask); - - return 0; + return regmap_update_bits(vc5->regmap, VC5_PRIM_SRC_SHDN, + VC5_PRIM_SRC_SHDN_EN_DOUBLE_XTAL_FREQ, + mask); } static const struct clk_ops vc5_dbl_ops = { @@ -334,14 +340,19 @@ static unsigned long vc5_pfd_recalc_rate(struct clk_hw *hw, struct vc5_driver_data *vc5 = container_of(hw, struct vc5_driver_data, clk_pfd); unsigned int prediv, div; + int ret; - regmap_read(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV, &prediv); + ret = regmap_read(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV, &prediv); + if (ret) + return 0; /* The bypass_prediv is set, PLL fed from Ref_in directly. */ if (prediv & VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV) return parent_rate; - regmap_read(vc5->regmap, VC5_REF_DIVIDER, &div); + ret = regmap_read(vc5->regmap, VC5_REF_DIVIDER, &div); + if (ret) + return 0; /* The Sel_prediv2 is set, PLL fed from prediv2 (Ref_in / 2) */ if (div & VC5_REF_DIVIDER_SEL_PREDIV2) @@ -376,15 +387,18 @@ static int vc5_pfd_set_rate(struct clk_hw *hw, unsigned long rate, struct vc5_driver_data *vc5 = container_of(hw, struct vc5_driver_data, clk_pfd); unsigned long idiv; + int ret; u8 div; /* CLKIN within range of PLL input, feed directly to PLL. */ if (parent_rate <= 50000000) { - regmap_update_bits(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV, - VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV, - VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV); - regmap_update_bits(vc5->regmap, VC5_REF_DIVIDER, 0xff, 0x00); - return 0; + ret = regmap_update_bits(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV, + VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV, + VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV); + if (ret) + return ret; + + return regmap_update_bits(vc5->regmap, VC5_REF_DIVIDER, 0xff, 0x00); } idiv = DIV_ROUND_UP(parent_rate, rate); @@ -395,11 +409,12 @@ static int vc5_pfd_set_rate(struct clk_hw *hw, unsigned long rate, else div = VC5_REF_DIVIDER_REF_DIV(idiv); - regmap_update_bits(vc5->regmap, VC5_REF_DIVIDER, 0xff, div); - regmap_update_bits(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV, - VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV, 0); + ret = regmap_update_bits(vc5->regmap, VC5_REF_DIVIDER, 0xff, div); + if (ret) + return ret; - return 0; + return regmap_update_bits(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV, + VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV, 0); } static const struct clk_ops vc5_pfd_ops = { @@ -551,9 +566,12 @@ static int vc5_fod_set_rate(struct clk_hw *hw, unsigned long rate, hwdata->div_int >> 4, hwdata->div_int << 4, 0 }; + int ret; - regmap_bulk_write(vc5->regmap, VC5_OUT_DIV_FRAC(hwdata->num, 0), - data, 14); + ret = regmap_bulk_write(vc5->regmap, VC5_OUT_DIV_FRAC(hwdata->num, 0), + data, 14); + if (ret) + return ret; /* * Toggle magic bit in undocumented register for unknown reason. @@ -561,12 +579,14 @@ static int vc5_fod_set_rate(struct clk_hw *hw, unsigned long rate, * datasheet somewhat implies this is needed, but the register * and the bit is not documented. */ - regmap_update_bits(vc5->regmap, VC5_GLOBAL_REGISTER, - VC5_GLOBAL_REGISTER_GLOBAL_RESET, 0); - regmap_update_bits(vc5->regmap, VC5_GLOBAL_REGISTER, - VC5_GLOBAL_REGISTER_GLOBAL_RESET, - VC5_GLOBAL_REGISTER_GLOBAL_RESET); - return 0; + ret = regmap_update_bits(vc5->regmap, VC5_GLOBAL_REGISTER, + VC5_GLOBAL_REGISTER_GLOBAL_RESET, 0); + if (ret) + return ret; + + return regmap_update_bits(vc5->regmap, VC5_GLOBAL_REGISTER, + VC5_GLOBAL_REGISTER_GLOBAL_RESET, + VC5_GLOBAL_REGISTER_GLOBAL_RESET); } static const struct clk_ops vc5_fod_ops = { @@ -606,7 +626,10 @@ static int vc5_clk_out_prepare(struct clk_hw *hw) * If the input mux is disabled, enable it first and * select source from matching FOD. */ - regmap_read(vc5->regmap, VC5_OUT_DIV_CONTROL(hwdata->num), &src); + ret = regmap_read(vc5->regmap, VC5_OUT_DIV_CONTROL(hwdata->num), &src); + if (ret) + return ret; + if ((src & mask) == 0) { src = VC5_OUT_DIV_CONTROL_RESET | VC5_OUT_DIV_CONTROL_EN_FOD; ret = regmap_update_bits(vc5->regmap, @@ -617,18 +640,24 @@ static int vc5_clk_out_prepare(struct clk_hw *hw) } /* Enable the clock buffer */ - regmap_update_bits(vc5->regmap, VC5_CLK_OUTPUT_CFG(hwdata->num, 1), - VC5_CLK_OUTPUT_CFG1_EN_CLKBUF, - VC5_CLK_OUTPUT_CFG1_EN_CLKBUF); + ret = regmap_update_bits(vc5->regmap, + VC5_CLK_OUTPUT_CFG(hwdata->num, 1), + VC5_CLK_OUTPUT_CFG1_EN_CLKBUF, + VC5_CLK_OUTPUT_CFG1_EN_CLKBUF); + if (ret) + return ret; + if (hwdata->clk_output_cfg0_mask) { dev_dbg(&vc5->client->dev, "Update output %d mask 0x%0X val 0x%0X\n", hwdata->num, hwdata->clk_output_cfg0_mask, hwdata->clk_output_cfg0); - regmap_update_bits(vc5->regmap, - VC5_CLK_OUTPUT_CFG(hwdata->num, 0), - hwdata->clk_output_cfg0_mask, - hwdata->clk_output_cfg0); + ret = regmap_update_bits(vc5->regmap, + VC5_CLK_OUTPUT_CFG(hwdata->num, 0), + hwdata->clk_output_cfg0_mask, + hwdata->clk_output_cfg0); + if (ret) + return ret; } return 0; @@ -656,8 +685,12 @@ static unsigned char vc5_clk_out_get_parent(struct clk_hw *hw) const u8 extclk = VC5_OUT_DIV_CONTROL_SELB_NORM | VC5_OUT_DIV_CONTROL_SEL_EXT; unsigned int src; + int ret; + + ret = regmap_read(vc5->regmap, VC5_OUT_DIV_CONTROL(hwdata->num), &src); + if (ret) + return 0; - regmap_read(vc5->regmap, VC5_OUT_DIV_CONTROL(hwdata->num), &src); src &= mask; if (src == 0) /* Input mux set to DISABLED */ @@ -819,22 +852,27 @@ static int vc5_update_cap_load(struct device_node *node, struct vc5_driver_data { u32 value; int mapped_value; + int ret; - if (!of_property_read_u32(node, "idt,xtal-load-femtofarads", &value)) { - mapped_value = vc5_map_cap_value(value); - if (mapped_value < 0) - return mapped_value; + if (of_property_read_u32(node, "idt,xtal-load-femtofarads", &value)) + return 0; - /* - * The mapped_value is really the high 6 bits of - * VC5_XTAL_X1_LOAD_CAP and VC5_XTAL_X2_LOAD_CAP, so - * shift the value 2 places. - */ - regmap_update_bits(vc5->regmap, VC5_XTAL_X1_LOAD_CAP, ~0x03, mapped_value << 2); - regmap_update_bits(vc5->regmap, VC5_XTAL_X2_LOAD_CAP, ~0x03, mapped_value << 2); - } + mapped_value = vc5_map_cap_value(value); + if (mapped_value < 0) + return mapped_value; - return 0; + /* + * The mapped_value is really the high 6 bits of + * VC5_XTAL_X1_LOAD_CAP and VC5_XTAL_X2_LOAD_CAP, so + * shift the value 2 places. + */ + ret = regmap_update_bits(vc5->regmap, VC5_XTAL_X1_LOAD_CAP, ~0x03, + mapped_value << 2); + if (ret) + return ret; + + return regmap_update_bits(vc5->regmap, VC5_XTAL_X2_LOAD_CAP, ~0x03, + mapped_value << 2); } static int vc5_update_slew(struct device_node *np_output, @@ -956,7 +994,10 @@ static int vc5_probe(struct i2c_client *client) "could not read idt,output-enable-active\n"); } - regmap_update_bits(vc5->regmap, VC5_PRIM_SRC_SHDN, src_mask, src_val); + ret = regmap_update_bits(vc5->regmap, VC5_PRIM_SRC_SHDN, src_mask, + src_val); + if (ret) + return ret; /* Register clock input mux */ memset(&init, 0, sizeof(init)); From 01874fb2a3e60365d9cc68cd931ac9ad6e90025a Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Tue, 19 Jul 2022 11:46:37 +0200 Subject: [PATCH 16/20] clk: vc5: Use regmap_{set,clear}_bits() where appropriate regmap_set_bits() and regmap_clear_bits() are variations of regmap_update_bits() that can be used if all bits of the mask have to be set to either 1 or 0 respectively. Update the versaclk driver to use regmap_set_bits() and regmap_clear_bits() where appropriate. This results in slightly more compact code and also makes the intention of the code clearer which can help with review. Signed-off-by: Lars-Peter Clausen Link: https://lore.kernel.org/r/20220719094637.844946-2-lars@metafoo.de Reviewed-by: Luca Ceresoli Signed-off-by: Stephen Boyd --- drivers/clk/clk-versaclock5.c | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/drivers/clk/clk-versaclock5.c b/drivers/clk/clk-versaclock5.c index e83148eb2c24..681006884097 100644 --- a/drivers/clk/clk-versaclock5.c +++ b/drivers/clk/clk-versaclock5.c @@ -392,9 +392,8 @@ static int vc5_pfd_set_rate(struct clk_hw *hw, unsigned long rate, /* CLKIN within range of PLL input, feed directly to PLL. */ if (parent_rate <= 50000000) { - ret = regmap_update_bits(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV, - VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV, - VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV); + ret = regmap_set_bits(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV, + VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV); if (ret) return ret; @@ -413,8 +412,8 @@ static int vc5_pfd_set_rate(struct clk_hw *hw, unsigned long rate, if (ret) return ret; - return regmap_update_bits(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV, - VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV, 0); + return regmap_clear_bits(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV, + VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV); } static const struct clk_ops vc5_pfd_ops = { @@ -579,14 +578,13 @@ static int vc5_fod_set_rate(struct clk_hw *hw, unsigned long rate, * datasheet somewhat implies this is needed, but the register * and the bit is not documented. */ - ret = regmap_update_bits(vc5->regmap, VC5_GLOBAL_REGISTER, - VC5_GLOBAL_REGISTER_GLOBAL_RESET, 0); + ret = regmap_clear_bits(vc5->regmap, VC5_GLOBAL_REGISTER, + VC5_GLOBAL_REGISTER_GLOBAL_RESET); if (ret) return ret; - return regmap_update_bits(vc5->regmap, VC5_GLOBAL_REGISTER, - VC5_GLOBAL_REGISTER_GLOBAL_RESET, - VC5_GLOBAL_REGISTER_GLOBAL_RESET); + return regmap_set_bits(vc5->regmap, VC5_GLOBAL_REGISTER, + VC5_GLOBAL_REGISTER_GLOBAL_RESET); } static const struct clk_ops vc5_fod_ops = { @@ -614,10 +612,9 @@ static int vc5_clk_out_prepare(struct clk_hw *hw) * registers. */ if (vc5->chip_info->flags & VC5_HAS_BYPASS_SYNC_BIT) { - ret = regmap_update_bits(vc5->regmap, - VC5_RESERVED_X0(hwdata->num), - VC5_RESERVED_X0_BYPASS_SYNC, - VC5_RESERVED_X0_BYPASS_SYNC); + ret = regmap_set_bits(vc5->regmap, + VC5_RESERVED_X0(hwdata->num), + VC5_RESERVED_X0_BYPASS_SYNC); if (ret) return ret; } @@ -640,10 +637,8 @@ static int vc5_clk_out_prepare(struct clk_hw *hw) } /* Enable the clock buffer */ - ret = regmap_update_bits(vc5->regmap, - VC5_CLK_OUTPUT_CFG(hwdata->num, 1), - VC5_CLK_OUTPUT_CFG1_EN_CLKBUF, - VC5_CLK_OUTPUT_CFG1_EN_CLKBUF); + ret = regmap_set_bits(vc5->regmap, VC5_CLK_OUTPUT_CFG(hwdata->num, 1), + VC5_CLK_OUTPUT_CFG1_EN_CLKBUF); if (ret) return ret; @@ -669,8 +664,8 @@ static void vc5_clk_out_unprepare(struct clk_hw *hw) struct vc5_driver_data *vc5 = hwdata->vc5; /* Disable the clock buffer */ - regmap_update_bits(vc5->regmap, VC5_CLK_OUTPUT_CFG(hwdata->num, 1), - VC5_CLK_OUTPUT_CFG1_EN_CLKBUF, 0); + regmap_clear_bits(vc5->regmap, VC5_CLK_OUTPUT_CFG(hwdata->num, 1), + VC5_CLK_OUTPUT_CFG1_EN_CLKBUF); } static unsigned char vc5_clk_out_get_parent(struct clk_hw *hw) From 09d1855656dad04127aee195baf2eedae029175d Mon Sep 17 00:00:00 2001 From: Alex Helms Date: Mon, 12 Sep 2022 11:36:12 -0700 Subject: [PATCH 17/20] dt-bindings: Renesas versaclock7 device tree bindings Renesas Versaclock7 is a family of configurable clock generator ICs with fractional and integer dividers. This driver has basic support for the RC21008A device, a clock synthesizer with a crystal input and 8 outputs. The supports changing the FOD and IOD rates, and each output can be gated. Signed-off-by: Alex Helms Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20220912183613.22213-2-alexander.helms.jy@renesas.com Tested-by: Saeed Nowshadi [sboyd@kernel.org: Rename nodes in example to generic names] Signed-off-by: Stephen Boyd --- .../bindings/clock/renesas,versaclock7.yaml | 64 +++++++++++++++++++ MAINTAINERS | 5 ++ 2 files changed, 69 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml diff --git a/Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml b/Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml new file mode 100644 index 000000000000..8d4eb4475fc8 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/renesas,versaclock7.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas Versaclock7 Programmable Clock Device Tree Bindings + +maintainers: + - Alex Helms + +description: | + Renesas Versaclock7 is a family of configurable clock generator and + jitter attenuator ICs with fractional and integer dividers. + +properties: + '#clock-cells': + const: 1 + + compatible: + enum: + - renesas,rc21008a + + reg: + maxItems: 1 + + clocks: + items: + - description: External crystal or oscillator + + clock-names: + items: + - const: xin + +required: + - '#clock-cells' + - compatible + - reg + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + vc7_xin: clock { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <49152000>; + }; + + i2c@0 { + reg = <0x0 0x100>; + #address-cells = <1>; + #size-cells = <0>; + + vc7: clock-controller@9 { + compatible = "renesas,rc21008a"; + reg = <0x9>; + #clock-cells = <1>; + clocks = <&vc7_xin>; + clock-names = "xin"; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 8a5012ba6ff9..c1b1c7ead11d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17441,6 +17441,11 @@ S: Maintained F: Documentation/devicetree/bindings/mtd/renesas-nandc.yaml F: drivers/mtd/nand/raw/renesas-nand-controller.c +RENESAS VERSACLOCK 7 CLOCK DRIVER +M: Alex Helms +S: Maintained +F: Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml + RESET CONTROLLER FRAMEWORK M: Philipp Zabel S: Maintained From 48c5e98fedd9e0b164df4de592fd740537ead9e2 Mon Sep 17 00:00:00 2001 From: Alex Helms Date: Mon, 12 Sep 2022 11:36:13 -0700 Subject: [PATCH 18/20] clk: Renesas versaclock7 ccf device driver Renesas Versaclock7 is a family of configurable clock generator ICs with fractional and integer dividers. This driver has basic support for the RC21008A device, a clock synthesizer with a crystal input and 8 outputs. The supports changing the FOD and IOD rates, and each output can be gated. Signed-off-by: Alex Helms Link: https://lore.kernel.org/r/20220912183613.22213-3-alexander.helms.jy@renesas.com Tested-by: Saeed Nowshadi Signed-off-by: Stephen Boyd --- MAINTAINERS | 1 + drivers/clk/Kconfig | 9 + drivers/clk/Makefile | 1 + drivers/clk/clk-versaclock7.c | 1311 +++++++++++++++++++++++++++++++++ 4 files changed, 1322 insertions(+) create mode 100644 drivers/clk/clk-versaclock7.c diff --git a/MAINTAINERS b/MAINTAINERS index c1b1c7ead11d..9bebe0165404 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17445,6 +17445,7 @@ RENESAS VERSACLOCK 7 CLOCK DRIVER M: Alex Helms S: Maintained F: Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml +F: drivers/clk/clk-versaclock7.c RESET CONTROLLER FRAMEWORK M: Philipp Zabel diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 48f8f4221e21..87481e4fa746 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -377,6 +377,15 @@ config COMMON_CLK_VC5 This driver supports the IDT VersaClock 5 and VersaClock 6 programmable clock generators. +config COMMON_CLK_VC7 + tristate "Clock driver for Renesas Versaclock 7 devices" + depends on I2C + depends on OF + select REGMAP_I2C + help + Renesas Versaclock7 is a family of configurable clock generator + and jitter attenuator ICs with fractional and integer dividers. + config COMMON_CLK_STM32MP135 def_bool COMMON_CLK && MACH_STM32MP13 help diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index d5db170d38d2..e3ca0d058a25 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -73,6 +73,7 @@ obj-$(CONFIG_CLK_TWL6040) += clk-twl6040.o obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o obj-$(CONFIG_COMMON_CLK_RS9_PCIE) += clk-renesas-pcie.o obj-$(CONFIG_COMMON_CLK_VC5) += clk-versaclock5.o +obj-$(CONFIG_COMMON_CLK_VC7) += clk-versaclock7.o obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o diff --git a/drivers/clk/clk-versaclock7.c b/drivers/clk/clk-versaclock7.c new file mode 100644 index 000000000000..050807cf971f --- /dev/null +++ b/drivers/clk/clk-versaclock7.c @@ -0,0 +1,1311 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common clock framework driver for the Versaclock7 family of timing devices. + * + * Copyright (c) 2022 Renesas Electronics Corporation + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 16-bit register address: the lower 8 bits of the register address come + * from the offset addr byte and the upper 8 bits come from the page register. + */ +#define VC7_PAGE_ADDR 0xFD +#define VC7_PAGE_WINDOW 256 +#define VC7_MAX_REG 0x364 + +/* Maximum number of banks supported by VC7 */ +#define VC7_NUM_BANKS 7 + +/* Maximum number of FODs supported by VC7 */ +#define VC7_NUM_FOD 3 + +/* Maximum number of IODs supported by VC7 */ +#define VC7_NUM_IOD 4 + +/* Maximum number of outputs supported by VC7 */ +#define VC7_NUM_OUT 12 + +/* VCO valid range is 9.5 GHz to 10.7 GHz */ +#define VC7_APLL_VCO_MIN 9500000000UL +#define VC7_APLL_VCO_MAX 10700000000UL + +/* APLL denominator is fixed at 2^27 */ +#define VC7_APLL_DENOMINATOR_BITS 27 + +/* FOD 1st stage denominator is fixed 2^34 */ +#define VC7_FOD_DENOMINATOR_BITS 34 + +/* IOD can operate between 1kHz and 650MHz */ +#define VC7_IOD_RATE_MIN 1000UL +#define VC7_IOD_RATE_MAX 650000000UL +#define VC7_IOD_MIN_DIVISOR 14 +#define VC7_IOD_MAX_DIVISOR 0x1ffffff /* 25-bit */ + +#define VC7_FOD_RATE_MIN 1000UL +#define VC7_FOD_RATE_MAX 650000000UL +#define VC7_FOD_1ST_STAGE_RATE_MIN 33000000UL /* 33 MHz */ +#define VC7_FOD_1ST_STAGE_RATE_MAX 650000000UL /* 650 MHz */ +#define VC7_FOD_1ST_INT_MAX 324 +#define VC7_FOD_2ND_INT_MIN 2 +#define VC7_FOD_2ND_INT_MAX 0x1ffff /* 17-bit */ + +/* VC7 Registers */ + +#define VC7_REG_XO_CNFG 0x2C +#define VC7_REG_XO_CNFG_COUNT 4 +#define VC7_REG_XO_IB_H_DIV_SHIFT 24 +#define VC7_REG_XO_IB_H_DIV_MASK GENMASK(28, VC7_REG_XO_IB_H_DIV_SHIFT) + +#define VC7_REG_APLL_FB_DIV_FRAC 0x120 +#define VC7_REG_APLL_FB_DIV_FRAC_COUNT 4 +#define VC7_REG_APLL_FB_DIV_FRAC_MASK GENMASK(26, 0) + +#define VC7_REG_APLL_FB_DIV_INT 0x124 +#define VC7_REG_APLL_FB_DIV_INT_COUNT 2 +#define VC7_REG_APLL_FB_DIV_INT_MASK GENMASK(9, 0) + +#define VC7_REG_APLL_CNFG 0x127 +#define VC7_REG_APLL_EN_DOUBLER BIT(0) + +#define VC7_REG_OUT_BANK_CNFG(idx) (0x280 + (0x4 * (idx))) +#define VC7_REG_OUTPUT_BANK_SRC_MASK GENMASK(2, 0) + +#define VC7_REG_FOD_INT_CNFG(idx) (0x1E0 + (0x10 * (idx))) +#define VC7_REG_FOD_INT_CNFG_COUNT 8 +#define VC7_REG_FOD_1ST_INT_MASK GENMASK(8, 0) +#define VC7_REG_FOD_2ND_INT_SHIFT 9 +#define VC7_REG_FOD_2ND_INT_MASK GENMASK(25, VC7_REG_FOD_2ND_INT_SHIFT) +#define VC7_REG_FOD_FRAC_SHIFT 26 +#define VC7_REG_FOD_FRAC_MASK GENMASK_ULL(59, VC7_REG_FOD_FRAC_SHIFT) + +#define VC7_REG_IOD_INT_CNFG(idx) (0x1C0 + (0x8 * (idx))) +#define VC7_REG_IOD_INT_CNFG_COUNT 4 +#define VC7_REG_IOD_INT_MASK GENMASK(24, 0) + +#define VC7_REG_ODRV_EN(idx) (0x240 + (0x4 * (idx))) +#define VC7_REG_OUT_DIS BIT(0) + +struct vc7_driver_data; +static const struct regmap_config vc7_regmap_config; + +/* Supported Renesas VC7 models */ +enum vc7_model { + VC7_RC21008A, +}; + +struct vc7_chip_info { + const enum vc7_model model; + const unsigned int banks[VC7_NUM_BANKS]; + const unsigned int num_banks; + const unsigned int outputs[VC7_NUM_OUT]; + const unsigned int num_outputs; +}; + +/* + * Changing the APLL frequency is currently not supported. + * The APLL will consist of an opaque block between the XO and FOD/IODs and + * its frequency will be computed based on the current state of the device. + */ +struct vc7_apll_data { + struct clk *clk; + struct vc7_driver_data *vc7; + u8 xo_ib_h_div; + u8 en_doubler; + u16 apll_fb_div_int; + u32 apll_fb_div_frac; +}; + +struct vc7_fod_data { + struct clk_hw hw; + struct vc7_driver_data *vc7; + unsigned int num; + u32 fod_1st_int; + u32 fod_2nd_int; + u64 fod_frac; +}; + +struct vc7_iod_data { + struct clk_hw hw; + struct vc7_driver_data *vc7; + unsigned int num; + u32 iod_int; +}; + +struct vc7_out_data { + struct clk_hw hw; + struct vc7_driver_data *vc7; + unsigned int num; + unsigned int out_dis; +}; + +struct vc7_driver_data { + struct i2c_client *client; + struct regmap *regmap; + const struct vc7_chip_info *chip_info; + + struct clk *pin_xin; + struct vc7_apll_data clk_apll; + struct vc7_fod_data clk_fod[VC7_NUM_FOD]; + struct vc7_iod_data clk_iod[VC7_NUM_IOD]; + struct vc7_out_data clk_out[VC7_NUM_OUT]; +}; + +struct vc7_bank_src_map { + enum vc7_bank_src_type { + VC7_FOD, + VC7_IOD, + } type; + union _divider { + struct vc7_iod_data *iod; + struct vc7_fod_data *fod; + } src; +}; + +static struct clk_hw *vc7_of_clk_get(struct of_phandle_args *clkspec, + void *data) +{ + struct vc7_driver_data *vc7 = data; + unsigned int idx = clkspec->args[0]; + + if (idx >= vc7->chip_info->num_outputs) + return ERR_PTR(-EINVAL); + + return &vc7->clk_out[idx].hw; +} + +static const unsigned int RC21008A_index_to_output_mapping[] = { + 1, 2, 3, 6, 7, 8, 10, 11 +}; + +static int vc7_map_index_to_output(const enum vc7_model model, const unsigned int i) +{ + switch (model) { + case VC7_RC21008A: + return RC21008A_index_to_output_mapping[i]; + default: + return i; + } +} + +/* bank to output mapping, same across all variants */ +static const unsigned int output_bank_mapping[] = { + 0, /* Output 0 */ + 1, /* Output 1 */ + 2, /* Output 2 */ + 2, /* Output 3 */ + 3, /* Output 4 */ + 3, /* Output 5 */ + 3, /* Output 6 */ + 3, /* Output 7 */ + 4, /* Output 8 */ + 4, /* Output 9 */ + 5, /* Output 10 */ + 6 /* Output 11 */ +}; + +/** + * vc7_64_mul_64_to_128() - Multiply two u64 and return an unsigned 128-bit integer + * as an upper and lower part. + * + * @left: The left argument. + * @right: The right argument. + * @hi: The upper 64-bits of the 128-bit product. + * @lo: The lower 64-bits of the 128-bit product. + * + * From mul_64_64 in crypto/ecc.c:350 in the linux kernel, accessed in v5.17.2. + */ +static void vc7_64_mul_64_to_128(u64 left, u64 right, u64 *hi, u64 *lo) +{ + u64 a0 = left & 0xffffffffull; + u64 a1 = left >> 32; + u64 b0 = right & 0xffffffffull; + u64 b1 = right >> 32; + u64 m0 = a0 * b0; + u64 m1 = a0 * b1; + u64 m2 = a1 * b0; + u64 m3 = a1 * b1; + + m2 += (m0 >> 32); + m2 += m1; + + /* Overflow */ + if (m2 < m1) + m3 += 0x100000000ull; + + *lo = (m0 & 0xffffffffull) | (m2 << 32); + *hi = m3 + (m2 >> 32); +} + +/** + * vc7_128_div_64_to_64() - Divides a 128-bit uint by a 64-bit divisor, return a 64-bit quotient. + * + * @numhi: The uppper 64-bits of the dividend. + * @numlo: The lower 64-bits of the dividend. + * @den: The denominator (divisor). + * @r: The remainder, pass NULL if the remainder is not needed. + * + * Originally from libdivide, modified to use kernel u64/u32 types. + * + * See https://github.com/ridiculousfish/libdivide/blob/master/libdivide.h#L471. + * + * Return: The 64-bit quotient of the division. + * + * In case of overflow of division by zero, max(u64) is returned. + */ +static u64 vc7_128_div_64_to_64(u64 numhi, u64 numlo, u64 den, u64 *r) +{ + /* + * We work in base 2**32. + * A uint32 holds a single digit. A uint64 holds two digits. + * Our numerator is conceptually [num3, num2, num1, num0]. + * Our denominator is [den1, den0]. + */ + const u64 b = ((u64)1 << 32); + + /* The high and low digits of our computed quotient. */ + u32 q1, q0; + + /* The normalization shift factor */ + int shift; + + /* + * The high and low digits of our denominator (after normalizing). + * Also the low 2 digits of our numerator (after normalizing). + */ + u32 den1, den0, num1, num0; + + /* A partial remainder; */ + u64 rem; + + /* + * The estimated quotient, and its corresponding remainder (unrelated + * to true remainder). + */ + u64 qhat, rhat; + + /* Variables used to correct the estimated quotient. */ + u64 c1, c2; + + /* Check for overflow and divide by 0. */ + if (numhi >= den) { + if (r) + *r = ~0ull; + return ~0ull; + } + + /* + * Determine the normalization factor. We multiply den by this, so that + * its leading digit is at least half b. In binary this means just + * shifting left by the number of leading zeros, so that there's a 1 in + * the MSB. + * + * We also shift numer by the same amount. This cannot overflow because + * numhi < den. The expression (-shift & 63) is the same as (64 - + * shift), except it avoids the UB of shifting by 64. The funny bitwise + * 'and' ensures that numlo does not get shifted into numhi if shift is + * 0. clang 11 has an x86 codegen bug here: see LLVM bug 50118. The + * sequence below avoids it. + */ + shift = __builtin_clzll(den); + den <<= shift; + numhi <<= shift; + numhi |= (numlo >> (-shift & 63)) & (-(s64)shift >> 63); + numlo <<= shift; + + /* + * Extract the low digits of the numerator and both digits of the + * denominator. + */ + num1 = (u32)(numlo >> 32); + num0 = (u32)(numlo & 0xFFFFFFFFu); + den1 = (u32)(den >> 32); + den0 = (u32)(den & 0xFFFFFFFFu); + + /* + * We wish to compute q1 = [n3 n2 n1] / [d1 d0]. + * Estimate q1 as [n3 n2] / [d1], and then correct it. + * Note while qhat may be 2 digits, q1 is always 1 digit. + */ + qhat = div64_u64_rem(numhi, den1, &rhat); + c1 = qhat * den0; + c2 = rhat * b + num1; + if (c1 > c2) + qhat -= (c1 - c2 > den) ? 2 : 1; + q1 = (u32)qhat; + + /* Compute the true (partial) remainder. */ + rem = numhi * b + num1 - q1 * den; + + /* + * We wish to compute q0 = [rem1 rem0 n0] / [d1 d0]. + * Estimate q0 as [rem1 rem0] / [d1] and correct it. + */ + qhat = div64_u64_rem(rem, den1, &rhat); + c1 = qhat * den0; + c2 = rhat * b + num0; + if (c1 > c2) + qhat -= (c1 - c2 > den) ? 2 : 1; + q0 = (u32)qhat; + + /* Return remainder if requested. */ + if (r) + *r = (rem * b + num0 - q0 * den) >> shift; + return ((u64)q1 << 32) | q0; +} + +static int vc7_get_bank_clk(struct vc7_driver_data *vc7, + unsigned int bank_idx, + unsigned int output_bank_src, + struct vc7_bank_src_map *map) +{ + /* Mapping from Table 38 in datasheet */ + if (bank_idx == 0 || bank_idx == 1) { + switch (output_bank_src) { + case 0: + map->type = VC7_IOD, + map->src.iod = &vc7->clk_iod[0]; + return 0; + case 1: + map->type = VC7_IOD, + map->src.iod = &vc7->clk_iod[1]; + return 0; + case 4: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[0]; + return 0; + case 5: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[1]; + return 0; + default: + break; + } + } else if (bank_idx == 2) { + switch (output_bank_src) { + case 1: + map->type = VC7_IOD, + map->src.iod = &vc7->clk_iod[1]; + return 0; + case 4: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[0]; + return 0; + case 5: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[1]; + return 0; + default: + break; + } + } else if (bank_idx == 3) { + switch (output_bank_src) { + case 4: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[0]; + return 0; + case 5: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[1]; + return 0; + case 6: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[2]; + return 0; + default: + break; + } + } else if (bank_idx == 4) { + switch (output_bank_src) { + case 0: + /* CLKIN1 not supported in this driver */ + break; + case 2: + map->type = VC7_IOD, + map->src.iod = &vc7->clk_iod[2]; + return 0; + case 5: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[1]; + return 0; + case 6: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[2]; + return 0; + case 7: + /* CLKIN0 not supported in this driver */ + break; + default: + break; + } + } else if (bank_idx == 5) { + switch (output_bank_src) { + case 0: + /* CLKIN1 not supported in this driver */ + break; + case 1: + /* XIN_REFIN not supported in this driver */ + break; + case 2: + map->type = VC7_IOD, + map->src.iod = &vc7->clk_iod[2]; + return 0; + case 3: + map->type = VC7_IOD, + map->src.iod = &vc7->clk_iod[3]; + return 0; + case 5: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[1]; + return 0; + case 6: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[2]; + return 0; + case 7: + /* CLKIN0 not supported in this driver */ + break; + default: + break; + } + } else if (bank_idx == 6) { + switch (output_bank_src) { + case 0: + /* CLKIN1 not supported in this driver */ + break; + case 2: + map->type = VC7_IOD, + map->src.iod = &vc7->clk_iod[2]; + return 0; + case 3: + map->type = VC7_IOD, + map->src.iod = &vc7->clk_iod[3]; + return 0; + case 5: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[1]; + return 0; + case 6: + map->type = VC7_FOD, + map->src.fod = &vc7->clk_fod[2]; + return 0; + case 7: + /* CLKIN0 not supported in this driver */ + break; + default: + break; + } + } + + pr_warn("bank_src%d = %d is not supported\n", bank_idx, output_bank_src); + return -1; +} + +static int vc7_read_apll(struct vc7_driver_data *vc7) +{ + int err; + u32 val32; + u16 val16; + + err = regmap_bulk_read(vc7->regmap, + VC7_REG_XO_CNFG, + (u32 *)&val32, + VC7_REG_XO_CNFG_COUNT); + if (err) { + dev_err(&vc7->client->dev, "failed to read XO_CNFG\n"); + return err; + } + + vc7->clk_apll.xo_ib_h_div = (val32 & VC7_REG_XO_IB_H_DIV_MASK) + >> VC7_REG_XO_IB_H_DIV_SHIFT; + + err = regmap_read(vc7->regmap, + VC7_REG_APLL_CNFG, + &val32); + if (err) { + dev_err(&vc7->client->dev, "failed to read APLL_CNFG\n"); + return err; + } + + vc7->clk_apll.en_doubler = val32 & VC7_REG_APLL_EN_DOUBLER; + + err = regmap_bulk_read(vc7->regmap, + VC7_REG_APLL_FB_DIV_FRAC, + (u32 *)&val32, + VC7_REG_APLL_FB_DIV_FRAC_COUNT); + if (err) { + dev_err(&vc7->client->dev, "failed to read APLL_FB_DIV_FRAC\n"); + return err; + } + + vc7->clk_apll.apll_fb_div_frac = val32 & VC7_REG_APLL_FB_DIV_FRAC_MASK; + + err = regmap_bulk_read(vc7->regmap, + VC7_REG_APLL_FB_DIV_INT, + (u16 *)&val16, + VC7_REG_APLL_FB_DIV_INT_COUNT); + if (err) { + dev_err(&vc7->client->dev, "failed to read APLL_FB_DIV_INT\n"); + return err; + } + + vc7->clk_apll.apll_fb_div_int = val16 & VC7_REG_APLL_FB_DIV_INT_MASK; + + return 0; +} + +static int vc7_read_fod(struct vc7_driver_data *vc7, unsigned int idx) +{ + int err; + u64 val; + + err = regmap_bulk_read(vc7->regmap, + VC7_REG_FOD_INT_CNFG(idx), + (u64 *)&val, + VC7_REG_FOD_INT_CNFG_COUNT); + if (err) { + dev_err(&vc7->client->dev, "failed to read FOD%d\n", idx); + return err; + } + + vc7->clk_fod[idx].fod_1st_int = (val & VC7_REG_FOD_1ST_INT_MASK); + vc7->clk_fod[idx].fod_2nd_int = + (val & VC7_REG_FOD_2ND_INT_MASK) >> VC7_REG_FOD_2ND_INT_SHIFT; + vc7->clk_fod[idx].fod_frac = (val & VC7_REG_FOD_FRAC_MASK) + >> VC7_REG_FOD_FRAC_SHIFT; + + return 0; +} + +static int vc7_write_fod(struct vc7_driver_data *vc7, unsigned int idx) +{ + int err; + u64 val; + + /* + * FOD dividers are part of an atomic group where fod_1st_int, + * fod_2nd_int, and fod_frac must be written together. The new divider + * is applied when the MSB of fod_frac is written. + */ + + err = regmap_bulk_read(vc7->regmap, + VC7_REG_FOD_INT_CNFG(idx), + (u64 *)&val, + VC7_REG_FOD_INT_CNFG_COUNT); + if (err) { + dev_err(&vc7->client->dev, "failed to read FOD%d\n", idx); + return err; + } + + val = u64_replace_bits(val, + vc7->clk_fod[idx].fod_1st_int, + VC7_REG_FOD_1ST_INT_MASK); + val = u64_replace_bits(val, + vc7->clk_fod[idx].fod_2nd_int, + VC7_REG_FOD_2ND_INT_MASK); + val = u64_replace_bits(val, + vc7->clk_fod[idx].fod_frac, + VC7_REG_FOD_FRAC_MASK); + + err = regmap_bulk_write(vc7->regmap, + VC7_REG_FOD_INT_CNFG(idx), + (u64 *)&val, + sizeof(u64)); + if (err) { + dev_err(&vc7->client->dev, "failed to write FOD%d\n", idx); + return err; + } + + return 0; +} + +static int vc7_read_iod(struct vc7_driver_data *vc7, unsigned int idx) +{ + int err; + u32 val; + + err = regmap_bulk_read(vc7->regmap, + VC7_REG_IOD_INT_CNFG(idx), + (u32 *)&val, + VC7_REG_IOD_INT_CNFG_COUNT); + if (err) { + dev_err(&vc7->client->dev, "failed to read IOD%d\n", idx); + return err; + } + + vc7->clk_iod[idx].iod_int = (val & VC7_REG_IOD_INT_MASK); + + return 0; +} + +static int vc7_write_iod(struct vc7_driver_data *vc7, unsigned int idx) +{ + int err; + u32 val; + + /* + * IOD divider field is atomic and all bits must be written. + * The new divider is applied when the MSB of iod_int is written. + */ + + err = regmap_bulk_read(vc7->regmap, + VC7_REG_IOD_INT_CNFG(idx), + (u32 *)&val, + VC7_REG_IOD_INT_CNFG_COUNT); + if (err) { + dev_err(&vc7->client->dev, "failed to read IOD%d\n", idx); + return err; + } + + val = u32_replace_bits(val, + vc7->clk_iod[idx].iod_int, + VC7_REG_IOD_INT_MASK); + + err = regmap_bulk_write(vc7->regmap, + VC7_REG_IOD_INT_CNFG(idx), + (u32 *)&val, + sizeof(u32)); + if (err) { + dev_err(&vc7->client->dev, "failed to write IOD%d\n", idx); + return err; + } + + return 0; +} + +static int vc7_read_output(struct vc7_driver_data *vc7, unsigned int idx) +{ + int err; + unsigned int val, out_num; + + out_num = vc7_map_index_to_output(vc7->chip_info->model, idx); + err = regmap_read(vc7->regmap, + VC7_REG_ODRV_EN(out_num), + &val); + if (err) { + dev_err(&vc7->client->dev, "failed to read ODRV_EN[%d]\n", idx); + return err; + } + + vc7->clk_out[idx].out_dis = val & VC7_REG_OUT_DIS; + + return 0; +} + +static int vc7_write_output(struct vc7_driver_data *vc7, unsigned int idx) +{ + int err; + unsigned int out_num; + + out_num = vc7_map_index_to_output(vc7->chip_info->model, idx); + err = regmap_write_bits(vc7->regmap, + VC7_REG_ODRV_EN(out_num), + VC7_REG_OUT_DIS, + vc7->clk_out[idx].out_dis); + + if (err) { + dev_err(&vc7->client->dev, "failed to write ODRV_EN[%d]\n", idx); + return err; + } + + return 0; +} + +static unsigned long vc7_get_apll_rate(struct vc7_driver_data *vc7) +{ + int err; + unsigned long xtal_rate; + u64 refin_div, apll_rate; + + xtal_rate = clk_get_rate(vc7->pin_xin); + err = vc7_read_apll(vc7); + if (err) { + dev_err(&vc7->client->dev, "unable to read apll\n"); + return err; + } + + /* 0 is bypassed, 1 is reserved */ + if (vc7->clk_apll.xo_ib_h_div < 2) + refin_div = xtal_rate; + else + refin_div = div64_u64(xtal_rate, vc7->clk_apll.xo_ib_h_div); + + if (vc7->clk_apll.en_doubler) + refin_div *= 2; + + /* divider = int + (frac / 2^27) */ + apll_rate = (refin_div * vc7->clk_apll.apll_fb_div_int) + + ((refin_div * vc7->clk_apll.apll_fb_div_frac) >> VC7_APLL_DENOMINATOR_BITS); + + pr_debug("%s - xo_ib_h_div: %u, apll_fb_div_int: %u, apll_fb_div_frac: %u\n", + __func__, vc7->clk_apll.xo_ib_h_div, vc7->clk_apll.apll_fb_div_int, + vc7->clk_apll.apll_fb_div_frac); + pr_debug("%s - refin_div: %llu, apll rate: %llu\n", + __func__, refin_div, apll_rate); + + return apll_rate; +} + +static void vc7_calc_iod_divider(unsigned long rate, unsigned long parent_rate, + u32 *divider) +{ + *divider = DIV_ROUND_UP(parent_rate, rate); + if (*divider < VC7_IOD_MIN_DIVISOR) + *divider = VC7_IOD_MIN_DIVISOR; + if (*divider > VC7_IOD_MAX_DIVISOR) + *divider = VC7_IOD_MAX_DIVISOR; +} + +static void vc7_calc_fod_1st_stage(unsigned long rate, unsigned long parent_rate, + u32 *div_int, u64 *div_frac) +{ + u64 rem; + + *div_int = (u32)div64_u64_rem(parent_rate, rate, &rem); + *div_frac = div64_u64(rem << VC7_FOD_DENOMINATOR_BITS, rate); +} + +static unsigned long vc7_calc_fod_1st_stage_rate(unsigned long parent_rate, + u32 fod_1st_int, u64 fod_frac) +{ + u64 numer, denom, hi, lo, divisor; + + numer = fod_frac; + denom = BIT_ULL(VC7_FOD_DENOMINATOR_BITS); + + if (fod_frac) { + vc7_64_mul_64_to_128(parent_rate, denom, &hi, &lo); + divisor = ((u64)fod_1st_int * denom) + numer; + return vc7_128_div_64_to_64(hi, lo, divisor, NULL); + } + + return div64_u64(parent_rate, fod_1st_int); +} + +static unsigned long vc7_calc_fod_2nd_stage_rate(unsigned long parent_rate, + u32 fod_1st_int, u32 fod_2nd_int, u64 fod_frac) +{ + unsigned long fod_1st_stage_rate; + + fod_1st_stage_rate = vc7_calc_fod_1st_stage_rate(parent_rate, fod_1st_int, fod_frac); + + if (fod_2nd_int < 2) + return fod_1st_stage_rate; + + /* + * There is a div-by-2 preceding the 2nd stage integer divider + * (not shown on block diagram) so the actual 2nd stage integer + * divisor is 2 * N. + */ + return div64_u64(fod_1st_stage_rate >> 1, fod_2nd_int); +} + +static void vc7_calc_fod_divider(unsigned long rate, unsigned long parent_rate, + u32 *fod_1st_int, u32 *fod_2nd_int, u64 *fod_frac) +{ + unsigned int allow_frac, i, best_frac_i; + unsigned long first_stage_rate; + + vc7_calc_fod_1st_stage(rate, parent_rate, fod_1st_int, fod_frac); + first_stage_rate = vc7_calc_fod_1st_stage_rate(parent_rate, *fod_1st_int, *fod_frac); + + *fod_2nd_int = 0; + + /* Do we need the second stage integer divider? */ + if (first_stage_rate < VC7_FOD_1ST_STAGE_RATE_MIN) { + allow_frac = 0; + best_frac_i = VC7_FOD_2ND_INT_MIN; + + for (i = VC7_FOD_2ND_INT_MIN; i <= VC7_FOD_2ND_INT_MAX; i++) { + /* + * 1) There is a div-by-2 preceding the 2nd stage integer divider + * (not shown on block diagram) so the actual 2nd stage integer + * divisor is 2 * N. + * 2) Attempt to find an integer solution first. This means stepping + * through each 2nd stage integer and recalculating the 1st stage + * until the 1st stage frequency is out of bounds. If no integer + * solution is found, use the best fractional solution. + */ + vc7_calc_fod_1st_stage(parent_rate, rate * 2 * i, fod_1st_int, fod_frac); + first_stage_rate = vc7_calc_fod_1st_stage_rate(parent_rate, + *fod_1st_int, + *fod_frac); + + /* Remember the first viable fractional solution */ + if (best_frac_i == VC7_FOD_2ND_INT_MIN && + first_stage_rate > VC7_FOD_1ST_STAGE_RATE_MIN) { + best_frac_i = i; + } + + /* Is the divider viable? Prefer integer solutions over fractional. */ + if (*fod_1st_int < VC7_FOD_1ST_INT_MAX && + first_stage_rate >= VC7_FOD_1ST_STAGE_RATE_MIN && + (allow_frac || *fod_frac == 0)) { + *fod_2nd_int = i; + break; + } + + /* Ran out of divisors or the 1st stage frequency is out of range */ + if (i >= VC7_FOD_2ND_INT_MAX || + first_stage_rate > VC7_FOD_1ST_STAGE_RATE_MAX) { + allow_frac = 1; + i = best_frac_i; + + /* Restore the best frac and rerun the loop for the last time */ + if (best_frac_i != VC7_FOD_2ND_INT_MIN) + i--; + + continue; + } + } + } +} + +static unsigned long vc7_fod_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct vc7_fod_data *fod = container_of(hw, struct vc7_fod_data, hw); + struct vc7_driver_data *vc7 = fod->vc7; + int err; + unsigned long fod_rate; + + err = vc7_read_fod(vc7, fod->num); + if (err) { + dev_err(&vc7->client->dev, "error reading registers for %s\n", + clk_hw_get_name(hw)); + return err; + } + + pr_debug("%s - %s: parent_rate: %lu\n", __func__, clk_hw_get_name(hw), parent_rate); + + fod_rate = vc7_calc_fod_2nd_stage_rate(parent_rate, fod->fod_1st_int, + fod->fod_2nd_int, fod->fod_frac); + + pr_debug("%s - %s: fod_1st_int: %u, fod_2nd_int: %u, fod_frac: %llu\n", + __func__, clk_hw_get_name(hw), + fod->fod_1st_int, fod->fod_2nd_int, fod->fod_frac); + pr_debug("%s - %s rate: %lu\n", __func__, clk_hw_get_name(hw), fod_rate); + + return fod_rate; +} + +static long vc7_fod_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) +{ + struct vc7_fod_data *fod = container_of(hw, struct vc7_fod_data, hw); + unsigned long fod_rate; + + pr_debug("%s - %s: requested rate: %lu, parent_rate: %lu\n", + __func__, clk_hw_get_name(hw), rate, *parent_rate); + + vc7_calc_fod_divider(rate, *parent_rate, + &fod->fod_1st_int, &fod->fod_2nd_int, &fod->fod_frac); + fod_rate = vc7_calc_fod_2nd_stage_rate(*parent_rate, fod->fod_1st_int, + fod->fod_2nd_int, fod->fod_frac); + + pr_debug("%s - %s: fod_1st_int: %u, fod_2nd_int: %u, fod_frac: %llu\n", + __func__, clk_hw_get_name(hw), + fod->fod_1st_int, fod->fod_2nd_int, fod->fod_frac); + pr_debug("%s - %s rate: %lu\n", __func__, clk_hw_get_name(hw), fod_rate); + + return fod_rate; +} + +static int vc7_fod_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) +{ + struct vc7_fod_data *fod = container_of(hw, struct vc7_fod_data, hw); + struct vc7_driver_data *vc7 = fod->vc7; + unsigned long fod_rate; + + pr_debug("%s - %s: rate: %lu, parent_rate: %lu\n", + __func__, clk_hw_get_name(hw), rate, parent_rate); + + if (rate < VC7_FOD_RATE_MIN || rate > VC7_FOD_RATE_MAX) { + dev_err(&vc7->client->dev, + "requested frequency %lu Hz for %s is out of range\n", + rate, clk_hw_get_name(hw)); + return -EINVAL; + } + + vc7_write_fod(vc7, fod->num); + + fod_rate = vc7_calc_fod_2nd_stage_rate(parent_rate, fod->fod_1st_int, + fod->fod_2nd_int, fod->fod_frac); + + pr_debug("%s - %s: fod_1st_int: %u, fod_2nd_int: %u, fod_frac: %llu\n", + __func__, clk_hw_get_name(hw), + fod->fod_1st_int, fod->fod_2nd_int, fod->fod_frac); + pr_debug("%s - %s rate: %lu\n", __func__, clk_hw_get_name(hw), fod_rate); + + return 0; +} + +static const struct clk_ops vc7_fod_ops = { + .recalc_rate = vc7_fod_recalc_rate, + .round_rate = vc7_fod_round_rate, + .set_rate = vc7_fod_set_rate, +}; + +static unsigned long vc7_iod_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct vc7_iod_data *iod = container_of(hw, struct vc7_iod_data, hw); + struct vc7_driver_data *vc7 = iod->vc7; + int err; + unsigned long iod_rate; + + err = vc7_read_iod(vc7, iod->num); + if (err) { + dev_err(&vc7->client->dev, "error reading registers for %s\n", + clk_hw_get_name(hw)); + return err; + } + + iod_rate = div64_u64(parent_rate, iod->iod_int); + + pr_debug("%s - %s: iod_int: %u\n", __func__, clk_hw_get_name(hw), iod->iod_int); + pr_debug("%s - %s rate: %lu\n", __func__, clk_hw_get_name(hw), iod_rate); + + return iod_rate; +} + +static long vc7_iod_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) +{ + struct vc7_iod_data *iod = container_of(hw, struct vc7_iod_data, hw); + unsigned long iod_rate; + + pr_debug("%s - %s: requested rate: %lu, parent_rate: %lu\n", + __func__, clk_hw_get_name(hw), rate, *parent_rate); + + vc7_calc_iod_divider(rate, *parent_rate, &iod->iod_int); + iod_rate = div64_u64(*parent_rate, iod->iod_int); + + pr_debug("%s - %s: iod_int: %u\n", __func__, clk_hw_get_name(hw), iod->iod_int); + pr_debug("%s - %s rate: %ld\n", __func__, clk_hw_get_name(hw), iod_rate); + + return iod_rate; +} + +static int vc7_iod_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) +{ + struct vc7_iod_data *iod = container_of(hw, struct vc7_iod_data, hw); + struct vc7_driver_data *vc7 = iod->vc7; + unsigned long iod_rate; + + pr_debug("%s - %s: rate: %lu, parent_rate: %lu\n", + __func__, clk_hw_get_name(hw), rate, parent_rate); + + if (rate < VC7_IOD_RATE_MIN || rate > VC7_IOD_RATE_MAX) { + dev_err(&vc7->client->dev, + "requested frequency %lu Hz for %s is out of range\n", + rate, clk_hw_get_name(hw)); + return -EINVAL; + } + + vc7_write_iod(vc7, iod->num); + + iod_rate = div64_u64(parent_rate, iod->iod_int); + + pr_debug("%s - %s: iod_int: %u\n", __func__, clk_hw_get_name(hw), iod->iod_int); + pr_debug("%s - %s rate: %ld\n", __func__, clk_hw_get_name(hw), iod_rate); + + return 0; +} + +static const struct clk_ops vc7_iod_ops = { + .recalc_rate = vc7_iod_recalc_rate, + .round_rate = vc7_iod_round_rate, + .set_rate = vc7_iod_set_rate, +}; + +static int vc7_clk_out_prepare(struct clk_hw *hw) +{ + struct vc7_out_data *out = container_of(hw, struct vc7_out_data, hw); + struct vc7_driver_data *vc7 = out->vc7; + int err; + + out->out_dis = 0; + + err = vc7_write_output(vc7, out->num); + if (err) { + dev_err(&vc7->client->dev, "error writing registers for %s\n", + clk_hw_get_name(hw)); + return err; + } + + pr_debug("%s - %s: clk prepared\n", __func__, clk_hw_get_name(hw)); + + return 0; +} + +static void vc7_clk_out_unprepare(struct clk_hw *hw) +{ + struct vc7_out_data *out = container_of(hw, struct vc7_out_data, hw); + struct vc7_driver_data *vc7 = out->vc7; + int err; + + out->out_dis = 1; + + err = vc7_write_output(vc7, out->num); + if (err) { + dev_err(&vc7->client->dev, "error writing registers for %s\n", + clk_hw_get_name(hw)); + return; + } + + pr_debug("%s - %s: clk unprepared\n", __func__, clk_hw_get_name(hw)); +} + +static int vc7_clk_out_is_enabled(struct clk_hw *hw) +{ + struct vc7_out_data *out = container_of(hw, struct vc7_out_data, hw); + struct vc7_driver_data *vc7 = out->vc7; + int err, is_enabled; + + err = vc7_read_output(vc7, out->num); + if (err) { + dev_err(&vc7->client->dev, "error reading registers for %s\n", + clk_hw_get_name(hw)); + return err; + } + + is_enabled = !out->out_dis; + + pr_debug("%s - %s: is_enabled=%d\n", __func__, clk_hw_get_name(hw), is_enabled); + + return is_enabled; +} + +static const struct clk_ops vc7_clk_out_ops = { + .prepare = vc7_clk_out_prepare, + .unprepare = vc7_clk_out_unprepare, + .is_enabled = vc7_clk_out_is_enabled, +}; + +static int vc7_probe(struct i2c_client *client) +{ + struct vc7_driver_data *vc7; + struct clk_init_data clk_init; + struct vc7_bank_src_map bank_src_map; + const char *node_name, *apll_name; + const char *parent_names[1]; + unsigned int i, val, bank_idx, out_num; + unsigned long apll_rate; + int ret; + + vc7 = devm_kzalloc(&client->dev, sizeof(*vc7), GFP_KERNEL); + if (!vc7) + return -ENOMEM; + + i2c_set_clientdata(client, vc7); + vc7->client = client; + vc7->chip_info = of_device_get_match_data(&client->dev); + + vc7->pin_xin = devm_clk_get(&client->dev, "xin"); + if (PTR_ERR(vc7->pin_xin) == -EPROBE_DEFER) { + return dev_err_probe(&client->dev, -EPROBE_DEFER, + "xin not specified\n"); + } + + vc7->regmap = devm_regmap_init_i2c(client, &vc7_regmap_config); + if (IS_ERR(vc7->regmap)) { + return dev_err_probe(&client->dev, PTR_ERR(vc7->regmap), + "failed to allocate register map\n"); + } + + if (of_property_read_string(client->dev.of_node, "clock-output-names", + &node_name)) + node_name = client->dev.of_node->name; + + /* Register APLL */ + apll_rate = vc7_get_apll_rate(vc7); + apll_name = kasprintf(GFP_KERNEL, "%s_apll", node_name); + vc7->clk_apll.clk = clk_register_fixed_rate(&client->dev, apll_name, + __clk_get_name(vc7->pin_xin), + 0, apll_rate); + kfree(apll_name); /* ccf made a copy of the name */ + if (IS_ERR(vc7->clk_apll.clk)) { + return dev_err_probe(&client->dev, PTR_ERR(vc7->clk_apll.clk), + "failed to register apll\n"); + } + + /* Register FODs */ + for (i = 0; i < VC7_NUM_FOD; i++) { + memset(&clk_init, 0, sizeof(clk_init)); + clk_init.name = kasprintf(GFP_KERNEL, "%s_fod%d", node_name, i); + clk_init.ops = &vc7_fod_ops; + clk_init.parent_names = parent_names; + parent_names[0] = __clk_get_name(vc7->clk_apll.clk); + clk_init.num_parents = 1; + vc7->clk_fod[i].num = i; + vc7->clk_fod[i].vc7 = vc7; + vc7->clk_fod[i].hw.init = &clk_init; + ret = devm_clk_hw_register(&client->dev, &vc7->clk_fod[i].hw); + if (ret) + goto err_clk_register; + kfree(clk_init.name); /* ccf made a copy of the name */ + } + + /* Register IODs */ + for (i = 0; i < VC7_NUM_IOD; i++) { + memset(&clk_init, 0, sizeof(clk_init)); + clk_init.name = kasprintf(GFP_KERNEL, "%s_iod%d", node_name, i); + clk_init.ops = &vc7_iod_ops; + clk_init.parent_names = parent_names; + parent_names[0] = __clk_get_name(vc7->clk_apll.clk); + clk_init.num_parents = 1; + vc7->clk_iod[i].num = i; + vc7->clk_iod[i].vc7 = vc7; + vc7->clk_iod[i].hw.init = &clk_init; + ret = devm_clk_hw_register(&client->dev, &vc7->clk_iod[i].hw); + if (ret) + goto err_clk_register; + kfree(clk_init.name); /* ccf made a copy of the name */ + } + + /* Register outputs */ + for (i = 0; i < vc7->chip_info->num_outputs; i++) { + out_num = vc7_map_index_to_output(vc7->chip_info->model, i); + + /* + * This driver does not support remapping FOD/IOD to banks. + * The device state is read and the driver is setup to match + * the device's existing mapping. + */ + bank_idx = output_bank_mapping[out_num]; + + regmap_read(vc7->regmap, VC7_REG_OUT_BANK_CNFG(bank_idx), &val); + val &= VC7_REG_OUTPUT_BANK_SRC_MASK; + + memset(&bank_src_map, 0, sizeof(bank_src_map)); + ret = vc7_get_bank_clk(vc7, bank_idx, val, &bank_src_map); + if (ret) { + dev_err_probe(&client->dev, ret, + "unable to register output %d\n", i); + return ret; + } + + switch (bank_src_map.type) { + case VC7_FOD: + parent_names[0] = clk_hw_get_name(&bank_src_map.src.fod->hw); + break; + case VC7_IOD: + parent_names[0] = clk_hw_get_name(&bank_src_map.src.iod->hw); + break; + } + + memset(&clk_init, 0, sizeof(clk_init)); + clk_init.name = kasprintf(GFP_KERNEL, "%s_out%d", node_name, i); + clk_init.ops = &vc7_clk_out_ops; + clk_init.flags = CLK_SET_RATE_PARENT; + clk_init.parent_names = parent_names; + clk_init.num_parents = 1; + vc7->clk_out[i].num = i; + vc7->clk_out[i].vc7 = vc7; + vc7->clk_out[i].hw.init = &clk_init; + ret = devm_clk_hw_register(&client->dev, &vc7->clk_out[i].hw); + if (ret) + goto err_clk_register; + kfree(clk_init.name); /* ccf made a copy of the name */ + } + + ret = of_clk_add_hw_provider(client->dev.of_node, vc7_of_clk_get, vc7); + if (ret) { + dev_err_probe(&client->dev, ret, "unable to add clk provider\n"); + goto err_clk; + } + + return ret; + +err_clk_register: + dev_err_probe(&client->dev, ret, + "unable to register %s\n", clk_init.name); + kfree(clk_init.name); /* ccf made a copy of the name */ +err_clk: + clk_unregister_fixed_rate(vc7->clk_apll.clk); + return ret; +} + +static int vc7_remove(struct i2c_client *client) +{ + struct vc7_driver_data *vc7 = i2c_get_clientdata(client); + + of_clk_del_provider(client->dev.of_node); + clk_unregister_fixed_rate(vc7->clk_apll.clk); + + return 0; +} + +static bool vc7_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg == VC7_PAGE_ADDR) + return false; + + return true; +} + +static const struct vc7_chip_info vc7_rc21008a_info = { + .model = VC7_RC21008A, + .num_banks = 6, + .num_outputs = 8, +}; + +static struct regmap_range_cfg vc7_range_cfg[] = { +{ + .range_min = 0, + .range_max = VC7_MAX_REG, + .selector_reg = VC7_PAGE_ADDR, + .selector_mask = 0xFF, + .selector_shift = 0, + .window_start = 0, + .window_len = VC7_PAGE_WINDOW, +}}; + +static const struct regmap_config vc7_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = VC7_MAX_REG, + .ranges = vc7_range_cfg, + .num_ranges = ARRAY_SIZE(vc7_range_cfg), + .volatile_reg = vc7_volatile_reg, + .cache_type = REGCACHE_RBTREE, + .can_multi_write = true, + .reg_format_endian = REGMAP_ENDIAN_LITTLE, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static const struct i2c_device_id vc7_i2c_id[] = { + { "rc21008a", VC7_RC21008A }, + {} +}; +MODULE_DEVICE_TABLE(i2c, vc7_i2c_id); + +static const struct of_device_id vc7_of_match[] = { + { .compatible = "renesas,rc21008a", .data = &vc7_rc21008a_info }, + {} +}; +MODULE_DEVICE_TABLE(of, vc7_of_match); + +static struct i2c_driver vc7_i2c_driver = { + .driver = { + .name = "vc7", + .of_match_table = vc7_of_match, + }, + .probe_new = vc7_probe, + .remove = vc7_remove, + .id_table = vc7_i2c_id, +}; +module_i2c_driver(vc7_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alex Helms Date: Wed, 11 May 2022 07:34:54 +0200 Subject: [PATCH 19/20] dt-bindings: clock: vc5: Add 5P49V6975 The 5P49V6975 is a member of the VersaClock 6E family and supports four fractional dividers (FODs), five clock outputs and an internal oscillator. Signed-off-by: Matthias Fend Link: https://lore.kernel.org/r/20220511053455.360335-1-matthias.fend@emfend.at Signed-off-by: Stephen Boyd --- Documentation/devicetree/bindings/clock/idt,versaclock5.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/clock/idt,versaclock5.yaml b/Documentation/devicetree/bindings/clock/idt,versaclock5.yaml index 7c331bfbe370..222ee358f348 100644 --- a/Documentation/devicetree/bindings/clock/idt,versaclock5.yaml +++ b/Documentation/devicetree/bindings/clock/idt,versaclock5.yaml @@ -56,6 +56,7 @@ properties: - idt,5p49v5935 - idt,5p49v6901 - idt,5p49v6965 + - idt,5p49v6975 reg: description: I2C device address @@ -134,6 +135,7 @@ allOf: enum: - idt,5p49v5933 - idt,5p49v5935 + - idt,5p49v6975 then: # Devices with builtin crystal + optional external input properties: From d8473831066b163d3af59ab0bb75a93b211ee1aa Mon Sep 17 00:00:00 2001 From: Matthias Fend Date: Wed, 11 May 2022 07:34:55 +0200 Subject: [PATCH 20/20] clk: vc5: Add support for IDT/Renesas VersaClock 5P49V6975 Update IDT VersaClock 5 driver to support 5P49V6975. The 5P49V6975 is a member of the VersaClock 6E family and supports four fractional dividers (FODs), five clock outputs and an internal oscillator. Signed-off-by: Matthias Fend Link: https://lore.kernel.org/r/20220511053455.360335-2-matthias.fend@emfend.at Reviewed-by: Luca Ceresoli Acked-by: Krzysztof Kozlowski Reviewed-by: Luca Ceresoli Signed-off-by: Stephen Boyd --- drivers/clk/clk-versaclock5.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/clk/clk-versaclock5.c b/drivers/clk/clk-versaclock5.c index 681006884097..5dedb2b8d927 100644 --- a/drivers/clk/clk-versaclock5.c +++ b/drivers/clk/clk-versaclock5.c @@ -153,6 +153,7 @@ enum vc5_model { IDT_VC5_5P49V5935, IDT_VC6_5P49V6901, IDT_VC6_5P49V6965, + IDT_VC6_5P49V6975, }; /* Structure to describe features of a particular VC5 model */ @@ -753,6 +754,7 @@ static int vc5_map_index_to_output(const enum vc5_model model, case IDT_VC5_5P49V5935: case IDT_VC6_5P49V6901: case IDT_VC6_5P49V6965: + case IDT_VC6_5P49V6975: default: return n; } @@ -1250,6 +1252,13 @@ static const struct vc5_chip_info idt_5p49v6965_info = { .flags = VC5_HAS_BYPASS_SYNC_BIT, }; +static const struct vc5_chip_info idt_5p49v6975_info = { + .model = IDT_VC6_5P49V6975, + .clk_fod_cnt = 4, + .clk_out_cnt = 5, + .flags = VC5_HAS_BYPASS_SYNC_BIT | VC5_HAS_INTERNAL_XTAL, +}; + static const struct i2c_device_id vc5_id[] = { { "5p49v5923", .driver_data = IDT_VC5_5P49V5923 }, { "5p49v5925", .driver_data = IDT_VC5_5P49V5925 }, @@ -1257,6 +1266,7 @@ static const struct i2c_device_id vc5_id[] = { { "5p49v5935", .driver_data = IDT_VC5_5P49V5935 }, { "5p49v6901", .driver_data = IDT_VC6_5P49V6901 }, { "5p49v6965", .driver_data = IDT_VC6_5P49V6965 }, + { "5p49v6975", .driver_data = IDT_VC6_5P49V6975 }, { } }; MODULE_DEVICE_TABLE(i2c, vc5_id); @@ -1268,6 +1278,7 @@ static const struct of_device_id clk_vc5_of_match[] = { { .compatible = "idt,5p49v5935", .data = &idt_5p49v5935_info }, { .compatible = "idt,5p49v6901", .data = &idt_5p49v6901_info }, { .compatible = "idt,5p49v6965", .data = &idt_5p49v6965_info }, + { .compatible = "idt,5p49v6975", .data = &idt_5p49v6975_info }, { }, }; MODULE_DEVICE_TABLE(of, clk_vc5_of_match);