Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 1 | /* |
| 2 | * ADIS16060 Wide Bandwidth Yaw Rate Gyroscope with SPI driver |
| 3 | * |
| 4 | * Copyright 2010 Analog Devices Inc. |
| 5 | * |
| 6 | * Licensed under the GPL-2 or later. |
| 7 | */ |
| 8 | |
Paul Gortmaker | 4529623 | 2011-08-30 17:50:46 -0400 | [diff] [blame] | 9 | #include <linux/module.h> |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 10 | #include <linux/delay.h> |
| 11 | #include <linux/mutex.h> |
| 12 | #include <linux/device.h> |
| 13 | #include <linux/kernel.h> |
| 14 | #include <linux/spi/spi.h> |
| 15 | #include <linux/slab.h> |
| 16 | #include <linux/sysfs.h> |
Jonathan Cameron | 14f9832 | 2011-02-28 12:33:42 +0000 | [diff] [blame] | 17 | |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 18 | #include "../iio.h" |
| 19 | #include "../sysfs.h" |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 20 | |
Jonathan Cameron | 14f9832 | 2011-02-28 12:33:42 +0000 | [diff] [blame] | 21 | #define ADIS16060_GYRO 0x20 /* Measure Angular Rate (Gyro) */ |
| 22 | #define ADIS16060_TEMP_OUT 0x10 /* Measure Temperature */ |
| 23 | #define ADIS16060_AIN2 0x80 /* Measure AIN2 */ |
| 24 | #define ADIS16060_AIN1 0x40 /* Measure AIN1 */ |
| 25 | |
| 26 | /** |
| 27 | * struct adis16060_state - device instance specific data |
| 28 | * @us_w: actual spi_device to write config |
| 29 | * @us_r: actual spi_device to read back data |
Lucas De Marchi | 25985ed | 2011-03-30 22:57:33 -0300 | [diff] [blame] | 30 | * @buf: transmit or receive buffer |
Jonathan Cameron | 14f9832 | 2011-02-28 12:33:42 +0000 | [diff] [blame] | 31 | * @buf_lock: mutex to protect tx and rx |
| 32 | **/ |
| 33 | struct adis16060_state { |
| 34 | struct spi_device *us_w; |
| 35 | struct spi_device *us_r; |
Jonathan Cameron | 14f9832 | 2011-02-28 12:33:42 +0000 | [diff] [blame] | 36 | struct mutex buf_lock; |
| 37 | |
| 38 | u8 buf[3] ____cacheline_aligned; |
| 39 | }; |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 40 | |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 41 | static struct iio_dev *adis16060_iio_dev; |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 42 | |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 43 | static int adis16060_spi_write(struct iio_dev *indio_dev, u8 val) |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 44 | { |
| 45 | int ret; |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 46 | struct adis16060_state *st = iio_priv(indio_dev); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 47 | |
| 48 | mutex_lock(&st->buf_lock); |
Jonathan Cameron | 14f9832 | 2011-02-28 12:33:42 +0000 | [diff] [blame] | 49 | st->buf[2] = val; /* The last 8 bits clocked in are latched */ |
| 50 | ret = spi_write(st->us_w, st->buf, 3); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 51 | mutex_unlock(&st->buf_lock); |
| 52 | |
| 53 | return ret; |
| 54 | } |
| 55 | |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 56 | static int adis16060_spi_read(struct iio_dev *indio_dev, u16 *val) |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 57 | { |
| 58 | int ret; |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 59 | struct adis16060_state *st = iio_priv(indio_dev); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 60 | |
| 61 | mutex_lock(&st->buf_lock); |
| 62 | |
Jonathan Cameron | 14f9832 | 2011-02-28 12:33:42 +0000 | [diff] [blame] | 63 | ret = spi_read(st->us_r, st->buf, 3); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 64 | |
Jonathan Cameron | d14ae85 | 2011-02-11 14:20:00 +0000 | [diff] [blame] | 65 | /* The internal successive approximation ADC begins the |
| 66 | * conversion process on the falling edge of MSEL1 and |
| 67 | * starts to place data MSB first on the DOUT line at |
| 68 | * the 6th falling edge of SCLK |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 69 | */ |
| 70 | if (ret == 0) |
Jonathan Cameron | 14f9832 | 2011-02-28 12:33:42 +0000 | [diff] [blame] | 71 | *val = ((st->buf[0] & 0x3) << 12) | |
| 72 | (st->buf[1] << 4) | |
| 73 | ((st->buf[2] >> 4) & 0xF); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 74 | mutex_unlock(&st->buf_lock); |
| 75 | |
| 76 | return ret; |
| 77 | } |
| 78 | |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 79 | static int adis16060_read_raw(struct iio_dev *indio_dev, |
| 80 | struct iio_chan_spec const *chan, |
| 81 | int *val, int *val2, |
| 82 | long mask) |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 83 | { |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 84 | u16 tval = 0; |
| 85 | int ret; |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 86 | |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 87 | switch (mask) { |
Jonathan Cameron | fbaff21 | 2012-04-15 17:41:20 +0100 | [diff] [blame^] | 88 | case IIO_CHAN_INFO_RAW: |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 89 | /* Take the iio_dev status lock */ |
| 90 | mutex_lock(&indio_dev->mlock); |
| 91 | ret = adis16060_spi_write(indio_dev, chan->address); |
| 92 | if (ret < 0) { |
| 93 | mutex_unlock(&indio_dev->mlock); |
| 94 | return ret; |
| 95 | } |
| 96 | ret = adis16060_spi_read(indio_dev, &tval); |
| 97 | mutex_unlock(&indio_dev->mlock); |
| 98 | *val = tval; |
| 99 | return IIO_VAL_INT; |
Jonathan Cameron | c8a9f80 | 2011-10-26 17:41:36 +0100 | [diff] [blame] | 100 | case IIO_CHAN_INFO_OFFSET: |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 101 | *val = -7; |
| 102 | *val2 = 461117; |
| 103 | return IIO_VAL_INT_PLUS_MICRO; |
Jonathan Cameron | c8a9f80 | 2011-10-26 17:41:36 +0100 | [diff] [blame] | 104 | case IIO_CHAN_INFO_SCALE: |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 105 | *val = 0; |
| 106 | *val2 = 34000; |
| 107 | return IIO_VAL_INT_PLUS_MICRO; |
| 108 | } |
Jonathan Cameron | 14f9832 | 2011-02-28 12:33:42 +0000 | [diff] [blame] | 109 | |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 110 | return -EINVAL; |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 111 | } |
| 112 | |
Jonathan Cameron | 6fe8135 | 2011-05-18 14:42:37 +0100 | [diff] [blame] | 113 | static const struct iio_info adis16060_info = { |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 114 | .read_raw = &adis16060_read_raw, |
Jonathan Cameron | 6fe8135 | 2011-05-18 14:42:37 +0100 | [diff] [blame] | 115 | .driver_module = THIS_MODULE, |
| 116 | }; |
| 117 | |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 118 | static const struct iio_chan_spec adis16060_channels[] = { |
| 119 | { |
Jonathan Cameron | 41ea040 | 2011-10-05 15:27:59 +0100 | [diff] [blame] | 120 | .type = IIO_ANGL_VEL, |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 121 | .modified = 1, |
| 122 | .channel2 = IIO_MOD_Z, |
Jonathan Cameron | fbaff21 | 2012-04-15 17:41:20 +0100 | [diff] [blame^] | 123 | .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT, |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 124 | .address = ADIS16060_GYRO, |
| 125 | }, { |
Jonathan Cameron | 6835cb6 | 2011-09-27 09:56:41 +0100 | [diff] [blame] | 126 | .type = IIO_VOLTAGE, |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 127 | .indexed = 1, |
| 128 | .channel = 0, |
Jonathan Cameron | fbaff21 | 2012-04-15 17:41:20 +0100 | [diff] [blame^] | 129 | .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT, |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 130 | .address = ADIS16060_AIN1, |
| 131 | }, { |
Jonathan Cameron | 6835cb6 | 2011-09-27 09:56:41 +0100 | [diff] [blame] | 132 | .type = IIO_VOLTAGE, |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 133 | .indexed = 1, |
| 134 | .channel = 1, |
Jonathan Cameron | fbaff21 | 2012-04-15 17:41:20 +0100 | [diff] [blame^] | 135 | .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT, |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 136 | .address = ADIS16060_AIN2, |
| 137 | }, { |
| 138 | .type = IIO_TEMP, |
| 139 | .indexed = 1, |
| 140 | .channel = 0, |
Jonathan Cameron | fbaff21 | 2012-04-15 17:41:20 +0100 | [diff] [blame^] | 141 | .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | |
| 142 | IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | |
Jonathan Cameron | c8a9f80 | 2011-10-26 17:41:36 +0100 | [diff] [blame] | 143 | IIO_CHAN_INFO_SCALE_SEPARATE_BIT, |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 144 | .address = ADIS16060_TEMP_OUT, |
| 145 | } |
| 146 | }; |
| 147 | |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 148 | static int __devinit adis16060_r_probe(struct spi_device *spi) |
| 149 | { |
Jonathan Cameron | 26d25ae | 2011-09-02 17:14:40 +0100 | [diff] [blame] | 150 | int ret; |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 151 | struct adis16060_state *st; |
| 152 | struct iio_dev *indio_dev; |
| 153 | |
| 154 | /* setup the industrialio driver allocated elements */ |
| 155 | indio_dev = iio_allocate_device(sizeof(*st)); |
| 156 | if (indio_dev == NULL) { |
| 157 | ret = -ENOMEM; |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 158 | goto error_ret; |
| 159 | } |
| 160 | /* this is only used for removal purposes */ |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 161 | spi_set_drvdata(spi, indio_dev); |
| 162 | st = iio_priv(indio_dev); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 163 | st->us_r = spi; |
| 164 | mutex_init(&st->buf_lock); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 165 | |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 166 | indio_dev->name = spi->dev.driver->name; |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 167 | indio_dev->dev.parent = &spi->dev; |
| 168 | indio_dev->info = &adis16060_info; |
| 169 | indio_dev->modes = INDIO_DIRECT_MODE; |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 170 | indio_dev->channels = adis16060_channels; |
| 171 | indio_dev->num_channels = ARRAY_SIZE(adis16060_channels); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 172 | |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 173 | ret = iio_device_register(indio_dev); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 174 | if (ret) |
Jonathan Cameron | d14ae85 | 2011-02-11 14:20:00 +0000 | [diff] [blame] | 175 | goto error_free_dev; |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 176 | |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 177 | adis16060_iio_dev = indio_dev; |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 178 | return 0; |
| 179 | |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 180 | error_free_dev: |
Jonathan Cameron | 26d25ae | 2011-09-02 17:14:40 +0100 | [diff] [blame] | 181 | iio_free_device(indio_dev); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 182 | error_ret: |
| 183 | return ret; |
| 184 | } |
| 185 | |
| 186 | /* fixme, confirm ordering in this function */ |
| 187 | static int adis16060_r_remove(struct spi_device *spi) |
| 188 | { |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 189 | iio_device_unregister(spi_get_drvdata(spi)); |
Jonathan Cameron | d2fffd6 | 2011-10-14 14:46:58 +0100 | [diff] [blame] | 190 | iio_free_device(spi_get_drvdata(spi)); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 191 | |
| 192 | return 0; |
| 193 | } |
| 194 | |
| 195 | static int __devinit adis16060_w_probe(struct spi_device *spi) |
| 196 | { |
| 197 | int ret; |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 198 | struct iio_dev *indio_dev = adis16060_iio_dev; |
| 199 | struct adis16060_state *st; |
| 200 | if (!indio_dev) { |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 201 | ret = -ENODEV; |
| 202 | goto error_ret; |
| 203 | } |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 204 | st = iio_priv(indio_dev); |
| 205 | spi_set_drvdata(spi, indio_dev); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 206 | st->us_w = spi; |
| 207 | return 0; |
| 208 | |
| 209 | error_ret: |
| 210 | return ret; |
| 211 | } |
| 212 | |
| 213 | static int adis16060_w_remove(struct spi_device *spi) |
| 214 | { |
| 215 | return 0; |
| 216 | } |
| 217 | |
| 218 | static struct spi_driver adis16060_r_driver = { |
| 219 | .driver = { |
| 220 | .name = "adis16060_r", |
| 221 | .owner = THIS_MODULE, |
| 222 | }, |
| 223 | .probe = adis16060_r_probe, |
| 224 | .remove = __devexit_p(adis16060_r_remove), |
| 225 | }; |
| 226 | |
| 227 | static struct spi_driver adis16060_w_driver = { |
| 228 | .driver = { |
| 229 | .name = "adis16060_w", |
| 230 | .owner = THIS_MODULE, |
| 231 | }, |
| 232 | .probe = adis16060_w_probe, |
| 233 | .remove = __devexit_p(adis16060_w_remove), |
| 234 | }; |
| 235 | |
| 236 | static __init int adis16060_init(void) |
| 237 | { |
| 238 | int ret; |
| 239 | |
| 240 | ret = spi_register_driver(&adis16060_r_driver); |
| 241 | if (ret < 0) |
| 242 | return ret; |
| 243 | |
| 244 | ret = spi_register_driver(&adis16060_w_driver); |
| 245 | if (ret < 0) { |
| 246 | spi_unregister_driver(&adis16060_r_driver); |
| 247 | return ret; |
| 248 | } |
| 249 | |
| 250 | return 0; |
| 251 | } |
| 252 | module_init(adis16060_init); |
| 253 | |
| 254 | static __exit void adis16060_exit(void) |
| 255 | { |
| 256 | spi_unregister_driver(&adis16060_w_driver); |
| 257 | spi_unregister_driver(&adis16060_r_driver); |
| 258 | } |
| 259 | module_exit(adis16060_exit); |
| 260 | |
| 261 | MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); |
Jonathan Cameron | d14ae85 | 2011-02-11 14:20:00 +0000 | [diff] [blame] | 262 | MODULE_DESCRIPTION("Analog Devices ADIS16060 Yaw Rate Gyroscope Driver"); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 263 | MODULE_LICENSE("GPL v2"); |