Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

iio: position: Add support for Azoteq IQS624/625 angle sensors

This patch adds support for the Azoteq IQS624 and IQS625 angular position
sensors, capable of reporting the angle of a rotating shaft down to 1 and
10 degrees of accuracy, respectively.

This patch also introduces a home for linear and angular position sensors.
Unlike resolvers, they are typically contactless and use the Hall effect.

Signed-off-by: Jeff LaBundy <jeff@labundy.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Lee Jones <lee.jones@linaro.org>

authored by

Jeff LaBundy and committed by
Lee Jones
189c3c49 b081b738

+312
+1
drivers/iio/Kconfig
··· 88 88 if IIO_TRIGGER 89 89 source "drivers/iio/trigger/Kconfig" 90 90 endif #IIO_TRIGGER 91 + source "drivers/iio/position/Kconfig" 91 92 source "drivers/iio/potentiometer/Kconfig" 92 93 source "drivers/iio/potentiostat/Kconfig" 93 94 source "drivers/iio/pressure/Kconfig"
+1
drivers/iio/Makefile
··· 31 31 obj-y += magnetometer/ 32 32 obj-y += multiplexer/ 33 33 obj-y += orientation/ 34 + obj-y += position/ 34 35 obj-y += potentiometer/ 35 36 obj-y += potentiostat/ 36 37 obj-y += pressure/
+19
drivers/iio/position/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + # 3 + # Linear and angular position sensors 4 + # 5 + # When adding new entries keep the list in alphabetical order 6 + 7 + menu "Linear and angular position sensors" 8 + 9 + config IQS624_POS 10 + tristate "Azoteq IQS624/625 angular position sensors" 11 + depends on MFD_IQS62X || COMPILE_TEST 12 + help 13 + Say Y here if you want to build support for the Azoteq IQS624 14 + and IQS625 angular position sensors. 15 + 16 + To compile this driver as a module, choose M here: the module 17 + will be called iqs624-pos. 18 + 19 + endmenu
+7
drivers/iio/position/Makefile
··· 1 + # 2 + # Makefile for IIO linear and angular position sensors 3 + # 4 + 5 + # When adding new entries keep the list in alphabetical order 6 + 7 + obj-$(CONFIG_IQS624_POS) += iqs624-pos.o
+284
drivers/iio/position/iqs624-pos.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + /* 3 + * Azoteq IQS624/625 Angular Position Sensors 4 + * 5 + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com> 6 + */ 7 + 8 + #include <linux/device.h> 9 + #include <linux/iio/events.h> 10 + #include <linux/iio/iio.h> 11 + #include <linux/kernel.h> 12 + #include <linux/mfd/iqs62x.h> 13 + #include <linux/module.h> 14 + #include <linux/mutex.h> 15 + #include <linux/notifier.h> 16 + #include <linux/platform_device.h> 17 + #include <linux/regmap.h> 18 + 19 + #define IQS624_POS_DEG_OUT 0x16 20 + 21 + #define IQS624_POS_SCALE1 (314159 / 180) 22 + #define IQS624_POS_SCALE2 100000 23 + 24 + struct iqs624_pos_private { 25 + struct iqs62x_core *iqs62x; 26 + struct notifier_block notifier; 27 + struct mutex lock; 28 + bool angle_en; 29 + u16 angle; 30 + }; 31 + 32 + static int iqs624_pos_angle_en(struct iqs62x_core *iqs62x, bool angle_en) 33 + { 34 + unsigned int event_mask = IQS624_HALL_UI_WHL_EVENT; 35 + 36 + /* 37 + * The IQS625 reports angular position in the form of coarse intervals, 38 + * so only interval change events are unmasked. Conversely, the IQS624 39 + * reports angular position down to one degree of resolution, so wheel 40 + * movement events are unmasked instead. 41 + */ 42 + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) 43 + event_mask = IQS624_HALL_UI_INT_EVENT; 44 + 45 + return regmap_update_bits(iqs62x->regmap, IQS624_HALL_UI, event_mask, 46 + angle_en ? 0 : 0xFF); 47 + } 48 + 49 + static int iqs624_pos_notifier(struct notifier_block *notifier, 50 + unsigned long event_flags, void *context) 51 + { 52 + struct iqs62x_event_data *event_data = context; 53 + struct iqs624_pos_private *iqs624_pos; 54 + struct iqs62x_core *iqs62x; 55 + struct iio_dev *indio_dev; 56 + u16 angle = event_data->ui_data; 57 + s64 timestamp; 58 + int ret; 59 + 60 + iqs624_pos = container_of(notifier, struct iqs624_pos_private, 61 + notifier); 62 + indio_dev = iio_priv_to_dev(iqs624_pos); 63 + timestamp = iio_get_time_ns(indio_dev); 64 + 65 + iqs62x = iqs624_pos->iqs62x; 66 + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) 67 + angle = event_data->interval; 68 + 69 + mutex_lock(&iqs624_pos->lock); 70 + 71 + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { 72 + ret = iqs624_pos_angle_en(iqs62x, iqs624_pos->angle_en); 73 + if (ret) { 74 + dev_err(indio_dev->dev.parent, 75 + "Failed to re-initialize device: %d\n", ret); 76 + ret = NOTIFY_BAD; 77 + } else { 78 + ret = NOTIFY_OK; 79 + } 80 + } else if (iqs624_pos->angle_en && (angle != iqs624_pos->angle)) { 81 + iio_push_event(indio_dev, 82 + IIO_UNMOD_EVENT_CODE(IIO_ANGL, 0, 83 + IIO_EV_TYPE_CHANGE, 84 + IIO_EV_DIR_NONE), 85 + timestamp); 86 + 87 + iqs624_pos->angle = angle; 88 + ret = NOTIFY_OK; 89 + } else { 90 + ret = NOTIFY_DONE; 91 + } 92 + 93 + mutex_unlock(&iqs624_pos->lock); 94 + 95 + return ret; 96 + } 97 + 98 + static void iqs624_pos_notifier_unregister(void *context) 99 + { 100 + struct iqs624_pos_private *iqs624_pos = context; 101 + struct iio_dev *indio_dev = iio_priv_to_dev(iqs624_pos); 102 + int ret; 103 + 104 + ret = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh, 105 + &iqs624_pos->notifier); 106 + if (ret) 107 + dev_err(indio_dev->dev.parent, 108 + "Failed to unregister notifier: %d\n", ret); 109 + } 110 + 111 + static int iqs624_pos_angle_get(struct iqs62x_core *iqs62x, unsigned int *val) 112 + { 113 + int ret; 114 + __le16 val_buf; 115 + 116 + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) 117 + return regmap_read(iqs62x->regmap, iqs62x->dev_desc->interval, 118 + val); 119 + 120 + ret = regmap_raw_read(iqs62x->regmap, IQS624_POS_DEG_OUT, &val_buf, 121 + sizeof(val_buf)); 122 + if (ret) 123 + return ret; 124 + 125 + *val = le16_to_cpu(val_buf); 126 + 127 + return 0; 128 + } 129 + 130 + static int iqs624_pos_read_raw(struct iio_dev *indio_dev, 131 + struct iio_chan_spec const *chan, 132 + int *val, int *val2, long mask) 133 + { 134 + struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); 135 + struct iqs62x_core *iqs62x = iqs624_pos->iqs62x; 136 + unsigned int scale = 1; 137 + int ret; 138 + 139 + switch (mask) { 140 + case IIO_CHAN_INFO_RAW: 141 + ret = iqs624_pos_angle_get(iqs62x, val); 142 + if (ret) 143 + return ret; 144 + 145 + return IIO_VAL_INT; 146 + 147 + case IIO_CHAN_INFO_SCALE: 148 + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) { 149 + ret = regmap_read(iqs62x->regmap, IQS624_INTERVAL_DIV, 150 + &scale); 151 + if (ret) 152 + return ret; 153 + } 154 + 155 + *val = scale * IQS624_POS_SCALE1; 156 + *val2 = IQS624_POS_SCALE2; 157 + return IIO_VAL_FRACTIONAL; 158 + 159 + default: 160 + return -EINVAL; 161 + } 162 + } 163 + 164 + static int iqs624_pos_read_event_config(struct iio_dev *indio_dev, 165 + const struct iio_chan_spec *chan, 166 + enum iio_event_type type, 167 + enum iio_event_direction dir) 168 + { 169 + struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); 170 + int ret; 171 + 172 + mutex_lock(&iqs624_pos->lock); 173 + ret = iqs624_pos->angle_en; 174 + mutex_unlock(&iqs624_pos->lock); 175 + 176 + return ret; 177 + } 178 + 179 + static int iqs624_pos_write_event_config(struct iio_dev *indio_dev, 180 + const struct iio_chan_spec *chan, 181 + enum iio_event_type type, 182 + enum iio_event_direction dir, 183 + int state) 184 + { 185 + struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); 186 + struct iqs62x_core *iqs62x = iqs624_pos->iqs62x; 187 + unsigned int val; 188 + int ret; 189 + 190 + mutex_lock(&iqs624_pos->lock); 191 + 192 + ret = iqs624_pos_angle_get(iqs62x, &val); 193 + if (ret) 194 + goto err_mutex; 195 + 196 + ret = iqs624_pos_angle_en(iqs62x, state); 197 + if (ret) 198 + goto err_mutex; 199 + 200 + iqs624_pos->angle = val; 201 + iqs624_pos->angle_en = state; 202 + 203 + err_mutex: 204 + mutex_unlock(&iqs624_pos->lock); 205 + 206 + return ret; 207 + } 208 + 209 + static const struct iio_info iqs624_pos_info = { 210 + .read_raw = &iqs624_pos_read_raw, 211 + .read_event_config = iqs624_pos_read_event_config, 212 + .write_event_config = iqs624_pos_write_event_config, 213 + }; 214 + 215 + static const struct iio_event_spec iqs624_pos_events[] = { 216 + { 217 + .type = IIO_EV_TYPE_CHANGE, 218 + .dir = IIO_EV_DIR_NONE, 219 + .mask_separate = BIT(IIO_EV_INFO_ENABLE), 220 + }, 221 + }; 222 + 223 + static const struct iio_chan_spec iqs624_pos_channels[] = { 224 + { 225 + .type = IIO_ANGL, 226 + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | 227 + BIT(IIO_CHAN_INFO_SCALE), 228 + .event_spec = iqs624_pos_events, 229 + .num_event_specs = ARRAY_SIZE(iqs624_pos_events), 230 + }, 231 + }; 232 + 233 + static int iqs624_pos_probe(struct platform_device *pdev) 234 + { 235 + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); 236 + struct iqs624_pos_private *iqs624_pos; 237 + struct iio_dev *indio_dev; 238 + int ret; 239 + 240 + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs624_pos)); 241 + if (!indio_dev) 242 + return -ENOMEM; 243 + 244 + iqs624_pos = iio_priv(indio_dev); 245 + iqs624_pos->iqs62x = iqs62x; 246 + 247 + indio_dev->modes = INDIO_DIRECT_MODE; 248 + indio_dev->dev.parent = &pdev->dev; 249 + indio_dev->channels = iqs624_pos_channels; 250 + indio_dev->num_channels = ARRAY_SIZE(iqs624_pos_channels); 251 + indio_dev->name = iqs62x->dev_desc->dev_name; 252 + indio_dev->info = &iqs624_pos_info; 253 + 254 + mutex_init(&iqs624_pos->lock); 255 + 256 + iqs624_pos->notifier.notifier_call = iqs624_pos_notifier; 257 + ret = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh, 258 + &iqs624_pos->notifier); 259 + if (ret) { 260 + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); 261 + return ret; 262 + } 263 + 264 + ret = devm_add_action_or_reset(&pdev->dev, 265 + iqs624_pos_notifier_unregister, 266 + iqs624_pos); 267 + if (ret) 268 + return ret; 269 + 270 + return devm_iio_device_register(&pdev->dev, indio_dev); 271 + } 272 + 273 + static struct platform_driver iqs624_pos_platform_driver = { 274 + .driver = { 275 + .name = "iqs624-pos", 276 + }, 277 + .probe = iqs624_pos_probe, 278 + }; 279 + module_platform_driver(iqs624_pos_platform_driver); 280 + 281 + MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); 282 + MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensors"); 283 + MODULE_LICENSE("GPL"); 284 + MODULE_ALIAS("platform:iqs624-pos");