On 04/19/11 22:36, Guenter Roeck wrote: > This driver adds support for Si570, Si571, Si598, and Si599 > programmable XO/VCXO. cc'd Michael Hennerich, as I would imagine this has some similarities to the DDS chips we have in IIO (be it a very simple one). Guenter, what is your use case for this part? > Signed-off-by: Guenter Roeck <guenter.roeck@xxxxxxxxxxxx> > --- > v2: > - Fixed checkpatch errors and warnings > - Moved si570.h platform data include file to include/linux/platform_data > - Documented reset sysfs attribute > - Added Documentation/ABI/testing/sysfs-bus-i2c-si570 > > Documentation/ABI/testing/sysfs-bus-i2c-si570 | 21 ++ > Documentation/misc-devices/si570 | 62 ++++ > MAINTAINERS | 7 + > drivers/misc/Kconfig | 10 + > drivers/misc/Makefile | 1 + > drivers/misc/si570.c | 398 +++++++++++++++++++++++++ > include/linux/platform_data/si570.h | 23 ++ > 7 files changed, 522 insertions(+), 0 deletions(-) > create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-si570 > create mode 100644 Documentation/misc-devices/si570 > create mode 100644 drivers/misc/si570.c > create mode 100644 include/linux/platform_data/si570.h > > diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-si570 b/Documentation/ABI/testing/sysfs-bus-i2c-si570 > new file mode 100644 > index 0000000..78c43f7 > --- /dev/null > +++ b/Documentation/ABI/testing/sysfs-bus-i2c-si570 > @@ -0,0 +1,21 @@ > +What: /sys/bus/i2c/devices/<busnum>-<devaddr>/frequency > +Date: April 2011 > +Kernel version: 2.6.40 ? > +Contact: Guenter Roeck <guenter.roeck@xxxxxxxxxxxx> > +Description: > + Read or write XO/VCXO frequency of the device in Hz. > + > + Accepted values: 10 MHz to 1400 MHz for Si570 and Si571, > + 10 MHz to 525 MHz for Si598 and Si599. > +Users: > + Userspace applications interested in knowing or changing the > + device frequency. > + > +What: /sys/bus/i2c/devices/<busnum>-<devaddr>/reset > +Date: April 2011 > +Kernel Version: 2.6.40 ? > +Contact: Guenter Roeck <guenter.roeck@xxxxxxxxxxxx> > +Description: Writing a value other than 0 resets the device. > + Reading always returns 0 and has no effect. > +Users: > + Userspace applications interested in resetting the device. > diff --git a/Documentation/misc-devices/si570 b/Documentation/misc-devices/si570 > new file mode 100644 > index 0000000..54e32f7 > --- /dev/null > +++ b/Documentation/misc-devices/si570 > @@ -0,0 +1,62 @@ > +Kernel driver si570 > +===================== > + > +Supported chips: > + * Silicon Labs Si570/Si571 > + Prefix: 'si570' > + Addresses scanned: None (see below) > + Datasheets: > + http://www.silabs.com/Support%20Documents/TechnicalDocs/si570.pdf > + * Silicon Labs Si598/Si599 > + Prefix: 'si598' > + Addresses scanned: None (see below) > + Datasheets: > + http://www.silabs.com/Support%20Documents/TechnicalDocs/si598-99.pdf > + > +Author: Guenter Roeck <guenter.roeck@xxxxxxxxxxxx> > + > + > +Description > +----------- > + > +The Si570/Si598 XO and Si571/Si599 VCXO provide a low-jitter clock at any > +frequency. > + > +The Si570/Si571 are user-programmable to any output frequency from 10 to 945 MHz > +and select frequencies to 1400 MHz with <1 ppb resolution. > + > +The Si598/Si599 are user programmable to any output frequency from 10 to 525 MHz > +with 28 parts per trillion (ppt) resolution. > + > +See the datasheets for more information. > + > + > +Sysfs entries > +------------- > + > +frequency - Selected frequency > +reset - Writing a value other than 0 resets the device > + > + > +General Remarks > +--------------- > + > +The chips support all valid 7-bit I2C addresses. I2C addresses are assigned > +during the ordering process. > + > +This driver does not auto-detect devices. You will have to instantiate the > +devices explicitly. Please see Documentation/i2c/instantiating-devices for > +details. > + > + > +Platform data > +------------- > + > +The devices can be provided with platform data to select the factory default > +output frequency. If platform data is not specified, the driver will assume a > +default factory output frequency of 125 MHz for Si570/Si571 and 10 MHz for > +Si598/Si599. > + > +struct si570_platform_data { > + unsigned long fout; /* Factory default output frequency */ > +}; > diff --git a/MAINTAINERS b/MAINTAINERS > index ec36003..4c339ea 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5406,6 +5406,13 @@ L: linux-serial@xxxxxxxxxxxxxxx > S: Maintained > F: drivers/tty/serial > > +SI570 DRIVER > +M: Guenter Roeck <guenter.roeck@xxxxxxxxxxxx> > +L: linux-kernel@xxxxxxxxxxxxxxx > +S: Maintained > +F: drivers/misc/si570.c > +F: include/linux/platform_data/si570.h > + > TIMEKEEPING, NTP > M: John Stultz <johnstul@xxxxxxxxxx> > M: Thomas Gleixner <tglx@xxxxxxxxxxxxx> > diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig > index 4e007c6..c7041c9 100644 > --- a/drivers/misc/Kconfig > +++ b/drivers/misc/Kconfig > @@ -475,6 +475,16 @@ config PCH_PHUB > To compile this driver as a module, choose M here: the module will > be called pch_phub. > > +config SI570 > + tristate "Silicon Labs Si570/Si571/Si598/Si599" > + depends on I2C && SYSFS && EXPERIMENTAL > + help > + This is a driver for Silicon Labs Si570/Si571/Si598/Si599 programmable > + XO/VCXO. > + > + To compile this driver as a module, choose M here: the module will > + be called si570. > + > source "drivers/misc/c2port/Kconfig" > source "drivers/misc/eeprom/Kconfig" > source "drivers/misc/cb710/Kconfig" > diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile > index f546860..a2397c6 100644 > --- a/drivers/misc/Makefile > +++ b/drivers/misc/Makefile > @@ -41,6 +41,7 @@ obj-$(CONFIG_SPEAR13XX_PCIE_GADGET) += spear13xx_pcie_gadget.o > obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o > obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o > obj-$(CONFIG_PCH_PHUB) += pch_phub.o > +obj-$(CONFIG_SI570) += si570.o > obj-y += ti-st/ > obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o > obj-y += lis3lv02d/ > diff --git a/drivers/misc/si570.c b/drivers/misc/si570.c > new file mode 100644 > index 0000000..69a3806 > --- /dev/null > +++ b/drivers/misc/si570.c > @@ -0,0 +1,398 @@ > +/* > + * Driver for Silicon Labs Si570/Si571 Programmable XO/VCXO > + * > + * Copyright (C) 2011 Ericsson AB. > + * > + * Author: Guenter Roeck <guenter.roeck@xxxxxxxxxxxx> > + * > + * 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/platform_data/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 125000000LL > +#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) > + > +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 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 = (fdco << 24) / (data->rfreq >> 4); > + else > + data->fxtal = (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) > +{ > + i2c_smbus_write_byte_data(client, SI570_REG_N1_RFREQ0, > + ((data->n1 - 1) << 6) > + | ((data->rfreq >> 32) & RFREQ_37_32_MASK)); > + i2c_smbus_write_byte_data(client, SI570_REG_RFREQ1, > + (data->rfreq >> 24) & 0xff); > + i2c_smbus_write_byte_data(client, SI570_REG_RFREQ2, > + (data->rfreq >> 16) & 0xff); > + i2c_smbus_write_byte_data(client, SI570_REG_RFREQ3, > + (data->rfreq >> 8) & 0xff); > + i2c_smbus_write_byte_data(client, SI570_REG_RFREQ4, > + data->rfreq & 0xff); > +} > + > +/* > + * 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; > + data->rfreq = DIV_ROUND_CLOSEST(data->rfreq * frequency, > + 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; > +} > + > +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 = 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 = (fdco << 28) / data->fxtal; > + best_fdco = fdco; > + } > + n1 += (n1 == 1 ? 1 : 2); > + } > + } > + if (best_fdco == ULLONG_MAX) > + return -EINVAL; > + > + 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(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); > +} > + > +static ssize_t set_frequency(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 = strict_strtoul(buf, 10, &val); > + if (err) > + return err; > + > + if (val < SI570_MIN_FREQ || val > data->max_freq) > + return -EINVAL; > + > + mutex_lock(&data->lock); > + if (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) > + return err; > + > + return count; > +} > +static ssize_t show_reset(struct device *dev, > + struct device_attribute *devattr, > + char *buf) > +{ > + return sprintf(buf, "%d\n", 0); > +} > + > +static ssize_t set_reset(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 = strict_strtoul(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; > +} > + > +static DEVICE_ATTR(frequency, S_IWUSR | S_IRUGO, show_frequency, set_frequency); > +static DEVICE_ATTR(reset, S_IWUSR | S_IRUGO, show_reset, set_reset); > + > +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; > + > + 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->fout) > + data->fout = pdata->fout; > + > + 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; > + > + 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; > +} > + > +static struct i2c_driver si570_driver = { > + .driver = { > + .name = "si570", > + }, > + .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@xxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("Si570 driver"); > +MODULE_LICENSE("GPL"); > + > +module_init(si570_init); > +module_exit(si570_exit); > diff --git a/include/linux/platform_data/si570.h b/include/linux/platform_data/si570.h > new file mode 100644 > index 0000000..22ec865 > --- /dev/null > +++ b/include/linux/platform_data/si570.h > @@ -0,0 +1,23 @@ > +/* > + * 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> > + > +struct si570_platform_data { > + unsigned long fout; /* Factory default output frequency */ > +}; > + > +#endif /* __LINUX_SI570_H */ -- To unsubscribe from this list: send the line "unsubscribe linux-i2c" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html