root/drivers/iio/potentiometer/x9250.c
// SPDX-License-Identifier: GPL-2.0
/*
 *
 * x9250.c  --  Renesas X9250 potentiometers IIO driver
 *
 * Copyright 2023 CS GROUP France
 *
 * Author: Herve Codina <herve.codina@bootlin.com>
 */

#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/iio/iio.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>

struct x9250_cfg {
        const char *name;
        int kohms;
};

struct x9250 {
        struct spi_device *spi;
        const struct x9250_cfg *cfg;
        struct gpio_desc *wp_gpio;
};

#define X9250_ID                0x50
#define X9250_CMD_RD_WCR(_p)    (0x90 | (_p))
#define X9250_CMD_WR_WCR(_p)    (0xa0 | (_p))

static int x9250_write8(struct x9250 *x9250, u8 cmd, u8 val)
{
        u8 txbuf[3];

        txbuf[0] = X9250_ID;
        txbuf[1] = cmd;
        txbuf[2] = val;

        return spi_write_then_read(x9250->spi, txbuf, ARRAY_SIZE(txbuf), NULL, 0);
}

static int x9250_read8(struct x9250 *x9250, u8 cmd, u8 *val)
{
        u8 txbuf[2];

        txbuf[0] = X9250_ID;
        txbuf[1] = cmd;

        return spi_write_then_read(x9250->spi, txbuf, ARRAY_SIZE(txbuf), val, 1);
}

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

static const struct iio_chan_spec x9250_channels[] = {
        X9250_CHANNEL(0),
        X9250_CHANNEL(1),
        X9250_CHANNEL(2),
        X9250_CHANNEL(3),
};

static int x9250_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
                          int *val, int *val2, long mask)
{
        struct x9250 *x9250 = iio_priv(indio_dev);
        int ch = chan->channel;
        int ret;
        u8 v;

        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                ret = x9250_read8(x9250, X9250_CMD_RD_WCR(ch), &v);
                if (ret)
                        return ret;
                *val = v;
                return IIO_VAL_INT;

        case IIO_CHAN_INFO_SCALE:
                *val = 1000 * x9250->cfg->kohms;
                *val2 = U8_MAX;
                return IIO_VAL_FRACTIONAL;
        }

        return -EINVAL;
}

static int x9250_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
                            const int **vals, int *type, int *length, long mask)
{
        static const int range[] = {0, 1, 255}; /* min, step, max */

        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                *length = ARRAY_SIZE(range);
                *vals = range;
                *type = IIO_VAL_INT;
                return IIO_AVAIL_RANGE;
        }

        return -EINVAL;
}

static int x9250_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
                           int val, int val2, long mask)
{
        struct x9250 *x9250 = iio_priv(indio_dev);
        int ch = chan->channel;
        int ret;

        if (mask != IIO_CHAN_INFO_RAW)
                return -EINVAL;

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

        gpiod_set_value_cansleep(x9250->wp_gpio, 0);
        ret = x9250_write8(x9250, X9250_CMD_WR_WCR(ch), val);
        gpiod_set_value_cansleep(x9250->wp_gpio, 1);

        return ret;
}

static const struct iio_info x9250_info = {
        .read_raw = x9250_read_raw,
        .read_avail = x9250_read_avail,
        .write_raw = x9250_write_raw,
};

enum x9250_type {
        X9250T,
        X9250U,
};

static const struct x9250_cfg x9250_cfg[] = {
        [X9250T] = { .name = "x9250t", .kohms =  100, },
        [X9250U] = { .name = "x9250u", .kohms =  50, },
};

static const char *const x9250_regulator_names[] = {
        "vcc",
        "avp",
        "avn",
};

static int x9250_probe(struct spi_device *spi)
{
        struct iio_dev *indio_dev;
        struct x9250 *x9250;
        int ret;

        ret = devm_regulator_bulk_get_enable(&spi->dev, ARRAY_SIZE(x9250_regulator_names),
                                             x9250_regulator_names);
        if (ret)
                return dev_err_probe(&spi->dev, ret, "Failed to get regulators\n");

        /*
         * The x9250 needs a 5ms maximum delay after the power-supplies are set
         * before performing the first write (1ms for the first read).
         */
        usleep_range(5000, 6000);

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

        x9250 = iio_priv(indio_dev);
        x9250->spi = spi;
        x9250->cfg = spi_get_device_match_data(spi);
        x9250->wp_gpio = devm_gpiod_get_optional(&spi->dev, "wp", GPIOD_OUT_LOW);
        if (IS_ERR(x9250->wp_gpio))
                return dev_err_probe(&spi->dev, PTR_ERR(x9250->wp_gpio),
                                     "failed to get wp gpio\n");

        indio_dev->info = &x9250_info;
        indio_dev->channels = x9250_channels;
        indio_dev->num_channels = ARRAY_SIZE(x9250_channels);
        indio_dev->name = x9250->cfg->name;

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

static const struct of_device_id x9250_of_match[] = {
        { .compatible = "renesas,x9250t", .data = &x9250_cfg[X9250T]},
        { .compatible = "renesas,x9250u", .data = &x9250_cfg[X9250U]},
        { }
};
MODULE_DEVICE_TABLE(of, x9250_of_match);

static const struct spi_device_id x9250_id_table[] = {
        { "x9250t", (kernel_ulong_t)&x9250_cfg[X9250T] },
        { "x9250u", (kernel_ulong_t)&x9250_cfg[X9250U] },
        { }
};
MODULE_DEVICE_TABLE(spi, x9250_id_table);

static struct spi_driver x9250_spi_driver = {
        .driver  = {
                .name = "x9250",
                .of_match_table = x9250_of_match,
        },
        .id_table = x9250_id_table,
        .probe  = x9250_probe,
};

module_spi_driver(x9250_spi_driver);

MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
MODULE_DESCRIPTION("X9250 ALSA SoC driver");
MODULE_LICENSE("GPL");