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 | |
Jonathan Cameron | 06458e2 | 2012-04-25 15:54:58 +0100 | [diff] [blame] | 18 | #include <linux/iio/iio.h> |
| 19 | #include <linux/iio/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 | cdcb0ea | 2013-02-27 19:38:43 +0000 | [diff] [blame] | 123 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
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 | cdcb0ea | 2013-02-27 19:38:43 +0000 | [diff] [blame] | 129 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
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 | cdcb0ea | 2013-02-27 19:38:43 +0000 | [diff] [blame] | 135 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
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 | cdcb0ea | 2013-02-27 19:38:43 +0000 | [diff] [blame] | 141 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
| 142 | BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE), |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 143 | .address = ADIS16060_TEMP_OUT, |
| 144 | } |
| 145 | }; |
| 146 | |
Bill Pemberton | 4ae1c61 | 2012-11-19 13:21:57 -0500 | [diff] [blame] | 147 | static int adis16060_r_probe(struct spi_device *spi) |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 148 | { |
Jonathan Cameron | 26d25ae | 2011-09-02 17:14:40 +0100 | [diff] [blame] | 149 | int ret; |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 150 | struct adis16060_state *st; |
| 151 | struct iio_dev *indio_dev; |
| 152 | |
| 153 | /* setup the industrialio driver allocated elements */ |
Sachin Kamat | 1f5ac52 | 2013-08-13 07:34:00 +0100 | [diff] [blame] | 154 | indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); |
| 155 | if (!indio_dev) |
| 156 | return -ENOMEM; |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 157 | /* this is only used for removal purposes */ |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 158 | spi_set_drvdata(spi, indio_dev); |
| 159 | st = iio_priv(indio_dev); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 160 | st->us_r = spi; |
| 161 | mutex_init(&st->buf_lock); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 162 | |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 163 | indio_dev->name = spi->dev.driver->name; |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 164 | indio_dev->dev.parent = &spi->dev; |
| 165 | indio_dev->info = &adis16060_info; |
| 166 | indio_dev->modes = INDIO_DIRECT_MODE; |
Jonathan Cameron | 4f2ca08 | 2011-08-12 17:48:00 +0100 | [diff] [blame] | 167 | indio_dev->channels = adis16060_channels; |
| 168 | indio_dev->num_channels = ARRAY_SIZE(adis16060_channels); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 169 | |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 170 | ret = iio_device_register(indio_dev); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 171 | if (ret) |
Sachin Kamat | 1f5ac52 | 2013-08-13 07:34:00 +0100 | [diff] [blame] | 172 | return ret; |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 173 | |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 174 | adis16060_iio_dev = indio_dev; |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 175 | return 0; |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 176 | } |
| 177 | |
| 178 | /* fixme, confirm ordering in this function */ |
Bill Pemberton | 447d4f2 | 2012-11-19 13:26:37 -0500 | [diff] [blame] | 179 | static int adis16060_r_remove(struct spi_device *spi) |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 180 | { |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 181 | iio_device_unregister(spi_get_drvdata(spi)); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 182 | return 0; |
| 183 | } |
| 184 | |
Bill Pemberton | 4ae1c61 | 2012-11-19 13:21:57 -0500 | [diff] [blame] | 185 | static int adis16060_w_probe(struct spi_device *spi) |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 186 | { |
| 187 | int ret; |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 188 | struct iio_dev *indio_dev = adis16060_iio_dev; |
| 189 | struct adis16060_state *st; |
| 190 | if (!indio_dev) { |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 191 | ret = -ENODEV; |
| 192 | goto error_ret; |
| 193 | } |
Jonathan Cameron | 3a5952f | 2011-06-27 13:07:43 +0100 | [diff] [blame] | 194 | st = iio_priv(indio_dev); |
| 195 | spi_set_drvdata(spi, indio_dev); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 196 | st->us_w = spi; |
| 197 | return 0; |
| 198 | |
| 199 | error_ret: |
| 200 | return ret; |
| 201 | } |
| 202 | |
Bill Pemberton | 447d4f2 | 2012-11-19 13:26:37 -0500 | [diff] [blame] | 203 | static int adis16060_w_remove(struct spi_device *spi) |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 204 | { |
| 205 | return 0; |
| 206 | } |
| 207 | |
| 208 | static struct spi_driver adis16060_r_driver = { |
| 209 | .driver = { |
| 210 | .name = "adis16060_r", |
| 211 | .owner = THIS_MODULE, |
| 212 | }, |
| 213 | .probe = adis16060_r_probe, |
Bill Pemberton | e543acf | 2012-11-19 13:21:38 -0500 | [diff] [blame] | 214 | .remove = adis16060_r_remove, |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 215 | }; |
| 216 | |
| 217 | static struct spi_driver adis16060_w_driver = { |
| 218 | .driver = { |
| 219 | .name = "adis16060_w", |
| 220 | .owner = THIS_MODULE, |
| 221 | }, |
| 222 | .probe = adis16060_w_probe, |
Bill Pemberton | e543acf | 2012-11-19 13:21:38 -0500 | [diff] [blame] | 223 | .remove = adis16060_w_remove, |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 224 | }; |
| 225 | |
| 226 | static __init int adis16060_init(void) |
| 227 | { |
| 228 | int ret; |
| 229 | |
| 230 | ret = spi_register_driver(&adis16060_r_driver); |
| 231 | if (ret < 0) |
| 232 | return ret; |
| 233 | |
| 234 | ret = spi_register_driver(&adis16060_w_driver); |
| 235 | if (ret < 0) { |
| 236 | spi_unregister_driver(&adis16060_r_driver); |
| 237 | return ret; |
| 238 | } |
| 239 | |
| 240 | return 0; |
| 241 | } |
| 242 | module_init(adis16060_init); |
| 243 | |
| 244 | static __exit void adis16060_exit(void) |
| 245 | { |
| 246 | spi_unregister_driver(&adis16060_w_driver); |
| 247 | spi_unregister_driver(&adis16060_r_driver); |
| 248 | } |
| 249 | module_exit(adis16060_exit); |
| 250 | |
| 251 | MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); |
Jonathan Cameron | d14ae85 | 2011-02-11 14:20:00 +0000 | [diff] [blame] | 252 | MODULE_DESCRIPTION("Analog Devices ADIS16060 Yaw Rate Gyroscope Driver"); |
Barry Song | e071f6b | 2010-10-27 21:44:04 -0400 | [diff] [blame] | 253 | MODULE_LICENSE("GPL v2"); |