brintos

brintos / linux-shallow public Read only

0
0
Text · 7.0 KiB · 4d74523 Raw
286 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Azoteq IQS624/625 Angular Position Sensors4 *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			0x1620 21#define IQS624_POS_SCALE1			(314159 / 180)22#define IQS624_POS_SCALE2			10000023 24struct 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 33static 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 IQS62440	 * reports angular position down to one degree of resolution, so wheel41	 * 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 50static 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 99static 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 112static 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 131static 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 165static 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 180static 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 204err_mutex:205	mutex_unlock(&iqs624_pos->lock);206 207	return ret;208}209 210static 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 216static 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 224static 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 234static 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 274static struct platform_driver iqs624_pos_platform_driver = {275	.driver = {276		.name = "iqs624-pos",277	},278	.probe = iqs624_pos_probe,279};280module_platform_driver(iqs624_pos_platform_driver);281 282MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");283MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensors");284MODULE_LICENSE("GPL");285MODULE_ALIAS("platform:iqs624-pos");286