| 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * mcp9600.c - Support for Microchip MCP9600 thermocouple EMF converter |
| 4 | * |
| 5 | * Copyright (c) 2022 Andrew Hepp |
| 6 | * 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 | #include <dt-bindings/iio/temperature/thermocouple.h> |
| 26 | |
| 27 | /* MCP9600 registers */ |
| 28 | #define MCP9600_HOT_JUNCTION 0x00 |
| 29 | #define MCP9600_COLD_JUNCTION 0x02 |
| 30 | #define MCP9600_STATUS 0x04 |
| 31 | #define MCP9600_STATUS_ALERT(x) BIT(x) |
| 32 | #define MCP9600_SENSOR_CFG 0x05 |
| 33 | #define MCP9600_SENSOR_TYPE_MASK GENMASK(6, 4) |
| 34 | #define MCP9600_ALERT_CFG1 0x08 |
| 35 | #define MCP9600_ALERT_CFG(x) (MCP9600_ALERT_CFG1 + (x - 1)) |
| 36 | #define MCP9600_ALERT_CFG_ENABLE BIT(0) |
| 37 | #define MCP9600_ALERT_CFG_ACTIVE_HIGH BIT(2) |
| 38 | #define MCP9600_ALERT_CFG_FALLING BIT(3) |
| 39 | #define MCP9600_ALERT_CFG_COLD_JUNCTION BIT(4) |
| 40 | #define MCP9600_ALERT_HYSTERESIS1 0x0c |
| 41 | #define MCP9600_ALERT_HYSTERESIS(x) (MCP9600_ALERT_HYSTERESIS1 + (x - 1)) |
| 42 | #define MCP9600_ALERT_LIMIT1 0x10 |
| 43 | #define MCP9600_ALERT_LIMIT(x) (MCP9600_ALERT_LIMIT1 + (x - 1)) |
| 44 | #define MCP9600_ALERT_LIMIT_MASK GENMASK(15, 2) |
| 45 | #define MCP9600_DEVICE_ID 0x20 |
| 46 | |
| 47 | /* MCP9600 device id value */ |
| 48 | #define MCP9600_DEVICE_ID_MCP9600 0x40 |
| 49 | #define MCP9600_DEVICE_ID_MCP9601 0x41 |
| 50 | |
| 51 | #define MCP9600_ALERT_COUNT 4 |
| 52 | |
| 53 | #define MCP9600_MIN_TEMP_HOT_JUNCTION_MICRO -200000000 |
| 54 | #define MCP9600_MAX_TEMP_HOT_JUNCTION_MICRO 1800000000 |
| 55 | |
| 56 | #define MCP9600_MIN_TEMP_COLD_JUNCTION_MICRO -40000000 |
| 57 | #define MCP9600_MAX_TEMP_COLD_JUNCTION_MICRO 125000000 |
| 58 | |
| 59 | enum mcp9600_alert { |
| 60 | MCP9600_ALERT1, |
| 61 | MCP9600_ALERT2, |
| 62 | MCP9600_ALERT3, |
| 63 | MCP9600_ALERT4 |
| 64 | }; |
| 65 | |
| 66 | static const char * const mcp9600_alert_name[MCP9600_ALERT_COUNT] = { |
| 67 | [MCP9600_ALERT1] = "alert1" , |
| 68 | [MCP9600_ALERT2] = "alert2" , |
| 69 | [MCP9600_ALERT3] = "alert3" , |
| 70 | [MCP9600_ALERT4] = "alert4" , |
| 71 | }; |
| 72 | |
| 73 | /* Map between dt-bindings enum and the chip's type value */ |
| 74 | static const unsigned int mcp9600_type_map[] = { |
| 75 | [THERMOCOUPLE_TYPE_K] = 0, |
| 76 | [THERMOCOUPLE_TYPE_J] = 1, |
| 77 | [THERMOCOUPLE_TYPE_T] = 2, |
| 78 | [THERMOCOUPLE_TYPE_N] = 3, |
| 79 | [THERMOCOUPLE_TYPE_S] = 4, |
| 80 | [THERMOCOUPLE_TYPE_E] = 5, |
| 81 | [THERMOCOUPLE_TYPE_B] = 6, |
| 82 | [THERMOCOUPLE_TYPE_R] = 7, |
| 83 | }; |
| 84 | |
| 85 | /* Map thermocouple type to a char for iio info in sysfs */ |
| 86 | static const int mcp9600_tc_types[] = { |
| 87 | [THERMOCOUPLE_TYPE_K] = 'K', |
| 88 | [THERMOCOUPLE_TYPE_J] = 'J', |
| 89 | [THERMOCOUPLE_TYPE_T] = 'T', |
| 90 | [THERMOCOUPLE_TYPE_N] = 'N', |
| 91 | [THERMOCOUPLE_TYPE_S] = 'S', |
| 92 | [THERMOCOUPLE_TYPE_E] = 'E', |
| 93 | [THERMOCOUPLE_TYPE_B] = 'B', |
| 94 | [THERMOCOUPLE_TYPE_R] = 'R', |
| 95 | }; |
| 96 | |
| 97 | static const struct iio_event_spec mcp9600_events[] = { |
| 98 | { |
| 99 | .type = IIO_EV_TYPE_THRESH, |
| 100 | .dir = IIO_EV_DIR_RISING, |
| 101 | .mask_separate = BIT(IIO_EV_INFO_ENABLE) | |
| 102 | BIT(IIO_EV_INFO_VALUE) | |
| 103 | BIT(IIO_EV_INFO_HYSTERESIS), |
| 104 | }, |
| 105 | { |
| 106 | .type = IIO_EV_TYPE_THRESH, |
| 107 | .dir = IIO_EV_DIR_FALLING, |
| 108 | .mask_separate = BIT(IIO_EV_INFO_ENABLE) | |
| 109 | BIT(IIO_EV_INFO_VALUE) | |
| 110 | BIT(IIO_EV_INFO_HYSTERESIS), |
| 111 | }, |
| 112 | }; |
| 113 | |
| 114 | struct mcp_chip_info { |
| 115 | u8 chip_id; |
| 116 | const char *chip_name; |
| 117 | }; |
| 118 | |
| 119 | struct mcp9600_data { |
| 120 | struct i2c_client *client; |
| 121 | u32 thermocouple_type; |
| 122 | }; |
| 123 | |
| 124 | static int mcp9600_config(struct mcp9600_data *data) |
| 125 | { |
| 126 | struct i2c_client *client = data->client; |
| 127 | int ret; |
| 128 | u8 cfg; |
| 129 | |
| 130 | cfg = FIELD_PREP(MCP9600_SENSOR_TYPE_MASK, |
| 131 | mcp9600_type_map[data->thermocouple_type]); |
| 132 | |
| 133 | ret = i2c_smbus_write_byte_data(client, MCP9600_SENSOR_CFG, value: cfg); |
| 134 | if (ret < 0) { |
| 135 | dev_err(&client->dev, "Failed to set sensor configuration\n" ); |
| 136 | return ret; |
| 137 | } |
| 138 | |
| 139 | return 0; |
| 140 | } |
| 141 | |
| 142 | #define MCP9600_CHANNELS(hj_num_ev, hj_ev_spec_off, cj_num_ev, cj_ev_spec_off) \ |
| 143 | { \ |
| 144 | { \ |
| 145 | .type = IIO_TEMP, \ |
| 146 | .address = MCP9600_HOT_JUNCTION, \ |
| 147 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ |
| 148 | BIT(IIO_CHAN_INFO_THERMOCOUPLE_TYPE) | \ |
| 149 | BIT(IIO_CHAN_INFO_SCALE), \ |
| 150 | .event_spec = &mcp9600_events[hj_ev_spec_off], \ |
| 151 | .num_event_specs = hj_num_ev, \ |
| 152 | }, \ |
| 153 | { \ |
| 154 | .type = IIO_TEMP, \ |
| 155 | .address = MCP9600_COLD_JUNCTION, \ |
| 156 | .channel2 = IIO_MOD_TEMP_AMBIENT, \ |
| 157 | .modified = 1, \ |
| 158 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ |
| 159 | BIT(IIO_CHAN_INFO_SCALE), \ |
| 160 | .event_spec = &mcp9600_events[cj_ev_spec_off], \ |
| 161 | .num_event_specs = cj_num_ev, \ |
| 162 | }, \ |
| 163 | } |
| 164 | |
| 165 | static const struct iio_chan_spec mcp9600_channels[][2] = { |
| 166 | MCP9600_CHANNELS(0, 0, 0, 0), /* Alerts: - - - - */ |
| 167 | MCP9600_CHANNELS(1, 0, 0, 0), /* Alerts: 1 - - - */ |
| 168 | MCP9600_CHANNELS(1, 1, 0, 0), /* Alerts: - 2 - - */ |
| 169 | MCP9600_CHANNELS(2, 0, 0, 0), /* Alerts: 1 2 - - */ |
| 170 | MCP9600_CHANNELS(0, 0, 1, 0), /* Alerts: - - 3 - */ |
| 171 | MCP9600_CHANNELS(1, 0, 1, 0), /* Alerts: 1 - 3 - */ |
| 172 | MCP9600_CHANNELS(1, 1, 1, 0), /* Alerts: - 2 3 - */ |
| 173 | MCP9600_CHANNELS(2, 0, 1, 0), /* Alerts: 1 2 3 - */ |
| 174 | MCP9600_CHANNELS(0, 0, 1, 1), /* Alerts: - - - 4 */ |
| 175 | MCP9600_CHANNELS(1, 0, 1, 1), /* Alerts: 1 - - 4 */ |
| 176 | MCP9600_CHANNELS(1, 1, 1, 1), /* Alerts: - 2 - 4 */ |
| 177 | MCP9600_CHANNELS(2, 0, 1, 1), /* Alerts: 1 2 - 4 */ |
| 178 | MCP9600_CHANNELS(0, 0, 2, 0), /* Alerts: - - 3 4 */ |
| 179 | MCP9600_CHANNELS(1, 0, 2, 0), /* Alerts: 1 - 3 4 */ |
| 180 | MCP9600_CHANNELS(1, 1, 2, 0), /* Alerts: - 2 3 4 */ |
| 181 | MCP9600_CHANNELS(2, 0, 2, 0), /* Alerts: 1 2 3 4 */ |
| 182 | }; |
| 183 | |
| 184 | static int mcp9600_read(struct mcp9600_data *data, |
| 185 | struct iio_chan_spec const *chan, int *val) |
| 186 | { |
| 187 | int ret; |
| 188 | |
| 189 | ret = i2c_smbus_read_word_swapped(client: data->client, command: chan->address); |
| 190 | |
| 191 | if (ret < 0) |
| 192 | return ret; |
| 193 | |
| 194 | *val = sign_extend32(value: ret, index: 15); |
| 195 | |
| 196 | return 0; |
| 197 | } |
| 198 | |
| 199 | static int mcp9600_read_raw(struct iio_dev *indio_dev, |
| 200 | struct iio_chan_spec const *chan, int *val, |
| 201 | int *val2, long mask) |
| 202 | { |
| 203 | struct mcp9600_data *data = iio_priv(indio_dev); |
| 204 | int ret; |
| 205 | |
| 206 | switch (mask) { |
| 207 | case IIO_CHAN_INFO_RAW: |
| 208 | ret = mcp9600_read(data, chan, val); |
| 209 | if (ret) |
| 210 | return ret; |
| 211 | return IIO_VAL_INT; |
| 212 | case IIO_CHAN_INFO_SCALE: |
| 213 | *val = 62; |
| 214 | *val2 = 500000; |
| 215 | return IIO_VAL_INT_PLUS_MICRO; |
| 216 | case IIO_CHAN_INFO_THERMOCOUPLE_TYPE: |
| 217 | *val = mcp9600_tc_types[data->thermocouple_type]; |
| 218 | return IIO_VAL_CHAR; |
| 219 | default: |
| 220 | return -EINVAL; |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | static int mcp9600_get_alert_index(int channel2, enum iio_event_direction dir) |
| 225 | { |
| 226 | if (channel2 == IIO_MOD_TEMP_AMBIENT) { |
| 227 | if (dir == IIO_EV_DIR_RISING) |
| 228 | return MCP9600_ALERT3; |
| 229 | else |
| 230 | return MCP9600_ALERT4; |
| 231 | } else { |
| 232 | if (dir == IIO_EV_DIR_RISING) |
| 233 | return MCP9600_ALERT1; |
| 234 | else |
| 235 | return MCP9600_ALERT2; |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | static int mcp9600_read_event_config(struct iio_dev *indio_dev, |
| 240 | const struct iio_chan_spec *chan, |
| 241 | enum iio_event_type type, |
| 242 | enum iio_event_direction dir) |
| 243 | { |
| 244 | struct mcp9600_data *data = iio_priv(indio_dev); |
| 245 | struct i2c_client *client = data->client; |
| 246 | int i, ret; |
| 247 | |
| 248 | i = mcp9600_get_alert_index(channel2: chan->channel2, dir); |
| 249 | ret = i2c_smbus_read_byte_data(client, MCP9600_ALERT_CFG(i + 1)); |
| 250 | if (ret < 0) |
| 251 | return ret; |
| 252 | |
| 253 | return FIELD_GET(MCP9600_ALERT_CFG_ENABLE, ret); |
| 254 | } |
| 255 | |
| 256 | static int mcp9600_write_event_config(struct iio_dev *indio_dev, |
| 257 | const struct iio_chan_spec *chan, |
| 258 | enum iio_event_type type, |
| 259 | enum iio_event_direction dir, |
| 260 | bool state) |
| 261 | { |
| 262 | struct mcp9600_data *data = iio_priv(indio_dev); |
| 263 | struct i2c_client *client = data->client; |
| 264 | int i, ret; |
| 265 | |
| 266 | i = mcp9600_get_alert_index(channel2: chan->channel2, dir); |
| 267 | ret = i2c_smbus_read_byte_data(client, MCP9600_ALERT_CFG(i + 1)); |
| 268 | if (ret < 0) |
| 269 | return ret; |
| 270 | |
| 271 | if (state) |
| 272 | ret |= MCP9600_ALERT_CFG_ENABLE; |
| 273 | else |
| 274 | ret &= ~MCP9600_ALERT_CFG_ENABLE; |
| 275 | |
| 276 | return i2c_smbus_write_byte_data(client, MCP9600_ALERT_CFG(i + 1), value: ret); |
| 277 | } |
| 278 | |
| 279 | static int mcp9600_read_thresh(struct iio_dev *indio_dev, |
| 280 | const struct iio_chan_spec *chan, |
| 281 | enum iio_event_type type, |
| 282 | enum iio_event_direction dir, |
| 283 | enum iio_event_info info, int *val, int *val2) |
| 284 | { |
| 285 | struct mcp9600_data *data = iio_priv(indio_dev); |
| 286 | struct i2c_client *client = data->client; |
| 287 | s32 ret; |
| 288 | int i; |
| 289 | |
| 290 | i = mcp9600_get_alert_index(channel2: chan->channel2, dir); |
| 291 | switch (info) { |
| 292 | case IIO_EV_INFO_VALUE: |
| 293 | ret = i2c_smbus_read_word_swapped(client, MCP9600_ALERT_LIMIT(i + 1)); |
| 294 | if (ret < 0) |
| 295 | return ret; |
| 296 | /* |
| 297 | * Temperature is stored in two’s complement format in |
| 298 | * bits(15:2), LSB is 0.25 degree celsius. |
| 299 | */ |
| 300 | *val = sign_extend32(FIELD_GET(MCP9600_ALERT_LIMIT_MASK, ret), index: 13); |
| 301 | *val2 = 4; |
| 302 | return IIO_VAL_FRACTIONAL; |
| 303 | case IIO_EV_INFO_HYSTERESIS: |
| 304 | ret = i2c_smbus_read_byte_data(client, MCP9600_ALERT_HYSTERESIS(i + 1)); |
| 305 | if (ret < 0) |
| 306 | return ret; |
| 307 | |
| 308 | *val = ret; |
| 309 | return IIO_VAL_INT; |
| 310 | default: |
| 311 | return -EINVAL; |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | static int mcp9600_write_thresh(struct iio_dev *indio_dev, |
| 316 | const struct iio_chan_spec *chan, |
| 317 | enum iio_event_type type, |
| 318 | enum iio_event_direction dir, |
| 319 | enum iio_event_info info, int val, int val2) |
| 320 | { |
| 321 | struct mcp9600_data *data = iio_priv(indio_dev); |
| 322 | struct i2c_client *client = data->client; |
| 323 | int s_val, i; |
| 324 | s16 thresh; |
| 325 | u8 hyst; |
| 326 | |
| 327 | i = mcp9600_get_alert_index(channel2: chan->channel2, dir); |
| 328 | switch (info) { |
| 329 | case IIO_EV_INFO_VALUE: |
| 330 | /* Scale value to include decimal part into calculations */ |
| 331 | s_val = (val < 0) ? ((val * 1000000) - val2) : |
| 332 | ((val * 1000000) + val2); |
| 333 | if (chan->channel2 == IIO_MOD_TEMP_AMBIENT) { |
| 334 | s_val = max(s_val, MCP9600_MIN_TEMP_COLD_JUNCTION_MICRO); |
| 335 | s_val = min(s_val, MCP9600_MAX_TEMP_COLD_JUNCTION_MICRO); |
| 336 | } else { |
| 337 | s_val = max(s_val, MCP9600_MIN_TEMP_HOT_JUNCTION_MICRO); |
| 338 | s_val = min(s_val, MCP9600_MAX_TEMP_HOT_JUNCTION_MICRO); |
| 339 | } |
| 340 | |
| 341 | /* |
| 342 | * Shift length 4 bits = 2(15:2) + 2(0.25 LSB), temperature is |
| 343 | * stored in two’s complement format. |
| 344 | */ |
| 345 | thresh = (s16)(s_val / (1000000 >> 4)); |
| 346 | return i2c_smbus_write_word_swapped(client, |
| 347 | MCP9600_ALERT_LIMIT(i + 1), |
| 348 | value: thresh); |
| 349 | case IIO_EV_INFO_HYSTERESIS: |
| 350 | hyst = min(abs(val), 255); |
| 351 | return i2c_smbus_write_byte_data(client, |
| 352 | MCP9600_ALERT_HYSTERESIS(i + 1), |
| 353 | value: hyst); |
| 354 | default: |
| 355 | return -EINVAL; |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | static const struct iio_info mcp9600_info = { |
| 360 | .read_raw = mcp9600_read_raw, |
| 361 | .read_event_config = mcp9600_read_event_config, |
| 362 | .write_event_config = mcp9600_write_event_config, |
| 363 | .read_event_value = mcp9600_read_thresh, |
| 364 | .write_event_value = mcp9600_write_thresh, |
| 365 | }; |
| 366 | |
| 367 | static irqreturn_t mcp9600_alert_handler(void *private, |
| 368 | enum mcp9600_alert alert, |
| 369 | enum iio_modifier mod, |
| 370 | enum iio_event_direction dir) |
| 371 | { |
| 372 | struct iio_dev *indio_dev = private; |
| 373 | struct mcp9600_data *data = iio_priv(indio_dev); |
| 374 | int ret; |
| 375 | |
| 376 | ret = i2c_smbus_read_byte_data(client: data->client, MCP9600_STATUS); |
| 377 | if (ret < 0) |
| 378 | return IRQ_HANDLED; |
| 379 | |
| 380 | if (!(ret & MCP9600_STATUS_ALERT(alert))) |
| 381 | return IRQ_NONE; |
| 382 | |
| 383 | iio_push_event(indio_dev, |
| 384 | IIO_MOD_EVENT_CODE(IIO_TEMP, 0, mod, IIO_EV_TYPE_THRESH, |
| 385 | dir), |
| 386 | timestamp: iio_get_time_ns(indio_dev)); |
| 387 | |
| 388 | return IRQ_HANDLED; |
| 389 | } |
| 390 | |
| 391 | static irqreturn_t mcp9600_alert1_handler(int irq, void *private) |
| 392 | { |
| 393 | return mcp9600_alert_handler(private, alert: MCP9600_ALERT1, mod: IIO_NO_MOD, |
| 394 | dir: IIO_EV_DIR_RISING); |
| 395 | } |
| 396 | |
| 397 | static irqreturn_t mcp9600_alert2_handler(int irq, void *private) |
| 398 | { |
| 399 | return mcp9600_alert_handler(private, alert: MCP9600_ALERT2, mod: IIO_NO_MOD, |
| 400 | dir: IIO_EV_DIR_FALLING); |
| 401 | } |
| 402 | |
| 403 | static irqreturn_t mcp9600_alert3_handler(int irq, void *private) |
| 404 | { |
| 405 | return mcp9600_alert_handler(private, alert: MCP9600_ALERT3, |
| 406 | mod: IIO_MOD_TEMP_AMBIENT, dir: IIO_EV_DIR_RISING); |
| 407 | } |
| 408 | |
| 409 | static irqreturn_t mcp9600_alert4_handler(int irq, void *private) |
| 410 | { |
| 411 | return mcp9600_alert_handler(private, alert: MCP9600_ALERT4, |
| 412 | mod: IIO_MOD_TEMP_AMBIENT, dir: IIO_EV_DIR_FALLING); |
| 413 | } |
| 414 | |
| 415 | static irqreturn_t (*mcp9600_alert_handler_func[MCP9600_ALERT_COUNT]) (int, void *) = { |
| 416 | mcp9600_alert1_handler, |
| 417 | mcp9600_alert2_handler, |
| 418 | mcp9600_alert3_handler, |
| 419 | mcp9600_alert4_handler, |
| 420 | }; |
| 421 | |
| 422 | static int mcp9600_probe_alerts(struct iio_dev *indio_dev) |
| 423 | { |
| 424 | struct mcp9600_data *data = iio_priv(indio_dev); |
| 425 | struct i2c_client *client = data->client; |
| 426 | struct device *dev = &client->dev; |
| 427 | struct fwnode_handle *fwnode = dev_fwnode(dev); |
| 428 | unsigned int irq_type; |
| 429 | int ret, irq, i; |
| 430 | u8 val, ch_sel; |
| 431 | |
| 432 | /* |
| 433 | * alert1: hot junction, rising temperature |
| 434 | * alert2: hot junction, falling temperature |
| 435 | * alert3: cold junction, rising temperature |
| 436 | * alert4: cold junction, falling temperature |
| 437 | */ |
| 438 | ch_sel = 0; |
| 439 | for (i = 0; i < MCP9600_ALERT_COUNT; i++) { |
| 440 | irq = fwnode_irq_get_byname(fwnode, name: mcp9600_alert_name[i]); |
| 441 | if (irq <= 0) |
| 442 | continue; |
| 443 | |
| 444 | val = 0; |
| 445 | irq_type = irq_get_trigger_type(irq); |
| 446 | if (irq_type == IRQ_TYPE_EDGE_RISING) |
| 447 | val |= MCP9600_ALERT_CFG_ACTIVE_HIGH; |
| 448 | |
| 449 | if (i == MCP9600_ALERT2 || i == MCP9600_ALERT4) |
| 450 | val |= MCP9600_ALERT_CFG_FALLING; |
| 451 | |
| 452 | if (i == MCP9600_ALERT3 || i == MCP9600_ALERT4) |
| 453 | val |= MCP9600_ALERT_CFG_COLD_JUNCTION; |
| 454 | |
| 455 | ret = i2c_smbus_write_byte_data(client, |
| 456 | MCP9600_ALERT_CFG(i + 1), |
| 457 | value: val); |
| 458 | if (ret < 0) |
| 459 | return ret; |
| 460 | |
| 461 | ret = devm_request_threaded_irq(dev, irq, NULL, |
| 462 | thread_fn: mcp9600_alert_handler_func[i], |
| 463 | IRQF_ONESHOT, devname: "mcp9600" , |
| 464 | dev_id: indio_dev); |
| 465 | if (ret) |
| 466 | return ret; |
| 467 | |
| 468 | ch_sel |= BIT(i); |
| 469 | } |
| 470 | |
| 471 | return ch_sel; |
| 472 | } |
| 473 | |
| 474 | static int mcp9600_probe(struct i2c_client *client) |
| 475 | { |
| 476 | struct device *dev = &client->dev; |
| 477 | const struct mcp_chip_info *chip_info; |
| 478 | struct iio_dev *indio_dev; |
| 479 | struct mcp9600_data *data; |
| 480 | int ch_sel, dev_id, ret; |
| 481 | |
| 482 | chip_info = i2c_get_match_data(client); |
| 483 | if (!chip_info) |
| 484 | return dev_err_probe(dev, err: -ENODEV, |
| 485 | fmt: "No chip-info found for device\n" ); |
| 486 | |
| 487 | dev_id = i2c_smbus_read_byte_data(client, MCP9600_DEVICE_ID); |
| 488 | if (dev_id < 0) |
| 489 | return dev_err_probe(dev, err: dev_id, fmt: "Failed to read device ID\n" ); |
| 490 | |
| 491 | switch (dev_id) { |
| 492 | case MCP9600_DEVICE_ID_MCP9600: |
| 493 | case MCP9600_DEVICE_ID_MCP9601: |
| 494 | if (dev_id != chip_info->chip_id) |
| 495 | dev_warn(dev, |
| 496 | "Expected id %02x, but device responded with %02x\n" , |
| 497 | chip_info->chip_id, dev_id); |
| 498 | break; |
| 499 | |
| 500 | default: |
| 501 | dev_warn(dev, "Unknown id %x, using %x\n" , dev_id, |
| 502 | chip_info->chip_id); |
| 503 | } |
| 504 | |
| 505 | indio_dev = devm_iio_device_alloc(parent: dev, sizeof_priv: sizeof(*data)); |
| 506 | if (!indio_dev) |
| 507 | return -ENOMEM; |
| 508 | |
| 509 | data = iio_priv(indio_dev); |
| 510 | data->client = client; |
| 511 | |
| 512 | /* Accept type from dt with default of Type-K. */ |
| 513 | data->thermocouple_type = THERMOCOUPLE_TYPE_K; |
| 514 | ret = device_property_read_u32(dev, propname: "thermocouple-type" , |
| 515 | val: &data->thermocouple_type); |
| 516 | if (ret && ret != -EINVAL) |
| 517 | return dev_err_probe(dev, err: ret, |
| 518 | fmt: "Error reading thermocouple-type property\n" ); |
| 519 | |
| 520 | if (data->thermocouple_type >= ARRAY_SIZE(mcp9600_type_map)) |
| 521 | return dev_err_probe(dev, err: -EINVAL, |
| 522 | fmt: "Invalid thermocouple-type property %u.\n" , |
| 523 | data->thermocouple_type); |
| 524 | |
| 525 | /* Set initial config. */ |
| 526 | ret = mcp9600_config(data); |
| 527 | if (ret) |
| 528 | return ret; |
| 529 | |
| 530 | ch_sel = mcp9600_probe_alerts(indio_dev); |
| 531 | if (ch_sel < 0) |
| 532 | return ch_sel; |
| 533 | |
| 534 | indio_dev->info = &mcp9600_info; |
| 535 | indio_dev->name = chip_info->chip_name; |
| 536 | indio_dev->modes = INDIO_DIRECT_MODE; |
| 537 | indio_dev->channels = mcp9600_channels[ch_sel]; |
| 538 | indio_dev->num_channels = ARRAY_SIZE(mcp9600_channels[ch_sel]); |
| 539 | |
| 540 | return devm_iio_device_register(dev, indio_dev); |
| 541 | } |
| 542 | |
| 543 | static const struct mcp_chip_info mcp9600_chip_info = { |
| 544 | .chip_id = MCP9600_DEVICE_ID_MCP9600, |
| 545 | .chip_name = "mcp9600" , |
| 546 | }; |
| 547 | |
| 548 | static const struct mcp_chip_info mcp9601_chip_info = { |
| 549 | .chip_id = MCP9600_DEVICE_ID_MCP9601, |
| 550 | .chip_name = "mcp9601" , |
| 551 | }; |
| 552 | |
| 553 | static const struct i2c_device_id mcp9600_id[] = { |
| 554 | { "mcp9600" , .driver_data = (kernel_ulong_t)&mcp9600_chip_info }, |
| 555 | { "mcp9601" , .driver_data = (kernel_ulong_t)&mcp9601_chip_info }, |
| 556 | { } |
| 557 | }; |
| 558 | MODULE_DEVICE_TABLE(i2c, mcp9600_id); |
| 559 | |
| 560 | static const struct of_device_id mcp9600_of_match[] = { |
| 561 | { .compatible = "microchip,mcp9600" , .data = &mcp9600_chip_info }, |
| 562 | { .compatible = "microchip,mcp9601" , .data = &mcp9601_chip_info }, |
| 563 | { } |
| 564 | }; |
| 565 | MODULE_DEVICE_TABLE(of, mcp9600_of_match); |
| 566 | |
| 567 | static struct i2c_driver mcp9600_driver = { |
| 568 | .driver = { |
| 569 | .name = "mcp9600" , |
| 570 | .of_match_table = mcp9600_of_match, |
| 571 | }, |
| 572 | .probe = mcp9600_probe, |
| 573 | .id_table = mcp9600_id |
| 574 | }; |
| 575 | module_i2c_driver(mcp9600_driver); |
| 576 | |
| 577 | MODULE_AUTHOR("Dimitri Fedrau <dima.fedrau@gmail.com>" ); |
| 578 | MODULE_AUTHOR("Andrew Hepp <andrew.hepp@ahepp.dev>" ); |
| 579 | MODULE_DESCRIPTION("Microchip MCP9600 thermocouple EMF converter driver" ); |
| 580 | MODULE_LICENSE("GPL" ); |
| 581 | |