This drivers allows a communication with the Analog Devices ADIS16255 Low Power Gyroscope over SPI. Signed-off-by: Matthias Brugger <mensch0815@xxxxxxxxx> --- diff --git a/drivers/staging/adis16255/Kconfig b/drivers/staging/adis16255/Kconfig new file mode 100644 index 0000000..3952cf9 --- /dev/null +++ b/drivers/staging/adis16255/Kconfig @@ -0,0 +1,9 @@ +config ADIS16255 + tristate "Ananlog Devices ADIS16250/16255" + depends on SPI && SYSFS + ---help--- + If you say yes here you get support for the Analog Devices + ADIS16250/16255 Low Power Gyroscope. + + This driver can also be built as a module. If so, the module + will be called adis16255. diff --git a/drivers/staging/adis16255/Makefile b/drivers/staging/adis16255/Makefile new file mode 100644 index 0000000..796dac4 --- /dev/null +++ b/drivers/staging/adis16255/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_ADIS16255) += adis16255.o diff --git a/drivers/staging/adis16255/TODO b/drivers/staging/adis16255/TODO new file mode 100644 index 0000000..255b367 --- /dev/null +++ b/drivers/staging/adis16255/TODO @@ -0,0 +1,4 @@ +* documentation about sample rate, possible changes via ioctls etc pp +* sample rate changeable or at least readable from sysfs +* reset gyroscope +* encapsulate adis_init and adis_turn_off diff --git a/drivers/staging/adis16255/adis16255.c b/drivers/staging/adis16255/adis16255.c new file mode 100644 index 0000000..19d7d9c --- /dev/null +++ b/drivers/staging/adis16255/adis16255.c @@ -0,0 +1,388 @@ +/* + * Analog Devices ADIS16250/ADIS16255 Low Power Gyroscope + * + * Written by: Matthias Brugger <mensch0815@xxxxxxxxx> + * + * 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. + */ + +#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. + * @wq: work for working queue + * @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 x, y, z axis the sensor is meassuring + */ +struct spi_adis16255_data { + struct device dev; + struct spi_device *spi; + struct work_struct wq; + s16 data; + int irq_adis; + 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 = kmalloc(4, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + rx = kzalloc(4, GFP_KERNEL); + if (!rx) + return -ENOMEM; + + buf[0] = adr; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + + 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(buf); + kfree(rx); + + 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) + return -ENOMEM; + + rx = kzalloc(4, GFP_KERNEL); + if (!rx) + return -ENOMEM; + + 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_info(&spi->dev, "wirte data to %#x %#x failed\n", buf[0], buf[2]); + + kfree(buf); + kfree(rx); + return ret; +} + +/*-------------------------------------------------------------------------*/ + +void spi_workqueue(struct work_struct *work) +{ + struct spi_adis16255_data *spiadis = container_of(work, struct spi_adis16255_data, wq); + int status; + u16 value; + + status = spi_adis16255_read_data(spiadis, ADIS_GYRO_OUT, (u8 *)&value); + if (status == 0) { + // perform on new data only... + // delete error and new data bit + // set negative value + if (value & 0x8000) { + value = value & 0x3fff; + if (value & 0x2000) { + value = value | 0xe000; + + if(likely(spiadis->negative)) + value = -value; + + spiadis->data = (s16) value; + } + } + else { + dev_warn(&spiadis->spi->dev, "SPI FAILED"); + } + +} + +static irqreturn_t adis_interrupt(int irq, void *dev_id) { + struct spi_adis16255_data *spiadis = (struct spi_adis16255_data *) dev_id; + + schedule_work(&spiadis->wq); + + 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); + +static struct attribute *adis16255_attributes[] = { + &dev_attr_data.attr, + &dev_attr_direction.attr, + NULL +}; + +static const struct attribute_group adis16255_attr_group = { + .attrs = adis16255_attributes, +}; + +/*-------------------------------------------------------------------------*/ + +static int spi_adis16255_probe(struct spi_device *spi) +{ + +#define AD_CHK(_ss) \ +{ \ + status = _ss; \ + if(status != 0) \ + goto irq_err; \ +} + + struct adis16255_init_data *init_data = spi->dev.platform_data; + struct spi_adis16255_data *spiadis; + int status=0; + u16 value; + + /* Allocate driver data */ + spiadis = kzalloc(sizeof(*spiadis), GFP_KERNEL); + if (!spiadis) + return -ENOMEM; + + spiadis->spi = spi; // save spi_device in driver-data + spiadis->irq_adis = init_data->irq; + spiadis->direction = init_data->direction; + + if (init_data->negative) { + spiadis->negative = 1; + } + + INIT_WORK(&spiadis->wq, spi_workqueue); + + + status = gpio_request(spiadis->irq_adis, "adis16255"); + if(status != 0) + goto irq_err; + + status = gpio_direction_input(spiadis->irq_adis); + if(status != 0) + goto irq_err; + + spiadis->irq = gpio_to_irq(spiadis->irq_adis); + + status = request_irq(spiadis->irq, adis_interrupt, IRQF_DISABLED, "adis-driver", spiadis); + if(status != 0) { + dev_err(&spi->dev, "IRQ request failed\n"); + goto irq_err; + } + + dev_dbg(&spi->dev, "GPIO %d IRQ %d\n", spiadis->irq_adis, spiadis->irq); + + dev_set_drvdata(&spi->dev, spiadis); + AD_CHK(sysfs_create_group(&spi->dev.kobj, &adis16255_attr_group)); + + dev_info(&spi->dev,"spi_adis16255 driver added!\n"); + + AD_CHK( spi_adis16255_read_data(spiadis, ADIS_SUPPLY_OUT, (u8 *)&value) ); + dev_info(&spi->dev, "sensor works with %d mV (%.4x)!\n", ((value & 0x0fff)*18315)/10000, (value & 0x0fff)); + + AD_CHK( spi_adis16255_read_data(spiadis, ADIS_GYRO_SCALE, (u8 *)&value) ); + dev_info(&spi->dev, "adis GYRO_SCALE is %.4x\n", value); + + AD_CHK( spi_adis16255_read_data(spiadis, ADIS_STATUS, (u8 *)&value) ); + dev_info(&spi->dev, "adis STATUS is %.4x\n", value); + + value = 0x0001;// 0x008a; + AD_CHK( spi_adis16255_write_data(spiadis, ADIS_SMPL_PRD_MSB, ADIS_SMPL_PRD_LSB, (u8 *)&value) ); + value = 0x0000; + AD_CHK( spi_adis16255_read_data(spiadis, ADIS_SMPL_PRD_MSB, (u8 *)&value) ); + dev_info(&spi->dev, "adis SMP_PRD is %.4x\n", value); + + // set interrupt on new data... + value = 0x0006; + AD_CHK( spi_adis16255_write_data(spiadis, ADIS_MSC_CTRL_MSB, ADIS_MSC_CTRL_LSB, (u8 *)&value) ); + value = 0x0000; + AD_CHK( spi_adis16255_read_data(spiadis, ADIS_MSC_CTRL_MSB, (u8 *)&value) ); + dev_info(&spi->dev, "adis MSC_CONTROL is %.4x\n", value); + + return status; + +irq_err: + kfree(spiadis); + return status; +} + +static int spi_adis16255_remove(struct spi_device *spi) +{ + u16 value=0; + struct spi_adis16255_data *spiadis = dev_get_drvdata(&spi->dev); + struct adis16255_init_data *init_data = spi->dev.platform_data; + + if(spiadis == NULL) { + dev_info(&spi->dev, "unregister: no spiadis struct %c\n", init_data->direction); + goto end; + } + + // trun 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); + + dev_info(&spi->dev, "unregister: GPIO %d IRQ %d \n",spiadis->irq_adis, spiadis->irq); + free_irq(spiadis->irq, spiadis); + gpio_free(spiadis->irq_adis); + + sysfs_remove_group(&spiadis->spi->dev.kobj, &adis16255_attr_group); + + kfree(spiadis); + +end: + 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) +{ + int status; + + status = spi_register_driver(&spi_adis16255_drv); + return status; +} +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"); diff --git a/drivers/staging/adis16255/adis16255.h b/drivers/staging/adis16255/adis16255.h new file mode 100644 index 0000000..26c9f84 --- /dev/null +++ b/drivers/staging/adis16255/adis16255.h @@ -0,0 +1,12 @@ +#ifndef ADIS16255_H +#define ADIS16255_H + +#include <linux/types.h> + +struct adis16255_init_data { + char direction; + u8 negative; + int irq; +}; + +#endif _______________________________________________ devel mailing list devel@xxxxxxxxxxxxxxxxxxxxxx http://driverdev.linuxdriverproject.org/mailman/listinfo/devel