brintos

brintos / linux-shallow public Read only

0
0
Text · 12.9 KiB · f1bb097 Raw
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