Commit c6c56bf2 authored by Andrey Filippov's avatar Andrey Filippov
Browse files

si5338.c: PLL and MS dividers setup

parent e818012c
Loading
Loading
Loading
Loading
+300 −34
Original line number Diff line number Diff line
Index: git/drivers/misc/Kconfig
===================================================================
--- git.orig/drivers/misc/Kconfig	2013-12-02 23:08:01.002329798 -0700
+++ git/drivers/misc/Kconfig	2013-12-02 23:08:03.994329829 -0700
--- git.orig/drivers/misc/Kconfig	2013-12-03 15:26:18.686938629 -0700
+++ git/drivers/misc/Kconfig	2013-12-03 15:26:21.590938659 -0700
@@ -549,6 +549,19 @@
 	  their requirements.
 
@@ -24,8 +24,8 @@ Index: git/drivers/misc/Kconfig
 source "drivers/misc/eeprom/Kconfig"
Index: git/drivers/misc/Makefile
===================================================================
--- git.orig/drivers/misc/Makefile	2013-12-02 23:08:01.002329798 -0700
+++ git/drivers/misc/Makefile	2013-12-02 23:08:03.994329829 -0700
--- git.orig/drivers/misc/Makefile	2013-12-03 15:26:18.686938629 -0700
+++ git/drivers/misc/Makefile	2013-12-03 15:26:21.590938659 -0700
@@ -55,3 +55,5 @@
 obj-$(CONFIG_LATTICE_ECP3_CONFIG)	+= lattice-ecp3-config.o
 obj-$(CONFIG_SRAM)		+= sram.o
@@ -35,7 +35,7 @@ Index: git/drivers/misc/Makefile
Index: git/drivers/misc/vsc330x.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ git/drivers/misc/vsc330x.c	2013-12-02 23:08:03.998329829 -0700
+++ git/drivers/misc/vsc330x.c	2013-12-03 15:26:21.594938659 -0700
@@ -0,0 +1,872 @@
+/*!***************************************************************************
+ *! FILE NAME  : vsc330x.c
@@ -912,8 +912,8 @@ Index: git/drivers/misc/vsc330x.c
Index: git/drivers/misc/si5338.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ git/drivers/misc/si5338.c	2013-12-02 23:17:35.422335756 -0700
@@ -0,0 +1,1493 @@
+++ git/drivers/misc/si5338.c	2013-12-03 22:08:01.215188627 -0700
@@ -0,0 +1,1759 @@
+/*!***************************************************************************
+ *! FILE NAME  : si5338.c
+ *! DESCRIPTION: control of the Silicon Laboratories SI5338 clock generator
@@ -986,6 +986,8 @@ Index: git/drivers/misc/si5338.c
+#define AWE_VCO_GAIN               0x3170
+#define AWE_RSEL                   0x310c
+#define AWE_BWSEL                  0x3103
+#define AWE_VCO_GAIN_RSEL_BWSEL    0x317f
+
+#define AWE_PLL_EN                 0x32c0
+#define AWE_MSCAL                  0x323f
+#define AWE_MS3_HS                 0x3380
@@ -1071,6 +1073,13 @@ Index: git/drivers/misc/si5338.c
+#define AWE_MSN_P3_23_16           0x69ff
+#define AWE_MSN_P3_29_24           0x6a3f
+
+#define AWE_MISC_47                0x2ffc /* write 0x5 */
+#define AWE_MISC_106               0x6a80 /* write 0x1 */
+#define AWE_MISC_116               0x7480 /* write 0x1 */
+#define AWE_MISC_42                0x2a20 /* write 0x1 */
+#define AWE_MISC_06A               0x06e0 /* write 0x0 */
+#define AWE_MISC_06B               0x0602 /* write 0x0 */
+#define AWE_MISC_28                0x1cc0 /* write 0x0 */
+
+static void si5338_init_of(struct i2c_client *client);
+
@@ -1142,9 +1151,25 @@ Index: git/drivers/misc/si5338.c
+static ssize_t pll_freq_int_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t pll_by_out_fract_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t pll_by_out_int_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t _pll_ms_freq_show(struct device *dev, struct device_attribute *attr, char *buf, int chn);
+static ssize_t _pll_ms_freq_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count,int int_div, int chn);
+static ssize_t pll_ms0_freq_show(struct device *dev, struct device_attribute *attr, char *buf);
+static ssize_t pll_ms1_freq_show(struct device *dev, struct device_attribute *attr, char *buf);
+static ssize_t pll_ms2_freq_show(struct device *dev, struct device_attribute *attr, char *buf);
+static ssize_t pll_ms3_freq_show(struct device *dev, struct device_attribute *attr, char *buf);
+static ssize_t pll_ms0_freq_fract_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t pll_ms1_freq_fract_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t pll_ms2_freq_fract_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t pll_ms3_freq_fract_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t pll_ms0_freq_int_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t pll_ms1_freq_int_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t pll_ms2_freq_int_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t pll_ms3_freq_int_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+
+
+/* -------------------------------------- */
+static int set_pll_paremeters(struct i2c_client *client);
+static int set_misc_registers(struct i2c_client *client);
+static int ms_to_p123(u64* ms,u32 * p123);
+static int p123_to_ms(u64* ms,u32 * p123);
+static int get_ms_p123(struct i2c_client *client,u32 * p123, int chn); /* chn 0,1,2,3,4 (4 - msn) */
@@ -1153,7 +1178,8 @@ Index: git/drivers/misc/si5338.c
+static int get_pll_freq(struct i2c_client *client,u64 * pll_freq);
+static int set_pll_freq_by_out(struct i2c_client *client, u64 *out_freq, int int_msn_div);
+
+
+static int get_pll_ms_freq(struct i2c_client *client, u64 *out_freq, int chn);
+static int set_pll_ms_by_out(struct i2c_client *client, u64 *out_freq, int chn, int int_div);
+static s64 get_pll_in_frequency(struct i2c_client *client);
+static int set_in_mux(struct i2c_client *client, int data);
+static int get_in_mux(struct i2c_client *client);
@@ -1273,12 +1299,30 @@ Index: git/drivers/misc/si5338.c
+static DEVICE_ATTR(pll_by_out_int,  SYSFS_PERMISSIONS,  pll_freq_show,  pll_by_out_int_store);
+
+
+static DEVICE_ATTR(pll_ms0_freq_fract,SYSFS_PERMISSIONS,  pll_ms0_freq_show,  pll_ms0_freq_fract_store);
+static DEVICE_ATTR(pll_ms0_freq_int,  SYSFS_PERMISSIONS,  pll_ms0_freq_show,  pll_ms0_freq_int_store);
+static DEVICE_ATTR(pll_ms1_freq_fract,SYSFS_PERMISSIONS,  pll_ms1_freq_show,  pll_ms1_freq_fract_store);
+static DEVICE_ATTR(pll_ms1_freq_int,  SYSFS_PERMISSIONS,  pll_ms1_freq_show,  pll_ms1_freq_int_store);
+static DEVICE_ATTR(pll_ms2_freq_fract,SYSFS_PERMISSIONS,  pll_ms2_freq_show,  pll_ms2_freq_fract_store);
+static DEVICE_ATTR(pll_ms2_freq_int,  SYSFS_PERMISSIONS,  pll_ms2_freq_show,  pll_ms2_freq_int_store);
+static DEVICE_ATTR(pll_ms3_freq_fract,SYSFS_PERMISSIONS,  pll_ms3_freq_show,  pll_ms3_freq_fract_store);
+static DEVICE_ATTR(pll_ms3_freq_int,  SYSFS_PERMISSIONS,  pll_ms3_freq_show,  pll_ms3_freq_int_store);
+
+
+static struct attribute *pll_dev_attrs[] = {
+	&dev_attr_pll_ref_frequency.attr,
+	&dev_attr_pll_freq_fract.attr,
+	&dev_attr_pll_freq_int.attr,
+	&dev_attr_pll_by_out_fract.attr,
+	&dev_attr_pll_by_out_int.attr,
+	&dev_attr_pll_ms0_freq_fract.attr,
+	&dev_attr_pll_ms0_freq_int.attr,
+	&dev_attr_pll_ms1_freq_fract.attr,
+	&dev_attr_pll_ms1_freq_int.attr,
+	&dev_attr_pll_ms2_freq_fract.attr,
+	&dev_attr_pll_ms2_freq_int.attr,
+	&dev_attr_pll_ms3_freq_fract.attr,
+	&dev_attr_pll_ms3_freq_int.attr,
+	NULL
+};
+
@@ -1439,22 +1483,7 @@ Index: git/drivers/misc/si5338.c
+	mutex_unlock(&clientdata->lock);
+    return count;
+}
+/*
+static ssize_t input_xtal_freq_show (struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client =       to_i2c_client(dev);
+	int data= read_field (client, AWE_XTAL_FREQ);
+    return sprintf(buf, "0x%02x\n",data);
+}
+static ssize_t input_xtal_freq_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client =       to_i2c_client(dev);
+	int data;
+    sscanf(buf, "%x", &data);
+    write_field (client, data, AWE_XTAL_FREQ);
+    return count;
+}
+*/
+
+static ssize_t input_xtal_freq_txt_show (struct device *dev, struct device_attribute *attr, char *buf)
+{
+	const char *txt[]= {"8MHz..11Mhz", "11MHz..19Mhz", "19MHz..26Mhz", "26MHz..30Mhz"};
@@ -1734,8 +1763,13 @@ Index: git/drivers/misc/si5338.c
+	}
+	if (by_out) {
+		if (((rc=set_pll_freq_by_out(client, freq, int_div)))<0) return rc;
+		if (((rc=set_pll_paremeters(client)))<0) return rc;
+		if (((rc=set_misc_registers(client)))<0) return rc;
+
+	} else {
+		if (((rc=set_pll_freq       (client, freq, int_div)))<0) return rc;
+		if (((rc=set_pll_paremeters(client)))<0) return rc;
+		if (((rc=set_misc_registers(client)))<0) return rc;
+	}
+    return count;
+}
@@ -1748,14 +1782,129 @@ Index: git/drivers/misc/si5338.c
+static ssize_t pll_by_out_int_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+	{return _pll_freq_store(dev, attr, buf, count, 1, 1);}
+
+static ssize_t _pll_ms_freq_show(struct device *dev, struct device_attribute *attr, char *buf, int chn)
+{
+	int rc;
+	u64 ms_freq[3];
+	struct i2c_client *client =       to_i2c_client(dev);
+
+	if (((rc=get_pll_ms_freq(client, ms_freq, chn)))<0) return rc;
+    return sprintf(buf, "%lld %lld %lld\n",ms_freq[0],ms_freq[1],ms_freq[2]);
+}
+static ssize_t _pll_ms_freq_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count,int int_div, int chn)
+{
+	struct i2c_client *client =       to_i2c_client(dev);
+	int rc;
+	u64 freq[3];
+	int num_items;
+	num_items=sscanf(buf, "%lld %lld %lld", &freq[0], &freq[1], &freq[2]);
+	if (num_items<3){
+		freq[1]=0;
+		freq[2]=1;
+	}
+	if (((rc=set_pll_ms_by_out(client, freq, chn, int_div)))<0) return rc;
+    return count;
+}
+static ssize_t pll_ms0_freq_show(struct device *dev, struct device_attribute *attr, char *buf)
+{return _pll_ms_freq_show(dev, attr, buf, 0);}
+static ssize_t pll_ms1_freq_show(struct device *dev, struct device_attribute *attr, char *buf)
+{return _pll_ms_freq_show(dev, attr, buf, 1);}
+static ssize_t pll_ms2_freq_show(struct device *dev, struct device_attribute *attr, char *buf)
+{return _pll_ms_freq_show(dev, attr, buf, 2);}
+static ssize_t pll_ms3_freq_show(struct device *dev, struct device_attribute *attr, char *buf)
+{return _pll_ms_freq_show(dev, attr, buf, 3);}
+static ssize_t pll_ms0_freq_fract_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{return _pll_ms_freq_store(dev, attr, buf, count, 0, 0);}
+static ssize_t pll_ms1_freq_fract_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{return _pll_ms_freq_store(dev, attr, buf, count, 0, 1);}
+static ssize_t pll_ms2_freq_fract_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{return _pll_ms_freq_store(dev, attr, buf, count, 0, 2);}
+static ssize_t pll_ms3_freq_fract_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{return _pll_ms_freq_store(dev, attr, buf, count, 0, 3);}
+static ssize_t pll_ms0_freq_int_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{return _pll_ms_freq_store(dev, attr, buf, count, 1, 0);}
+static ssize_t pll_ms1_freq_int_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{return _pll_ms_freq_store(dev, attr, buf, count, 1, 1);}
+static ssize_t pll_ms2_freq_int_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{return _pll_ms_freq_store(dev, attr, buf, count, 1, 2);}
+static ssize_t pll_ms3_freq_int_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{return _pll_ms_freq_store(dev, attr, buf, count, 1, 3);}
+
+
+/* -----------PLL section--------------------------- */
+/* Setting PLL frequency in 3 ways:
+ * 1 - specified directly, allow fractional MSN
+ * 2 - specified directly, integer MSN
+ * 3 - specified by output frequency, integer MSN
+ */
+static int set_pll_paremeters(struct i2c_client *client)
+{
+	int rc;
+	s64 pll_in_freq;
+	u64 pll_out_freq[3];
+	s64 K,Q,kphi_nom,kphi_denom,fvco_mhz, fpfd_mhz;
+	int rsel,bwsel,vco_gain,pll_kphi,mscal,ms_pec;
+	if (((pll_in_freq=get_pll_in_frequency(client)))<0) return (int) pll_in_freq;
+	if (((rc=get_pll_freq(client,pll_out_freq)))<0) return rc;
+	fpfd_mhz = div64_u64(pll_in_freq,1000000ll);
+	fvco_mhz = div64_u64(pll_out_freq[0],1000000ll);
+	if (fpfd_mhz>=15){
+		K=925;
+		rsel=0;
+		bwsel=0;
+	} else if (fpfd_mhz>=8){
+		K=325;
+		rsel=1;
+		bwsel=1;
+	} else {
+		K=185;
+		rsel=3;
+		bwsel=2;
+	}
+	if (fvco_mhz>2425){
+		Q=3;
+		vco_gain=0;
+	} else {
+		Q=4;
+		vco_gain=1;
+	}
+	kphi_nom=  K*2500LL*2500LL*2500LL;
+	kphi_denom=533LL*Q*fpfd_mhz*fvco_mhz*fvco_mhz;
+	pll_kphi=(int) div64_u64(kphi_nom + (kphi_denom>>1),kphi_denom);
+	if ((pll_kphi<1) || (pll_kphi>127)) {
+		dev_err(&client->dev, "Calculated PLL_KPHI does not fit 1<=%d<=127\n",pll_kphi);
+		if (pll_kphi<1) pll_kphi=1;
+		else if (pll_kphi>127) pll_kphi=127;
+	}
+	mscal = (int) div64_u64(2067000-667*fvco_mhz+50000,100000ll);
+	if ((mscal<0) || (mscal>63)) {
+		dev_err(&client->dev, "Calculated MSCAL does not fit 0<=%d<=63\n",mscal);
+		if (mscal<0) mscal=0;
+		else if (mscal>63) mscal=63;
+	}
+	ms_pec = 7;
+	dev_dbg(&client->dev, "Calculated values: PLL_KPHI=%d K=%lld RSEL=%d BWSEL=%d VCO_GAIN=%d MSCAL=%d MS_PEC=%d\n",
+			pll_kphi, K, rsel, bwsel, vco_gain, mscal, ms_pec);
+	/* setting actual registers */
+	if (((rc=write_field(client, (u8) pll_kphi, AWE_PLL_KPHI)))<0) return rc;
+	if (((rc=write_field(client, (u8) (((vco_gain & 7)<<4) | ((rsel & 3)<<2) | (bwsel & 3)),
+			AWE_VCO_GAIN_RSEL_BWSEL)))<0) return rc;
+	if (((rc=write_field(client, (u8) mscal,  AWE_MSCAL )))<0) return rc;
+	if (((rc=write_field(client, (u8) ms_pec, AWE_MS_PEC)))<0) return rc;
+	return 0;
+}
+
+static int set_misc_registers(struct i2c_client *client)
+{
+	/* ST52238 Reference Manual R1.2 p.28 */
+	int rc;
+	if (((rc=write_field(client, 0x5,  AWE_MISC_47 )))<0) return rc;
+	if (((rc=write_field(client, 0x1,  AWE_MISC_106 )))<0) return rc;
+	if (((rc=write_field(client, 0x1,  AWE_MISC_116 )))<0) return rc;
+	if (((rc=write_field(client, 0x1,  AWE_MISC_42 )))<0) return rc;
+	if (((rc=write_field(client, 0x0,  AWE_MISC_06A )))<0) return rc;
+	if (((rc=write_field(client, 0x0,  AWE_MISC_06B )))<0) return rc;
+	if (((rc=write_field(client, 0x0,  AWE_MISC_28 )))<0) return rc;
+	return 0;
+}
+
+
+/* -----------MultiSynth section--------------------------- */
+static u32 awe_msx[5][3][5]=
+	{{{AWE_MS0_P1_07_00, AWE_MS0_P1_15_08, AWE_MS0_P1_17_16, 0, 0},
+	  {AWE_MS0_P2_05_00, AWE_MS0_P2_13_06, AWE_MS0_P2_21_14, AWE_MS0_P2_29_22, 0},
@@ -1824,6 +1973,10 @@ Index: git/drivers/misc/si5338.c
+{
+	int i;
+	s64 rc;
+	if ((chn<0) || (chn>4)){
+		dev_err(&client->dev, "Invalid channel %d. Only 0,1,2,3 and 4 (for MSN) are supported\n",chn);
+		return - EINVAL;
+	}
+	for (i=0;i<3;i++){
+		if (((rc=read_multireg64 (client,  awe_msx[chn][i])))<0) return (int) rc;
+		p123[i]= (u32) rc;
@@ -1833,12 +1986,22 @@ Index: git/drivers/misc/si5338.c
+static int set_ms_p123(struct i2c_client *client,u32 * p123, int chn) /* chn 0,1,2,3,4 (4 - msn) */
+{
+	int i,rc;
+	if ((chn<0) || (chn>4)){
+		dev_err(&client->dev, "Invalid channel %d. Only 0,1,2,3 and 4 (for MSN) are supported\n",chn);
+		return - EINVAL;
+	}
+	for (i=0;i<3;i++){
+		if (((rc=write_multireg64(client, (u64) p123[i], awe_msx[chn][i])))<0) return rc;
+	}
+	return 0;
+}
+
+/* Setting PLL frequency in 3 ways:
+ * 1 - specified directly, allow fractional MSN
+ * 2 - specified directly, integer MSN
+ * 3 - specified by output frequency, allow fractional MSN (use PPL frequency closest to the middle)
+ * 4 - specified by output frequency, integer MSN
+ */
+static int set_pll_freq(struct i2c_client *client, u64 *vco_freq, int int_div)
+{
+	s64 pll_in_freq, pll_in_freq_scaled,pll_out_freq_scaled,d;
@@ -1892,7 +2055,7 @@ Index: git/drivers/misc/si5338.c
+	ms_to_p123(msn, msn_p123);
+	return set_ms_p123(client,msn_p123, 4); // MSN
+}
+
+/* normalizes output */
+static int get_pll_freq(struct i2c_client *client,u64 * pll_freq)
+{
+	int rc;
@@ -1924,15 +2087,20 @@ Index: git/drivers/misc/si5338.c
+{
+	/* use r-divider if the output frequency is too low (less than 5 MHz) */
+	u64 out_int=out_freq[0],out_nom=out_freq[1],out_denom=out_freq[2],
+			pll_out_freq[3], scaled_max,scaled_min;
+			pll_out_freq[3], scaled_max,scaled_min,d;
+	s64 pll_freq_scaled, out_freq_scaled,err,best_err=-1,out_div,pll_in_freq,in_div,best_in_div,
+			pll_in_freq_scaled,synth_out_scaled;
+	int r_div=1;
+	if (out_denom<out_nom){
+		dev_err(&client->dev, "Nominator (%lld) should be less than denominator (%lld)\n",
+				out_nom,out_denom);
+	if (out_denom==0){
+		dev_err(&client->dev, "Denominator should not be 0 in %lld+%lld/%lld\n",
+				out_int,out_nom,out_denom);
+		return -EINVAL;
+	}
+	if (out_nom>=out_denom){ /* normalize */
+		d=div64_u64(out_nom,out_denom);
+		out_int+=d;
+		out_nom-=d*out_denom;
+	}
+	if (out_int<(FVCOMAX/MSINT_MAX)){
+		while ((r_div < 32) && (out_int<(FVCOMAX/MSINT_MAX))){
+			out_int<<=1;
@@ -2014,8 +2182,106 @@ Index: git/drivers/misc/si5338.c
+		return set_pll_freq(client, pll_out_freq, 0); /* integer result */
+	}
+}
+static int get_pll_ms_freq(struct i2c_client *client, u64 *out_freq, int chn)
+{
+	int rc;
+	u64 pll_out_freq[3], ms[3], pll_freq_scaled, ms_scaled;
+	u32 p123[3];
+	if (((rc=get_pll_freq(client,pll_out_freq)))<0) return rc;
+	/* trim PLL frequency fraction */
+	while (pll_out_freq[2]>=0x1000){
+		pll_out_freq[1] >>= 1;
+		pll_out_freq[2] >>= 1;
+	}
+	pll_freq_scaled=pll_out_freq[0]*pll_out_freq[2]+pll_out_freq[1];
+
+	if (((rc=get_ms_p123(client,p123, chn)))<0) return rc; /* includes invalid chn */
+	p123_to_ms(ms,p123);
+	/* trim MS divisor fraction */
+	while (ms[2]>=0x1000){
+		ms[1] >>= 1;
+		ms[2] >>= 1;
+	}
+	ms_scaled=ms[0]*ms[2]+ms[1];
+	out_freq[1]=pll_freq_scaled*ms[2];
+	out_freq[2]=ms[0]*pll_out_freq[2];
+	out_freq[0]=div64_u64(out_freq[1],out_freq[2]);
+	out_freq[1]-=out_freq[0]*out_freq[2];
+	dev_dbg(&client->dev, "MS%d output frequency: %llu+%llu/%llu Hz\n",chn,out_freq[0],out_freq[1],out_freq[2]);
+	return 0;
+}
+
+/*
+ *  Adjust MultiSynth divisor (MS0..MS3) for specified output frequency
+ *  MSN, input frequency should be already set
+ *  out_freq as int,nom,denom
+ */
+static int set_pll_ms_by_out(struct i2c_client *client, u64 *out_freq, int chn, int int_div)
+{
+	/* use r-divider if the output frequency is too low (less than 5 MHz) */
+	u64 out_int=out_freq[0],out_nom=out_freq[1],out_denom=out_freq[2],
+			pll_out_freq[3],d;
+	s64 pll_freq_scaled, out_freq_scaled;
+	u64 ms[3];
+	u32 p123[3];
+	int r_div=1,rc;
+	if (out_denom==0){
+		dev_err(&client->dev, "Denominator should not be 0 in %lld+%lld/%lld\n",
+				out_int,out_nom,out_denom);
+		return -EINVAL;
+	}
+	if (out_nom>=out_denom){ /* normalize */
+		d=div64_u64(out_nom,out_denom);
+		out_int+=d;
+		out_nom-=d*out_denom;
+	}
+	if (out_int<(FVCOMAX/MSINT_MAX)){
+		while ((r_div < 32) && (out_int<(FVCOMAX/MSINT_MAX))){
+			out_int<<=1;
+			out_nom<<=1;
+			r_div<<=1;
+			if (out_nom>out_denom) {
+				out_int++;
+				out_nom-=out_denom;
+			}
+
+		}
+		if (out_int<(FVCOMAX/MSINT_MAX)){
+			dev_err(&client->dev, "Specified output frequency is too low: %lld < %lld\n",
+					out_freq[0], FVCOMAX/MSINT_MAX/32);
+			return -EINVAL;
+		}
+	}
+	dev_dbg(&client->dev, "Output divider by %u, Output frequency before divider: %llu+%llu/%llu Hz\n",
+			r_div,out_int, out_nom,out_denom);
+	/* trim output frequency fraction */
+	while (out_denom>=0x1000){
+		out_nom >>= 1;
+		out_denom >>= 1;
+	}
+	out_freq_scaled=out_int*out_denom+out_nom;
+	if (((rc=get_pll_freq(client,pll_out_freq)))<0) return rc;
+	/* trim PLL frequency fraction */
+	while (pll_out_freq[2]>=0x1000){
+		pll_out_freq[1] >>= 1;
+		pll_out_freq[2] >>= 1;
+	}
+	pll_freq_scaled=pll_out_freq[0]*pll_out_freq[2]+pll_out_freq[1];
+	ms[1]=pll_freq_scaled*out_denom;
+	ms[2]=out_freq_scaled*pll_out_freq[2];
+	ms[0]=div64_u64(ms[1],ms[2]);
+	ms[1]-=ms[0]*ms[2];
+	if (int_div){
+		if (ms[1]>(ms[2]>>1)) ms[0]++;
+		ms[1]=0;
+		ms[2]=1;
+	}
+	dev_dbg(&client->dev, "MS%d divider: %llu+%llu/%llu\n",chn,ms[0],ms[1],ms[2]);
+	/* set up registers */
+	ms_to_p123(ms,p123);
+	if (((rc=set_ms_p123(client,p123, chn)))<0) return rc;
+	return 0;
+}
+
+/* ----------- Input section ----------------- */
+