linux/drivers/iio/potentiometer/mcp41010.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Industrial I/O driver for Microchip digital potentiometers
   4 *
   5 * Copyright (c) 2018 Chris Coffey <cmc@babblebit.net>
   6 * Based on: Slawomir Stepien's code from mcp4131.c
   7 *
   8 * Datasheet: https://ww1.microchip.com/downloads/en/devicedoc/11195c.pdf
   9 *
  10 * DEVID        #Wipers #Positions      Resistance (kOhm)
  11 * mcp41010     1       256             10
  12 * mcp41050     1       256             50
  13 * mcp41100     1       256             100
  14 * mcp42010     2       256             10
  15 * mcp42050     2       256             50
  16 * mcp42100     2       256             100
  17 */
  18
  19#include <linux/cache.h>
  20#include <linux/err.h>
  21#include <linux/iio/iio.h>
  22#include <linux/iio/types.h>
  23#include <linux/module.h>
  24#include <linux/mutex.h>
  25#include <linux/of.h>
  26#include <linux/of_device.h>
  27#include <linux/spi/spi.h>
  28
  29#define MCP41010_MAX_WIPERS     2
  30#define MCP41010_WRITE          BIT(4)
  31#define MCP41010_WIPER_MAX      255
  32#define MCP41010_WIPER_CHANNEL  BIT(0)
  33
  34struct mcp41010_cfg {
  35        char name[16];
  36        int wipers;
  37        int kohms;
  38};
  39
  40enum mcp41010_type {
  41        MCP41010,
  42        MCP41050,
  43        MCP41100,
  44        MCP42010,
  45        MCP42050,
  46        MCP42100,
  47};
  48
  49static const struct mcp41010_cfg mcp41010_cfg[] = {
  50        [MCP41010] = { .name = "mcp41010", .wipers = 1, .kohms =  10, },
  51        [MCP41050] = { .name = "mcp41050", .wipers = 1, .kohms =  50, },
  52        [MCP41100] = { .name = "mcp41100", .wipers = 1, .kohms = 100, },
  53        [MCP42010] = { .name = "mcp42010", .wipers = 2, .kohms =  10, },
  54        [MCP42050] = { .name = "mcp42050", .wipers = 2, .kohms =  50, },
  55        [MCP42100] = { .name = "mcp42100", .wipers = 2, .kohms = 100, },
  56};
  57
  58struct mcp41010_data {
  59        struct spi_device *spi;
  60        const struct mcp41010_cfg *cfg;
  61        struct mutex lock; /* Protect write sequences */
  62        unsigned int value[MCP41010_MAX_WIPERS]; /* Cache wiper values */
  63        u8 buf[2] ____cacheline_aligned;
  64};
  65
  66#define MCP41010_CHANNEL(ch) {                                  \
  67        .type = IIO_RESISTANCE,                                 \
  68        .indexed = 1,                                           \
  69        .output = 1,                                            \
  70        .channel = (ch),                                        \
  71        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),           \
  72        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),   \
  73}
  74
  75static const struct iio_chan_spec mcp41010_channels[] = {
  76        MCP41010_CHANNEL(0),
  77        MCP41010_CHANNEL(1),
  78};
  79
  80static int mcp41010_read_raw(struct iio_dev *indio_dev,
  81                            struct iio_chan_spec const *chan,
  82                            int *val, int *val2, long mask)
  83{
  84        struct mcp41010_data *data = iio_priv(indio_dev);
  85        int channel = chan->channel;
  86
  87        switch (mask) {
  88        case IIO_CHAN_INFO_RAW:
  89                *val = data->value[channel];
  90                return IIO_VAL_INT;
  91
  92        case IIO_CHAN_INFO_SCALE:
  93                *val = 1000 * data->cfg->kohms;
  94                *val2 = MCP41010_WIPER_MAX;
  95                return IIO_VAL_FRACTIONAL;
  96        }
  97
  98        return -EINVAL;
  99}
 100
 101static int mcp41010_write_raw(struct iio_dev *indio_dev,
 102                             struct iio_chan_spec const *chan,
 103                             int val, int val2, long mask)
 104{
 105        int err;
 106        struct mcp41010_data *data = iio_priv(indio_dev);
 107        int channel = chan->channel;
 108
 109        if (mask != IIO_CHAN_INFO_RAW)
 110                return -EINVAL;
 111
 112        if (val > MCP41010_WIPER_MAX || val < 0)
 113                return -EINVAL;
 114
 115        mutex_lock(&data->lock);
 116
 117        data->buf[0] = MCP41010_WIPER_CHANNEL << channel;
 118        data->buf[0] |= MCP41010_WRITE;
 119        data->buf[1] = val & 0xff;
 120
 121        err = spi_write(data->spi, data->buf, sizeof(data->buf));
 122        if (!err)
 123                data->value[channel] = val;
 124
 125        mutex_unlock(&data->lock);
 126
 127        return err;
 128}
 129
 130static const struct iio_info mcp41010_info = {
 131        .read_raw = mcp41010_read_raw,
 132        .write_raw = mcp41010_write_raw,
 133};
 134
 135static int mcp41010_probe(struct spi_device *spi)
 136{
 137        int err;
 138        struct device *dev = &spi->dev;
 139        struct mcp41010_data *data;
 140        struct iio_dev *indio_dev;
 141
 142        indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
 143        if (!indio_dev)
 144                return -ENOMEM;
 145
 146        data = iio_priv(indio_dev);
 147        spi_set_drvdata(spi, indio_dev);
 148        data->spi = spi;
 149        data->cfg = of_device_get_match_data(&spi->dev);
 150        if (!data->cfg)
 151                data->cfg = &mcp41010_cfg[spi_get_device_id(spi)->driver_data];
 152
 153        mutex_init(&data->lock);
 154
 155        indio_dev->info = &mcp41010_info;
 156        indio_dev->channels = mcp41010_channels;
 157        indio_dev->num_channels = data->cfg->wipers;
 158        indio_dev->name = data->cfg->name;
 159
 160        err = devm_iio_device_register(dev, indio_dev);
 161        if (err)
 162                dev_info(&spi->dev, "Unable to register %s\n", indio_dev->name);
 163
 164        return err;
 165}
 166
 167static const struct of_device_id mcp41010_match[] = {
 168        { .compatible = "microchip,mcp41010", .data = &mcp41010_cfg[MCP41010] },
 169        { .compatible = "microchip,mcp41050", .data = &mcp41010_cfg[MCP41050] },
 170        { .compatible = "microchip,mcp41100", .data = &mcp41010_cfg[MCP41100] },
 171        { .compatible = "microchip,mcp42010", .data = &mcp41010_cfg[MCP42010] },
 172        { .compatible = "microchip,mcp42050", .data = &mcp41010_cfg[MCP42050] },
 173        { .compatible = "microchip,mcp42100", .data = &mcp41010_cfg[MCP42100] },
 174        {}
 175};
 176MODULE_DEVICE_TABLE(of, mcp41010_match);
 177
 178static const struct spi_device_id mcp41010_id[] = {
 179        { "mcp41010", MCP41010 },
 180        { "mcp41050", MCP41050 },
 181        { "mcp41100", MCP41100 },
 182        { "mcp42010", MCP42010 },
 183        { "mcp42050", MCP42050 },
 184        { "mcp42100", MCP42100 },
 185        {}
 186};
 187MODULE_DEVICE_TABLE(spi, mcp41010_id);
 188
 189static struct spi_driver mcp41010_driver = {
 190        .driver = {
 191                .name   = "mcp41010",
 192                .of_match_table = mcp41010_match,
 193        },
 194        .probe          = mcp41010_probe,
 195        .id_table       = mcp41010_id,
 196};
 197
 198module_spi_driver(mcp41010_driver);
 199
 200MODULE_AUTHOR("Chris Coffey <cmc@babblebit.net>");
 201MODULE_DESCRIPTION("MCP41010 digital potentiometer");
 202MODULE_LICENSE("GPL v2");
 203