diff mbox

[V2,02/12] i2c: si570: merge support for si570 clock generator

Message ID 1389077290-13613-3-git-send-email-daniel.sangorrin@toshiba.co.jp (mailing list archive)
State New, archived
Headers show

Commit Message

Daniel Sangorrin Jan. 7, 2014, 6:48 a.m. UTC
From: Rob Armstrong <ra@xilinx.com>

This merges support for the si5790 clock generator from the Xilinx
repository (commit efc27505715e64526653f35274717c0fc56491e3 from
master branch).

Signed-off-by: Daniel Sangorrin <daniel.sangorrin@toshiba.co.jp>
Signed-off-by: Yoshitake Kobayashi <yoshitake.kobayashi@toshiba.co.jp>
---
 drivers/misc/Kconfig      |  10 +
 drivers/misc/Makefile     |   1 +
 drivers/misc/si570.c      | 575 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/i2c/si570.h |  31 +++
 4 files changed, 617 insertions(+)
 create mode 100644 drivers/misc/si570.c
 create mode 100644 include/linux/i2c/si570.h

--
1.8.5
diff mbox

Patch

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c002d86..1c9fd72 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -507,6 +507,16 @@  config USB_SWITCH_FSA9480
 	  stereo and mono audio, video, microphone and UART data to use
 	  a common connector port.

+config SI570
+	tristate "Silicon Labs Si570 Clock Generator"
+	depends on I2C && SYSFS
+	help
+	  If you say yes here you get support for the Silicon Labs Si570
+	  digital clock generator.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called si570
+
 config LATTICE_ECP3_CONFIG
 	tristate "Lattice ECP3 FPGA bitstream configuration via SPI"
 	depends on SPI && SYSFS
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index c235d5b..e84bcaa 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -50,6 +50,7 @@  obj-y				+= carma/
 obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o
 obj-$(CONFIG_ALTERA_STAPL)	+=altera-stapl/
 obj-$(CONFIG_INTEL_MEI)		+= mei/
+obj-$(CONFIG_SI570)		+= si570.o
 obj-$(CONFIG_VMWARE_VMCI)	+= vmw_vmci/
 obj-$(CONFIG_LATTICE_ECP3_CONFIG)	+= lattice-ecp3-config.o
 obj-$(CONFIG_SRAM)		+= sram.o
diff --git a/drivers/misc/si570.c b/drivers/misc/si570.c
new file mode 100644
index 0000000..3568ac0
--- /dev/null
+++ b/drivers/misc/si570.c
@@ -0,0 +1,575 @@ 
+/*
+ * Driver for Silicon Labs Si570/Si571 Programmable XO/VCXO
+ *
+ * Copyright (C) 2010, 2011 Ericsson AB.
+ * Copyright (C) 2011 Guenter Roeck.
+ *
+ * Author: Guenter Roeck <guenter.roeck@ericsson.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/log2.h>
+#include <linux/slab.h>
+#include <linux/i2c/si570.h>
+
+/* Si570 registers */
+#define SI570_REG_HS_N1		7
+#define SI570_REG_N1_RFREQ0	8
+#define SI570_REG_RFREQ1	9
+#define SI570_REG_RFREQ2	10
+#define SI570_REG_RFREQ3	11
+#define SI570_REG_RFREQ4	12
+#define SI570_REG_CONTROL	135
+#define SI570_REG_FREEZE_DCO	137
+
+#define HS_DIV_SHIFT		5
+#define HS_DIV_MASK		0xe0
+#define HS_DIV_OFFSET		4
+#define N1_6_2_MASK		0x1f
+#define N1_1_0_MASK		0xc0
+#define RFREQ_37_32_MASK	0x3f
+
+#define SI570_FOUT_FACTORY_DFLT	156250000LL
+#define SI598_FOUT_FACTORY_DFLT	10000000LL
+
+#define SI570_MIN_FREQ		10000000L
+#define SI570_MAX_FREQ		1417500000L
+#define SI598_MAX_FREQ		525000000L
+
+#define FDCO_MIN		4850000000LL
+#define FDCO_MAX		5670000000LL
+#define FDCO_CENTER		((FDCO_MIN + FDCO_MAX) / 2)
+
+#define SI570_CNTRL_RECALL	(1 << 0)
+#define SI570_CNTRL_FREEZE_ADC	(1 << 4)
+#define SI570_CNTRL_FREEZE_M	(1 << 5)
+#define SI570_CNTRL_NEWFREQ	(1 << 6)
+#define SI570_CNTRL_RESET	(1 << 7)
+
+#define SI570_FREEZE_DCO	(1 << 4)
+#define SI570_UNFREEZE_DCO	0xEF
+
+struct si570_data {
+	struct attribute_group attrs;
+	struct mutex lock;
+	u64 max_freq;
+	u64 fout;		/* Factory default frequency */
+	u64 fxtal;		/* Factory xtal frequency */
+	unsigned int n1;
+	unsigned int hs_div;
+	u64 rfreq;
+	u64 frequency;
+};
+
+
+static struct i2c_client *si570_client;
+
+
+static int si570_get_defaults(struct i2c_client *client)
+{
+	struct si570_data *data = i2c_get_clientdata(client);
+	int reg1, reg2, reg3, reg4, reg5, reg6;
+	u64 fdco;
+
+	i2c_smbus_write_byte_data(client, SI570_REG_CONTROL,
+				  SI570_CNTRL_RECALL);
+
+	reg1 = i2c_smbus_read_byte_data(client, SI570_REG_HS_N1);
+	if (reg1 < 0)
+		return reg1;
+	reg2 = i2c_smbus_read_byte_data(client, SI570_REG_N1_RFREQ0);
+	if (reg2 < 0)
+		return reg2;
+	reg3 = i2c_smbus_read_byte_data(client, SI570_REG_RFREQ1);
+	if (reg3 < 0)
+		return reg3;
+	reg4 = i2c_smbus_read_byte_data(client, SI570_REG_RFREQ2);
+	if (reg4 < 0)
+		return reg4;
+	reg5 = i2c_smbus_read_byte_data(client, SI570_REG_RFREQ3);
+	if (reg5 < 0)
+		return reg5;
+	reg6 = i2c_smbus_read_byte_data(client, SI570_REG_RFREQ4);
+	if (reg6 < 0)
+		return reg6;
+
+	data->hs_div = ((reg1 & HS_DIV_MASK) >> HS_DIV_SHIFT) + HS_DIV_OFFSET;
+	data->n1 = ((reg1 & N1_6_2_MASK) << 2) + ((reg2 & N1_1_0_MASK) >> 6)
+	  + 1;
+	/* Handle invalid cases */
+	if (data->n1 > 1)
+		data->n1 &= ~1;
+
+	data->rfreq = reg2 & RFREQ_37_32_MASK;
+	data->rfreq = (data->rfreq << 8) + reg3;
+	data->rfreq = (data->rfreq << 8) + reg4;
+	data->rfreq = (data->rfreq << 8) + reg5;
+	data->rfreq = (data->rfreq << 8) + reg6;
+
+	/*
+	 * Accept optional precision loss to avoid arithmetic overflows.
+	 * Acceptable per Silicon Labs Application Note AN334.
+	 */
+	fdco = data->fout * data->n1 * data->hs_div;
+	if (fdco >= (1LL << 36))
+		data->fxtal = div64_u64((fdco << 24), (data->rfreq >> 4));
+	else
+		data->fxtal = div64_u64((fdco << 28), data->rfreq);
+
+	data->frequency = data->fout;
+
+	return 0;
+}
+
+/*
+ * Update rfreq registers
+ * This function must be called with update mutex lock held.
+ */
+static void si570_update_rfreq(struct i2c_client *client,
+			       struct si570_data *data)
+{
+	int status;
+	status = i2c_smbus_write_byte_data(client, SI570_REG_N1_RFREQ0,
+				  ((data->n1 - 1) << 6)
+				  | ((data->rfreq >> 32) & RFREQ_37_32_MASK));
+	if (status < 0)
+		dev_err(&client->dev,
+			"unable to write 0x%llX to REG_N1_RFREQ0: %d\n",
+			(((data->n1 - 1) << 6) | ((data->rfreq >> 32) &
+			RFREQ_37_32_MASK)) & 0xff, status);
+	status = i2c_smbus_write_byte_data(client, SI570_REG_RFREQ1,
+				  (data->rfreq >> 24) & 0xff);
+	if (status < 0)
+		dev_err(&client->dev,
+			"unable to write 0x%llX to REG_RFREQ1: %d\n",
+			(data->rfreq >> 24) & 0xff, status);
+	status = i2c_smbus_write_byte_data(client, SI570_REG_RFREQ2,
+				  (data->rfreq >> 16) & 0xff);
+	if (status < 0)
+		dev_err(&client->dev,
+			"unable to write 0x%llX to REG_RFREQ2: %d\n",
+			(data->rfreq >> 16) & 0xff, status);
+	status = i2c_smbus_write_byte_data(client, SI570_REG_RFREQ3,
+				  (data->rfreq >> 8) & 0xff);
+	if (status < 0)
+		dev_err(&client->dev,
+			"unable to write 0x%llX to REG_RFREQ3: %d\n",
+			(data->rfreq >> 8) & 0xff, status);
+	status = i2c_smbus_write_byte_data(client, SI570_REG_RFREQ4,
+				  data->rfreq & 0xff);
+	if (status < 0)
+		dev_err(&client->dev,
+			"unable to write 0x%llX to REG_RFREQ4: %d\n",
+			data->rfreq & 0xff, status);
+}
+
+/*
+ * Update si570 frequency for small frequency changes (< 3,500 ppm)
+ * This function must be called with update mutex lock held.
+ */
+static int si570_set_frequency_small(struct i2c_client *client,
+				     struct si570_data *data,
+				     unsigned long frequency)
+{
+	data->frequency = frequency;
+	/* This is a re-implementation of DIV_ROUND_CLOSEST
+	 * using the div64_u64 function lieu of letting the compiler
+	 * insert EABI calls
+	 */
+	data->rfreq = div64_u64((data->rfreq * frequency) +
+		div64_u64(data->frequency, 2), data->frequency);
+	i2c_smbus_write_byte_data(client, SI570_REG_CONTROL,
+				  SI570_CNTRL_FREEZE_M);
+	si570_update_rfreq(client, data);
+	i2c_smbus_write_byte_data(client, SI570_REG_CONTROL, 0);
+
+	return 0;
+}
+
+static const uint8_t si570_hs_div_values[] = { 11, 9, 7, 6, 5, 4 };
+
+/*
+ * Set si570 frequency.
+ * This function must be called with update mutex lock held.
+ */
+static int si570_set_frequency(struct i2c_client *client,
+			       struct si570_data *data,
+			       unsigned long frequency)
+{
+	int i, n1, hs_div;
+	u64 fdco, best_fdco = ULLONG_MAX;
+
+	for (i = 0; i < ARRAY_SIZE(si570_hs_div_values); i++) {
+		hs_div = si570_hs_div_values[i];
+		/* Calculate lowest possible value for n1 */
+		n1 = div64_u64(div64_u64(FDCO_MIN, (u64)hs_div),
+			(u64)frequency);
+		if (!n1 || (n1 & 1))
+			n1++;
+		while (n1 <= 128) {
+			fdco = (u64)frequency * (u64)hs_div * (u64)n1;
+			if (fdco > FDCO_MAX)
+				break;
+			if (fdco >= FDCO_MIN && fdco < best_fdco) {
+				data->n1 = n1;
+				data->hs_div = hs_div;
+				data->frequency = frequency;
+				data->rfreq = div64_u64((fdco << 28),
+					data->fxtal);
+				best_fdco = fdco;
+			}
+			n1 += (n1 == 1 ? 1 : 2);
+		}
+	}
+	if (best_fdco == ULLONG_MAX) {
+		dev_err(&client->dev, "error - best FDCO is out of range\n");
+		return -EINVAL;
+	}
+
+	/* The DCO reg should be accessed with a read-modify-write operation
+	 * per AN334
+	 */
+	i2c_smbus_write_byte_data(client, SI570_REG_FREEZE_DCO,
+				  SI570_FREEZE_DCO);
+	i2c_smbus_write_byte_data(client, SI570_REG_HS_N1,
+				  ((data->hs_div - HS_DIV_OFFSET) <<
+				   HS_DIV_SHIFT)
+				  | (((data->n1 - 1) >> 2) & N1_6_2_MASK));
+	si570_update_rfreq(client, data);
+	i2c_smbus_write_byte_data(client, SI570_REG_FREEZE_DCO,
+				  0);
+	i2c_smbus_write_byte_data(client, SI570_REG_CONTROL,
+				  SI570_CNTRL_NEWFREQ);
+	return 0;
+}
+
+/*
+ * Reset chip.
+ * This function must be called with update mutex lock held.
+ */
+static int si570_reset(struct i2c_client *client, struct si570_data *data)
+{
+	i2c_smbus_write_byte_data(client, SI570_REG_CONTROL,
+				  SI570_CNTRL_RESET);
+	usleep_range(1000, 5000);
+	return si570_set_frequency(client, data, data->frequency);
+}
+
+static ssize_t show_frequency_attr(struct device *dev,
+			      struct device_attribute *devattr,
+			      char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct si570_data *data = i2c_get_clientdata(client);
+
+	return sprintf(buf, "%llu\n", data->frequency);
+}
+
+int get_frequency_si570(struct device *dev, unsigned long *freq)
+{
+	int err;
+	char buf[10+1];
+
+	if ((!dev) || (to_i2c_client(dev) != si570_client))
+		return -EINVAL;
+
+	show_frequency_attr(dev, NULL, buf);
+
+	err = kstrtoul(buf, 10, freq);
+	if (err)
+		return err;
+
+	return 0;
+}
+EXPORT_SYMBOL(get_frequency_si570);
+
+static ssize_t set_frequency_attr(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct si570_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err)
+		return err;
+
+	if (val < SI570_MIN_FREQ || val > data->max_freq) {
+		dev_err(&client->dev,
+			"requested frequency %lu Hz is out of range\n", val);
+		return -EINVAL;
+	}
+
+	mutex_lock(&data->lock);
+
+	if (div64_u64(abs(val - data->frequency) * 10000LL,
+		data->frequency) < 35)
+		err = si570_set_frequency_small(client, data, val);
+	else
+		err = si570_set_frequency(client, data, val);
+	mutex_unlock(&data->lock);
+	if (err) {
+		dev_warn(&client->dev,
+			"unable to set output frequency %lu Hz: %d\n",
+			val, err);
+		return err;
+	}
+
+	dev_info(&client->dev,
+		"set new output frequency %lu Hz\n", val);
+
+	return count;
+}
+
+int set_frequency_si570(struct device *dev, unsigned long freq)
+{
+	char buf[10+1];
+
+	if ((!dev) || (to_i2c_client(dev) != si570_client))
+		return -EINVAL;
+
+	sprintf(buf, "%lu", freq);
+
+	return set_frequency_attr(dev, NULL, buf,  0);
+}
+EXPORT_SYMBOL(set_frequency_si570);
+
+static ssize_t show_reset_attr(struct device *dev,
+			  struct device_attribute *devattr,
+			  char *buf)
+{
+	return sprintf(buf, "%d\n", 0);
+}
+
+static ssize_t set_reset_attr(struct device *dev,
+			 struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct si570_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err)
+		return err;
+	if (val == 0)
+		goto done;
+
+	mutex_lock(&data->lock);
+	err = si570_reset(client, data);
+	mutex_unlock(&data->lock);
+	if (err)
+		return err;
+done:
+	return count;
+}
+
+int reset_si570(struct device *dev, int id)
+{
+	char buf[4];
+
+	if ((!dev) || (to_i2c_client(dev) != si570_client))
+		return -EINVAL;
+
+	sprintf(buf, "%lu", (unsigned long)id);
+	return set_reset_attr(dev, NULL, buf, 0);
+}
+EXPORT_SYMBOL(reset_si570);
+
+struct i2c_client *get_i2c_client_si570(void)
+{
+	return si570_client;
+}
+EXPORT_SYMBOL(get_i2c_client_si570);
+
+static DEVICE_ATTR(frequency, S_IWUSR | S_IRUGO, show_frequency_attr,
+		   set_frequency_attr);
+static DEVICE_ATTR(reset, S_IWUSR | S_IRUGO, show_reset_attr, set_reset_attr);
+
+static struct attribute *si570_attr[] = {
+	&dev_attr_frequency.attr,
+	&dev_attr_reset.attr,
+	NULL
+};
+
+static const struct i2c_device_id si570_id[] = {
+	{ "si570", 0 },
+	{ "si571", 0 },
+	{ "si598", 1 },
+	{ "si599", 1 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si570_id);
+
+static int si570_probe(struct i2c_client *client,
+		       const struct i2c_device_id *id)
+{
+	struct si570_platform_data *pdata = client->dev.platform_data;
+	struct si570_data *data;
+	int err;
+	unsigned long initial_fout;
+	u32 tmp = SI570_FOUT_FACTORY_DFLT;
+
+	data = kzalloc(sizeof(struct si570_data), GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	if (id->driver_data) {
+		data->fout = SI598_FOUT_FACTORY_DFLT;
+		data->max_freq = SI598_MAX_FREQ;
+	} else {
+		data->fout = SI570_FOUT_FACTORY_DFLT;
+		data->max_freq = SI570_MAX_FREQ;
+	}
+
+	if (pdata && pdata->factory_fout)
+		data->fout = pdata->factory_fout;
+
+	if (client->dev.of_node &&
+		(of_property_read_u32(client->dev.of_node, "factory-fout",
+			&tmp) < 0))
+		dev_warn(&client->dev,
+			"DTS does not contain factory-fout, using default\n");
+	else
+		data->fout = tmp;
+
+	i2c_set_clientdata(client, data);
+	err = si570_get_defaults(client);
+	if (err < 0)
+		goto exit_free;
+
+	mutex_init(&data->lock);
+
+	/* Register sysfs hooks */
+	data->attrs.attrs = si570_attr;
+	err = sysfs_create_group(&client->dev.kobj, &data->attrs);
+	if (err)
+		goto exit_free;
+
+	/* Display a message indicating that we've successfully registered */
+	dev_info(&client->dev,
+		"registered %s with default frequency %llu Hz\n",
+		id->name, data->fout);
+
+	/* Read the requested initial fout from either platform data or the
+	 * device tree
+	 */
+	initial_fout = 0;
+	if (pdata && pdata->initial_fout)
+		initial_fout = pdata->initial_fout;
+	if (client->dev.of_node) {
+		of_property_read_u32(client->dev.of_node, "initial-fout",
+			(u32 *)&initial_fout);
+		if (pdata && pdata->initial_fout &&
+			(pdata->initial_fout != initial_fout)) {
+			dev_warn(&client->dev,
+				"OF initial fout %lu overrides platform data fout %lu\n",
+				initial_fout,
+				pdata->initial_fout);
+		}
+	}
+
+	if (initial_fout != 0) {
+		if (initial_fout < SI570_MIN_FREQ ||
+			initial_fout > data->max_freq) {
+			dev_err(&client->dev,
+				"requested initial frequency %lu is out of range, using default\n",
+				initial_fout);
+			return 0;
+		}
+
+		mutex_lock(&data->lock);
+
+		if (div64_u64(abs(initial_fout - data->frequency) *
+			10000LL, data->frequency) < 35)
+			err = si570_set_frequency_small(client, data,
+				initial_fout);
+		else
+			err = si570_set_frequency(client, data,
+				initial_fout);
+		mutex_unlock(&data->lock);
+		if (err) {
+			dev_warn(&client->dev,
+				"unable to set initial output frequency %lu: %d\n",
+				initial_fout, err);
+			return err;
+		}
+
+		dev_info(&client->dev,
+			"set initial output frequency %lu Hz\n",
+			initial_fout);
+	}
+
+	si570_client = client;
+
+	return 0;
+
+exit_free:
+	kfree(data);
+exit:
+	return err;
+}
+
+static int si570_remove(struct i2c_client *client)
+{
+	struct si570_data *data = i2c_get_clientdata(client);
+
+	sysfs_remove_group(&client->dev.kobj, &data->attrs);
+	kfree(data);
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id i2c_si570_of_match[] = {
+	{ .compatible = "si570" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, i2c_si570_of_match);
+#endif
+
+static struct i2c_driver si570_driver = {
+	.driver = {
+		.name	= "si570",
+		.of_match_table = of_match_ptr(i2c_si570_of_match),
+	},
+	.probe		= si570_probe,
+	.remove		= si570_remove,
+	.id_table	= si570_id,
+};
+
+static int __init si570_init(void)
+{
+	return i2c_add_driver(&si570_driver);
+}
+
+static void __exit si570_exit(void)
+{
+	i2c_del_driver(&si570_driver);
+}
+
+MODULE_AUTHOR("Guenter Roeck <guenter.roeck@ericsson.com>");
+MODULE_DESCRIPTION("Si570 driver");
+MODULE_LICENSE("GPL");
+
+module_init(si570_init);
+module_exit(si570_exit);
diff --git a/include/linux/i2c/si570.h b/include/linux/i2c/si570.h
new file mode 100644
index 0000000..f2cf285
--- /dev/null
+++ b/include/linux/i2c/si570.h
@@ -0,0 +1,31 @@ 
+/*
+ * si570.h - Configuration for si570 misc driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation (version 2 of the License only).
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LINUX_SI570_H
+#define __LINUX_SI570_H
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+
+struct si570_platform_data {
+	u64 factory_fout;		/* Factory default output frequency */
+	unsigned long initial_fout;	/* Requested initial frequency */
+};
+
+int get_frequency_si570(struct device *dev, unsigned long *freq);
+int set_frequency_si570(struct device *dev, unsigned long freq);
+int reset_si570(struct device *dev, int id);
+struct i2c_client *get_i2c_client_si570(void);
+
+#endif /* __LINUX_SI570_H */