1 // SPDX-License-Identifier: GPL-2.0+
3 * Azoteq IQS624/625 Angular Position Sensors
5 * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
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>
19 #define IQS624_POS_DEG_OUT 0x16
21 #define IQS624_POS_SCALE1 (314159 / 180)
22 #define IQS624_POS_SCALE2 100000
24 struct iqs624_pos_private {
25 struct iqs62x_core *iqs62x;
26 struct iio_dev *indio_dev;
27 struct notifier_block notifier;
33 static int iqs624_pos_angle_en(struct iqs62x_core *iqs62x, bool angle_en)
35 unsigned int event_mask = IQS624_HALL_UI_WHL_EVENT;
38 * The IQS625 reports angular position in the form of coarse intervals,
39 * so only interval change events are unmasked. Conversely, the IQS624
40 * reports angular position down to one degree of resolution, so wheel
41 * movement events are unmasked instead.
43 if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
44 event_mask = IQS624_HALL_UI_INT_EVENT;
46 return regmap_update_bits(iqs62x->regmap, IQS624_HALL_UI, event_mask,
50 static int iqs624_pos_notifier(struct notifier_block *notifier,
51 unsigned long event_flags, void *context)
53 struct iqs62x_event_data *event_data = context;
54 struct iqs624_pos_private *iqs624_pos;
55 struct iqs62x_core *iqs62x;
56 struct iio_dev *indio_dev;
57 u16 angle = event_data->ui_data;
61 iqs624_pos = container_of(notifier, struct iqs624_pos_private,
63 indio_dev = iqs624_pos->indio_dev;
64 timestamp = iio_get_time_ns(indio_dev);
66 iqs62x = iqs624_pos->iqs62x;
67 if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
68 angle = event_data->interval;
70 mutex_lock(&iqs624_pos->lock);
72 if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
73 ret = iqs624_pos_angle_en(iqs62x, iqs624_pos->angle_en);
75 dev_err(indio_dev->dev.parent,
76 "Failed to re-initialize device: %d\n", ret);
81 } else if (iqs624_pos->angle_en && (angle != iqs624_pos->angle)) {
82 iio_push_event(indio_dev,
83 IIO_UNMOD_EVENT_CODE(IIO_ANGL, 0,
88 iqs624_pos->angle = angle;
94 mutex_unlock(&iqs624_pos->lock);
99 static void iqs624_pos_notifier_unregister(void *context)
101 struct iqs624_pos_private *iqs624_pos = context;
102 struct iio_dev *indio_dev = iqs624_pos->indio_dev;
105 ret = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh,
106 &iqs624_pos->notifier);
108 dev_err(indio_dev->dev.parent,
109 "Failed to unregister notifier: %d\n", ret);
112 static int iqs624_pos_angle_get(struct iqs62x_core *iqs62x, unsigned int *val)
117 if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
118 return regmap_read(iqs62x->regmap, iqs62x->dev_desc->interval,
121 ret = regmap_raw_read(iqs62x->regmap, IQS624_POS_DEG_OUT, &val_buf,
126 *val = le16_to_cpu(val_buf);
131 static int iqs624_pos_read_raw(struct iio_dev *indio_dev,
132 struct iio_chan_spec const *chan,
133 int *val, int *val2, long mask)
135 struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
136 struct iqs62x_core *iqs62x = iqs624_pos->iqs62x;
137 unsigned int scale = 1;
141 case IIO_CHAN_INFO_RAW:
142 ret = iqs624_pos_angle_get(iqs62x, val);
148 case IIO_CHAN_INFO_SCALE:
149 if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) {
150 ret = regmap_read(iqs62x->regmap, IQS624_INTERVAL_DIV,
156 *val = scale * IQS624_POS_SCALE1;
157 *val2 = IQS624_POS_SCALE2;
158 return IIO_VAL_FRACTIONAL;
165 static int iqs624_pos_read_event_config(struct iio_dev *indio_dev,
166 const struct iio_chan_spec *chan,
167 enum iio_event_type type,
168 enum iio_event_direction dir)
170 struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
173 mutex_lock(&iqs624_pos->lock);
174 ret = iqs624_pos->angle_en;
175 mutex_unlock(&iqs624_pos->lock);
180 static int iqs624_pos_write_event_config(struct iio_dev *indio_dev,
181 const struct iio_chan_spec *chan,
182 enum iio_event_type type,
183 enum iio_event_direction dir,
186 struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
187 struct iqs62x_core *iqs62x = iqs624_pos->iqs62x;
191 mutex_lock(&iqs624_pos->lock);
193 ret = iqs624_pos_angle_get(iqs62x, &val);
197 ret = iqs624_pos_angle_en(iqs62x, state);
201 iqs624_pos->angle = val;
202 iqs624_pos->angle_en = state;
205 mutex_unlock(&iqs624_pos->lock);
210 static const struct iio_info iqs624_pos_info = {
211 .read_raw = &iqs624_pos_read_raw,
212 .read_event_config = iqs624_pos_read_event_config,
213 .write_event_config = iqs624_pos_write_event_config,
216 static const struct iio_event_spec iqs624_pos_events[] = {
218 .type = IIO_EV_TYPE_CHANGE,
219 .dir = IIO_EV_DIR_NONE,
220 .mask_separate = BIT(IIO_EV_INFO_ENABLE),
224 static const struct iio_chan_spec iqs624_pos_channels[] = {
227 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
228 BIT(IIO_CHAN_INFO_SCALE),
229 .event_spec = iqs624_pos_events,
230 .num_event_specs = ARRAY_SIZE(iqs624_pos_events),
234 static int iqs624_pos_probe(struct platform_device *pdev)
236 struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
237 struct iqs624_pos_private *iqs624_pos;
238 struct iio_dev *indio_dev;
241 indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs624_pos));
245 iqs624_pos = iio_priv(indio_dev);
246 iqs624_pos->iqs62x = iqs62x;
247 iqs624_pos->indio_dev = indio_dev;
249 indio_dev->modes = INDIO_DIRECT_MODE;
250 indio_dev->channels = iqs624_pos_channels;
251 indio_dev->num_channels = ARRAY_SIZE(iqs624_pos_channels);
252 indio_dev->name = iqs62x->dev_desc->dev_name;
253 indio_dev->info = &iqs624_pos_info;
255 mutex_init(&iqs624_pos->lock);
257 iqs624_pos->notifier.notifier_call = iqs624_pos_notifier;
258 ret = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh,
259 &iqs624_pos->notifier);
261 dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
265 ret = devm_add_action_or_reset(&pdev->dev,
266 iqs624_pos_notifier_unregister,
271 return devm_iio_device_register(&pdev->dev, indio_dev);
274 static struct platform_driver iqs624_pos_platform_driver = {
276 .name = "iqs624-pos",
278 .probe = iqs624_pos_probe,
280 module_platform_driver(iqs624_pos_platform_driver);
282 MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
283 MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensors");
284 MODULE_LICENSE("GPL");
285 MODULE_ALIAS("platform:iqs624-pos");