summaryrefslogtreecommitdiff
path: root/drivers/clk
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk')
-rw-r--r--drivers/clk/Kconfig9
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/baikal-t1/Kconfig12
-rw-r--r--drivers/clk/baikal-t1/Makefile1
-rw-r--r--drivers/clk/baikal-t1/ccu-div.c84
-rw-r--r--drivers/clk/baikal-t1/ccu-div.h17
-rw-r--r--drivers/clk/baikal-t1/ccu-pll.h8
-rw-r--r--drivers/clk/baikal-t1/ccu-rst.c217
-rw-r--r--drivers/clk/baikal-t1/ccu-rst.h67
-rw-r--r--drivers/clk/baikal-t1/clk-ccu-div.c260
-rw-r--r--drivers/clk/baikal-t1/clk-ccu-pll.c123
-rw-r--r--drivers/clk/bcm/clk-bcm2835.c43
-rw-r--r--drivers/clk/bcm/clk-raspberrypi.c11
-rw-r--r--drivers/clk/clk-versaclock5.c161
-rw-r--r--drivers/clk/clk-versaclock7.c1311
15 files changed, 2101 insertions, 224 deletions
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index ac77d4b2c742..d79905f3e174 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/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 4062092d67f9..8d5fc7158f33 100644
--- a/drivers/clk/baikal-t1/ccu-div.c
+++ b/drivers/clk/baikal-t1/ccu-div.c
@@ -34,9 +34,9 @@
#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
#define CCU_DIV_LOCK_CHECK_RETRIES 50
#define CCU_DIV_CLKDIV_MIN 0
@@ -170,6 +170,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)
{
@@ -288,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 {
@@ -323,6 +339,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 +458,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 +497,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 +524,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 +556,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 +609,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;
@@ -579,6 +624,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..76d8ee44d415 100644
--- a/drivers/clk/baikal-t1/ccu-div.h
+++ b/drivers/clk/baikal-t1/ccu-div.h
@@ -14,14 +14,25 @@
#include <linux/of.h>
/*
+ * 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
+
+/*
* 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].
* 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_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)
@@ -31,11 +42,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
};
@@ -105,6 +118,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-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
@@ -14,6 +14,12 @@
#include <linux/of.h>
/*
+ * 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.
* @name: Clocks name.
@@ -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/ccu-rst.c b/drivers/clk/baikal-t1/ccu-rst.c
new file mode 100644
index 000000000000..40023ea67463
--- /dev/null
+++ b/drivers/clk/baikal-t1/ccu-rst.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021 BAIKAL ELECTRONICS, JSC
+ *
+ * Authors:
+ * Serge Semin <Sergey.Semin@baikalelectronics.ru>
+ *
+ * Baikal-T1 CCU Resets interface driver
+ */
+
+#define pr_fmt(fmt) "bt1-ccu-rst: " fmt
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/printk.h>
+#include <linux/regmap.h>
+#include <linux/reset-controller.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/reset/bt1-ccu.h>
+
+#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_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;
+};
+
+/*
+ * 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),
+ [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)
+{
+ 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. */
+ udelay(CCU_RST_DELAY_US);
+
+ 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)
+{
+ 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..d6e8b2f671f4
--- /dev/null
+++ b/drivers/clk/baikal-t1/ccu-rst.h
@@ -0,0 +1,67 @@
+/* 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 <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/reset-controller.h>
+
+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.
+ * @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 f141fda12b09..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 <linux/kernel.h>
+#include <linux/platform_device.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/clk-provider.h>
@@ -24,9 +25,9 @@
#include <linux/regmap.h>
#include <dt-bindings/clock/bt1-ccu.h>
-#include <dt-bindings/reset/bt1-ccu.h>
#include "ccu-div.h"
+#include "ccu-rst.h"
#define CCU_AXI_MAIN_BASE 0x030
#define CCU_AXI_DDR_BASE 0x034
@@ -76,6 +77,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, \
@@ -85,12 +96,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;
@@ -105,11 +110,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;
@@ -118,11 +118,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
@@ -169,33 +166,22 @@ 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.
*/
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),
+ 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",
@@ -204,10 +190,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", 10),
+ "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",
@@ -227,74 +215,58 @@ 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 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_data *axi_data;
+static struct ccu_div_data *sys_data;
-static struct ccu_div *ccu_div_find_desc(struct ccu_div_data *data,
- unsigned int clk_id)
+static void ccu_div_set_data(struct ccu_div_data *data)
{
- 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;
- }
-
- return ERR_PTR(-EINVAL);
+ 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 int ccu_div_reset(struct reset_controller_dev *rcdev,
- unsigned long rst_id)
+static struct ccu_div_data *ccu_div_get_data(struct device_node *np)
{
- struct ccu_div_data *data = to_ccu_div_data(rcdev);
- const struct ccu_div_rst_map *map;
- struct ccu_div *div;
- int idx, ret;
+ 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;
- 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;
- }
+ pr_err("Invalid DT node '%s' specified\n", of_node_full_name(np));
- 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);
- }
+ return NULL;
+}
- 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)));
+static struct ccu_div *ccu_div_find_desc(struct ccu_div_data *data,
+ unsigned int clk_id)
+{
+ int idx;
+
+ for (idx = 0; idx < data->divs_num; ++idx) {
+ if (data->divs_info[idx].id == clk_id)
+ return data->divs[idx];
}
- return ret;
+ return ERR_PTR(-EINVAL);
}
-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;
@@ -308,13 +280,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));
@@ -365,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;
@@ -380,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;
@@ -396,6 +373,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;
}
@@ -409,49 +389,104 @@ 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_rst_register(struct ccu_div_data *data)
+static int ccu_div_of_register(struct ccu_div_data *data)
{
int ret;
- data->rcdev.ops = &ccu_div_rst_ops;
- data->rcdev.of_node = data->np;
- data->rcdev.nr_resets = data->rst_num;
+ 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;
+}
- ret = reset_controller_register(&data->rcdev);
- if (ret)
+static int ccu_div_rst_register(struct ccu_div_data *data)
+{
+ struct ccu_rst_init_data init = {0};
+
+ init.sys_regs = data->sys_regs;
+ init.np = data->np;
+
+ 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 0;
+}
+
+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 void ccu_div_init(struct device_node *np)
+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;
@@ -464,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 <linux/kernel.h>
+#include <linux/platform_device.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/clk-provider.h>
@@ -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;
+ return ret;
+}
-err_hw_unregister:
- for (--idx; idx >= 0; --idx)
- ccu_pll_hw_unregister(data->plls[idx]);
+static int ccu_pll_probe(struct platform_device *pdev)
+{
+ struct ccu_pll_data *data = pll_data;
- return ret;
+ 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);
diff --git a/drivers/clk/bcm/clk-bcm2835.c b/drivers/clk/bcm/clk-bcm2835.c
index 48a1eb9f2d55..e74fe6219d14 100644
--- a/drivers/clk/bcm/clk-bcm2835.c
+++ b/drivers/clk/bcm/clk-bcm2835.c
@@ -30,6 +30,7 @@
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/io.h>
+#include <linux/math.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
@@ -502,6 +503,8 @@ struct bcm2835_clock_data {
bool low_jitter;
u32 tcnt_mux;
+
+ bool round_up;
};
struct bcm2835_gate_data {
@@ -966,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;
@@ -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)
@@ -1784,7 +1814,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.
@@ -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(
diff --git a/drivers/clk/bcm/clk-raspberrypi.c b/drivers/clk/bcm/clk-raspberrypi.c
index 73518009a0f2..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)
@@ -129,9 +131,18 @@ 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,
+ },
[RPI_FIRMWARE_PIXEL_BVB_CLK_ID] = {
.export = true,
},
+ [RPI_FIRMWARE_VEC_CLK_ID] = {
+ .export = true,
+ },
};
/*
diff --git a/drivers/clk/clk-versaclock5.c b/drivers/clk/clk-versaclock5.c
index b8c556dbeba0..2cf3e577bc2b 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 */
@@ -230,8 +231,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 +291,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 +324,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 +341,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 +388,17 @@ 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_set_bits(vc5->regmap, VC5_VCO_CTRL_AND_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_clear_bits(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV,
+ VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV);
}
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,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.
*/
- 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_clear_bits(vc5->regmap, VC5_GLOBAL_REGISTER,
+ VC5_GLOBAL_REGISTER_GLOBAL_RESET);
+ if (ret)
+ return ret;
+
+ return regmap_set_bits(vc5->regmap, VC5_GLOBAL_REGISTER,
+ VC5_GLOBAL_REGISTER_GLOBAL_RESET);
}
static const struct clk_ops vc5_fod_ops = {
@@ -594,10 +613,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;
}
@@ -606,7 +624,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 +638,22 @@ 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_set_bits(vc5->regmap, VC5_CLK_OUTPUT_CFG(hwdata->num, 1),
+ 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;
@@ -640,8 +665,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)
@@ -656,8 +681,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 */
@@ -725,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;
}
@@ -819,22 +849,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;
-
- /*
- * 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);
- }
+ if (of_property_read_u32(node, "idt,xtal-load-femtofarads", &value))
+ return 0;
- return 0;
+ mapped_value = vc5_map_cap_value(value);
+ if (mapped_value < 0)
+ return mapped_value;
+
+ /*
+ * 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 +991,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));
@@ -1204,7 +1242,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 = {
@@ -1214,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 },
@@ -1221,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);
@@ -1232,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);
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 <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/i2c.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/swab.h>
+
+/*
+ * 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 <alexander.helms.jy@renesas.com");
+MODULE_DESCRIPTION("Renesas Versaclock7 common clock framework driver");