From 072de836f53be56a70cecf70b43ae43b7ce17376 Mon Sep 17 00:00:00 2001
From: hc <hc@nodka.com>
Date: Mon, 11 Dec 2023 10:08:36 +0000
Subject: [PATCH] mk-rootfs.sh

---
 kernel/drivers/clk/clk-si544.c |  106 +++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 94 insertions(+), 12 deletions(-)

diff --git a/kernel/drivers/clk/clk-si544.c b/kernel/drivers/clk/clk-si544.c
index 7d7055f..d9ec908 100644
--- a/kernel/drivers/clk/clk-si544.c
+++ b/kernel/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>
@@ -50,6 +51,11 @@
 /* Lowest frequency synthesizeable using only the HS divider */
 #define MIN_HSDIV_FREQ	(FVCO_MIN / HS_DIV_MAX)
 
+/* Range and interpretation of the adjustment value */
+#define DELTA_M_MAX	8161512
+#define DELTA_M_FRAC_NUM	19
+#define DELTA_M_FRAC_DEN	20000
+
 enum si544_speed_grade {
 	si544a,
 	si544b,
@@ -71,12 +77,14 @@
  * @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,7 +142,28 @@
 	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,
@@ -238,11 +267,15 @@
 	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;
@@ -259,6 +292,25 @@
 	do_div(vco, d);
 
 	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,
@@ -279,33 +331,60 @@
 		unsigned long *parent_rate)
 {
 	struct clk_si544 *data = to_clk_si544(hw);
-	struct clk_si544_muldiv settings;
-	int err;
 
 	if (!is_valid_frequency(data, rate))
 		return -EINVAL;
 
-	err = si544_calc_muldiv(&settings, rate);
-	if (err)
-		return err;
-
-	return si544_calc_rate(&settings);
+	/* The accuracy is less than 1 Hz, so any rate is possible */
+	return rate;
 }
 
-/*
- * Update output frequency for "big" frequency changes
- */
+/* 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);
+}
+
 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 +400,9 @@
 	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)
@@ -373,7 +455,7 @@
 		const struct i2c_device_id *id)
 {
 	struct clk_si544 *data;
-	struct clk_init_data init = {};
+	struct clk_init_data init;
 	int err;
 
 	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);

--
Gitblit v1.6.2