/* * Analog Devices ADIS16250/ADIS16255 Low Power Gyroscope * * Written by: Matthias Brugger * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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");