xref: /linux/drivers/iio/position/iqs624-pos.c (revision 3d0fe49454652117522f60bfbefb978ba0e5300b)
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(iqs62x->regmap, IQS624_HALL_UI, event_mask,
47 				  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, 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 
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 
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 
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 
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 
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 
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