/* * AD7314 digital temperature sensor driver for AD7314, ADT7301 and ADT7302 * * Copyright 2010 Analog Devices Inc. * * Licensed under the GPL-2 or later. */ #include <linux/interrupt.h> #include <linux/gpio.h> #include <linux/workqueue.h> #include <linux/device.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/sysfs.h> #include <linux/list.h> #include <linux/spi/spi.h> #include <linux/rtc.h> #include "../iio.h" #include "../sysfs.h" /* * AD7314 power mode */ #define AD7314_PD 0x2000 /* * AD7314 temperature masks */ #define AD7314_TEMP_SIGN 0x200 #define AD7314_TEMP_MASK 0x7FE0 #define AD7314_TEMP_OFFSET 5 #define AD7314_TEMP_FLOAT_OFFSET 2 #define AD7314_TEMP_FLOAT_MASK 0x3 /* * ADT7301 and ADT7302 temperature masks */ #define ADT7301_TEMP_SIGN 0x2000 #define ADT7301_TEMP_MASK 0x2FFF #define ADT7301_TEMP_FLOAT_OFFSET 5 #define ADT7301_TEMP_FLOAT_MASK 0x1F /* * struct ad7314_chip_info - chip specifc information */ struct ad7314_chip_info { const char *name; struct spi_device *spi_dev; struct iio_dev *indio_dev; s64 last_timestamp; u8 mode; }; /* * ad7314 register access by SPI */ static int ad7314_spi_read(struct ad7314_chip_info *chip, u16 *data) { struct spi_device *spi_dev = chip->spi_dev; int ret = 0; u16 value; ret = spi_read(spi_dev, (u8 *)&value, sizeof(value)); if (ret < 0) { dev_err(&spi_dev->dev, "SPI read error\n"); return ret; } *data = be16_to_cpu((u16)value); return ret; } static int ad7314_spi_write(struct ad7314_chip_info *chip, u16 data) { struct spi_device *spi_dev = chip->spi_dev; int ret = 0; u16 value = cpu_to_be16(data); ret = spi_write(spi_dev, (u8 *)&value, sizeof(value)); if (ret < 0) dev_err(&spi_dev->dev, "SPI write error\n"); return ret; } static ssize_t ad7314_show_mode(struct device *dev, struct device_attribute *attr, char *buf) { struct iio_dev *dev_info = dev_get_drvdata(dev); struct ad7314_chip_info *chip = dev_info->dev_data; if (chip->mode) return sprintf(buf, "power-save\n"); else return sprintf(buf, "full\n"); } static ssize_t ad7314_store_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct iio_dev *dev_info = dev_get_drvdata(dev); struct ad7314_chip_info *chip = dev_info->dev_data; u16 mode = 0; int ret; if (!strcmp(buf, "full")) mode = AD7314_PD; ret = ad7314_spi_write(chip, mode); if (ret) return -EIO; chip->mode = mode; return len; } static IIO_DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, ad7314_show_mode, ad7314_store_mode, 0); static ssize_t ad7314_show_available_modes(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "full\npower-save\n"); } static IIO_DEVICE_ATTR(available_modes, S_IRUGO, ad7314_show_available_modes, NULL, 0); static ssize_t ad7314_show_temperature(struct device *dev, struct device_attribute *attr, char *buf) { struct iio_dev *dev_info = dev_get_drvdata(dev); struct ad7314_chip_info *chip = dev_info->dev_data; u16 data; char sign = ' '; int ret; if (chip->mode) { ret = ad7314_spi_write(chip, 0); if (ret) return -EIO; } ret = ad7314_spi_read(chip, &data); if (ret) return -EIO; if (chip->mode) ad7314_spi_write(chip, chip->mode); if (strcmp(chip->name, "ad7314")) { data = (data & AD7314_TEMP_MASK) >> AD7314_TEMP_OFFSET; if (data & AD7314_TEMP_SIGN) { data = (AD7314_TEMP_SIGN << 1) - data; sign = '-'; } return sprintf(buf, "%c%d.%.2d\n", sign, data >> AD7314_TEMP_FLOAT_OFFSET, (data & AD7314_TEMP_FLOAT_MASK) * 25); } else { data &= ADT7301_TEMP_MASK; if (data & ADT7301_TEMP_SIGN) { data = (ADT7301_TEMP_SIGN << 1) - data; sign = '-'; } return sprintf(buf, "%c%d.%.5d\n", sign, data >> ADT7301_TEMP_FLOAT_OFFSET, (data & ADT7301_TEMP_FLOAT_MASK) * 3125); } } static IIO_DEVICE_ATTR(temperature, S_IRUGO, ad7314_show_temperature, NULL, 0); static ssize_t ad7314_show_name(struct device *dev, struct device_attribute *attr, char *buf) { struct iio_dev *dev_info = dev_get_drvdata(dev); struct ad7314_chip_info *chip = dev_info->dev_data; return sprintf(buf, "%s\n", chip->name); } static IIO_DEVICE_ATTR(name, S_IRUGO, ad7314_show_name, NULL, 0); static struct attribute *ad7314_attributes[] = { &iio_dev_attr_available_modes.dev_attr.attr, &iio_dev_attr_mode.dev_attr.attr, &iio_dev_attr_temperature.dev_attr.attr, &iio_dev_attr_name.dev_attr.attr, NULL, }; static const struct attribute_group ad7314_attribute_group = { .attrs = ad7314_attributes, }; /* * device probe and remove */ static int __devinit ad7314_probe(struct spi_device *spi_dev) { struct ad7314_chip_info *chip; int ret = 0; chip = kzalloc(sizeof(struct ad7314_chip_info), GFP_KERNEL); if (chip == NULL) return -ENOMEM; /* this is only used for device removal purposes */ dev_set_drvdata(&spi_dev->dev, chip); chip->spi_dev = spi_dev; chip->name = spi_dev->modalias; chip->indio_dev = iio_allocate_device(); if (chip->indio_dev == NULL) { ret = -ENOMEM; goto error_free_chip; } chip->indio_dev->dev.parent = &spi_dev->dev; chip->indio_dev->attrs = &ad7314_attribute_group; chip->indio_dev->dev_data = (void *)chip; chip->indio_dev->driver_module = THIS_MODULE; ret = iio_device_register(chip->indio_dev); if (ret) goto error_free_dev; dev_info(&spi_dev->dev, "%s temperature sensor registered.\n", chip->name); return 0; error_free_dev: iio_free_device(chip->indio_dev); error_free_chip: kfree(chip); return ret; } static int __devexit ad7314_remove(struct spi_device *spi_dev) { struct ad7314_chip_info *chip = dev_get_drvdata(&spi_dev->dev); struct iio_dev *indio_dev = chip->indio_dev; dev_set_drvdata(&spi_dev->dev, NULL); if (spi_dev->irq) iio_unregister_interrupt_line(indio_dev, 0); iio_device_unregister(indio_dev); iio_free_device(chip->indio_dev); kfree(chip); return 0; } static const struct spi_device_id ad7314_id[] = { { "adt7301", 0 }, { "adt7302", 0 }, { "ad7314", 0 }, {} }; static struct spi_driver ad7314_driver = { .driver = { .name = "ad7314", .bus = &spi_bus_type, .owner = THIS_MODULE, }, .probe = ad7314_probe, .remove = __devexit_p(ad7314_remove), .id_table = ad7314_id, }; static __init int ad7314_init(void) { return spi_register_driver(&ad7314_driver); } static __exit void ad7314_exit(void) { spi_unregister_driver(&ad7314_driver); } MODULE_AUTHOR("Sonic Zhang <sonic.zhang@analog.com>"); MODULE_DESCRIPTION("Analog Devices AD7314, ADT7301 and ADT7302 digital" " temperature sensor driver"); MODULE_LICENSE("GPL v2"); module_init(ad7314_init); module_exit(ad7314_exit);