root/drivers/iio/temperature/max31865.c
// SPDX-License-Identifier: GPL-2.0-only

/*
 * Copyright (c) Linumiz 2021
 *
 * max31865.c - Maxim MAX31865 RTD-to-Digital Converter sensor driver
 *
 * Author: Navin Sankar Velliangiri <navin@linumiz.com>
 */

#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/property.h>
#include <linux/spi/spi.h>
#include <linux/unaligned.h>

/*
 * The MSB of the register value determines whether the following byte will
 * be written or read. If it is 0, read will follow and if it is 1, write
 * will follow.
 */
#define MAX31865_RD_WR_BIT                      BIT(7)

#define MAX31865_CFG_VBIAS                      BIT(7)
#define MAX31865_CFG_1SHOT                      BIT(5)
#define MAX31865_3WIRE_RTD                      BIT(4)
#define MAX31865_FAULT_STATUS_CLEAR             BIT(1)
#define MAX31865_FILTER_50HZ                    BIT(0)

/* The MAX31865 registers */
#define MAX31865_CFG_REG                        0x00
#define MAX31865_RTD_MSB                        0x01
#define MAX31865_FAULT_STATUS                   0x07

#define MAX31865_FAULT_OVUV                     BIT(2)

static const char max31865_show_samp_freq[] = "50 60";

static const struct iio_chan_spec max31865_channels[] = {
        {       /* RTD Temperature */
                .type = IIO_TEMP,
                .info_mask_separate =
                        BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)
        },
};

struct max31865_data {
        struct spi_device *spi;
        struct mutex lock;
        bool filter_50hz;
        bool three_wire;
        u8 buf[2] __aligned(IIO_DMA_MINALIGN);
};

static int max31865_read(struct max31865_data *data, u8 reg,
                         unsigned int read_size)
{
        return spi_write_then_read(data->spi, &reg, 1, data->buf, read_size);
}

static int max31865_write(struct max31865_data *data, size_t len)
{
        return spi_write(data->spi, data->buf, len);
}

static int enable_bias(struct max31865_data *data)
{
        u8 cfg;
        int ret;

        ret = max31865_read(data, MAX31865_CFG_REG, 1);
        if (ret)
                return ret;

        cfg = data->buf[0];

        data->buf[0] = MAX31865_CFG_REG | MAX31865_RD_WR_BIT;
        data->buf[1] = cfg | MAX31865_CFG_VBIAS;

        return max31865_write(data, 2);
}

static int disable_bias(struct max31865_data *data)
{
        u8 cfg;
        int ret;

        ret = max31865_read(data, MAX31865_CFG_REG, 1);
        if (ret)
                return ret;

        cfg = data->buf[0];
        cfg &= ~MAX31865_CFG_VBIAS;

        data->buf[0] = MAX31865_CFG_REG | MAX31865_RD_WR_BIT;
        data->buf[1] = cfg;

        return max31865_write(data, 2);
}

static int max31865_rtd_read(struct max31865_data *data, int *val)
{
        u8 reg;
        int ret;

        /* Enable BIAS to start the conversion */
        ret = enable_bias(data);
        if (ret)
                return ret;

        /* wait 10.5ms before initiating the conversion */
        msleep(11);

        ret = max31865_read(data, MAX31865_CFG_REG, 1);
        if (ret)
                return ret;

        reg = data->buf[0];
        reg |= MAX31865_CFG_1SHOT | MAX31865_FAULT_STATUS_CLEAR;
        data->buf[0] = MAX31865_CFG_REG | MAX31865_RD_WR_BIT;
        data->buf[1] = reg;

        ret = max31865_write(data, 2);
        if (ret)
                return ret;

        if (data->filter_50hz) {
                /* 50Hz filter mode requires 62.5ms to complete */
                msleep(63);
        } else {
                /* 60Hz filter mode requires 52ms to complete */
                msleep(52);
        }

        ret = max31865_read(data, MAX31865_RTD_MSB, 2);
        if (ret)
                return ret;

        *val = get_unaligned_be16(&data->buf) >> 1;

        return disable_bias(data);
}

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

        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                mutex_lock(&data->lock);
                ret = max31865_rtd_read(data, val);
                mutex_unlock(&data->lock);
                if (ret)
                        return ret;
                return IIO_VAL_INT;
        case IIO_CHAN_INFO_SCALE:
                /* Temp. Data resolution is 0.03125 degree centigrade */
                *val = 31;
                *val2 = 250000; /* 1000 * 0.03125 */
                return IIO_VAL_INT_PLUS_MICRO;
        default:
                return -EINVAL;
        }
}

static int max31865_init(struct max31865_data *data)
{
        u8 cfg;
        int ret;

        ret = max31865_read(data, MAX31865_CFG_REG, 1);
        if (ret)
                return ret;

        cfg = data->buf[0];

        if (data->three_wire)
                /* 3-wire RTD connection */
                cfg |= MAX31865_3WIRE_RTD;

        if (data->filter_50hz)
                /* 50Hz noise rejection filter */
                cfg |= MAX31865_FILTER_50HZ;

        data->buf[0] = MAX31865_CFG_REG | MAX31865_RD_WR_BIT;
        data->buf[1] = cfg;

        return max31865_write(data, 2);
}

static ssize_t show_fault(struct device *dev, u8 faultbit, char *buf)
{
        int ret;
        bool fault;
        struct iio_dev *indio_dev = dev_to_iio_dev(dev);
        struct max31865_data *data = iio_priv(indio_dev);

        ret = max31865_read(data, MAX31865_FAULT_STATUS, 1);
        if (ret)
                return ret;

        fault = data->buf[0] & faultbit;

        return sysfs_emit(buf, "%d\n", fault);
}

static ssize_t show_fault_ovuv(struct device *dev,
                              struct device_attribute *attr,
                              char *buf)
{
        return show_fault(dev, MAX31865_FAULT_OVUV, buf);
}

static ssize_t show_filter(struct device *dev,
                           struct device_attribute *attr,
                           char *buf)
{
        struct iio_dev *indio_dev = dev_to_iio_dev(dev);
        struct max31865_data *data = iio_priv(indio_dev);

        return sysfs_emit(buf, "%d\n", data->filter_50hz ? 50 : 60);
}

static ssize_t set_filter(struct device *dev,
                          struct device_attribute *attr,
                          const char *buf,
                          size_t len)
{
        struct iio_dev *indio_dev = dev_to_iio_dev(dev);
        struct max31865_data *data = iio_priv(indio_dev);
        unsigned int freq;
        int ret;

        ret = kstrtouint(buf, 10, &freq);
        if (ret)
                return ret;

        switch (freq) {
        case 50:
                data->filter_50hz = true;
                break;
        case 60:
                data->filter_50hz = false;
                break;
        default:
                return -EINVAL;
        }

        mutex_lock(&data->lock);
        ret = max31865_init(data);
        mutex_unlock(&data->lock);
        if (ret)
                return ret;

        return len;
}

static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(max31865_show_samp_freq);
static IIO_DEVICE_ATTR(fault_ovuv, 0444, show_fault_ovuv, NULL, 0);
static IIO_DEVICE_ATTR(in_filter_notch_center_frequency, 0644,
                    show_filter, set_filter, 0);

static struct attribute *max31865_attributes[] = {
        &iio_dev_attr_fault_ovuv.dev_attr.attr,
        &iio_const_attr_sampling_frequency_available.dev_attr.attr,
        &iio_dev_attr_in_filter_notch_center_frequency.dev_attr.attr,
        NULL,
};

static const struct attribute_group max31865_group = {
        .attrs = max31865_attributes,
};

static const struct iio_info max31865_info = {
        .read_raw = max31865_read_raw,
        .attrs = &max31865_group,
};

static int max31865_probe(struct spi_device *spi)
{
        const struct spi_device_id *id = spi_get_device_id(spi);
        struct iio_dev *indio_dev;
        struct max31865_data *data;
        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->filter_50hz = false;
        mutex_init(&data->lock);

        indio_dev->info = &max31865_info;
        indio_dev->name = id->name;
        indio_dev->modes = INDIO_DIRECT_MODE;
        indio_dev->channels = max31865_channels;
        indio_dev->num_channels = ARRAY_SIZE(max31865_channels);

        if (device_property_read_bool(&spi->dev, "maxim,3-wire")) {
                /* select 3 wire */
                data->three_wire = 1;
        } else {
                /* select 2 or 4 wire */
                data->three_wire = 0;
        }

        ret = max31865_init(data);
        if (ret) {
                dev_err(&spi->dev, "error: Failed to configure max31865\n");
                return ret;
        }

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

static const struct spi_device_id max31865_id[] = {
        { "max31865", 0 },
        { }
};
MODULE_DEVICE_TABLE(spi, max31865_id);

static const struct of_device_id max31865_of_match[] = {
        { .compatible = "maxim,max31865" },
        { }
};
MODULE_DEVICE_TABLE(of, max31865_of_match);

static struct spi_driver max31865_driver = {
        .driver = {
                .name   = "max31865",
                .of_match_table = max31865_of_match,
        },
        .probe = max31865_probe,
        .id_table = max31865_id,
};
module_spi_driver(max31865_driver);

MODULE_AUTHOR("Navin Sankar Velliangiri <navin@linumiz.com>");
MODULE_DESCRIPTION("Maxim MAX31865 RTD-to-Digital Converter sensor driver");
MODULE_LICENSE("GPL v2");