1 /* 2 * Copyright (C) 2013 Oskar Andero <oskar.andero@gmail.com> 3 * 4 * Driver for Microchip Technology's MCP3204 and MCP3208 ADC chips. 5 * Datasheet can be found here: 6 * http://ww1.microchip.com/downloads/en/devicedoc/21298c.pdf 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License version 2 as 10 * published by the Free Software Foundation. 11 */ 12 13 #include <linux/err.h> 14 #include <linux/spi/spi.h> 15 #include <linux/module.h> 16 #include <linux/iio/iio.h> 17 #include <linux/regulator/consumer.h> 18 19 #define MCP_SINGLE_ENDED (1 << 3) 20 #define MCP_START_BIT (1 << 4) 21 22 enum { 23 mcp3204, 24 mcp3208, 25 }; 26 27 struct mcp320x { 28 struct spi_device *spi; 29 struct spi_message msg; 30 struct spi_transfer transfer[2]; 31 32 u8 tx_buf; 33 u8 rx_buf[2]; 34 35 struct regulator *reg; 36 struct mutex lock; 37 }; 38 39 static int mcp320x_adc_conversion(struct mcp320x *adc, u8 msg) 40 { 41 int ret; 42 43 adc->tx_buf = msg; 44 ret = spi_sync(adc->spi, &adc->msg); 45 if (ret < 0) 46 return ret; 47 48 return ((adc->rx_buf[0] & 0x3f) << 6) | 49 (adc->rx_buf[1] >> 2); 50 } 51 52 static int mcp320x_read_raw(struct iio_dev *indio_dev, 53 struct iio_chan_spec const *channel, int *val, 54 int *val2, long mask) 55 { 56 struct mcp320x *adc = iio_priv(indio_dev); 57 int ret = -EINVAL; 58 59 mutex_lock(&adc->lock); 60 61 switch (mask) { 62 case IIO_CHAN_INFO_RAW: 63 if (channel->differential) 64 ret = mcp320x_adc_conversion(adc, 65 MCP_START_BIT | channel->address); 66 else 67 ret = mcp320x_adc_conversion(adc, 68 MCP_START_BIT | MCP_SINGLE_ENDED | 69 channel->address); 70 if (ret < 0) 71 goto out; 72 73 *val = ret; 74 ret = IIO_VAL_INT; 75 break; 76 77 case IIO_CHAN_INFO_SCALE: 78 /* Digital output code = (4096 * Vin) / Vref */ 79 ret = regulator_get_voltage(adc->reg); 80 if (ret < 0) 81 goto out; 82 83 *val = ret / 1000; 84 *val2 = 12; 85 ret = IIO_VAL_FRACTIONAL_LOG2; 86 break; 87 88 default: 89 break; 90 } 91 92 out: 93 mutex_unlock(&adc->lock); 94 95 return ret; 96 } 97 98 #define MCP320X_VOLTAGE_CHANNEL(num) \ 99 { \ 100 .type = IIO_VOLTAGE, \ 101 .indexed = 1, \ 102 .channel = (num), \ 103 .address = (num), \ 104 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ 105 .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ 106 } 107 108 #define MCP320X_VOLTAGE_CHANNEL_DIFF(num) \ 109 { \ 110 .type = IIO_VOLTAGE, \ 111 .indexed = 1, \ 112 .channel = (num * 2), \ 113 .channel2 = (num * 2 + 1), \ 114 .address = (num * 2), \ 115 .differential = 1, \ 116 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ 117 .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ 118 } 119 120 static const struct iio_chan_spec mcp3204_channels[] = { 121 MCP320X_VOLTAGE_CHANNEL(0), 122 MCP320X_VOLTAGE_CHANNEL(1), 123 MCP320X_VOLTAGE_CHANNEL(2), 124 MCP320X_VOLTAGE_CHANNEL(3), 125 MCP320X_VOLTAGE_CHANNEL_DIFF(0), 126 MCP320X_VOLTAGE_CHANNEL_DIFF(1), 127 }; 128 129 static const struct iio_chan_spec mcp3208_channels[] = { 130 MCP320X_VOLTAGE_CHANNEL(0), 131 MCP320X_VOLTAGE_CHANNEL(1), 132 MCP320X_VOLTAGE_CHANNEL(2), 133 MCP320X_VOLTAGE_CHANNEL(3), 134 MCP320X_VOLTAGE_CHANNEL(4), 135 MCP320X_VOLTAGE_CHANNEL(5), 136 MCP320X_VOLTAGE_CHANNEL(6), 137 MCP320X_VOLTAGE_CHANNEL(7), 138 MCP320X_VOLTAGE_CHANNEL_DIFF(0), 139 MCP320X_VOLTAGE_CHANNEL_DIFF(1), 140 MCP320X_VOLTAGE_CHANNEL_DIFF(2), 141 MCP320X_VOLTAGE_CHANNEL_DIFF(3), 142 }; 143 144 static const struct iio_info mcp320x_info = { 145 .read_raw = mcp320x_read_raw, 146 .driver_module = THIS_MODULE, 147 }; 148 149 struct mcp3208_chip_info { 150 const struct iio_chan_spec *channels; 151 unsigned int num_channels; 152 }; 153 154 static const struct mcp3208_chip_info mcp3208_chip_infos[] = { 155 [mcp3204] = { 156 .channels = mcp3204_channels, 157 .num_channels = ARRAY_SIZE(mcp3204_channels) 158 }, 159 [mcp3208] = { 160 .channels = mcp3208_channels, 161 .num_channels = ARRAY_SIZE(mcp3208_channels) 162 }, 163 }; 164 165 static int mcp320x_probe(struct spi_device *spi) 166 { 167 struct iio_dev *indio_dev; 168 struct mcp320x *adc; 169 const struct mcp3208_chip_info *chip_info; 170 int ret; 171 172 indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); 173 if (!indio_dev) 174 return -ENOMEM; 175 176 adc = iio_priv(indio_dev); 177 adc->spi = spi; 178 179 indio_dev->dev.parent = &spi->dev; 180 indio_dev->name = spi_get_device_id(spi)->name; 181 indio_dev->modes = INDIO_DIRECT_MODE; 182 indio_dev->info = &mcp320x_info; 183 184 chip_info = &mcp3208_chip_infos[spi_get_device_id(spi)->driver_data]; 185 indio_dev->channels = chip_info->channels; 186 indio_dev->num_channels = chip_info->num_channels; 187 188 adc->transfer[0].tx_buf = &adc->tx_buf; 189 adc->transfer[0].len = sizeof(adc->tx_buf); 190 adc->transfer[1].rx_buf = adc->rx_buf; 191 adc->transfer[1].len = sizeof(adc->rx_buf); 192 193 spi_message_init_with_transfers(&adc->msg, adc->transfer, 194 ARRAY_SIZE(adc->transfer)); 195 196 adc->reg = devm_regulator_get(&spi->dev, "vref"); 197 if (IS_ERR(adc->reg)) 198 return PTR_ERR(adc->reg); 199 200 ret = regulator_enable(adc->reg); 201 if (ret < 0) 202 return ret; 203 204 mutex_init(&adc->lock); 205 206 ret = iio_device_register(indio_dev); 207 if (ret < 0) 208 goto reg_disable; 209 210 return 0; 211 212 reg_disable: 213 regulator_disable(adc->reg); 214 215 return ret; 216 } 217 218 static int mcp320x_remove(struct spi_device *spi) 219 { 220 struct iio_dev *indio_dev = spi_get_drvdata(spi); 221 struct mcp320x *adc = iio_priv(indio_dev); 222 223 iio_device_unregister(indio_dev); 224 regulator_disable(adc->reg); 225 226 return 0; 227 } 228 229 static const struct spi_device_id mcp320x_id[] = { 230 { "mcp3204", mcp3204 }, 231 { "mcp3208", mcp3208 }, 232 { } 233 }; 234 MODULE_DEVICE_TABLE(spi, mcp320x_id); 235 236 static struct spi_driver mcp320x_driver = { 237 .driver = { 238 .name = "mcp320x", 239 .owner = THIS_MODULE, 240 }, 241 .probe = mcp320x_probe, 242 .remove = mcp320x_remove, 243 .id_table = mcp320x_id, 244 }; 245 module_spi_driver(mcp320x_driver); 246 247 MODULE_AUTHOR("Oskar Andero <oskar.andero@gmail.com>"); 248 MODULE_DESCRIPTION("Microchip Technology MCP3204/08"); 249 MODULE_LICENSE("GPL v2"); 250