diff options
Diffstat (limited to 'drivers/staging/adis16255/adis16255.c')
-rw-r--r-- | drivers/staging/adis16255/adis16255.c | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/drivers/staging/adis16255/adis16255.c b/drivers/staging/adis16255/adis16255.c new file mode 100644 index 000000000000..1ba11f00b2e7 --- /dev/null +++ b/drivers/staging/adis16255/adis16255.c @@ -0,0 +1,466 @@ +/* + * Analog Devices ADIS16250/ADIS16255 Low Power Gyroscope + * + * Written by: Matthias Brugger <m_brugger@web.de> + * + * Copyright (C) 2010 Fraunhofer Institute for Integrated Circuits + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * The driver just has a bare interface to the sysfs (sample rate in Hz, + * orientation (x, y, z) and gyroscope data in °/sec. + * + * It should be added to iio subsystem when this has left staging. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/list.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +#include <linux/interrupt.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/delay.h> + +#include <linux/gpio.h> + +#include <linux/spi/spi.h> +#include <linux/workqueue.h> + +#include "adis16255.h" + +#define ADIS_STATUS 0x3d +#define ADIS_SMPL_PRD_MSB 0x37 +#define ADIS_SMPL_PRD_LSB 0x36 +#define ADIS_MSC_CTRL_MSB 0x35 +#define ADIS_MSC_CTRL_LSB 0x34 +#define ADIS_GPIO_CTRL 0x33 +#define ADIS_ALM_SMPL1 0x25 +#define ADIS_ALM_MAG1 0x21 +#define ADIS_GYRO_SCALE 0x17 +#define ADIS_GYRO_OUT 0x05 +#define ADIS_SUPPLY_OUT 0x03 +#define ADIS_ENDURANCE 0x01 + +/* + * data structure for every sensor + * + * @dev: Driver model representation of the device. + * @spi: Pointer to the spi device which will manage i/o to spi bus. + * @data: Last read data from device. + * @irq_adis: GPIO Number of IRQ signal + * @irq: irq line manage by kernel + * @negative: indicates if sensor is upside down (negative == 1) + * @direction: indicates axis (x, y, z) the sensor is meassuring + */ +struct spi_adis16255_data { + struct device dev; + struct spi_device *spi; + s16 data; + int irq; + u8 negative; + char direction; +}; + +/*-------------------------------------------------------------------------*/ + +static int spi_adis16255_read_data(struct spi_adis16255_data *spiadis, + u8 adr, + u8 *rbuf) +{ + struct spi_device *spi = spiadis->spi; + struct spi_message msg; + struct spi_transfer xfer1, xfer2; + u8 *buf, *rx; + int ret; + + buf = kzalloc(4, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + rx = kzalloc(4, GFP_KERNEL); + if (rx == NULL) { + ret = -ENOMEM; + goto err_buf; + } + + buf[0] = adr; + + spi_message_init(&msg); + memset(&xfer1, 0, sizeof(xfer1)); + memset(&xfer2, 0, sizeof(xfer2)); + + xfer1.tx_buf = buf; + xfer1.rx_buf = buf + 2; + xfer1.len = 2; + xfer1.delay_usecs = 9; + + xfer2.tx_buf = rx + 2; + xfer2.rx_buf = rx; + xfer2.len = 2; + + spi_message_add_tail(&xfer1, &msg); + spi_message_add_tail(&xfer2, &msg); + + ret = spi_sync(spi, &msg); + if (ret == 0) { + rbuf[0] = rx[0]; + rbuf[1] = rx[1]; + } + + kfree(rx); +err_buf: + kfree(buf); + + return ret; +} + +static int spi_adis16255_write_data(struct spi_adis16255_data *spiadis, + u8 adr1, + u8 adr2, + u8 *wbuf) +{ + struct spi_device *spi = spiadis->spi; + struct spi_message msg; + struct spi_transfer xfer1, xfer2; + u8 *buf, *rx; + int ret; + + buf = kmalloc(4, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + rx = kzalloc(4, GFP_KERNEL); + if (rx == NULL) { + ret = -ENOMEM; + goto err_buf; + } + + spi_message_init(&msg); + memset(&xfer1, 0, sizeof(xfer1)); + memset(&xfer2, 0, sizeof(xfer2)); + + buf[0] = adr1 | 0x80; + buf[1] = *wbuf; + + buf[2] = adr2 | 0x80; + buf[3] = *(wbuf + 1); + + xfer1.tx_buf = buf; + xfer1.rx_buf = rx; + xfer1.len = 2; + xfer1.delay_usecs = 9; + + xfer2.tx_buf = buf+2; + xfer2.rx_buf = rx+2; + xfer2.len = 2; + + spi_message_add_tail(&xfer1, &msg); + spi_message_add_tail(&xfer2, &msg); + + ret = spi_sync(spi, &msg); + if (ret != 0) + dev_warn(&spi->dev, "write data to %#x %#x failed\n", + buf[0], buf[2]); + + kfree(rx); +err_buf: + kfree(buf); + return ret; +} + +/*-------------------------------------------------------------------------*/ + +static irqreturn_t adis_irq_thread(int irq, void *dev_id) +{ + struct spi_adis16255_data *spiadis = dev_id; + int status; + u16 value = 0; + + status = spi_adis16255_read_data(spiadis, ADIS_GYRO_OUT, (u8 *)&value); + if (status != 0) { + dev_warn(&spiadis->spi->dev, "SPI FAILED\n"); + goto exit; + } + + /* perform on new data only... */ + if (value & 0x8000) { + /* delete error and new data bit */ + value = value & 0x3fff; + /* set negative value */ + if (value & 0x2000) + value = value | 0xe000; + + if (likely(spiadis->negative)) + value = -value; + + spiadis->data = (s16) value; + } + +exit: + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +ssize_t adis16255_show_data(struct device *device, + struct device_attribute *da, + char *buf) +{ + struct spi_adis16255_data *spiadis = dev_get_drvdata(device); + return snprintf(buf, PAGE_SIZE, "%d\n", spiadis->data); +} +DEVICE_ATTR(data, S_IRUGO , adis16255_show_data, NULL); + +ssize_t adis16255_show_direction(struct device *device, + struct device_attribute *da, + char *buf) +{ + struct spi_adis16255_data *spiadis = dev_get_drvdata(device); + return snprintf(buf, PAGE_SIZE, "%c\n", spiadis->direction); +} +DEVICE_ATTR(direction, S_IRUGO , adis16255_show_direction, NULL); + +ssize_t adis16255_show_sample_rate(struct device *device, + struct device_attribute *da, + char *buf) +{ + struct spi_adis16255_data *spiadis = dev_get_drvdata(device); + int status = 0; + u16 value = 0; + int ts = 0; + + status = spi_adis16255_read_data(spiadis, ADIS_SMPL_PRD_MSB, + (u8 *)&value); + if (status != 0) + return -EINVAL; + + if (value & 0x80) { + /* timebase = 60.54 ms */ + ts = 60540 * ((0x7f & value) + 1); + } else { + /* timebase = 1.953 ms */ + ts = 1953 * ((0x7f & value) + 1); + } + + return snprintf(buf, PAGE_SIZE, "%d\n", (1000*1000)/ts); +} +DEVICE_ATTR(sample_rate, S_IRUGO , adis16255_show_sample_rate, NULL); + +static struct attribute *adis16255_attributes[] = { + &dev_attr_data.attr, + &dev_attr_direction.attr, + &dev_attr_sample_rate.attr, + NULL +}; + +static const struct attribute_group adis16255_attr_group = { + .attrs = adis16255_attributes, +}; + +/*-------------------------------------------------------------------------*/ + +static int spi_adis16255_shutdown(struct spi_adis16255_data *spiadis) +{ + u16 value = 0; + /* turn sensor off */ + spi_adis16255_write_data(spiadis, + ADIS_SMPL_PRD_MSB, ADIS_SMPL_PRD_LSB, + (u8 *)&value); + spi_adis16255_write_data(spiadis, + ADIS_MSC_CTRL_MSB, ADIS_MSC_CTRL_LSB, + (u8 *)&value); + return 0; +} + +static int spi_adis16255_bringup(struct spi_adis16255_data *spiadis) +{ + int status = 0; + u16 value = 0; + + status = spi_adis16255_read_data(spiadis, ADIS_GYRO_SCALE, + (u8 *)&value); + if (status != 0) + goto err; + if (value != 0x0800) { + dev_warn(&spiadis->spi->dev, "Scale factor is none default" + "value (%.4x)\n", value); + } + + /* timebase = 1.953 ms, Ns = 0 -> 512 Hz sample rate */ + value = 0x0001; + status = spi_adis16255_write_data(spiadis, + ADIS_SMPL_PRD_MSB, ADIS_SMPL_PRD_LSB, + (u8 *)&value); + if (status != 0) + goto err; + + /* start internal self-test */ + value = 0x0400; + status = spi_adis16255_write_data(spiadis, + ADIS_MSC_CTRL_MSB, ADIS_MSC_CTRL_LSB, + (u8 *)&value); + if (status != 0) + goto err; + + /* wait 35 ms to finish self-test */ + msleep(35); + + value = 0x0000; + status = spi_adis16255_read_data(spiadis, ADIS_STATUS, + (u8 *)&value); + if (status != 0) + goto err; + + if (value & 0x23) { + if (value & 0x20) { + dev_warn(&spiadis->spi->dev, "self-test error\n"); + status = -ENODEV; + goto err; + } else if (value & 0x3) { + dev_warn(&spiadis->spi->dev, "Sensor voltage" + "out of range.\n"); + status = -ENODEV; + goto err; + } + } + + /* set interrupt to active high on DIO0 when data ready */ + value = 0x0006; + status = spi_adis16255_write_data(spiadis, + ADIS_MSC_CTRL_MSB, ADIS_MSC_CTRL_LSB, + (u8 *)&value); + if (status != 0) + goto err; + return status; + +err: + spi_adis16255_shutdown(spiadis); + return status; +} + +/*-------------------------------------------------------------------------*/ + +static int spi_adis16255_probe(struct spi_device *spi) +{ + + struct adis16255_init_data *init_data = spi->dev.platform_data; + struct spi_adis16255_data *spiadis; + int status = 0; + + spiadis = kzalloc(sizeof(*spiadis), GFP_KERNEL); + if (!spiadis) + return -ENOMEM; + + spiadis->spi = spi; + spiadis->direction = init_data->direction; + + if (init_data->negative) + spiadis->negative = 1; + + status = gpio_request(init_data->irq, "adis16255"); + if (status != 0) + goto err; + + status = gpio_direction_input(init_data->irq); + if (status != 0) + goto gpio_err; + + spiadis->irq = gpio_to_irq(init_data->irq); + + status = request_threaded_irq(spiadis->irq, + NULL, adis_irq_thread, + IRQF_DISABLED, "adis-driver", spiadis); + + if (status != 0) { + dev_err(&spi->dev, "IRQ request failed\n"); + goto gpio_err; + } + + dev_dbg(&spi->dev, "GPIO %d IRQ %d\n", init_data->irq, spiadis->irq); + + dev_set_drvdata(&spi->dev, spiadis); + status = sysfs_create_group(&spi->dev.kobj, &adis16255_attr_group); + if (status != 0) + goto irq_err; + + status = spi_adis16255_bringup(spiadis); + if (status != 0) + goto irq_err; + + dev_info(&spi->dev, "spi_adis16255 driver added!\n"); + + return status; + +irq_err: + free_irq(spiadis->irq, spiadis); +gpio_err: + gpio_free(init_data->irq); +err: + kfree(spiadis); + return status; +} + +static int spi_adis16255_remove(struct spi_device *spi) +{ + struct spi_adis16255_data *spiadis = dev_get_drvdata(&spi->dev); + + spi_adis16255_shutdown(spiadis); + + free_irq(spiadis->irq, spiadis); + gpio_free(irq_to_gpio(spiadis->irq)); + + sysfs_remove_group(&spiadis->spi->dev.kobj, &adis16255_attr_group); + + kfree(spiadis); + + dev_info(&spi->dev, "spi_adis16255 driver removed!\n"); + return 0; +} + +static struct spi_driver spi_adis16255_drv = { + .driver = { + .name = "spi_adis16255", + .owner = THIS_MODULE, + }, + .probe = spi_adis16255_probe, + .remove = __devexit_p(spi_adis16255_remove), +}; + +/*-------------------------------------------------------------------------*/ + +static int __init spi_adis16255_init(void) +{ + return spi_register_driver(&spi_adis16255_drv); +} +module_init(spi_adis16255_init); + +static void __exit spi_adis16255_exit(void) +{ + spi_unregister_driver(&spi_adis16255_drv); +} +module_exit(spi_adis16255_exit); + +MODULE_AUTHOR("Matthias Brugger"); +MODULE_DESCRIPTION("SPI device driver for ADIS16255 sensor"); +MODULE_LICENSE("GPL"); |