476 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * mcp9600.c - Support for Microchip MCP9600 thermocouple EMF converter4 *5 * Copyright (c) 2022 Andrew Hepp6 * Author: <andrew.hepp@ahepp.dev>7 */8 9#include <linux/bitfield.h>10#include <linux/bitops.h>11#include <linux/bits.h>12#include <linux/err.h>13#include <linux/i2c.h>14#include <linux/init.h>15#include <linux/interrupt.h>16#include <linux/irq.h>17#include <linux/math.h>18#include <linux/minmax.h>19#include <linux/mod_devicetable.h>20#include <linux/module.h>21 22#include <linux/iio/events.h>23#include <linux/iio/iio.h>24 25/* MCP9600 registers */26#define MCP9600_HOT_JUNCTION 0x027#define MCP9600_COLD_JUNCTION 0x228#define MCP9600_STATUS 0x429#define MCP9600_STATUS_ALERT(x) BIT(x)30#define MCP9600_ALERT_CFG1 0x831#define MCP9600_ALERT_CFG(x) (MCP9600_ALERT_CFG1 + (x - 1))32#define MCP9600_ALERT_CFG_ENABLE BIT(0)33#define MCP9600_ALERT_CFG_ACTIVE_HIGH BIT(2)34#define MCP9600_ALERT_CFG_FALLING BIT(3)35#define MCP9600_ALERT_CFG_COLD_JUNCTION BIT(4)36#define MCP9600_ALERT_HYSTERESIS1 0xc37#define MCP9600_ALERT_HYSTERESIS(x) (MCP9600_ALERT_HYSTERESIS1 + (x - 1))38#define MCP9600_ALERT_LIMIT1 0x1039#define MCP9600_ALERT_LIMIT(x) (MCP9600_ALERT_LIMIT1 + (x - 1))40#define MCP9600_ALERT_LIMIT_MASK GENMASK(15, 2)41#define MCP9600_DEVICE_ID 0x2042 43/* MCP9600 device id value */44#define MCP9600_DEVICE_ID_MCP9600 0x4045 46#define MCP9600_ALERT_COUNT 447 48#define MCP9600_MIN_TEMP_HOT_JUNCTION_MICRO -20000000049#define MCP9600_MAX_TEMP_HOT_JUNCTION_MICRO 180000000050 51#define MCP9600_MIN_TEMP_COLD_JUNCTION_MICRO -4000000052#define MCP9600_MAX_TEMP_COLD_JUNCTION_MICRO 12500000053 54enum mcp9600_alert {55 MCP9600_ALERT1,56 MCP9600_ALERT2,57 MCP9600_ALERT3,58 MCP9600_ALERT459};60 61static const char * const mcp9600_alert_name[MCP9600_ALERT_COUNT] = {62 [MCP9600_ALERT1] = "alert1",63 [MCP9600_ALERT2] = "alert2",64 [MCP9600_ALERT3] = "alert3",65 [MCP9600_ALERT4] = "alert4",66};67 68static const struct iio_event_spec mcp9600_events[] = {69 {70 .type = IIO_EV_TYPE_THRESH,71 .dir = IIO_EV_DIR_RISING,72 .mask_separate = BIT(IIO_EV_INFO_ENABLE) |73 BIT(IIO_EV_INFO_VALUE) |74 BIT(IIO_EV_INFO_HYSTERESIS),75 },76 {77 .type = IIO_EV_TYPE_THRESH,78 .dir = IIO_EV_DIR_FALLING,79 .mask_separate = BIT(IIO_EV_INFO_ENABLE) |80 BIT(IIO_EV_INFO_VALUE) |81 BIT(IIO_EV_INFO_HYSTERESIS),82 },83};84 85#define MCP9600_CHANNELS(hj_num_ev, hj_ev_spec_off, cj_num_ev, cj_ev_spec_off) \86 { \87 { \88 .type = IIO_TEMP, \89 .address = MCP9600_HOT_JUNCTION, \90 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \91 BIT(IIO_CHAN_INFO_SCALE), \92 .event_spec = &mcp9600_events[hj_ev_spec_off], \93 .num_event_specs = hj_num_ev, \94 }, \95 { \96 .type = IIO_TEMP, \97 .address = MCP9600_COLD_JUNCTION, \98 .channel2 = IIO_MOD_TEMP_AMBIENT, \99 .modified = 1, \100 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \101 BIT(IIO_CHAN_INFO_SCALE), \102 .event_spec = &mcp9600_events[cj_ev_spec_off], \103 .num_event_specs = cj_num_ev, \104 }, \105 }106 107static const struct iio_chan_spec mcp9600_channels[][2] = {108 MCP9600_CHANNELS(0, 0, 0, 0), /* Alerts: - - - - */109 MCP9600_CHANNELS(1, 0, 0, 0), /* Alerts: 1 - - - */110 MCP9600_CHANNELS(1, 1, 0, 0), /* Alerts: - 2 - - */111 MCP9600_CHANNELS(2, 0, 0, 0), /* Alerts: 1 2 - - */112 MCP9600_CHANNELS(0, 0, 1, 0), /* Alerts: - - 3 - */113 MCP9600_CHANNELS(1, 0, 1, 0), /* Alerts: 1 - 3 - */114 MCP9600_CHANNELS(1, 1, 1, 0), /* Alerts: - 2 3 - */115 MCP9600_CHANNELS(2, 0, 1, 0), /* Alerts: 1 2 3 - */116 MCP9600_CHANNELS(0, 0, 1, 1), /* Alerts: - - - 4 */117 MCP9600_CHANNELS(1, 0, 1, 1), /* Alerts: 1 - - 4 */118 MCP9600_CHANNELS(1, 1, 1, 1), /* Alerts: - 2 - 4 */119 MCP9600_CHANNELS(2, 0, 1, 1), /* Alerts: 1 2 - 4 */120 MCP9600_CHANNELS(0, 0, 2, 0), /* Alerts: - - 3 4 */121 MCP9600_CHANNELS(1, 0, 2, 0), /* Alerts: 1 - 3 4 */122 MCP9600_CHANNELS(1, 1, 2, 0), /* Alerts: - 2 3 4 */123 MCP9600_CHANNELS(2, 0, 2, 0), /* Alerts: 1 2 3 4 */124};125 126struct mcp9600_data {127 struct i2c_client *client;128};129 130static int mcp9600_read(struct mcp9600_data *data,131 struct iio_chan_spec const *chan, int *val)132{133 int ret;134 135 ret = i2c_smbus_read_word_swapped(data->client, chan->address);136 137 if (ret < 0)138 return ret;139 140 *val = sign_extend32(ret, 15);141 142 return 0;143}144 145static int mcp9600_read_raw(struct iio_dev *indio_dev,146 struct iio_chan_spec const *chan, int *val,147 int *val2, long mask)148{149 struct mcp9600_data *data = iio_priv(indio_dev);150 int ret;151 152 switch (mask) {153 case IIO_CHAN_INFO_RAW:154 ret = mcp9600_read(data, chan, val);155 if (ret)156 return ret;157 return IIO_VAL_INT;158 case IIO_CHAN_INFO_SCALE:159 *val = 62;160 *val2 = 500000;161 return IIO_VAL_INT_PLUS_MICRO;162 default:163 return -EINVAL;164 }165}166 167static int mcp9600_get_alert_index(int channel2, enum iio_event_direction dir)168{169 if (channel2 == IIO_MOD_TEMP_AMBIENT) {170 if (dir == IIO_EV_DIR_RISING)171 return MCP9600_ALERT3;172 else173 return MCP9600_ALERT4;174 } else {175 if (dir == IIO_EV_DIR_RISING)176 return MCP9600_ALERT1;177 else178 return MCP9600_ALERT2;179 }180}181 182static int mcp9600_read_event_config(struct iio_dev *indio_dev,183 const struct iio_chan_spec *chan,184 enum iio_event_type type,185 enum iio_event_direction dir)186{187 struct mcp9600_data *data = iio_priv(indio_dev);188 struct i2c_client *client = data->client;189 int i, ret;190 191 i = mcp9600_get_alert_index(chan->channel2, dir);192 ret = i2c_smbus_read_byte_data(client, MCP9600_ALERT_CFG(i + 1));193 if (ret < 0)194 return ret;195 196 return FIELD_GET(MCP9600_ALERT_CFG_ENABLE, ret);197}198 199static int mcp9600_write_event_config(struct iio_dev *indio_dev,200 const struct iio_chan_spec *chan,201 enum iio_event_type type,202 enum iio_event_direction dir,203 int state)204{205 struct mcp9600_data *data = iio_priv(indio_dev);206 struct i2c_client *client = data->client;207 int i, ret;208 209 i = mcp9600_get_alert_index(chan->channel2, dir);210 ret = i2c_smbus_read_byte_data(client, MCP9600_ALERT_CFG(i + 1));211 if (ret < 0)212 return ret;213 214 if (state)215 ret |= MCP9600_ALERT_CFG_ENABLE;216 else217 ret &= ~MCP9600_ALERT_CFG_ENABLE;218 219 return i2c_smbus_write_byte_data(client, MCP9600_ALERT_CFG(i + 1), ret);220}221 222static int mcp9600_read_thresh(struct iio_dev *indio_dev,223 const struct iio_chan_spec *chan,224 enum iio_event_type type,225 enum iio_event_direction dir,226 enum iio_event_info info, int *val, int *val2)227{228 struct mcp9600_data *data = iio_priv(indio_dev);229 struct i2c_client *client = data->client;230 s32 ret;231 int i;232 233 i = mcp9600_get_alert_index(chan->channel2, dir);234 switch (info) {235 case IIO_EV_INFO_VALUE:236 ret = i2c_smbus_read_word_swapped(client, MCP9600_ALERT_LIMIT(i + 1));237 if (ret < 0)238 return ret;239 /*240 * Temperature is stored in two’s complement format in241 * bits(15:2), LSB is 0.25 degree celsius.242 */243 *val = sign_extend32(FIELD_GET(MCP9600_ALERT_LIMIT_MASK, ret), 13);244 *val2 = 4;245 return IIO_VAL_FRACTIONAL;246 case IIO_EV_INFO_HYSTERESIS:247 ret = i2c_smbus_read_byte_data(client, MCP9600_ALERT_HYSTERESIS(i + 1));248 if (ret < 0)249 return ret;250 251 *val = ret;252 return IIO_VAL_INT;253 default:254 return -EINVAL;255 }256}257 258static int mcp9600_write_thresh(struct iio_dev *indio_dev,259 const struct iio_chan_spec *chan,260 enum iio_event_type type,261 enum iio_event_direction dir,262 enum iio_event_info info, int val, int val2)263{264 struct mcp9600_data *data = iio_priv(indio_dev);265 struct i2c_client *client = data->client;266 int s_val, i;267 s16 thresh;268 u8 hyst;269 270 i = mcp9600_get_alert_index(chan->channel2, dir);271 switch (info) {272 case IIO_EV_INFO_VALUE:273 /* Scale value to include decimal part into calculations */274 s_val = (val < 0) ? ((val * 1000000) - val2) :275 ((val * 1000000) + val2);276 if (chan->channel2 == IIO_MOD_TEMP_AMBIENT) {277 s_val = max(s_val, MCP9600_MIN_TEMP_COLD_JUNCTION_MICRO);278 s_val = min(s_val, MCP9600_MAX_TEMP_COLD_JUNCTION_MICRO);279 } else {280 s_val = max(s_val, MCP9600_MIN_TEMP_HOT_JUNCTION_MICRO);281 s_val = min(s_val, MCP9600_MAX_TEMP_HOT_JUNCTION_MICRO);282 }283 284 /*285 * Shift length 4 bits = 2(15:2) + 2(0.25 LSB), temperature is286 * stored in two’s complement format.287 */288 thresh = (s16)(s_val / (1000000 >> 4));289 return i2c_smbus_write_word_swapped(client,290 MCP9600_ALERT_LIMIT(i + 1),291 thresh);292 case IIO_EV_INFO_HYSTERESIS:293 hyst = min(abs(val), 255);294 return i2c_smbus_write_byte_data(client,295 MCP9600_ALERT_HYSTERESIS(i + 1),296 hyst);297 default:298 return -EINVAL;299 }300}301 302static const struct iio_info mcp9600_info = {303 .read_raw = mcp9600_read_raw,304 .read_event_config = mcp9600_read_event_config,305 .write_event_config = mcp9600_write_event_config,306 .read_event_value = mcp9600_read_thresh,307 .write_event_value = mcp9600_write_thresh,308};309 310static irqreturn_t mcp9600_alert_handler(void *private,311 enum mcp9600_alert alert,312 enum iio_modifier mod,313 enum iio_event_direction dir)314{315 struct iio_dev *indio_dev = private;316 struct mcp9600_data *data = iio_priv(indio_dev);317 int ret;318 319 ret = i2c_smbus_read_byte_data(data->client, MCP9600_STATUS);320 if (ret < 0)321 return IRQ_HANDLED;322 323 if (!(ret & MCP9600_STATUS_ALERT(alert)))324 return IRQ_NONE;325 326 iio_push_event(indio_dev,327 IIO_MOD_EVENT_CODE(IIO_TEMP, 0, mod, IIO_EV_TYPE_THRESH,328 dir),329 iio_get_time_ns(indio_dev));330 331 return IRQ_HANDLED;332}333 334static irqreturn_t mcp9600_alert1_handler(int irq, void *private)335{336 return mcp9600_alert_handler(private, MCP9600_ALERT1, IIO_NO_MOD,337 IIO_EV_DIR_RISING);338}339 340static irqreturn_t mcp9600_alert2_handler(int irq, void *private)341{342 return mcp9600_alert_handler(private, MCP9600_ALERT2, IIO_NO_MOD,343 IIO_EV_DIR_FALLING);344}345 346static irqreturn_t mcp9600_alert3_handler(int irq, void *private)347{348 return mcp9600_alert_handler(private, MCP9600_ALERT3,349 IIO_MOD_TEMP_AMBIENT, IIO_EV_DIR_RISING);350}351 352static irqreturn_t mcp9600_alert4_handler(int irq, void *private)353{354 return mcp9600_alert_handler(private, MCP9600_ALERT4,355 IIO_MOD_TEMP_AMBIENT, IIO_EV_DIR_FALLING);356}357 358static irqreturn_t (*mcp9600_alert_handler_func[MCP9600_ALERT_COUNT]) (int, void *) = {359 mcp9600_alert1_handler,360 mcp9600_alert2_handler,361 mcp9600_alert3_handler,362 mcp9600_alert4_handler,363};364 365static int mcp9600_probe_alerts(struct iio_dev *indio_dev)366{367 struct mcp9600_data *data = iio_priv(indio_dev);368 struct i2c_client *client = data->client;369 struct device *dev = &client->dev;370 struct fwnode_handle *fwnode = dev_fwnode(dev);371 unsigned int irq_type;372 int ret, irq, i;373 u8 val, ch_sel;374 375 /*376 * alert1: hot junction, rising temperature377 * alert2: hot junction, falling temperature378 * alert3: cold junction, rising temperature379 * alert4: cold junction, falling temperature380 */381 ch_sel = 0;382 for (i = 0; i < MCP9600_ALERT_COUNT; i++) {383 irq = fwnode_irq_get_byname(fwnode, mcp9600_alert_name[i]);384 if (irq <= 0)385 continue;386 387 val = 0;388 irq_type = irq_get_trigger_type(irq);389 if (irq_type == IRQ_TYPE_EDGE_RISING)390 val |= MCP9600_ALERT_CFG_ACTIVE_HIGH;391 392 if (i == MCP9600_ALERT2 || i == MCP9600_ALERT4)393 val |= MCP9600_ALERT_CFG_FALLING;394 395 if (i == MCP9600_ALERT3 || i == MCP9600_ALERT4)396 val |= MCP9600_ALERT_CFG_COLD_JUNCTION;397 398 ret = i2c_smbus_write_byte_data(client,399 MCP9600_ALERT_CFG(i + 1),400 val);401 if (ret < 0)402 return ret;403 404 ret = devm_request_threaded_irq(dev, irq, NULL,405 mcp9600_alert_handler_func[i],406 IRQF_ONESHOT, "mcp9600",407 indio_dev);408 if (ret)409 return ret;410 411 ch_sel |= BIT(i);412 }413 414 return ch_sel;415}416 417static int mcp9600_probe(struct i2c_client *client)418{419 struct iio_dev *indio_dev;420 struct mcp9600_data *data;421 int ret, ch_sel;422 423 ret = i2c_smbus_read_byte_data(client, MCP9600_DEVICE_ID);424 if (ret < 0)425 return dev_err_probe(&client->dev, ret, "Failed to read device ID\n");426 if (ret != MCP9600_DEVICE_ID_MCP9600)427 dev_warn(&client->dev, "Expected ID %x, got %x\n",428 MCP9600_DEVICE_ID_MCP9600, ret);429 430 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));431 if (!indio_dev)432 return -ENOMEM;433 434 data = iio_priv(indio_dev);435 data->client = client;436 437 ch_sel = mcp9600_probe_alerts(indio_dev);438 if (ch_sel < 0)439 return ch_sel;440 441 indio_dev->info = &mcp9600_info;442 indio_dev->name = "mcp9600";443 indio_dev->modes = INDIO_DIRECT_MODE;444 indio_dev->channels = mcp9600_channels[ch_sel];445 indio_dev->num_channels = ARRAY_SIZE(mcp9600_channels[ch_sel]);446 447 return devm_iio_device_register(&client->dev, indio_dev);448}449 450static const struct i2c_device_id mcp9600_id[] = {451 { "mcp9600" },452 {}453};454MODULE_DEVICE_TABLE(i2c, mcp9600_id);455 456static const struct of_device_id mcp9600_of_match[] = {457 { .compatible = "microchip,mcp9600" },458 {}459};460MODULE_DEVICE_TABLE(of, mcp9600_of_match);461 462static struct i2c_driver mcp9600_driver = {463 .driver = {464 .name = "mcp9600",465 .of_match_table = mcp9600_of_match,466 },467 .probe = mcp9600_probe,468 .id_table = mcp9600_id469};470module_i2c_driver(mcp9600_driver);471 472MODULE_AUTHOR("Dimitri Fedrau <dima.fedrau@gmail.com>");473MODULE_AUTHOR("Andrew Hepp <andrew.hepp@ahepp.dev>");474MODULE_DESCRIPTION("Microchip MCP9600 thermocouple EMF converter driver");475MODULE_LICENSE("GPL");476