Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | /* |
| 3 | * ADXRS290 SPI Gyroscope Driver |
| 4 | * |
| 5 | * Copyright (C) 2020 Nishant Malpani <nish.malpani25@gmail.com> |
| 6 | * Copyright (C) 2020 Analog Devices, Inc. |
| 7 | */ |
| 8 | |
| 9 | #include <linux/bitfield.h> |
Kister Genesis Jimenez | cbc04c0 | 2021-11-15 11:41:47 +0100 | [diff] [blame] | 10 | #include <linux/bitops.h> |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 11 | #include <linux/delay.h> |
| 12 | #include <linux/device.h> |
| 13 | #include <linux/kernel.h> |
| 14 | #include <linux/module.h> |
| 15 | #include <linux/spi/spi.h> |
| 16 | |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 17 | #include <linux/iio/buffer.h> |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 18 | #include <linux/iio/iio.h> |
| 19 | #include <linux/iio/sysfs.h> |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 20 | #include <linux/iio/trigger.h> |
| 21 | #include <linux/iio/triggered_buffer.h> |
| 22 | #include <linux/iio/trigger_consumer.h> |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 23 | |
| 24 | #define ADXRS290_ADI_ID 0xAD |
| 25 | #define ADXRS290_MEMS_ID 0x1D |
| 26 | #define ADXRS290_DEV_ID 0x92 |
| 27 | |
| 28 | #define ADXRS290_REG_ADI_ID 0x00 |
| 29 | #define ADXRS290_REG_MEMS_ID 0x01 |
| 30 | #define ADXRS290_REG_DEV_ID 0x02 |
| 31 | #define ADXRS290_REG_REV_ID 0x03 |
| 32 | #define ADXRS290_REG_SN0 0x04 /* Serial Number Registers, 4 bytes */ |
| 33 | #define ADXRS290_REG_DATAX0 0x08 /* Roll Rate o/p Data Regs, 2 bytes */ |
| 34 | #define ADXRS290_REG_DATAY0 0x0A /* Pitch Rate o/p Data Regs, 2 bytes */ |
| 35 | #define ADXRS290_REG_TEMP0 0x0C |
| 36 | #define ADXRS290_REG_POWER_CTL 0x10 |
| 37 | #define ADXRS290_REG_FILTER 0x11 |
| 38 | #define ADXRS290_REG_DATA_RDY 0x12 |
| 39 | |
| 40 | #define ADXRS290_READ BIT(7) |
| 41 | #define ADXRS290_TSM BIT(0) |
| 42 | #define ADXRS290_MEASUREMENT BIT(1) |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 43 | #define ADXRS290_DATA_RDY_OUT BIT(0) |
| 44 | #define ADXRS290_SYNC_MASK GENMASK(1, 0) |
| 45 | #define ADXRS290_SYNC(x) FIELD_PREP(ADXRS290_SYNC_MASK, x) |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 46 | #define ADXRS290_LPF_MASK GENMASK(2, 0) |
| 47 | #define ADXRS290_LPF(x) FIELD_PREP(ADXRS290_LPF_MASK, x) |
| 48 | #define ADXRS290_HPF_MASK GENMASK(7, 4) |
| 49 | #define ADXRS290_HPF(x) FIELD_PREP(ADXRS290_HPF_MASK, x) |
| 50 | |
| 51 | #define ADXRS290_READ_REG(reg) (ADXRS290_READ | (reg)) |
| 52 | |
| 53 | #define ADXRS290_MAX_TRANSITION_TIME_MS 100 |
| 54 | |
| 55 | enum adxrs290_mode { |
| 56 | ADXRS290_MODE_STANDBY, |
| 57 | ADXRS290_MODE_MEASUREMENT, |
| 58 | }; |
| 59 | |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 60 | enum adxrs290_scan_index { |
| 61 | ADXRS290_IDX_X, |
| 62 | ADXRS290_IDX_Y, |
| 63 | ADXRS290_IDX_TEMP, |
| 64 | ADXRS290_IDX_TS, |
| 65 | }; |
| 66 | |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 67 | struct adxrs290_state { |
| 68 | struct spi_device *spi; |
| 69 | /* Serialize reads and their subsequent processing */ |
| 70 | struct mutex lock; |
| 71 | enum adxrs290_mode mode; |
| 72 | unsigned int lpf_3db_freq_idx; |
| 73 | unsigned int hpf_3db_freq_idx; |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 74 | struct iio_trigger *dready_trig; |
| 75 | /* Ensure correct alignment of timestamp when present */ |
| 76 | struct { |
| 77 | s16 channels[3]; |
| 78 | s64 ts __aligned(8); |
| 79 | } buffer; |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 80 | }; |
| 81 | |
| 82 | /* |
| 83 | * Available cut-off frequencies of the low pass filter in Hz. |
| 84 | * The integer part and fractional part are represented separately. |
| 85 | */ |
| 86 | static const int adxrs290_lpf_3db_freq_hz_table[][2] = { |
| 87 | [0] = {480, 0}, |
| 88 | [1] = {320, 0}, |
| 89 | [2] = {160, 0}, |
| 90 | [3] = {80, 0}, |
| 91 | [4] = {56, 600000}, |
| 92 | [5] = {40, 0}, |
| 93 | [6] = {28, 300000}, |
| 94 | [7] = {20, 0}, |
| 95 | }; |
| 96 | |
| 97 | /* |
| 98 | * Available cut-off frequencies of the high pass filter in Hz. |
| 99 | * The integer part and fractional part are represented separately. |
| 100 | */ |
| 101 | static const int adxrs290_hpf_3db_freq_hz_table[][2] = { |
| 102 | [0] = {0, 0}, |
| 103 | [1] = {0, 11000}, |
| 104 | [2] = {0, 22000}, |
| 105 | [3] = {0, 44000}, |
| 106 | [4] = {0, 87000}, |
| 107 | [5] = {0, 175000}, |
| 108 | [6] = {0, 350000}, |
| 109 | [7] = {0, 700000}, |
| 110 | [8] = {1, 400000}, |
| 111 | [9] = {2, 800000}, |
| 112 | [10] = {11, 300000}, |
| 113 | }; |
| 114 | |
| 115 | static int adxrs290_get_rate_data(struct iio_dev *indio_dev, const u8 cmd, int *val) |
| 116 | { |
| 117 | struct adxrs290_state *st = iio_priv(indio_dev); |
| 118 | int ret = 0; |
| 119 | int temp; |
| 120 | |
| 121 | mutex_lock(&st->lock); |
| 122 | temp = spi_w8r16(st->spi, cmd); |
| 123 | if (temp < 0) { |
| 124 | ret = temp; |
| 125 | goto err_unlock; |
| 126 | } |
| 127 | |
Kister Genesis Jimenez | cbc04c0 | 2021-11-15 11:41:47 +0100 | [diff] [blame] | 128 | *val = sign_extend32(temp, 15); |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 129 | |
| 130 | err_unlock: |
| 131 | mutex_unlock(&st->lock); |
| 132 | return ret; |
| 133 | } |
| 134 | |
| 135 | static int adxrs290_get_temp_data(struct iio_dev *indio_dev, int *val) |
| 136 | { |
| 137 | const u8 cmd = ADXRS290_READ_REG(ADXRS290_REG_TEMP0); |
| 138 | struct adxrs290_state *st = iio_priv(indio_dev); |
| 139 | int ret = 0; |
| 140 | int temp; |
| 141 | |
| 142 | mutex_lock(&st->lock); |
| 143 | temp = spi_w8r16(st->spi, cmd); |
| 144 | if (temp < 0) { |
| 145 | ret = temp; |
| 146 | goto err_unlock; |
| 147 | } |
| 148 | |
| 149 | /* extract lower 12 bits temperature reading */ |
Kister Genesis Jimenez | cbc04c0 | 2021-11-15 11:41:47 +0100 | [diff] [blame] | 150 | *val = sign_extend32(temp, 11); |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 151 | |
| 152 | err_unlock: |
| 153 | mutex_unlock(&st->lock); |
| 154 | return ret; |
| 155 | } |
| 156 | |
| 157 | static int adxrs290_get_3db_freq(struct iio_dev *indio_dev, u8 *val, u8 *val2) |
| 158 | { |
| 159 | const u8 cmd = ADXRS290_READ_REG(ADXRS290_REG_FILTER); |
| 160 | struct adxrs290_state *st = iio_priv(indio_dev); |
| 161 | int ret = 0; |
| 162 | short temp; |
| 163 | |
| 164 | mutex_lock(&st->lock); |
| 165 | temp = spi_w8r8(st->spi, cmd); |
| 166 | if (temp < 0) { |
| 167 | ret = temp; |
| 168 | goto err_unlock; |
| 169 | } |
| 170 | |
| 171 | *val = FIELD_GET(ADXRS290_LPF_MASK, temp); |
| 172 | *val2 = FIELD_GET(ADXRS290_HPF_MASK, temp); |
| 173 | |
| 174 | err_unlock: |
| 175 | mutex_unlock(&st->lock); |
| 176 | return ret; |
| 177 | } |
| 178 | |
| 179 | static int adxrs290_spi_write_reg(struct spi_device *spi, const u8 reg, |
| 180 | const u8 val) |
| 181 | { |
| 182 | u8 buf[2]; |
| 183 | |
| 184 | buf[0] = reg; |
| 185 | buf[1] = val; |
| 186 | |
| 187 | return spi_write_then_read(spi, buf, ARRAY_SIZE(buf), NULL, 0); |
| 188 | } |
| 189 | |
| 190 | static int adxrs290_find_match(const int (*freq_tbl)[2], const int n, |
| 191 | const int val, const int val2) |
| 192 | { |
| 193 | int i; |
| 194 | |
| 195 | for (i = 0; i < n; i++) { |
| 196 | if (freq_tbl[i][0] == val && freq_tbl[i][1] == val2) |
| 197 | return i; |
| 198 | } |
| 199 | |
| 200 | return -EINVAL; |
| 201 | } |
| 202 | |
| 203 | static int adxrs290_set_filter_freq(struct iio_dev *indio_dev, |
| 204 | const unsigned int lpf_idx, |
| 205 | const unsigned int hpf_idx) |
| 206 | { |
| 207 | struct adxrs290_state *st = iio_priv(indio_dev); |
| 208 | u8 val; |
| 209 | |
| 210 | val = ADXRS290_HPF(hpf_idx) | ADXRS290_LPF(lpf_idx); |
| 211 | |
| 212 | return adxrs290_spi_write_reg(st->spi, ADXRS290_REG_FILTER, val); |
| 213 | } |
| 214 | |
Nishant Malpani | 5ac65da | 2020-09-10 17:57:11 +0530 | [diff] [blame] | 215 | static int adxrs290_set_mode(struct iio_dev *indio_dev, enum adxrs290_mode mode) |
| 216 | { |
| 217 | struct adxrs290_state *st = iio_priv(indio_dev); |
| 218 | int val, ret; |
| 219 | |
| 220 | if (st->mode == mode) |
| 221 | return 0; |
| 222 | |
| 223 | mutex_lock(&st->lock); |
| 224 | |
| 225 | ret = spi_w8r8(st->spi, ADXRS290_READ_REG(ADXRS290_REG_POWER_CTL)); |
| 226 | if (ret < 0) |
| 227 | goto out_unlock; |
| 228 | |
| 229 | val = ret; |
| 230 | |
| 231 | switch (mode) { |
| 232 | case ADXRS290_MODE_STANDBY: |
| 233 | val &= ~ADXRS290_MEASUREMENT; |
| 234 | break; |
| 235 | case ADXRS290_MODE_MEASUREMENT: |
| 236 | val |= ADXRS290_MEASUREMENT; |
| 237 | break; |
| 238 | default: |
| 239 | ret = -EINVAL; |
| 240 | goto out_unlock; |
| 241 | } |
| 242 | |
| 243 | ret = adxrs290_spi_write_reg(st->spi, ADXRS290_REG_POWER_CTL, val); |
| 244 | if (ret < 0) { |
| 245 | dev_err(&st->spi->dev, "unable to set mode: %d\n", ret); |
| 246 | goto out_unlock; |
| 247 | } |
| 248 | |
| 249 | /* update cached mode */ |
| 250 | st->mode = mode; |
| 251 | |
| 252 | out_unlock: |
| 253 | mutex_unlock(&st->lock); |
| 254 | return ret; |
| 255 | } |
| 256 | |
| 257 | static void adxrs290_chip_off_action(void *data) |
| 258 | { |
| 259 | struct iio_dev *indio_dev = data; |
| 260 | |
| 261 | adxrs290_set_mode(indio_dev, ADXRS290_MODE_STANDBY); |
| 262 | } |
| 263 | |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 264 | static int adxrs290_initial_setup(struct iio_dev *indio_dev) |
| 265 | { |
| 266 | struct adxrs290_state *st = iio_priv(indio_dev); |
Nishant Malpani | 5ac65da | 2020-09-10 17:57:11 +0530 | [diff] [blame] | 267 | struct spi_device *spi = st->spi; |
| 268 | int ret; |
| 269 | |
| 270 | ret = adxrs290_spi_write_reg(spi, ADXRS290_REG_POWER_CTL, |
| 271 | ADXRS290_MEASUREMENT | ADXRS290_TSM); |
| 272 | if (ret < 0) |
| 273 | return ret; |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 274 | |
| 275 | st->mode = ADXRS290_MODE_MEASUREMENT; |
| 276 | |
Nishant Malpani | 5ac65da | 2020-09-10 17:57:11 +0530 | [diff] [blame] | 277 | return devm_add_action_or_reset(&spi->dev, adxrs290_chip_off_action, |
| 278 | indio_dev); |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 279 | } |
| 280 | |
| 281 | static int adxrs290_read_raw(struct iio_dev *indio_dev, |
| 282 | struct iio_chan_spec const *chan, |
| 283 | int *val, |
| 284 | int *val2, |
| 285 | long mask) |
| 286 | { |
| 287 | struct adxrs290_state *st = iio_priv(indio_dev); |
| 288 | unsigned int t; |
| 289 | int ret; |
| 290 | |
| 291 | switch (mask) { |
| 292 | case IIO_CHAN_INFO_RAW: |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 293 | ret = iio_device_claim_direct_mode(indio_dev); |
| 294 | if (ret) |
| 295 | return ret; |
| 296 | |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 297 | switch (chan->type) { |
| 298 | case IIO_ANGL_VEL: |
| 299 | ret = adxrs290_get_rate_data(indio_dev, |
| 300 | ADXRS290_READ_REG(chan->address), |
| 301 | val); |
| 302 | if (ret < 0) |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 303 | break; |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 304 | |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 305 | ret = IIO_VAL_INT; |
| 306 | break; |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 307 | case IIO_TEMP: |
| 308 | ret = adxrs290_get_temp_data(indio_dev, val); |
| 309 | if (ret < 0) |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 310 | break; |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 311 | |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 312 | ret = IIO_VAL_INT; |
| 313 | break; |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 314 | default: |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 315 | ret = -EINVAL; |
| 316 | break; |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 317 | } |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 318 | |
| 319 | iio_device_release_direct_mode(indio_dev); |
| 320 | return ret; |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 321 | case IIO_CHAN_INFO_SCALE: |
| 322 | switch (chan->type) { |
| 323 | case IIO_ANGL_VEL: |
| 324 | /* 1 LSB = 0.005 degrees/sec */ |
| 325 | *val = 0; |
| 326 | *val2 = 87266; |
| 327 | return IIO_VAL_INT_PLUS_NANO; |
| 328 | case IIO_TEMP: |
| 329 | /* 1 LSB = 0.1 degrees Celsius */ |
| 330 | *val = 100; |
| 331 | return IIO_VAL_INT; |
| 332 | default: |
| 333 | return -EINVAL; |
| 334 | } |
| 335 | case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: |
| 336 | switch (chan->type) { |
| 337 | case IIO_ANGL_VEL: |
| 338 | t = st->lpf_3db_freq_idx; |
| 339 | *val = adxrs290_lpf_3db_freq_hz_table[t][0]; |
| 340 | *val2 = adxrs290_lpf_3db_freq_hz_table[t][1]; |
| 341 | return IIO_VAL_INT_PLUS_MICRO; |
| 342 | default: |
| 343 | return -EINVAL; |
| 344 | } |
| 345 | case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: |
| 346 | switch (chan->type) { |
| 347 | case IIO_ANGL_VEL: |
| 348 | t = st->hpf_3db_freq_idx; |
| 349 | *val = adxrs290_hpf_3db_freq_hz_table[t][0]; |
| 350 | *val2 = adxrs290_hpf_3db_freq_hz_table[t][1]; |
| 351 | return IIO_VAL_INT_PLUS_MICRO; |
| 352 | default: |
| 353 | return -EINVAL; |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | return -EINVAL; |
| 358 | } |
| 359 | |
| 360 | static int adxrs290_write_raw(struct iio_dev *indio_dev, |
| 361 | struct iio_chan_spec const *chan, |
| 362 | int val, |
| 363 | int val2, |
| 364 | long mask) |
| 365 | { |
| 366 | struct adxrs290_state *st = iio_priv(indio_dev); |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 367 | int ret, lpf_idx, hpf_idx; |
| 368 | |
| 369 | ret = iio_device_claim_direct_mode(indio_dev); |
| 370 | if (ret) |
| 371 | return ret; |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 372 | |
| 373 | switch (mask) { |
| 374 | case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: |
| 375 | lpf_idx = adxrs290_find_match(adxrs290_lpf_3db_freq_hz_table, |
| 376 | ARRAY_SIZE(adxrs290_lpf_3db_freq_hz_table), |
| 377 | val, val2); |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 378 | if (lpf_idx < 0) { |
| 379 | ret = -EINVAL; |
| 380 | break; |
| 381 | } |
| 382 | |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 383 | /* caching the updated state of the low-pass filter */ |
| 384 | st->lpf_3db_freq_idx = lpf_idx; |
| 385 | /* retrieving the current state of the high-pass filter */ |
| 386 | hpf_idx = st->hpf_3db_freq_idx; |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 387 | ret = adxrs290_set_filter_freq(indio_dev, lpf_idx, hpf_idx); |
| 388 | break; |
| 389 | |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 390 | case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: |
| 391 | hpf_idx = adxrs290_find_match(adxrs290_hpf_3db_freq_hz_table, |
| 392 | ARRAY_SIZE(adxrs290_hpf_3db_freq_hz_table), |
| 393 | val, val2); |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 394 | if (hpf_idx < 0) { |
| 395 | ret = -EINVAL; |
| 396 | break; |
| 397 | } |
| 398 | |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 399 | /* caching the updated state of the high-pass filter */ |
| 400 | st->hpf_3db_freq_idx = hpf_idx; |
| 401 | /* retrieving the current state of the low-pass filter */ |
| 402 | lpf_idx = st->lpf_3db_freq_idx; |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 403 | ret = adxrs290_set_filter_freq(indio_dev, lpf_idx, hpf_idx); |
| 404 | break; |
| 405 | |
| 406 | default: |
| 407 | ret = -EINVAL; |
| 408 | break; |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 409 | } |
| 410 | |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 411 | iio_device_release_direct_mode(indio_dev); |
| 412 | return ret; |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 413 | } |
| 414 | |
| 415 | static int adxrs290_read_avail(struct iio_dev *indio_dev, |
| 416 | struct iio_chan_spec const *chan, |
| 417 | const int **vals, int *type, int *length, |
| 418 | long mask) |
| 419 | { |
| 420 | switch (mask) { |
| 421 | case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: |
| 422 | *vals = (const int *)adxrs290_lpf_3db_freq_hz_table; |
| 423 | *type = IIO_VAL_INT_PLUS_MICRO; |
| 424 | /* Values are stored in a 2D matrix */ |
| 425 | *length = ARRAY_SIZE(adxrs290_lpf_3db_freq_hz_table) * 2; |
| 426 | |
| 427 | return IIO_AVAIL_LIST; |
| 428 | case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: |
| 429 | *vals = (const int *)adxrs290_hpf_3db_freq_hz_table; |
| 430 | *type = IIO_VAL_INT_PLUS_MICRO; |
| 431 | /* Values are stored in a 2D matrix */ |
| 432 | *length = ARRAY_SIZE(adxrs290_hpf_3db_freq_hz_table) * 2; |
| 433 | |
| 434 | return IIO_AVAIL_LIST; |
| 435 | default: |
| 436 | return -EINVAL; |
| 437 | } |
| 438 | } |
| 439 | |
Nishant Malpani | d3e0e14 | 2020-09-10 23:34:50 +0530 | [diff] [blame] | 440 | static int adxrs290_reg_access_rw(struct spi_device *spi, unsigned int reg, |
| 441 | unsigned int *readval) |
| 442 | { |
| 443 | int ret; |
| 444 | |
| 445 | ret = spi_w8r8(spi, ADXRS290_READ_REG(reg)); |
| 446 | if (ret < 0) |
| 447 | return ret; |
| 448 | |
| 449 | *readval = ret; |
| 450 | |
| 451 | return 0; |
| 452 | } |
| 453 | |
| 454 | static int adxrs290_reg_access(struct iio_dev *indio_dev, unsigned int reg, |
| 455 | unsigned int writeval, unsigned int *readval) |
| 456 | { |
| 457 | struct adxrs290_state *st = iio_priv(indio_dev); |
| 458 | |
| 459 | if (readval) |
| 460 | return adxrs290_reg_access_rw(st->spi, reg, readval); |
| 461 | else |
| 462 | return adxrs290_spi_write_reg(st->spi, reg, writeval); |
| 463 | } |
| 464 | |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 465 | static int adxrs290_data_rdy_trigger_set_state(struct iio_trigger *trig, |
| 466 | bool state) |
| 467 | { |
| 468 | struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); |
| 469 | struct adxrs290_state *st = iio_priv(indio_dev); |
| 470 | int ret; |
| 471 | u8 val; |
| 472 | |
| 473 | val = state ? ADXRS290_SYNC(ADXRS290_DATA_RDY_OUT) : 0; |
| 474 | |
| 475 | ret = adxrs290_spi_write_reg(st->spi, ADXRS290_REG_DATA_RDY, val); |
| 476 | if (ret < 0) |
| 477 | dev_err(&st->spi->dev, "failed to start data rdy interrupt\n"); |
| 478 | |
| 479 | return ret; |
| 480 | } |
| 481 | |
| 482 | static int adxrs290_reset_trig(struct iio_trigger *trig) |
| 483 | { |
| 484 | struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); |
| 485 | int val; |
| 486 | |
| 487 | /* |
| 488 | * Data ready interrupt is reset after a read of the data registers. |
| 489 | * Here, we only read the 16b DATAY registers as that marks the end of |
| 490 | * a read of the data registers and initiates a reset for the interrupt |
| 491 | * line. |
| 492 | */ |
| 493 | adxrs290_get_rate_data(indio_dev, |
| 494 | ADXRS290_READ_REG(ADXRS290_REG_DATAY0), &val); |
| 495 | |
| 496 | return 0; |
| 497 | } |
| 498 | |
| 499 | static const struct iio_trigger_ops adxrs290_trigger_ops = { |
| 500 | .set_trigger_state = &adxrs290_data_rdy_trigger_set_state, |
| 501 | .validate_device = &iio_trigger_validate_own_device, |
| 502 | .try_reenable = &adxrs290_reset_trig, |
| 503 | }; |
| 504 | |
| 505 | static irqreturn_t adxrs290_trigger_handler(int irq, void *p) |
| 506 | { |
| 507 | struct iio_poll_func *pf = p; |
| 508 | struct iio_dev *indio_dev = pf->indio_dev; |
| 509 | struct adxrs290_state *st = iio_priv(indio_dev); |
| 510 | u8 tx = ADXRS290_READ_REG(ADXRS290_REG_DATAX0); |
| 511 | int ret; |
| 512 | |
| 513 | mutex_lock(&st->lock); |
| 514 | |
| 515 | /* exercise a bulk data capture starting from reg DATAX0... */ |
| 516 | ret = spi_write_then_read(st->spi, &tx, sizeof(tx), st->buffer.channels, |
| 517 | sizeof(st->buffer.channels)); |
| 518 | if (ret < 0) |
| 519 | goto out_unlock_notify; |
| 520 | |
| 521 | iio_push_to_buffers_with_timestamp(indio_dev, &st->buffer, |
| 522 | pf->timestamp); |
| 523 | |
| 524 | out_unlock_notify: |
| 525 | mutex_unlock(&st->lock); |
| 526 | iio_trigger_notify_done(indio_dev->trig); |
| 527 | |
| 528 | return IRQ_HANDLED; |
| 529 | } |
| 530 | |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 531 | #define ADXRS290_ANGL_VEL_CHANNEL(reg, axis) { \ |
| 532 | .type = IIO_ANGL_VEL, \ |
| 533 | .address = reg, \ |
| 534 | .modified = 1, \ |
| 535 | .channel2 = IIO_MOD_##axis, \ |
| 536 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
| 537 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ |
| 538 | BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ |
| 539 | BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \ |
| 540 | .info_mask_shared_by_type_available = \ |
| 541 | BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ |
| 542 | BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \ |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 543 | .scan_index = ADXRS290_IDX_##axis, \ |
| 544 | .scan_type = { \ |
| 545 | .sign = 's', \ |
| 546 | .realbits = 16, \ |
| 547 | .storagebits = 16, \ |
| 548 | .endianness = IIO_LE, \ |
| 549 | }, \ |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 550 | } |
| 551 | |
| 552 | static const struct iio_chan_spec adxrs290_channels[] = { |
| 553 | ADXRS290_ANGL_VEL_CHANNEL(ADXRS290_REG_DATAX0, X), |
| 554 | ADXRS290_ANGL_VEL_CHANNEL(ADXRS290_REG_DATAY0, Y), |
| 555 | { |
| 556 | .type = IIO_TEMP, |
| 557 | .address = ADXRS290_REG_TEMP0, |
| 558 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
| 559 | BIT(IIO_CHAN_INFO_SCALE), |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 560 | .scan_index = ADXRS290_IDX_TEMP, |
| 561 | .scan_type = { |
| 562 | .sign = 's', |
| 563 | .realbits = 12, |
| 564 | .storagebits = 16, |
| 565 | .endianness = IIO_LE, |
| 566 | }, |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 567 | }, |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 568 | IIO_CHAN_SOFT_TIMESTAMP(ADXRS290_IDX_TS), |
| 569 | }; |
| 570 | |
| 571 | static const unsigned long adxrs290_avail_scan_masks[] = { |
| 572 | BIT(ADXRS290_IDX_X) | BIT(ADXRS290_IDX_Y) | BIT(ADXRS290_IDX_TEMP), |
| 573 | 0 |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 574 | }; |
| 575 | |
| 576 | static const struct iio_info adxrs290_info = { |
| 577 | .read_raw = &adxrs290_read_raw, |
| 578 | .write_raw = &adxrs290_write_raw, |
| 579 | .read_avail = &adxrs290_read_avail, |
Nishant Malpani | d3e0e14 | 2020-09-10 23:34:50 +0530 | [diff] [blame] | 580 | .debugfs_reg_access = &adxrs290_reg_access, |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 581 | }; |
| 582 | |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 583 | static int adxrs290_probe_trigger(struct iio_dev *indio_dev) |
| 584 | { |
| 585 | struct adxrs290_state *st = iio_priv(indio_dev); |
| 586 | int ret; |
| 587 | |
| 588 | if (!st->spi->irq) { |
| 589 | dev_info(&st->spi->dev, "no irq, using polling\n"); |
| 590 | return 0; |
| 591 | } |
| 592 | |
| 593 | st->dready_trig = devm_iio_trigger_alloc(&st->spi->dev, "%s-dev%d", |
| 594 | indio_dev->name, |
| 595 | indio_dev->id); |
| 596 | if (!st->dready_trig) |
| 597 | return -ENOMEM; |
| 598 | |
| 599 | st->dready_trig->dev.parent = &st->spi->dev; |
| 600 | st->dready_trig->ops = &adxrs290_trigger_ops; |
| 601 | iio_trigger_set_drvdata(st->dready_trig, indio_dev); |
| 602 | |
| 603 | ret = devm_request_irq(&st->spi->dev, st->spi->irq, |
| 604 | &iio_trigger_generic_data_rdy_poll, |
| 605 | IRQF_ONESHOT, "adxrs290_irq", st->dready_trig); |
| 606 | if (ret < 0) |
| 607 | return dev_err_probe(&st->spi->dev, ret, |
| 608 | "request irq %d failed\n", st->spi->irq); |
| 609 | |
| 610 | ret = devm_iio_trigger_register(&st->spi->dev, st->dready_trig); |
| 611 | if (ret) { |
| 612 | dev_err(&st->spi->dev, "iio trigger register failed\n"); |
| 613 | return ret; |
| 614 | } |
| 615 | |
| 616 | indio_dev->trig = iio_trigger_get(st->dready_trig); |
| 617 | |
| 618 | return 0; |
| 619 | } |
| 620 | |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 621 | static int adxrs290_probe(struct spi_device *spi) |
| 622 | { |
| 623 | struct iio_dev *indio_dev; |
| 624 | struct adxrs290_state *st; |
| 625 | u8 val, val2; |
| 626 | int ret; |
| 627 | |
| 628 | indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); |
| 629 | if (!indio_dev) |
| 630 | return -ENOMEM; |
| 631 | |
| 632 | st = iio_priv(indio_dev); |
| 633 | st->spi = spi; |
| 634 | |
| 635 | indio_dev->name = "adxrs290"; |
| 636 | indio_dev->modes = INDIO_DIRECT_MODE; |
| 637 | indio_dev->channels = adxrs290_channels; |
| 638 | indio_dev->num_channels = ARRAY_SIZE(adxrs290_channels); |
| 639 | indio_dev->info = &adxrs290_info; |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 640 | indio_dev->available_scan_masks = adxrs290_avail_scan_masks; |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 641 | |
Nishant Malpani | 7e604a3 | 2020-08-25 18:15:52 +0530 | [diff] [blame] | 642 | mutex_init(&st->lock); |
| 643 | |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 644 | val = spi_w8r8(spi, ADXRS290_READ_REG(ADXRS290_REG_ADI_ID)); |
| 645 | if (val != ADXRS290_ADI_ID) { |
| 646 | dev_err(&spi->dev, "Wrong ADI ID 0x%02x\n", val); |
| 647 | return -ENODEV; |
| 648 | } |
| 649 | |
| 650 | val = spi_w8r8(spi, ADXRS290_READ_REG(ADXRS290_REG_MEMS_ID)); |
| 651 | if (val != ADXRS290_MEMS_ID) { |
| 652 | dev_err(&spi->dev, "Wrong MEMS ID 0x%02x\n", val); |
| 653 | return -ENODEV; |
| 654 | } |
| 655 | |
| 656 | val = spi_w8r8(spi, ADXRS290_READ_REG(ADXRS290_REG_DEV_ID)); |
| 657 | if (val != ADXRS290_DEV_ID) { |
| 658 | dev_err(&spi->dev, "Wrong DEV ID 0x%02x\n", val); |
| 659 | return -ENODEV; |
| 660 | } |
| 661 | |
| 662 | /* default mode the gyroscope starts in */ |
| 663 | st->mode = ADXRS290_MODE_STANDBY; |
| 664 | |
| 665 | /* switch to measurement mode and switch on the temperature sensor */ |
| 666 | ret = adxrs290_initial_setup(indio_dev); |
| 667 | if (ret < 0) |
| 668 | return ret; |
| 669 | |
| 670 | /* max transition time to measurement mode */ |
| 671 | msleep(ADXRS290_MAX_TRANSITION_TIME_MS); |
| 672 | |
| 673 | ret = adxrs290_get_3db_freq(indio_dev, &val, &val2); |
| 674 | if (ret < 0) |
| 675 | return ret; |
| 676 | |
| 677 | st->lpf_3db_freq_idx = val; |
| 678 | st->hpf_3db_freq_idx = val2; |
| 679 | |
Nishant Malpani | 6725558 | 2020-09-10 23:34:48 +0530 | [diff] [blame] | 680 | ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, |
| 681 | &iio_pollfunc_store_time, |
| 682 | &adxrs290_trigger_handler, NULL); |
| 683 | if (ret < 0) |
| 684 | return dev_err_probe(&spi->dev, ret, |
| 685 | "iio triggered buffer setup failed\n"); |
| 686 | |
| 687 | ret = adxrs290_probe_trigger(indio_dev); |
| 688 | if (ret < 0) |
| 689 | return ret; |
| 690 | |
Nishant Malpani | 2c8920f | 2020-07-26 19:40:16 +0530 | [diff] [blame] | 691 | return devm_iio_device_register(&spi->dev, indio_dev); |
| 692 | } |
| 693 | |
| 694 | static const struct of_device_id adxrs290_of_match[] = { |
| 695 | { .compatible = "adi,adxrs290" }, |
| 696 | { } |
| 697 | }; |
| 698 | MODULE_DEVICE_TABLE(of, adxrs290_of_match); |
| 699 | |
| 700 | static struct spi_driver adxrs290_driver = { |
| 701 | .driver = { |
| 702 | .name = "adxrs290", |
| 703 | .of_match_table = adxrs290_of_match, |
| 704 | }, |
| 705 | .probe = adxrs290_probe, |
| 706 | }; |
| 707 | module_spi_driver(adxrs290_driver); |
| 708 | |
| 709 | MODULE_AUTHOR("Nishant Malpani <nish.malpani25@gmail.com>"); |
| 710 | MODULE_DESCRIPTION("Analog Devices ADXRS290 Gyroscope SPI driver"); |
| 711 | MODULE_LICENSE("GPL"); |