linux/drivers/iio/position/iqs624-pos.c
<<
>>
Prefs
   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
  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 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
  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