root/drivers/iio/potentiometer/max5481.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Maxim Integrated MAX5481-MAX5484 digital potentiometer driver
 * Copyright 2016 Rockwell Collins
 *
 * Datasheet:
 * https://datasheets.maximintegrated.com/en/ds/MAX5481-MAX5484.pdf
 */

#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/property.h>
#include <linux/spi/spi.h>

/* write wiper reg */
#define MAX5481_WRITE_WIPER (0 << 4)
/* copy wiper reg to NV reg */
#define MAX5481_COPY_AB_TO_NV (2 << 4)
/* copy NV reg to wiper reg */
#define MAX5481_COPY_NV_TO_AB (3 << 4)

#define MAX5481_MAX_POS    1023

enum max5481_variant {
        max5481,
        max5482,
        max5483,
        max5484,
};

struct max5481_cfg {
        int kohms;
};

static const struct max5481_cfg max5481_cfg[] = {
        [max5481] = { .kohms =  10, },
        [max5482] = { .kohms =  50, },
        [max5483] = { .kohms =  10, },
        [max5484] = { .kohms =  50, },
};

struct max5481_data {
        struct spi_device *spi;
        const struct max5481_cfg *cfg;
        u8 msg[3] __aligned(IIO_DMA_MINALIGN);
};

#define MAX5481_CHANNEL {                                       \
        .type = IIO_RESISTANCE,                                 \
        .indexed = 1,                                           \
        .output = 1,                                            \
        .channel = 0,                                           \
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),           \
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),   \
}

static const struct iio_chan_spec max5481_channels[] = {
        MAX5481_CHANNEL,
};

static int max5481_write_cmd(struct max5481_data *data, u8 cmd, u16 val)
{
        struct spi_device *spi = data->spi;

        data->msg[0] = cmd;

        switch (cmd) {
        case MAX5481_WRITE_WIPER:
                data->msg[1] = val >> 2;
                data->msg[2] = (val & 0x3) << 6;
                return spi_write(spi, data->msg, 3);

        case MAX5481_COPY_AB_TO_NV:
        case MAX5481_COPY_NV_TO_AB:
                return spi_write(spi, data->msg, 1);

        default:
                return -EIO;
        }
}

static int max5481_read_raw(struct iio_dev *indio_dev,
                struct iio_chan_spec const *chan,
                int *val, int *val2, long mask)
{
        struct max5481_data *data = iio_priv(indio_dev);

        if (mask != IIO_CHAN_INFO_SCALE)
                return -EINVAL;

        *val = 1000 * data->cfg->kohms;
        *val2 = MAX5481_MAX_POS;

        return IIO_VAL_FRACTIONAL;
}

static int max5481_write_raw(struct iio_dev *indio_dev,
                struct iio_chan_spec const *chan,
                int val, int val2, long mask)
{
        struct max5481_data *data = iio_priv(indio_dev);

        if (mask != IIO_CHAN_INFO_RAW)
                return -EINVAL;

        if (val < 0 || val > MAX5481_MAX_POS)
                return -EINVAL;

        return max5481_write_cmd(data, MAX5481_WRITE_WIPER, val);
}

static const struct iio_info max5481_info = {
        .read_raw = max5481_read_raw,
        .write_raw = max5481_write_raw,
};

static const struct of_device_id max5481_match[] = {
        { .compatible = "maxim,max5481", .data = &max5481_cfg[max5481] },
        { .compatible = "maxim,max5482", .data = &max5481_cfg[max5482] },
        { .compatible = "maxim,max5483", .data = &max5481_cfg[max5483] },
        { .compatible = "maxim,max5484", .data = &max5481_cfg[max5484] },
        { }
};
MODULE_DEVICE_TABLE(of, max5481_match);

static void max5481_wiper_save(void *data)
{
        max5481_write_cmd(data, MAX5481_COPY_AB_TO_NV, 0);
}

static int max5481_probe(struct spi_device *spi)
{
        struct iio_dev *indio_dev;
        struct max5481_data *data;
        const struct spi_device_id *id = spi_get_device_id(spi);
        int ret;

        indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
        if (!indio_dev)
                return -ENOMEM;

        data = iio_priv(indio_dev);

        data->spi = spi;

        data->cfg = device_get_match_data(&spi->dev);
        if (!data->cfg)
                data->cfg = &max5481_cfg[id->driver_data];

        indio_dev->name = id->name;
        indio_dev->modes = INDIO_DIRECT_MODE;

        /* variant specific configuration */
        indio_dev->info = &max5481_info;
        indio_dev->channels = max5481_channels;
        indio_dev->num_channels = ARRAY_SIZE(max5481_channels);

        /* restore wiper from NV */
        ret = max5481_write_cmd(data, MAX5481_COPY_NV_TO_AB, 0);
        if (ret < 0)
                return ret;

        ret = devm_add_action(&spi->dev, max5481_wiper_save, data);
        if (ret < 0)
                return ret;

        return devm_iio_device_register(&spi->dev, indio_dev);
}

static const struct spi_device_id max5481_id_table[] = {
        { "max5481", max5481 },
        { "max5482", max5482 },
        { "max5483", max5483 },
        { "max5484", max5484 },
        { }
};
MODULE_DEVICE_TABLE(spi, max5481_id_table);

static struct spi_driver max5481_driver = {
        .driver = {
                .name  = "max5481",
                .of_match_table = max5481_match,
        },
        .probe = max5481_probe,
        .id_table = max5481_id_table,
};

module_spi_driver(max5481_driver);

MODULE_AUTHOR("Maury Anderson <maury.anderson@rockwellcollins.com>");
MODULE_DESCRIPTION("max5481 SPI driver");
MODULE_LICENSE("GPL v2");