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 iio_dev *indio_dev;
27 struct notifier_block notifier;
28 struct mutex lock;
29 bool angle_en;
30 u16 angle;
31 };
32
iqs624_pos_angle_en(struct iqs62x_core * iqs62x,bool angle_en)33 static int iqs624_pos_angle_en(struct iqs62x_core *iqs62x, bool angle_en)
34 {
35 unsigned int event_mask = IQS624_HALL_UI_WHL_EVENT;
36
37 /*
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.
42 */
43 if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
44 event_mask = IQS624_HALL_UI_INT_EVENT;
45
46 return regmap_update_bits(iqs62x->regmap, IQS624_HALL_UI, event_mask,
47 angle_en ? 0 : 0xFF);
48 }
49
iqs624_pos_notifier(struct notifier_block * notifier,unsigned long event_flags,void * context)50 static int iqs624_pos_notifier(struct notifier_block *notifier,
51 unsigned long event_flags, void *context)
52 {
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;
58 s64 timestamp;
59 int ret;
60
61 iqs624_pos = container_of(notifier, struct iqs624_pos_private,
62 notifier);
63 indio_dev = iqs624_pos->indio_dev;
64 timestamp = iio_get_time_ns(indio_dev);
65
66 iqs62x = iqs624_pos->iqs62x;
67 if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
68 angle = event_data->interval;
69
70 mutex_lock(&iqs624_pos->lock);
71
72 if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
73 ret = iqs624_pos_angle_en(iqs62x, iqs624_pos->angle_en);
74 if (ret) {
75 dev_err(indio_dev->dev.parent,
76 "Failed to re-initialize device: %d\n", ret);
77 ret = NOTIFY_BAD;
78 } else {
79 ret = NOTIFY_OK;
80 }
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,
84 IIO_EV_TYPE_CHANGE,
85 IIO_EV_DIR_NONE),
86 timestamp);
87
88 iqs624_pos->angle = angle;
89 ret = NOTIFY_OK;
90 } else {
91 ret = NOTIFY_DONE;
92 }
93
94 mutex_unlock(&iqs624_pos->lock);
95
96 return ret;
97 }
98
iqs624_pos_notifier_unregister(void * context)99 static void iqs624_pos_notifier_unregister(void *context)
100 {
101 struct iqs624_pos_private *iqs624_pos = context;
102 struct iio_dev *indio_dev = iqs624_pos->indio_dev;
103 int ret;
104
105 ret = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh,
106 &iqs624_pos->notifier);
107 if (ret)
108 dev_err(indio_dev->dev.parent,
109 "Failed to unregister notifier: %d\n", ret);
110 }
111
iqs624_pos_angle_get(struct iqs62x_core * iqs62x,unsigned int * val)112 static int iqs624_pos_angle_get(struct iqs62x_core *iqs62x, unsigned int *val)
113 {
114 int ret;
115 __le16 val_buf;
116
117 if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM)
118 return regmap_read(iqs62x->regmap, iqs62x->dev_desc->interval,
119 val);
120
121 ret = regmap_raw_read(iqs62x->regmap, IQS624_POS_DEG_OUT, &val_buf,
122 sizeof(val_buf));
123 if (ret)
124 return ret;
125
126 *val = le16_to_cpu(val_buf);
127
128 return 0;
129 }
130
iqs624_pos_read_raw(struct iio_dev * indio_dev,struct iio_chan_spec const * chan,int * val,int * val2,long mask)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)
134 {
135 struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
136 struct iqs62x_core *iqs62x = iqs624_pos->iqs62x;
137 unsigned int scale = 1;
138 int ret;
139
140 switch (mask) {
141 case IIO_CHAN_INFO_RAW:
142 ret = iqs624_pos_angle_get(iqs62x, val);
143 if (ret)
144 return ret;
145
146 return IIO_VAL_INT;
147
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,
151 &scale);
152 if (ret)
153 return ret;
154 }
155
156 *val = scale * IQS624_POS_SCALE1;
157 *val2 = IQS624_POS_SCALE2;
158 return IIO_VAL_FRACTIONAL;
159
160 default:
161 return -EINVAL;
162 }
163 }
164
iqs624_pos_read_event_config(struct iio_dev * indio_dev,const struct iio_chan_spec * chan,enum iio_event_type type,enum iio_event_direction dir)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)
169 {
170 struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
171 int ret;
172
173 mutex_lock(&iqs624_pos->lock);
174 ret = iqs624_pos->angle_en;
175 mutex_unlock(&iqs624_pos->lock);
176
177 return ret;
178 }
179
iqs624_pos_write_event_config(struct iio_dev * indio_dev,const struct iio_chan_spec * chan,enum iio_event_type type,enum iio_event_direction dir,int state)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,
184 int state)
185 {
186 struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev);
187 struct iqs62x_core *iqs62x = iqs624_pos->iqs62x;
188 unsigned int val;
189 int ret;
190
191 mutex_lock(&iqs624_pos->lock);
192
193 ret = iqs624_pos_angle_get(iqs62x, &val);
194 if (ret)
195 goto err_mutex;
196
197 ret = iqs624_pos_angle_en(iqs62x, state);
198 if (ret)
199 goto err_mutex;
200
201 iqs624_pos->angle = val;
202 iqs624_pos->angle_en = state;
203
204 err_mutex:
205 mutex_unlock(&iqs624_pos->lock);
206
207 return ret;
208 }
209
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,
214 };
215
216 static const struct iio_event_spec iqs624_pos_events[] = {
217 {
218 .type = IIO_EV_TYPE_CHANGE,
219 .dir = IIO_EV_DIR_NONE,
220 .mask_separate = BIT(IIO_EV_INFO_ENABLE),
221 },
222 };
223
224 static const struct iio_chan_spec iqs624_pos_channels[] = {
225 {
226 .type = IIO_ANGL,
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),
231 },
232 };
233
iqs624_pos_probe(struct platform_device * pdev)234 static int iqs624_pos_probe(struct platform_device *pdev)
235 {
236 struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
237 struct iqs624_pos_private *iqs624_pos;
238 struct iio_dev *indio_dev;
239 int ret;
240
241 indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs624_pos));
242 if (!indio_dev)
243 return -ENOMEM;
244
245 iqs624_pos = iio_priv(indio_dev);
246 iqs624_pos->iqs62x = iqs62x;
247 iqs624_pos->indio_dev = indio_dev;
248
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;
254
255 mutex_init(&iqs624_pos->lock);
256
257 iqs624_pos->notifier.notifier_call = iqs624_pos_notifier;
258 ret = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh,
259 &iqs624_pos->notifier);
260 if (ret) {
261 dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
262 return ret;
263 }
264
265 ret = devm_add_action_or_reset(&pdev->dev,
266 iqs624_pos_notifier_unregister,
267 iqs624_pos);
268 if (ret)
269 return ret;
270
271 return devm_iio_device_register(&pdev->dev, indio_dev);
272 }
273
274 static struct platform_driver iqs624_pos_platform_driver = {
275 .driver = {
276 .name = "iqs624-pos",
277 },
278 .probe = iqs624_pos_probe,
279 };
280 module_platform_driver(iqs624_pos_platform_driver);
281
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");
286