[PATCH 4/5] clk: eyeq5: add OSPI table-based divider clock

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The driver supports PLLs on the platform. Add the single divider clock
of the platform.

Helpers from include/linux/clk-provider.h could have been used if it was
not for the use of regmap to access the register.

Signed-off-by: Théo Lebrun <theo.lebrun@xxxxxxxxxxx>
---
 drivers/clk/Kconfig     |   2 +-
 drivers/clk/clk-eyeq5.c | 143 ++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 140 insertions(+), 5 deletions(-)

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 84fe0a89b8df..63cc354f41ab 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -227,7 +227,7 @@ config COMMON_CLK_EYEQ5
 		This drivers provides the fixed clocks found on the Mobileye EyeQ5
 		SoC. Its registers live in a shared register region called OLB.
 		It provides 10 read-only PLLs derived from the main crystal clock which
-		must be constant.
+		must be constant and one divider clock based on one PLLs.
 
 config COMMON_CLK_FSL_FLEXSPI
 	tristate "Clock driver for FlexSPI on Layerscape SoCs"
diff --git a/drivers/clk/clk-eyeq5.c b/drivers/clk/clk-eyeq5.c
index 74bcb8cec5c1..3382f4d870d7 100644
--- a/drivers/clk/clk-eyeq5.c
+++ b/drivers/clk/clk-eyeq5.c
@@ -3,8 +3,9 @@
  * PLL clock driver for the Mobileye EyeQ5 platform.
  *
  * This controller handles 10 read-only PLLs, all derived from the same main
- * crystal clock. The parent clock is expected to be constant. This driver is
- * custom to this platform, its registers live in a shared region called OLB.
+ * crystal clock. It also exposes one divider clock, a child of one of the
+ * PLLs. The parent clock is expected to be constant. This driver is custom to
+ * this platform, its registers live in a shared region called OLB.
  *
  * We use eq5c_ as prefix, as-in "EyeQ5 Clock", but way shorter.
  *
@@ -77,6 +78,8 @@ static const struct eq5c_pll {
 	[EQ5C_PLL_DDR1] = { .name = "pll-ddr1", .reg = OLB_PCSR_DDR1(0), },
 };
 
+#define EQ5C_OSPI_DIV_CLK_NAME	"div-ospi"
+
 static int eq5c_pll_parse_registers(u32 r0, u32 r1, unsigned long *mult,
 				    unsigned long *div, unsigned long *acc)
 {
@@ -131,6 +134,128 @@ static int eq5c_pll_parse_registers(u32 r0, u32 r1, unsigned long *mult,
 	return 0;
 }
 
+#define OLB_OSPI_REG		0x11C
+#define OLB_OSPI_DIV_MASK	GENMASK(3, 0)
+#define OLB_OSPI_DIV_MASK_WIDTH	4
+
+static const struct clk_div_table eq5c_ospi_div_table[] = {
+	{ .val = 0, .div = 2 },
+	{ .val = 1, .div = 4 },
+	{ .val = 2, .div = 6 },
+	{ .val = 3, .div = 8 },
+	{ .val = 4, .div = 10 },
+	{ .val = 5, .div = 12 },
+	{ .val = 6, .div = 14 },
+	{ .val = 7, .div = 16 },
+	{} /* sentinel */
+};
+
+struct eq5c_ospi_div {
+	struct clk_hw	hw;
+	struct regmap	*olb;
+};
+
+static struct eq5c_ospi_div *clk_hw_to_ospi_priv(struct clk_hw *hw)
+{
+	return container_of(hw, struct eq5c_ospi_div, hw);
+}
+
+static unsigned long eq5c_ospi_div_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct eq5c_ospi_div *div = clk_hw_to_ospi_priv(hw);
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(div->olb, OLB_OSPI_REG, &val);
+
+	if (ret) {
+		pr_err("%s: regmap_read failed: %d\n", __func__, ret);
+		return 0;
+	}
+
+	val = FIELD_GET(OLB_OSPI_DIV_MASK, val);
+
+	return divider_recalc_rate(hw, parent_rate, val,
+				   eq5c_ospi_div_table, 0,
+				   OLB_OSPI_DIV_MASK_WIDTH);
+}
+
+static long eq5c_ospi_div_round_rate(struct clk_hw *hw,
+				     unsigned long rate, unsigned long *prate)
+{
+	return divider_round_rate(hw, rate, prate, eq5c_ospi_div_table,
+				  OLB_OSPI_DIV_MASK_WIDTH, 0);
+}
+
+static int eq5c_ospi_div_determine_rate(struct clk_hw *hw,
+					struct clk_rate_request *req)
+{
+	return divider_determine_rate(hw, req, eq5c_ospi_div_table,
+				      OLB_OSPI_DIV_MASK_WIDTH, 0);
+}
+
+static int eq5c_ospi_div_set_rate(struct clk_hw *hw,
+				  unsigned long rate, unsigned long parent_rate)
+{
+	struct eq5c_ospi_div *div = clk_hw_to_ospi_priv(hw);
+	unsigned int val;
+	int value, ret;
+
+	value = divider_get_val(rate, parent_rate, eq5c_ospi_div_table,
+				OLB_OSPI_DIV_MASK_WIDTH, 0);
+	if (value < 0)
+		return value;
+
+	ret = regmap_read(div->olb, OLB_OSPI_REG, &val);
+	if (ret) {
+		pr_err("%s: regmap_read failed: %d\n", __func__, ret);
+		return -ret;
+	}
+
+	val &= ~OLB_OSPI_DIV_MASK;
+	val |= FIELD_PREP(OLB_OSPI_DIV_MASK, value);
+
+	ret = regmap_write(div->olb, OLB_OSPI_REG, val);
+	if (ret) {
+		pr_err("%s: regmap_write failed: %d\n", __func__, ret);
+		return -ret;
+	}
+
+	return 0;
+}
+
+const struct clk_ops eq5c_ospi_div_ops = {
+	.recalc_rate = eq5c_ospi_div_recalc_rate,
+	.round_rate = eq5c_ospi_div_round_rate,
+	.determine_rate = eq5c_ospi_div_determine_rate,
+	.set_rate = eq5c_ospi_div_set_rate,
+};
+
+static struct clk_hw *eq5c_init_ospi_div(const struct clk_hw *parent,
+					 struct regmap *olb)
+{
+	struct eq5c_ospi_div *div;
+	int ret;
+
+	div = kzalloc(sizeof(*div), GFP_KERNEL);
+	if (!div)
+		return ERR_PTR(-ENOENT);
+
+	div->olb = olb;
+	div->hw.init = CLK_HW_INIT_HW(EQ5C_OSPI_DIV_CLK_NAME, parent,
+				      &eq5c_ospi_div_ops, 0);
+
+	ret = clk_hw_register(NULL, &div->hw);
+	if (ret) {
+		pr_err("failed registering div_ospi: %d\n", ret);
+		kfree(div);
+		return ERR_PTR(-ENOENT);
+	}
+
+	return &div->hw;
+}
+
 static void eq5c_init(struct device_node *np)
 {
 	struct device_node *parent_np = of_get_parent(np);
@@ -139,13 +264,15 @@ static void eq5c_init(struct device_node *np)
 	struct clk_hw *parent_clk_hw;
 	struct clk *parent_clk;
 	struct regmap *olb;
+	size_t nb_clks;
 	int i;
 
-	data = kzalloc(struct_size(data, hws, ARRAY_SIZE(eq5c_plls)), GFP_KERNEL);
+	nb_clks = ARRAY_SIZE(eq5c_plls) + 1;
+	data = kzalloc(struct_size(data, hws, nb_clks), GFP_KERNEL);
 	if (!data)
 		return;
 
-	data->num = ARRAY_SIZE(eq5c_plls);
+	data->num = nb_clks;
 
 	/*
 	 * TODO: currently, if OLB is not available, we log an error and early
@@ -205,6 +332,14 @@ static void eq5c_init(struct device_node *np)
 		}
 	}
 
+	/*
+	 * Register the OSPI table-based divider clock manually. This is
+	 * equivalent to drivers/clk/clk-divider.c, but using regmap to access
+	 * its register.
+	 */
+	i = ARRAY_SIZE(eq5c_plls);
+	data->hws[i] = eq5c_init_ospi_div(data->hws[EQ5C_PLL_PER], olb);
+
 	of_clk_add_hw_provider(np, of_clk_hw_onecell_get, data);
 }
 

-- 
2.43.0





[Index of Archives]     [LKML Archive]     [Linux ARM Kernel]     [Linux ARM]     [Git]     [Yosemite News]     [Linux SCSI]     [Linux Hams]

  Powered by Linux