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