diff options
Diffstat (limited to 'drivers/clk/clk-si544.c')
| -rw-r--r-- | drivers/clk/clk-si544.c | 155 |
1 files changed, 108 insertions, 47 deletions
diff --git a/drivers/clk/clk-si544.c b/drivers/clk/clk-si544.c index 64e607f3232a..09c06ecec1a5 100644 --- a/drivers/clk/clk-si544.c +++ b/drivers/clk/clk-si544.c @@ -7,6 +7,7 @@ #include <linux/clk-provider.h> #include <linux/delay.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/regmap.h> @@ -38,7 +39,7 @@ /* Max freq depends on speed grade */ #define SI544_MIN_FREQ 200000U -/* Si544 Internal oscilator runs at 55.05 MHz */ +/* Si544 Internal oscillator runs at 55.05 MHz */ #define FXO 55050000U /* VCO range is 10.8 .. 12.1 GHz, max depends on speed grade */ @@ -50,17 +51,16 @@ /* Lowest frequency synthesizeable using only the HS divider */ #define MIN_HSDIV_FREQ (FVCO_MIN / HS_DIV_MAX) -enum si544_speed_grade { - si544a, - si544b, - si544c, -}; +/* Range and interpretation of the adjustment value */ +#define DELTA_M_MAX 8161512 +#define DELTA_M_FRAC_NUM 19 +#define DELTA_M_FRAC_DEN 20000 struct clk_si544 { struct clk_hw hw; struct regmap *regmap; struct i2c_client *i2c_client; - enum si544_speed_grade speed_grade; + unsigned long max_freq; }; #define to_clk_si544(_hw) container_of(_hw, struct clk_si544, hw) @@ -71,12 +71,14 @@ struct clk_si544 { * @hs_div: 1st divider, 5..2046, must be even when >33 * @ls_div_bits: 2nd divider, as 2^x, range 0..5 * If ls_div_bits is non-zero, hs_div must be even + * @delta_m: Frequency shift for small -950..+950 ppm changes, 24 bit */ struct clk_si544_muldiv { u32 fb_div_frac; u16 fb_div_int; u16 hs_div; u8 ls_div_bits; + s32 delta_m; }; /* Enables or disables the output driver */ @@ -134,9 +136,30 @@ static int si544_get_muldiv(struct clk_si544 *data, settings->fb_div_int = reg[4] | (reg[5] & 0x07) << 8; settings->fb_div_frac = reg[0] | reg[1] << 8 | reg[2] << 16 | reg[3] << 24; + + err = regmap_bulk_read(data->regmap, SI544_REG_ADPLL_DELTA_M0, reg, 3); + if (err) + return err; + + /* Interpret as 24-bit signed number */ + settings->delta_m = reg[0] << 8 | reg[1] << 16 | reg[2] << 24; + settings->delta_m >>= 8; + return 0; } +static int si544_set_delta_m(struct clk_si544 *data, s32 delta_m) +{ + u8 reg[3]; + + reg[0] = delta_m; + reg[1] = delta_m >> 8; + reg[2] = delta_m >> 16; + + return regmap_bulk_write(data->regmap, SI544_REG_ADPLL_DELTA_M0, + reg, 3); +} + static int si544_set_muldiv(struct clk_si544 *data, struct clk_si544_muldiv *settings) { @@ -167,24 +190,10 @@ static int si544_set_muldiv(struct clk_si544 *data, static bool is_valid_frequency(const struct clk_si544 *data, unsigned long frequency) { - unsigned long max_freq = 0; - if (frequency < SI544_MIN_FREQ) return false; - switch (data->speed_grade) { - case si544a: - max_freq = 1500000000; - break; - case si544b: - max_freq = 800000000; - break; - case si544c: - max_freq = 350000000; - break; - } - - return frequency <= max_freq; + return frequency <= data->max_freq; } /* Calculate divider settings for a given frequency */ @@ -238,11 +247,15 @@ static int si544_calc_muldiv(struct clk_si544_muldiv *settings, do_div(vco, FXO); settings->fb_div_frac = vco; + /* Reset the frequency adjustment */ + settings->delta_m = 0; + return 0; } /* Calculate resulting frequency given the register settings */ -static unsigned long si544_calc_rate(struct clk_si544_muldiv *settings) +static unsigned long si544_calc_center_rate( + const struct clk_si544_muldiv *settings) { u32 d = settings->hs_div * BIT(settings->ls_div_bits); u64 vco; @@ -261,6 +274,25 @@ static unsigned long si544_calc_rate(struct clk_si544_muldiv *settings) return vco; } +static unsigned long si544_calc_rate(const struct clk_si544_muldiv *settings) +{ + unsigned long rate = si544_calc_center_rate(settings); + s64 delta = (s64)rate * (DELTA_M_FRAC_NUM * settings->delta_m); + + /* + * The clock adjustment is much smaller than 1 Hz, round to the + * nearest multiple. Apparently div64_s64 rounds towards zero, hence + * check the sign and adjust into the proper direction. + */ + if (settings->delta_m < 0) + delta -= ((s64)DELTA_M_MAX * DELTA_M_FRAC_DEN) / 2; + else + delta += ((s64)DELTA_M_MAX * DELTA_M_FRAC_DEN) / 2; + delta = div64_s64(delta, ((s64)DELTA_M_MAX * DELTA_M_FRAC_DEN)); + + return rate + delta; +} + static unsigned long si544_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { @@ -275,37 +307,64 @@ static unsigned long si544_recalc_rate(struct clk_hw *hw, return si544_calc_rate(&settings); } -static long si544_round_rate(struct clk_hw *hw, unsigned long rate, - unsigned long *parent_rate) +static int si544_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) { struct clk_si544 *data = to_clk_si544(hw); - struct clk_si544_muldiv settings; - int err; - if (!is_valid_frequency(data, rate)) + if (!is_valid_frequency(data, req->rate)) return -EINVAL; - err = si544_calc_muldiv(&settings, rate); - if (err) - return err; + /* The accuracy is less than 1 Hz, so any rate is possible */ + return 0; +} - return si544_calc_rate(&settings); +/* Calculates the maximum "small" change, 950 * rate / 1000000 */ +static unsigned long si544_max_delta(unsigned long rate) +{ + u64 num = rate; + + num *= DELTA_M_FRAC_NUM; + do_div(num, DELTA_M_FRAC_DEN); + + return num; +} + +static s32 si544_calc_delta(s32 delta, s32 max_delta) +{ + s64 n = (s64)delta * DELTA_M_MAX; + + return div_s64(n, max_delta); } -/* - * Update output frequency for "big" frequency changes - */ static int si544_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct clk_si544 *data = to_clk_si544(hw); struct clk_si544_muldiv settings; + unsigned long center; + long max_delta; + long delta; unsigned int old_oe_state; int err; if (!is_valid_frequency(data, rate)) return -EINVAL; + /* Try using the frequency adjustment feature for a <= 950ppm change */ + err = si544_get_muldiv(data, &settings); + if (err) + return err; + + center = si544_calc_center_rate(&settings); + max_delta = si544_max_delta(center); + delta = rate - center; + + if (abs(delta) <= max_delta) + return si544_set_delta_m(data, + si544_calc_delta(delta, max_delta)); + + /* Too big for the delta adjustment, need to reprogram */ err = si544_calc_muldiv(&settings, rate); if (err) return err; @@ -321,6 +380,9 @@ static int si544_set_rate(struct clk_hw *hw, unsigned long rate, if (err < 0) return err; + err = si544_set_delta_m(data, settings.delta_m); + if (err < 0) + return err; err = si544_set_muldiv(data, &settings); if (err < 0) @@ -346,7 +408,7 @@ static const struct clk_ops si544_clk_ops = { .unprepare = si544_unprepare, .is_prepared = si544_is_prepared, .recalc_rate = si544_recalc_rate, - .round_rate = si544_round_rate, + .determine_rate = si544_determine_rate, .set_rate = si544_set_rate, }; @@ -364,13 +426,12 @@ static bool si544_regmap_is_volatile(struct device *dev, unsigned int reg) static const struct regmap_config si544_regmap_config = { .reg_bits = 8, .val_bits = 8, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .max_register = SI544_REG_PAGE_SELECT, .volatile_reg = si544_regmap_is_volatile, }; -static int si544_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int si544_probe(struct i2c_client *client) { struct clk_si544 *data; struct clk_init_data init; @@ -385,7 +446,7 @@ static int si544_probe(struct i2c_client *client, init.num_parents = 0; data->hw.init = &init; data->i2c_client = client; - data->speed_grade = id->driver_data; + data->max_freq = (uintptr_t)i2c_get_match_data(client); if (of_property_read_string(client->dev.of_node, "clock-output-names", &init.name)) @@ -418,18 +479,18 @@ static int si544_probe(struct i2c_client *client, } static const struct i2c_device_id si544_id[] = { - { "si544a", si544a }, - { "si544b", si544b }, - { "si544c", si544c }, + { "si544a", 1500000000 }, + { "si544b", 800000000 }, + { "si544c", 350000000 }, { } }; MODULE_DEVICE_TABLE(i2c, si544_id); static const struct of_device_id clk_si544_of_match[] = { - { .compatible = "silabs,si544a" }, - { .compatible = "silabs,si544b" }, - { .compatible = "silabs,si544c" }, - { }, + { .compatible = "silabs,si544a", .data = (void *)1500000000 }, + { .compatible = "silabs,si544b", .data = (void *)800000000 }, + { .compatible = "silabs,si544c", .data = (void *)350000000 }, + { } }; MODULE_DEVICE_TABLE(of, clk_si544_of_match); |
