[PATCH 05/14] staging: clocking-wizard: Implement CCF clock provider

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

 



The CCF clock providers that are currently used by the driver are not
capable of supporting the Clocking Wizard IP register interface for
fractional ratios, nor are they able to enforce constraints require to
ensure the PLL will always lock.

None of the common CCF clock providers seem to be a good fit so we
implement a new CCF clock provider within the driver that can be expanded
upon in subsequent patches.  The initial implementation supports multiply
or divide by fixed integer ratios.

The CCF clock provider uses:
- devm kernel APIs for clock registration to simplify error recovery and
  module unloading.
- regmap kernel APIs for memory mapped I/O. Regmap is also able to manage
  prepare/enable/disable/unprepare of the AXI clock for us.

Note that use of the new CCF clock provider is deferred to a subsequent
patch.

Signed-off-by: James Kelly <jamespeterkelly@xxxxxxxxx>
---
 .../clocking-wizard/clk-xlnx-clock-wizard.c        | 164 +++++++++++++++++++++
 1 file changed, 164 insertions(+)

diff --git a/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c b/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c
index 3b66ac3b5ed0..ba9b1dc93d50 100644
--- a/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c
+++ b/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c
@@ -3,6 +3,7 @@
  * Xilinx 'Clocking Wizard' driver
  *
  *  Copyright (C) 2013 - 2014 Xilinx
+ *  Copyright (C) 2018 James Kelly
  *
  *  Sören Brinkmann <soren.brinkmann@xxxxxxxxxx>
  *
@@ -67,11 +68,13 @@
 #include <linux/of.h>
 #include <linux/module.h>
 #include <linux/err.h>
+#include <linux/regmap.h>
 
 #define WZRD_NUM_OUTPUTS	7
 #define WZRD_ACLK_MAX_FREQ	250000000UL
 #define WZRD_CLKNAME_AXI	"s_axi_aclk"
 #define WZRD_CLKNAME_IN1	"clk_in1"
+#define WZRD_FLAG_MULTIPLY	BIT(0)
 
 #define WZRD_CLK_CFG_REG(n)	(0x200 + 4 * (n))
 
@@ -91,28 +94,90 @@ enum clk_wzrd_int_clks {
 	wzrd_clk_int_max
 };
 
+enum clk_wzrd_clk {
+	WZRD_CLK_DIV,
+	WZRD_CLK_PLL,
+	WZRD_CLK_OUT,
+	WZRD_CLK_COUNT
+};
+
+/*
+ * Register map extracted from Xilinx document listed below.
+ *
+ * PG065 Clocking Wizard v6.0 LogiCORE IP Product Guide
+ */
+static const struct regmap_config clk_wzrd_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32
+};
+
+static const struct reg_field clk_wzrd_status_locked = REG_FIELD(0x004,  0,  0);
+static const struct reg_field clk_wzrd_divclk_divide = REG_FIELD(0x200,  0,  7);
+static const struct reg_field clk_wzrd_clkfbout_mult = REG_FIELD(0x200,  8, 15);
+static const struct reg_field clk_wzrd_clkfbout_frac = REG_FIELD(0x200, 16, 25);
+static const struct reg_field clk_wzrd_clkout_divide[WZRD_NUM_OUTPUTS] = {
+	REG_FIELD(0x208, 0, 7),
+	REG_FIELD(0x214, 0, 7),
+	REG_FIELD(0x220, 0, 7),
+	REG_FIELD(0x22C, 0, 7),
+	REG_FIELD(0x238, 0, 7),
+	REG_FIELD(0x244, 0, 7),
+	REG_FIELD(0x250, 0, 7)
+};
+
+static const struct reg_field clk_wzrd_clkout0_frac = REG_FIELD(0x208, 8, 17);
+static const struct reg_field clk_wzrd_reconfig     = REG_FIELD(0x25C, 0,  1);
+
+/**
+ * struct clk_wzrd_clk_data - Clocking Wizard component clock provider data
+ *
+ * @hw:			handle between common and hardware-specific interfaces
+ * @flags:		hardware specific flags
+ * @int_field:		pointer to regmap field for integer part
+ *
+ * Flags:
+ * WZRD_FLAG_MULTIPLY	Clock is a multiplier rather than a divider
+ */
+struct clk_wzrd_clk_data {
+	struct clk_hw			hw;
+	unsigned long			flags;
+	struct regmap_field		*int_field;
+};
+
+#define to_clk_wzrd_clk_data(_hw) \
+		container_of(_hw, struct clk_wzrd_clk_data, hw)
+
 /**
  * struct clk_wzrd:
  * @clk_data:		Clock data
  * @nb:			Notifier block
  * @base:		Memory base
+ * @regmap:		Register map for hardware
  * @clk_in1:		Handle to input clock 'clk_in1'
  * @axi_clk:		Handle to input clock 's_axi_aclk'
  * @clks_internal:	Internal clocks
  * @clkout:		Output clocks
  * @speed_grade:	Speed grade of the device
  * @suspended:		Flag indicating power state of the device
+ * @div:		Divider internal clock provider data
+ * @pll:		Phase locked loop internal clock provider data
+ * @clkout_data:	Array of output clock provider data
  */
 struct clk_wzrd {
 	struct clk_onecell_data		clk_data;
 	struct notifier_block		nb;
 	void __iomem			*base;
+	struct regmap			*regmap;
 	struct clk			*clk_in1;
 	struct clk			*axi_clk;
 	struct clk			*clks_internal[wzrd_clk_int_max];
 	struct clk			*clkout[WZRD_NUM_OUTPUTS];
 	unsigned int			speed_grade;
 	bool				suspended;
+	struct clk_wzrd_clk_data	div_data;
+	struct clk_wzrd_clk_data	pll_data;
+	struct clk_wzrd_clk_data	clkout_data[0];
 };
 
 #define to_clk_wzrd(_nb) container_of(_nb, struct clk_wzrd, nb)
@@ -124,6 +189,105 @@ static const unsigned long clk_wzrd_max_freq[] = {
 	1066000000UL
 };
 
+static unsigned long clk_wzrd_get_ratio(struct clk_wzrd_clk_data *cwc)
+{
+	u32 int_part;
+
+	regmap_field_read(cwc->int_field, &int_part);
+
+	return int_part;
+}
+
+static unsigned long clk_wzrd_calc_rate(unsigned long parent_rate,
+					unsigned long ratio,
+					unsigned long flags)
+{
+	if (flags & WZRD_FLAG_MULTIPLY)
+		return parent_rate * ratio;
+
+	return DIV_ROUND_CLOSEST(parent_rate, ratio);
+}
+
+static unsigned long clk_wzrd_recalc_rate(struct clk_hw *hw,
+					  unsigned long parent_rate)
+{
+	unsigned long ratio;
+	struct clk_wzrd_clk_data *cwc = to_clk_wzrd_clk_data(hw);
+
+	ratio = clk_wzrd_get_ratio(cwc);
+
+	/* Hardware giving invalid information */
+	if (ratio == 0)
+		return 0;
+
+	return clk_wzrd_calc_rate(parent_rate, ratio, cwc->flags);
+}
+
+static const struct clk_ops clk_wzrd_clk_ops = {
+	.recalc_rate = clk_wzrd_recalc_rate,
+};
+
+static int clk_wzrd_register_clk(struct device *dev, const char *name,
+				 enum clk_wzrd_clk type, unsigned int instance,
+				 unsigned long flags)
+{
+	int ret;
+	struct clk_init_data init;
+	const struct reg_field *int_reg_field;
+	struct clk_wzrd_clk_data *cwc;
+	const char *parent_name;
+	struct clk_wzrd *cw = dev_get_drvdata(dev);
+
+	init.ops = &clk_wzrd_clk_ops;
+	init.flags = flags;
+
+	switch (type) {
+	case WZRD_CLK_DIV:
+		cwc = &cw->div_data;
+		int_reg_field = &clk_wzrd_divclk_divide;
+		parent_name = __clk_get_name(cw->clk_in1);
+		break;
+	case WZRD_CLK_PLL:
+		cwc = &cw->pll_data;
+		cwc->flags |= WZRD_FLAG_MULTIPLY;
+		int_reg_field = &clk_wzrd_clkfbout_mult;
+		parent_name = clk_hw_get_name(&cw->div_data.hw);
+		break;
+	case WZRD_CLK_OUT:
+		if (instance >= WZRD_NUM_OUTPUTS) {
+			ret = -EINVAL;
+			goto err;
+		}
+		cwc = &cw->clkout_data[instance];
+		int_reg_field = &clk_wzrd_clkout_divide[instance];
+		parent_name = clk_hw_get_name(&cw->pll_data.hw);
+		break;
+	default:
+		ret = -EINVAL;
+		goto err;
+	}
+
+	init.name = name;
+	init.parent_names = &parent_name;
+	init.num_parents = 1;
+	cwc->hw.init = &init;
+	cwc->int_field = devm_regmap_field_alloc(dev, cw->regmap,
+						 *int_reg_field);
+	if (IS_ERR(cwc->int_field)) {
+		ret = PTR_ERR(cwc->int_field);
+		goto err;
+	}
+
+	ret = devm_clk_hw_register(dev, &cwc->hw);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_err(dev, "Unable to register component clock %s\n", name);
+	return ret;
+}
+
 static int clk_wzrd_clk_notifier(struct notifier_block *nb, unsigned long event,
 				 void *data)
 {
-- 
2.15.1 (Apple Git-101)

_______________________________________________
devel mailing list
devel@xxxxxxxxxxxxxxxxxxxxxx
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel




[Index of Archives]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux