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 | |
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(map: iqs62x->regmap, IQS624_HALL_UI, mask: event_mask, |
47 | val: angle_en ? 0 : 0xFF); |
48 | } |
49 | |
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, angle_en: 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(lock: &iqs624_pos->lock); |
95 | |
96 | return ret; |
97 | } |
98 | |
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(nh: &iqs624_pos->iqs62x->nh, |
106 | nb: &iqs624_pos->notifier); |
107 | if (ret) |
108 | dev_err(indio_dev->dev.parent, |
109 | "Failed to unregister notifier: %d\n" , ret); |
110 | } |
111 | |
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(map: iqs62x->regmap, reg: iqs62x->dev_desc->interval, |
119 | val); |
120 | |
121 | ret = regmap_raw_read(map: iqs62x->regmap, IQS624_POS_DEG_OUT, val: &val_buf, |
122 | val_len: sizeof(val_buf)); |
123 | if (ret) |
124 | return ret; |
125 | |
126 | *val = le16_to_cpu(val_buf); |
127 | |
128 | return 0; |
129 | } |
130 | |
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(map: iqs62x->regmap, IQS624_INTERVAL_DIV, |
151 | val: &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 | |
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(lock: &iqs624_pos->lock); |
176 | |
177 | return ret; |
178 | } |
179 | |
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: &val); |
194 | if (ret) |
195 | goto err_mutex; |
196 | |
197 | ret = iqs624_pos_angle_en(iqs62x, angle_en: 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(lock: &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 | |
234 | static int iqs624_pos_probe(struct platform_device *pdev) |
235 | { |
236 | struct iqs62x_core *iqs62x = dev_get_drvdata(dev: 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(parent: &pdev->dev, sizeof_priv: 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(nh: &iqs624_pos->iqs62x->nh, |
259 | nb: &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 | |