| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Azoteq IQS624/625 Angular Position Sensors |
| * |
| * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com> |
| */ |
| |
| #include <linux/device.h> |
| #include <linux/iio/events.h> |
| #include <linux/iio/iio.h> |
| #include <linux/kernel.h> |
| #include <linux/mfd/iqs62x.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/notifier.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| |
| #define IQS624_POS_DEG_OUT 0x16 |
| |
| #define IQS624_POS_SCALE1 (314159 / 180) |
| #define IQS624_POS_SCALE2 100000 |
| |
| struct iqs624_pos_private { |
| struct iqs62x_core *iqs62x; |
| struct notifier_block notifier; |
| struct mutex lock; |
| bool angle_en; |
| u16 angle; |
| }; |
| |
| static int iqs624_pos_angle_en(struct iqs62x_core *iqs62x, bool angle_en) |
| { |
| unsigned int event_mask = IQS624_HALL_UI_WHL_EVENT; |
| |
| /* |
| * The IQS625 reports angular position in the form of coarse intervals, |
| * so only interval change events are unmasked. Conversely, the IQS624 |
| * reports angular position down to one degree of resolution, so wheel |
| * movement events are unmasked instead. |
| */ |
| if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) |
| event_mask = IQS624_HALL_UI_INT_EVENT; |
| |
| return regmap_update_bits(iqs62x->regmap, IQS624_HALL_UI, event_mask, |
| angle_en ? 0 : 0xFF); |
| } |
| |
| static int iqs624_pos_notifier(struct notifier_block *notifier, |
| unsigned long event_flags, void *context) |
| { |
| struct iqs62x_event_data *event_data = context; |
| struct iqs624_pos_private *iqs624_pos; |
| struct iqs62x_core *iqs62x; |
| struct iio_dev *indio_dev; |
| u16 angle = event_data->ui_data; |
| s64 timestamp; |
| int ret; |
| |
| iqs624_pos = container_of(notifier, struct iqs624_pos_private, |
| notifier); |
| indio_dev = iio_priv_to_dev(iqs624_pos); |
| timestamp = iio_get_time_ns(indio_dev); |
| |
| iqs62x = iqs624_pos->iqs62x; |
| if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) |
| angle = event_data->interval; |
| |
| mutex_lock(&iqs624_pos->lock); |
| |
| if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { |
| ret = iqs624_pos_angle_en(iqs62x, iqs624_pos->angle_en); |
| if (ret) { |
| dev_err(indio_dev->dev.parent, |
| "Failed to re-initialize device: %d\n", ret); |
| ret = NOTIFY_BAD; |
| } else { |
| ret = NOTIFY_OK; |
| } |
| } else if (iqs624_pos->angle_en && (angle != iqs624_pos->angle)) { |
| iio_push_event(indio_dev, |
| IIO_UNMOD_EVENT_CODE(IIO_ANGL, 0, |
| IIO_EV_TYPE_CHANGE, |
| IIO_EV_DIR_NONE), |
| timestamp); |
| |
| iqs624_pos->angle = angle; |
| ret = NOTIFY_OK; |
| } else { |
| ret = NOTIFY_DONE; |
| } |
| |
| mutex_unlock(&iqs624_pos->lock); |
| |
| return ret; |
| } |
| |
| static void iqs624_pos_notifier_unregister(void *context) |
| { |
| struct iqs624_pos_private *iqs624_pos = context; |
| struct iio_dev *indio_dev = iio_priv_to_dev(iqs624_pos); |
| int ret; |
| |
| ret = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh, |
| &iqs624_pos->notifier); |
| if (ret) |
| dev_err(indio_dev->dev.parent, |
| "Failed to unregister notifier: %d\n", ret); |
| } |
| |
| static int iqs624_pos_angle_get(struct iqs62x_core *iqs62x, unsigned int *val) |
| { |
| int ret; |
| __le16 val_buf; |
| |
| if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) |
| return regmap_read(iqs62x->regmap, iqs62x->dev_desc->interval, |
| val); |
| |
| ret = regmap_raw_read(iqs62x->regmap, IQS624_POS_DEG_OUT, &val_buf, |
| sizeof(val_buf)); |
| if (ret) |
| return ret; |
| |
| *val = le16_to_cpu(val_buf); |
| |
| return 0; |
| } |
| |
| static int iqs624_pos_read_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, |
| int *val, int *val2, long mask) |
| { |
| struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); |
| struct iqs62x_core *iqs62x = iqs624_pos->iqs62x; |
| unsigned int scale = 1; |
| int ret; |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_RAW: |
| ret = iqs624_pos_angle_get(iqs62x, val); |
| if (ret) |
| return ret; |
| |
| return IIO_VAL_INT; |
| |
| case IIO_CHAN_INFO_SCALE: |
| if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) { |
| ret = regmap_read(iqs62x->regmap, IQS624_INTERVAL_DIV, |
| &scale); |
| if (ret) |
| return ret; |
| } |
| |
| *val = scale * IQS624_POS_SCALE1; |
| *val2 = IQS624_POS_SCALE2; |
| return IIO_VAL_FRACTIONAL; |
| |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int 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) |
| { |
| struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); |
| int ret; |
| |
| mutex_lock(&iqs624_pos->lock); |
| ret = iqs624_pos->angle_en; |
| mutex_unlock(&iqs624_pos->lock); |
| |
| return ret; |
| } |
| |
| static int 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) |
| { |
| struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); |
| struct iqs62x_core *iqs62x = iqs624_pos->iqs62x; |
| unsigned int val; |
| int ret; |
| |
| mutex_lock(&iqs624_pos->lock); |
| |
| ret = iqs624_pos_angle_get(iqs62x, &val); |
| if (ret) |
| goto err_mutex; |
| |
| ret = iqs624_pos_angle_en(iqs62x, state); |
| if (ret) |
| goto err_mutex; |
| |
| iqs624_pos->angle = val; |
| iqs624_pos->angle_en = state; |
| |
| err_mutex: |
| mutex_unlock(&iqs624_pos->lock); |
| |
| return ret; |
| } |
| |
| static const struct iio_info iqs624_pos_info = { |
| .read_raw = &iqs624_pos_read_raw, |
| .read_event_config = iqs624_pos_read_event_config, |
| .write_event_config = iqs624_pos_write_event_config, |
| }; |
| |
| static const struct iio_event_spec iqs624_pos_events[] = { |
| { |
| .type = IIO_EV_TYPE_CHANGE, |
| .dir = IIO_EV_DIR_NONE, |
| .mask_separate = BIT(IIO_EV_INFO_ENABLE), |
| }, |
| }; |
| |
| static const struct iio_chan_spec iqs624_pos_channels[] = { |
| { |
| .type = IIO_ANGL, |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
| BIT(IIO_CHAN_INFO_SCALE), |
| .event_spec = iqs624_pos_events, |
| .num_event_specs = ARRAY_SIZE(iqs624_pos_events), |
| }, |
| }; |
| |
| static int iqs624_pos_probe(struct platform_device *pdev) |
| { |
| struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); |
| struct iqs624_pos_private *iqs624_pos; |
| struct iio_dev *indio_dev; |
| int ret; |
| |
| indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs624_pos)); |
| if (!indio_dev) |
| return -ENOMEM; |
| |
| iqs624_pos = iio_priv(indio_dev); |
| iqs624_pos->iqs62x = iqs62x; |
| |
| indio_dev->modes = INDIO_DIRECT_MODE; |
| indio_dev->dev.parent = &pdev->dev; |
| indio_dev->channels = iqs624_pos_channels; |
| indio_dev->num_channels = ARRAY_SIZE(iqs624_pos_channels); |
| indio_dev->name = iqs62x->dev_desc->dev_name; |
| indio_dev->info = &iqs624_pos_info; |
| |
| mutex_init(&iqs624_pos->lock); |
| |
| iqs624_pos->notifier.notifier_call = iqs624_pos_notifier; |
| ret = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh, |
| &iqs624_pos->notifier); |
| if (ret) { |
| dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); |
| return ret; |
| } |
| |
| ret = devm_add_action_or_reset(&pdev->dev, |
| iqs624_pos_notifier_unregister, |
| iqs624_pos); |
| if (ret) |
| return ret; |
| |
| return devm_iio_device_register(&pdev->dev, indio_dev); |
| } |
| |
| static struct platform_driver iqs624_pos_platform_driver = { |
| .driver = { |
| .name = "iqs624-pos", |
| }, |
| .probe = iqs624_pos_probe, |
| }; |
| module_platform_driver(iqs624_pos_platform_driver); |
| |
| MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); |
| MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensors"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("platform:iqs624-pos"); |