Created
June 13, 2016 20:04
-
-
Save AlisonSchofield/3c4973fcb7130022a71194d2db9d8a9b to your computer and use it in GitHub Desktop.
mcp9808: add support for changing measurement resolution
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* mcp9808.c - Support for Microchip MCP9808 Digital Temperature Sensor | |
* | |
* Copyright (C) 2016 Alison Schofield <amsfield22@gmail.com> | |
* | |
* This program is free software; you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License version 2 as | |
* published by the Free Software Foundation. | |
* | |
* Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/25095A.pdf | |
*/ | |
#include <linux/i2c.h> | |
#include <linux/module.h> | |
#include <linux/iio/iio.h> | |
#include <linux/iio/sysfs.h> | |
#define MCP9808_REG_TAMBIENT 0x05 | |
#define MCP9808_REG_MANUF_ID 0x06 | |
#define MCP9808_REG_DEVICE_ID 0x07 | |
#define MCP9808_REG_RESOLUTION 0x08 | |
struct mcp9808_data { | |
struct i2c_client *client; | |
struct mutex lock; | |
int int_time; /* sensor integration time */ | |
}; | |
/* integration time in microseconds, register bit values to set */ | |
static const int mcp9808_int_time[][2] = { | |
{500000, 0}, | |
{250000, 1}, | |
{125000, 2}, | |
{ 62500, 3} | |
}; | |
static IIO_CONST_ATTR(temp_integration_time_available, | |
"0.5 0.25 0.125 0.0625"); | |
static struct attribute *mcp9808_attributes[] = { | |
&iio_const_attr_temp_integration_time_available.dev_attr.attr, | |
NULL | |
}; | |
static struct attribute_group mcp9808_attribute_group = { | |
.attrs = mcp9808_attributes, | |
}; | |
static int mcp9808_set_it_time(struct mcp9808_data *data, int val2) | |
{ | |
int ret = -EINVAL; | |
int i; | |
for (i = 0; i < ARRAY_SIZE(mcp9808_int_time); i++) { | |
if (val2 == mcp9808_int_time[i][0]) { | |
mutex_lock(&data->lock); | |
ret = i2c_smbus_write_byte_data(data->client, | |
MCP9808_REG_RESOLUTION, mcp9808_int_time[i][1]); | |
data->int_time = val2; | |
mutex_unlock(&data->lock); | |
break; | |
} | |
} | |
return ret; | |
} | |
static int mcp9808_read_raw(struct iio_dev *indio_dev, | |
struct iio_chan_spec const *channel, | |
int *val, int *val2, long mask) | |
{ | |
struct mcp9808_data *data = iio_priv(indio_dev); | |
int ret; | |
switch (mask) { | |
case IIO_CHAN_INFO_RAW: | |
if (channel->type == IIO_TEMP) { | |
ret = i2c_smbus_read_word_swapped(data->client, | |
MCP9808_REG_TAMBIENT); | |
if (ret < 0) | |
return ret; | |
*val = sign_extend32(ret, 12); | |
return IIO_VAL_INT; | |
} | |
case IIO_CHAN_INFO_SCALE: | |
*val = 0; | |
*val2 = 62500; | |
return IIO_VAL_INT_PLUS_MICRO; | |
case IIO_CHAN_INFO_INT_TIME: | |
*val = 0; | |
*val2 = data->int_time; | |
return IIO_VAL_INT_PLUS_MICRO; | |
default: | |
break; | |
} | |
return -EINVAL; | |
} | |
static int mcp9808_write_raw(struct iio_dev *indio_dev, | |
struct iio_chan_spec const *channel, | |
int val, int val2, long mask) | |
{ | |
struct mcp9808_data *data = iio_priv(indio_dev); | |
int ret; | |
if (mask == IIO_CHAN_INFO_INT_TIME) { | |
if (val != 0) | |
return -EINVAL; | |
ret = mcp9808_set_it_time(data, val2); | |
return ret; | |
} | |
return -EINVAL; | |
} | |
static const struct iio_chan_spec mcp9808_channels[] = { | |
{ | |
.type = IIO_TEMP, | |
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | | |
BIT(IIO_CHAN_INFO_SCALE) | | |
BIT(IIO_CHAN_INFO_INT_TIME) | |
} | |
}; | |
static const struct iio_info mcp9808_info = { | |
.read_raw = mcp9808_read_raw, | |
.write_raw = mcp9808_write_raw, | |
.attrs = &mcp9808_attribute_group, | |
.driver_module = THIS_MODULE, | |
}; | |
static bool mcp9808_check_id(struct i2c_client *client) | |
{ | |
int mid, did; | |
mid = i2c_smbus_read_word_swapped(client, MCP9808_REG_MANUF_ID); | |
if (mid < 0) | |
return false; | |
did = i2c_smbus_read_word_swapped(client, MCP9808_REG_DEVICE_ID); | |
if (did < 0) | |
return false; | |
if ((mid == 0x54) && ((did & 0xff00) == 0x0400)) { | |
dev_info(&client->dev, "MCP9808 Temperature Sensor\n"); | |
dev_info(&client->dev, | |
"MCP9808 Manuf_id: 0x54 Device_id: 0x04 Rev: 0x%x\n", | |
(did & 0x0f)); | |
return true; | |
} | |
return false; | |
} | |
static int mcp9808_probe(struct i2c_client *client, | |
const struct i2c_device_id *id) | |
{ | |
struct iio_dev *indio_dev; | |
struct mcp9808_data *data; | |
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) | |
return -EOPNOTSUPP; | |
if (!mcp9808_check_id(client)) { | |
dev_err(&client->dev, "no MCP9808 sensor\n"); | |
return -ENODEV; | |
} | |
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); | |
if (!indio_dev) | |
return -ENOMEM; | |
data = iio_priv(indio_dev); | |
i2c_set_clientdata(client, indio_dev); | |
data->client = client; | |
mutex_init(&data->lock); | |
data->int_time = 62500; /* power-on default integration time */ | |
indio_dev->dev.parent = &client->dev; | |
indio_dev->name = id->name; | |
indio_dev->info = &mcp9808_info; | |
indio_dev->channels = mcp9808_channels; | |
indio_dev->num_channels = ARRAY_SIZE(mcp9808_channels); | |
return devm_iio_device_register(&client->dev, indio_dev); | |
} | |
static const struct i2c_device_id mcp9808_id[] = { | |
{ "mcp9808", 0 }, | |
{ } | |
}; | |
MODULE_DEVICE_TABLE(i2c, mcp9808_id); | |
static struct i2c_driver mcp9808_driver = { | |
.driver = { | |
.name = "mcp9808", | |
}, | |
.probe = mcp9808_probe, | |
.id_table = mcp9808_id, | |
}; | |
module_i2c_driver(mcp9808_driver); | |
MODULE_AUTHOR("Alison Schofield <amsfield22@gmail.com>"); | |
MODULE_DESCRIPTION("MCP9808 Temperature Sensor Driver"); | |
MODULE_LICENSE("GPL v2"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment