1092cb71aSChris Coffey // SPDX-License-Identifier: GPL-2.0 2092cb71aSChris Coffey /* 3092cb71aSChris Coffey * Industrial I/O driver for Microchip digital potentiometers 4092cb71aSChris Coffey * 5092cb71aSChris Coffey * Copyright (c) 2018 Chris Coffey <cmc@babblebit.net> 6092cb71aSChris Coffey * Based on: Slawomir Stepien's code from mcp4131.c 7092cb71aSChris Coffey * 83593cd53SAlexander A. Klimov * Datasheet: https://ww1.microchip.com/downloads/en/devicedoc/11195c.pdf 9092cb71aSChris Coffey * 10092cb71aSChris Coffey * DEVID #Wipers #Positions Resistance (kOhm) 11092cb71aSChris Coffey * mcp41010 1 256 10 12092cb71aSChris Coffey * mcp41050 1 256 50 13092cb71aSChris Coffey * mcp41100 1 256 100 14092cb71aSChris Coffey * mcp42010 2 256 10 15092cb71aSChris Coffey * mcp42050 2 256 50 16092cb71aSChris Coffey * mcp42100 2 256 100 17092cb71aSChris Coffey */ 18092cb71aSChris Coffey 19092cb71aSChris Coffey #include <linux/cache.h> 20092cb71aSChris Coffey #include <linux/err.h> 21092cb71aSChris Coffey #include <linux/iio/iio.h> 22092cb71aSChris Coffey #include <linux/iio/types.h> 23092cb71aSChris Coffey #include <linux/module.h> 2492311717SJonathan Cameron #include <linux/mod_devicetable.h> 25092cb71aSChris Coffey #include <linux/mutex.h> 2692311717SJonathan Cameron #include <linux/property.h> 27092cb71aSChris Coffey #include <linux/spi/spi.h> 28092cb71aSChris Coffey 29092cb71aSChris Coffey #define MCP41010_MAX_WIPERS 2 30092cb71aSChris Coffey #define MCP41010_WRITE BIT(4) 31092cb71aSChris Coffey #define MCP41010_WIPER_MAX 255 32092cb71aSChris Coffey #define MCP41010_WIPER_CHANNEL BIT(0) 33092cb71aSChris Coffey 34092cb71aSChris Coffey struct mcp41010_cfg { 35092cb71aSChris Coffey char name[16]; 36092cb71aSChris Coffey int wipers; 37092cb71aSChris Coffey int kohms; 38092cb71aSChris Coffey }; 39092cb71aSChris Coffey 40092cb71aSChris Coffey enum mcp41010_type { 41092cb71aSChris Coffey MCP41010, 42092cb71aSChris Coffey MCP41050, 43092cb71aSChris Coffey MCP41100, 44092cb71aSChris Coffey MCP42010, 45092cb71aSChris Coffey MCP42050, 46092cb71aSChris Coffey MCP42100, 47092cb71aSChris Coffey }; 48092cb71aSChris Coffey 49092cb71aSChris Coffey static const struct mcp41010_cfg mcp41010_cfg[] = { 50092cb71aSChris Coffey [MCP41010] = { .name = "mcp41010", .wipers = 1, .kohms = 10, }, 51092cb71aSChris Coffey [MCP41050] = { .name = "mcp41050", .wipers = 1, .kohms = 50, }, 52092cb71aSChris Coffey [MCP41100] = { .name = "mcp41100", .wipers = 1, .kohms = 100, }, 53092cb71aSChris Coffey [MCP42010] = { .name = "mcp42010", .wipers = 2, .kohms = 10, }, 54092cb71aSChris Coffey [MCP42050] = { .name = "mcp42050", .wipers = 2, .kohms = 50, }, 55092cb71aSChris Coffey [MCP42100] = { .name = "mcp42100", .wipers = 2, .kohms = 100, }, 56092cb71aSChris Coffey }; 57092cb71aSChris Coffey 58092cb71aSChris Coffey struct mcp41010_data { 59092cb71aSChris Coffey struct spi_device *spi; 60092cb71aSChris Coffey const struct mcp41010_cfg *cfg; 61092cb71aSChris Coffey struct mutex lock; /* Protect write sequences */ 62092cb71aSChris Coffey unsigned int value[MCP41010_MAX_WIPERS]; /* Cache wiper values */ 63*c5f78f4dSJonathan Cameron u8 buf[2] __aligned(IIO_DMA_MINALIGN); 64092cb71aSChris Coffey }; 65092cb71aSChris Coffey 66092cb71aSChris Coffey #define MCP41010_CHANNEL(ch) { \ 67092cb71aSChris Coffey .type = IIO_RESISTANCE, \ 68092cb71aSChris Coffey .indexed = 1, \ 69092cb71aSChris Coffey .output = 1, \ 70092cb71aSChris Coffey .channel = (ch), \ 71092cb71aSChris Coffey .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ 72092cb71aSChris Coffey .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ 73092cb71aSChris Coffey } 74092cb71aSChris Coffey 75092cb71aSChris Coffey static const struct iio_chan_spec mcp41010_channels[] = { 76092cb71aSChris Coffey MCP41010_CHANNEL(0), 77092cb71aSChris Coffey MCP41010_CHANNEL(1), 78092cb71aSChris Coffey }; 79092cb71aSChris Coffey 80092cb71aSChris Coffey static int mcp41010_read_raw(struct iio_dev *indio_dev, 81092cb71aSChris Coffey struct iio_chan_spec const *chan, 82092cb71aSChris Coffey int *val, int *val2, long mask) 83092cb71aSChris Coffey { 84092cb71aSChris Coffey struct mcp41010_data *data = iio_priv(indio_dev); 85092cb71aSChris Coffey int channel = chan->channel; 86092cb71aSChris Coffey 87092cb71aSChris Coffey switch (mask) { 88092cb71aSChris Coffey case IIO_CHAN_INFO_RAW: 89092cb71aSChris Coffey *val = data->value[channel]; 90092cb71aSChris Coffey return IIO_VAL_INT; 91092cb71aSChris Coffey 92092cb71aSChris Coffey case IIO_CHAN_INFO_SCALE: 93092cb71aSChris Coffey *val = 1000 * data->cfg->kohms; 94092cb71aSChris Coffey *val2 = MCP41010_WIPER_MAX; 95092cb71aSChris Coffey return IIO_VAL_FRACTIONAL; 96092cb71aSChris Coffey } 97092cb71aSChris Coffey 98092cb71aSChris Coffey return -EINVAL; 99092cb71aSChris Coffey } 100092cb71aSChris Coffey 101092cb71aSChris Coffey static int mcp41010_write_raw(struct iio_dev *indio_dev, 102092cb71aSChris Coffey struct iio_chan_spec const *chan, 103092cb71aSChris Coffey int val, int val2, long mask) 104092cb71aSChris Coffey { 105092cb71aSChris Coffey int err; 106092cb71aSChris Coffey struct mcp41010_data *data = iio_priv(indio_dev); 107092cb71aSChris Coffey int channel = chan->channel; 108092cb71aSChris Coffey 109092cb71aSChris Coffey if (mask != IIO_CHAN_INFO_RAW) 110092cb71aSChris Coffey return -EINVAL; 111092cb71aSChris Coffey 112092cb71aSChris Coffey if (val > MCP41010_WIPER_MAX || val < 0) 113092cb71aSChris Coffey return -EINVAL; 114092cb71aSChris Coffey 115092cb71aSChris Coffey mutex_lock(&data->lock); 116092cb71aSChris Coffey 117092cb71aSChris Coffey data->buf[0] = MCP41010_WIPER_CHANNEL << channel; 118092cb71aSChris Coffey data->buf[0] |= MCP41010_WRITE; 119092cb71aSChris Coffey data->buf[1] = val & 0xff; 120092cb71aSChris Coffey 121092cb71aSChris Coffey err = spi_write(data->spi, data->buf, sizeof(data->buf)); 122092cb71aSChris Coffey if (!err) 123092cb71aSChris Coffey data->value[channel] = val; 124092cb71aSChris Coffey 125092cb71aSChris Coffey mutex_unlock(&data->lock); 126092cb71aSChris Coffey 127092cb71aSChris Coffey return err; 128092cb71aSChris Coffey } 129092cb71aSChris Coffey 130092cb71aSChris Coffey static const struct iio_info mcp41010_info = { 131092cb71aSChris Coffey .read_raw = mcp41010_read_raw, 132092cb71aSChris Coffey .write_raw = mcp41010_write_raw, 133092cb71aSChris Coffey }; 134092cb71aSChris Coffey 135092cb71aSChris Coffey static int mcp41010_probe(struct spi_device *spi) 136092cb71aSChris Coffey { 137092cb71aSChris Coffey int err; 138092cb71aSChris Coffey struct device *dev = &spi->dev; 139092cb71aSChris Coffey struct mcp41010_data *data; 140092cb71aSChris Coffey struct iio_dev *indio_dev; 141092cb71aSChris Coffey 142092cb71aSChris Coffey indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); 143092cb71aSChris Coffey if (!indio_dev) 144092cb71aSChris Coffey return -ENOMEM; 145092cb71aSChris Coffey 146092cb71aSChris Coffey data = iio_priv(indio_dev); 147092cb71aSChris Coffey spi_set_drvdata(spi, indio_dev); 148092cb71aSChris Coffey data->spi = spi; 14992311717SJonathan Cameron data->cfg = device_get_match_data(&spi->dev); 150092cb71aSChris Coffey if (!data->cfg) 151092cb71aSChris Coffey data->cfg = &mcp41010_cfg[spi_get_device_id(spi)->driver_data]; 152092cb71aSChris Coffey 153092cb71aSChris Coffey mutex_init(&data->lock); 154092cb71aSChris Coffey 155092cb71aSChris Coffey indio_dev->info = &mcp41010_info; 156092cb71aSChris Coffey indio_dev->channels = mcp41010_channels; 157092cb71aSChris Coffey indio_dev->num_channels = data->cfg->wipers; 158092cb71aSChris Coffey indio_dev->name = data->cfg->name; 159092cb71aSChris Coffey 160092cb71aSChris Coffey err = devm_iio_device_register(dev, indio_dev); 161092cb71aSChris Coffey if (err) 162092cb71aSChris Coffey dev_info(&spi->dev, "Unable to register %s\n", indio_dev->name); 163092cb71aSChris Coffey 164092cb71aSChris Coffey return err; 165092cb71aSChris Coffey } 166092cb71aSChris Coffey 167092cb71aSChris Coffey static const struct of_device_id mcp41010_match[] = { 168092cb71aSChris Coffey { .compatible = "microchip,mcp41010", .data = &mcp41010_cfg[MCP41010] }, 169092cb71aSChris Coffey { .compatible = "microchip,mcp41050", .data = &mcp41010_cfg[MCP41050] }, 170092cb71aSChris Coffey { .compatible = "microchip,mcp41100", .data = &mcp41010_cfg[MCP41100] }, 171092cb71aSChris Coffey { .compatible = "microchip,mcp42010", .data = &mcp41010_cfg[MCP42010] }, 172092cb71aSChris Coffey { .compatible = "microchip,mcp42050", .data = &mcp41010_cfg[MCP42050] }, 173092cb71aSChris Coffey { .compatible = "microchip,mcp42100", .data = &mcp41010_cfg[MCP42100] }, 174092cb71aSChris Coffey {} 175092cb71aSChris Coffey }; 176092cb71aSChris Coffey MODULE_DEVICE_TABLE(of, mcp41010_match); 177092cb71aSChris Coffey 178092cb71aSChris Coffey static const struct spi_device_id mcp41010_id[] = { 179092cb71aSChris Coffey { "mcp41010", MCP41010 }, 180092cb71aSChris Coffey { "mcp41050", MCP41050 }, 181092cb71aSChris Coffey { "mcp41100", MCP41100 }, 182092cb71aSChris Coffey { "mcp42010", MCP42010 }, 183092cb71aSChris Coffey { "mcp42050", MCP42050 }, 184092cb71aSChris Coffey { "mcp42100", MCP42100 }, 185092cb71aSChris Coffey {} 186092cb71aSChris Coffey }; 187092cb71aSChris Coffey MODULE_DEVICE_TABLE(spi, mcp41010_id); 188092cb71aSChris Coffey 189092cb71aSChris Coffey static struct spi_driver mcp41010_driver = { 190092cb71aSChris Coffey .driver = { 191092cb71aSChris Coffey .name = "mcp41010", 192092cb71aSChris Coffey .of_match_table = mcp41010_match, 193092cb71aSChris Coffey }, 194092cb71aSChris Coffey .probe = mcp41010_probe, 195092cb71aSChris Coffey .id_table = mcp41010_id, 196092cb71aSChris Coffey }; 197092cb71aSChris Coffey 198092cb71aSChris Coffey module_spi_driver(mcp41010_driver); 199092cb71aSChris Coffey 200092cb71aSChris Coffey MODULE_AUTHOR("Chris Coffey <cmc@babblebit.net>"); 201092cb71aSChris Coffey MODULE_DESCRIPTION("MCP41010 digital potentiometer"); 202092cb71aSChris Coffey MODULE_LICENSE("GPL v2"); 203