mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
285 lines
7.0 KiB
285 lines
7.0 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
/* |
|
* Azoteq IQS624/625 Angular Position Sensors |
|
* |
|
* Copyright (C) 2019 Jeff LaBundy <[email protected]> |
|
*/ |
|
|
|
#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 iio_dev *indio_dev; |
|
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 = iqs624_pos->indio_dev; |
|
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 = iqs624_pos->indio_dev; |
|
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; |
|
iqs624_pos->indio_dev = indio_dev; |
|
|
|
indio_dev->modes = INDIO_DIRECT_MODE; |
|
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 <[email protected]>"); |
|
MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensors"); |
|
MODULE_LICENSE("GPL"); |
|
MODULE_ALIAS("platform:iqs624-pos");
|
|
|