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