You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
506 lines
13 KiB
506 lines
13 KiB
/* Copyright (c) 2010 Christoph Mair <christoph.mair@gmail.com> |
|
* Copyright (c) 2012 Bosch Sensortec GmbH |
|
* Copyright (c) 2012 Unixphere AB |
|
* |
|
* This driver supports the bmp085 and bmp18x digital barometric pressure |
|
* and temperature sensors from Bosch Sensortec. The datasheets |
|
* are available from their website: |
|
* http://www.bosch-sensortec.com/content/language1/downloads/BST-BMP085-DS000-05.pdf |
|
* http://www.bosch-sensortec.com/content/language1/downloads/BST-BMP180-DS000-07.pdf |
|
* |
|
* A pressure measurement is issued by reading from pressure0_input. |
|
* The return value ranges from 30000 to 110000 pascal with a resulution |
|
* of 1 pascal (0.01 millibar) which enables measurements from 9000m above |
|
* to 500m below sea level. |
|
* |
|
* The temperature can be read from temp0_input. Values range from |
|
* -400 to 850 representing the ambient temperature in degree celsius |
|
* multiplied by 10.The resolution is 0.1 celsius. |
|
* |
|
* Because ambient pressure is temperature dependent, a temperature |
|
* measurement will be executed automatically even if the user is reading |
|
* from pressure0_input. This happens if the last temperature measurement |
|
* has been executed more then one second ago. |
|
* |
|
* To decrease RMS noise from pressure measurements, the bmp085 can |
|
* autonomously calculate the average of up to eight samples. This is |
|
* set up by writing to the oversampling sysfs file. Accepted values |
|
* are 0, 1, 2 and 3. 2^x when x is the value written to this file |
|
* specifies the number of samples used to calculate the ambient pressure. |
|
* RMS noise is specified with six pascal (without averaging) and decreases |
|
* down to 3 pascal when using an oversampling setting of 3. |
|
* |
|
* This program is free software; you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation; either version 2 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with this program; if not, write to the Free Software |
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
|
*/ |
|
|
|
#include <linux/module.h> |
|
#include <linux/device.h> |
|
#include <linux/slab.h> |
|
#include <linux/of.h> |
|
#include "bmp085.h" |
|
#include <linux/interrupt.h> |
|
#include <linux/completion.h> |
|
#include <linux/gpio.h> |
|
|
|
#define BMP085_CHIP_ID 0x55 |
|
#define BMP085_CALIBRATION_DATA_START 0xAA |
|
#define BMP085_CALIBRATION_DATA_LENGTH 11 /* 16 bit values */ |
|
#define BMP085_CHIP_ID_REG 0xD0 |
|
#define BMP085_CTRL_REG 0xF4 |
|
#define BMP085_TEMP_MEASUREMENT 0x2E |
|
#define BMP085_PRESSURE_MEASUREMENT 0x34 |
|
#define BMP085_CONVERSION_REGISTER_MSB 0xF6 |
|
#define BMP085_CONVERSION_REGISTER_LSB 0xF7 |
|
#define BMP085_CONVERSION_REGISTER_XLSB 0xF8 |
|
#define BMP085_TEMP_CONVERSION_TIME 5 |
|
|
|
struct bmp085_calibration_data { |
|
s16 AC1, AC2, AC3; |
|
u16 AC4, AC5, AC6; |
|
s16 B1, B2; |
|
s16 MB, MC, MD; |
|
}; |
|
|
|
struct bmp085_data { |
|
struct device *dev; |
|
struct regmap *regmap; |
|
struct mutex lock; |
|
struct bmp085_calibration_data calibration; |
|
u8 oversampling_setting; |
|
u32 raw_temperature; |
|
u32 raw_pressure; |
|
u32 temp_measurement_period; |
|
unsigned long last_temp_measurement; |
|
u8 chip_id; |
|
s32 b6; /* calculated temperature correction coefficient */ |
|
int irq; |
|
struct completion done; |
|
}; |
|
|
|
static irqreturn_t bmp085_eoc_isr(int irq, void *devid) |
|
{ |
|
struct bmp085_data *data = devid; |
|
|
|
complete(&data->done); |
|
|
|
return IRQ_HANDLED; |
|
} |
|
|
|
static s32 bmp085_read_calibration_data(struct bmp085_data *data) |
|
{ |
|
u16 tmp[BMP085_CALIBRATION_DATA_LENGTH]; |
|
struct bmp085_calibration_data *cali = &(data->calibration); |
|
s32 status = regmap_bulk_read(data->regmap, |
|
BMP085_CALIBRATION_DATA_START, (u8 *)tmp, |
|
(BMP085_CALIBRATION_DATA_LENGTH << 1)); |
|
if (status < 0) |
|
return status; |
|
|
|
cali->AC1 = be16_to_cpu(tmp[0]); |
|
cali->AC2 = be16_to_cpu(tmp[1]); |
|
cali->AC3 = be16_to_cpu(tmp[2]); |
|
cali->AC4 = be16_to_cpu(tmp[3]); |
|
cali->AC5 = be16_to_cpu(tmp[4]); |
|
cali->AC6 = be16_to_cpu(tmp[5]); |
|
cali->B1 = be16_to_cpu(tmp[6]); |
|
cali->B2 = be16_to_cpu(tmp[7]); |
|
cali->MB = be16_to_cpu(tmp[8]); |
|
cali->MC = be16_to_cpu(tmp[9]); |
|
cali->MD = be16_to_cpu(tmp[10]); |
|
return 0; |
|
} |
|
|
|
static s32 bmp085_update_raw_temperature(struct bmp085_data *data) |
|
{ |
|
u16 tmp; |
|
s32 status; |
|
|
|
mutex_lock(&data->lock); |
|
|
|
init_completion(&data->done); |
|
|
|
status = regmap_write(data->regmap, BMP085_CTRL_REG, |
|
BMP085_TEMP_MEASUREMENT); |
|
if (status < 0) { |
|
dev_err(data->dev, |
|
"Error while requesting temperature measurement.\n"); |
|
goto exit; |
|
} |
|
wait_for_completion_timeout(&data->done, 1 + msecs_to_jiffies( |
|
BMP085_TEMP_CONVERSION_TIME)); |
|
|
|
status = regmap_bulk_read(data->regmap, BMP085_CONVERSION_REGISTER_MSB, |
|
&tmp, sizeof(tmp)); |
|
if (status < 0) { |
|
dev_err(data->dev, |
|
"Error while reading temperature measurement result\n"); |
|
goto exit; |
|
} |
|
data->raw_temperature = be16_to_cpu(tmp); |
|
data->last_temp_measurement = jiffies; |
|
status = 0; /* everything ok, return 0 */ |
|
|
|
exit: |
|
mutex_unlock(&data->lock); |
|
return status; |
|
} |
|
|
|
static s32 bmp085_update_raw_pressure(struct bmp085_data *data) |
|
{ |
|
u32 tmp = 0; |
|
s32 status; |
|
|
|
mutex_lock(&data->lock); |
|
|
|
init_completion(&data->done); |
|
|
|
status = regmap_write(data->regmap, BMP085_CTRL_REG, |
|
BMP085_PRESSURE_MEASUREMENT + |
|
(data->oversampling_setting << 6)); |
|
if (status < 0) { |
|
dev_err(data->dev, |
|
"Error while requesting pressure measurement.\n"); |
|
goto exit; |
|
} |
|
|
|
/* wait for the end of conversion */ |
|
wait_for_completion_timeout(&data->done, 1 + msecs_to_jiffies( |
|
2+(3 << data->oversampling_setting))); |
|
/* copy data into a u32 (4 bytes), but skip the first byte. */ |
|
status = regmap_bulk_read(data->regmap, BMP085_CONVERSION_REGISTER_MSB, |
|
((u8 *)&tmp)+1, 3); |
|
if (status < 0) { |
|
dev_err(data->dev, |
|
"Error while reading pressure measurement results\n"); |
|
goto exit; |
|
} |
|
data->raw_pressure = be32_to_cpu((tmp)); |
|
data->raw_pressure >>= (8-data->oversampling_setting); |
|
status = 0; /* everything ok, return 0 */ |
|
|
|
exit: |
|
mutex_unlock(&data->lock); |
|
return status; |
|
} |
|
|
|
/* |
|
* This function starts the temperature measurement and returns the value |
|
* in tenth of a degree celsius. |
|
*/ |
|
static s32 bmp085_get_temperature(struct bmp085_data *data, int *temperature) |
|
{ |
|
struct bmp085_calibration_data *cali = &data->calibration; |
|
long x1, x2; |
|
int status; |
|
|
|
status = bmp085_update_raw_temperature(data); |
|
if (status < 0) |
|
goto exit; |
|
|
|
x1 = ((data->raw_temperature - cali->AC6) * cali->AC5) >> 15; |
|
x2 = (cali->MC << 11) / (x1 + cali->MD); |
|
data->b6 = x1 + x2 - 4000; |
|
/* if NULL just update b6. Used for pressure only measurements */ |
|
if (temperature != NULL) |
|
*temperature = (x1+x2+8) >> 4; |
|
|
|
exit: |
|
return status; |
|
} |
|
|
|
/* |
|
* This function starts the pressure measurement and returns the value |
|
* in millibar. Since the pressure depends on the ambient temperature, |
|
* a temperature measurement is executed according to the given temperature |
|
* measurement period (default is 1 sec boundary). This period could vary |
|
* and needs to be adjusted according to the sensor environment, i.e. if big |
|
* temperature variations then the temperature needs to be read out often. |
|
*/ |
|
static s32 bmp085_get_pressure(struct bmp085_data *data, int *pressure) |
|
{ |
|
struct bmp085_calibration_data *cali = &data->calibration; |
|
s32 x1, x2, x3, b3; |
|
u32 b4, b7; |
|
s32 p; |
|
int status; |
|
|
|
/* alt least every second force an update of the ambient temperature */ |
|
if ((data->last_temp_measurement == 0) || |
|
time_is_before_jiffies(data->last_temp_measurement + 1*HZ)) { |
|
status = bmp085_get_temperature(data, NULL); |
|
if (status < 0) |
|
return status; |
|
} |
|
|
|
status = bmp085_update_raw_pressure(data); |
|
if (status < 0) |
|
return status; |
|
|
|
x1 = (data->b6 * data->b6) >> 12; |
|
x1 *= cali->B2; |
|
x1 >>= 11; |
|
|
|
x2 = cali->AC2 * data->b6; |
|
x2 >>= 11; |
|
|
|
x3 = x1 + x2; |
|
|
|
b3 = (((((s32)cali->AC1) * 4 + x3) << data->oversampling_setting) + 2); |
|
b3 >>= 2; |
|
|
|
x1 = (cali->AC3 * data->b6) >> 13; |
|
x2 = (cali->B1 * ((data->b6 * data->b6) >> 12)) >> 16; |
|
x3 = (x1 + x2 + 2) >> 2; |
|
b4 = (cali->AC4 * (u32)(x3 + 32768)) >> 15; |
|
|
|
b7 = ((u32)data->raw_pressure - b3) * |
|
(50000 >> data->oversampling_setting); |
|
p = ((b7 < 0x80000000) ? ((b7 << 1) / b4) : ((b7 / b4) * 2)); |
|
|
|
x1 = p >> 8; |
|
x1 *= x1; |
|
x1 = (x1 * 3038) >> 16; |
|
x2 = (-7357 * p) >> 16; |
|
p += (x1 + x2 + 3791) >> 4; |
|
|
|
*pressure = p; |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* This function sets the chip-internal oversampling. Valid values are 0..3. |
|
* The chip will use 2^oversampling samples for internal averaging. |
|
* This influences the measurement time and the accuracy; larger values |
|
* increase both. The datasheet gives an overview on how measurement time, |
|
* accuracy and noise correlate. |
|
*/ |
|
static void bmp085_set_oversampling(struct bmp085_data *data, |
|
unsigned char oversampling) |
|
{ |
|
if (oversampling > 3) |
|
oversampling = 3; |
|
data->oversampling_setting = oversampling; |
|
} |
|
|
|
/* |
|
* Returns the currently selected oversampling. Range: 0..3 |
|
*/ |
|
static unsigned char bmp085_get_oversampling(struct bmp085_data *data) |
|
{ |
|
return data->oversampling_setting; |
|
} |
|
|
|
/* sysfs callbacks */ |
|
static ssize_t set_oversampling(struct device *dev, |
|
struct device_attribute *attr, |
|
const char *buf, size_t count) |
|
{ |
|
struct bmp085_data *data = dev_get_drvdata(dev); |
|
unsigned long oversampling; |
|
int err = kstrtoul(buf, 10, &oversampling); |
|
|
|
if (err == 0) { |
|
mutex_lock(&data->lock); |
|
bmp085_set_oversampling(data, oversampling); |
|
mutex_unlock(&data->lock); |
|
return count; |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static ssize_t show_oversampling(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct bmp085_data *data = dev_get_drvdata(dev); |
|
|
|
return sprintf(buf, "%u\n", bmp085_get_oversampling(data)); |
|
} |
|
static DEVICE_ATTR(oversampling, S_IWUSR | S_IRUGO, |
|
show_oversampling, set_oversampling); |
|
|
|
|
|
static ssize_t show_temperature(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
int temperature; |
|
int status; |
|
struct bmp085_data *data = dev_get_drvdata(dev); |
|
|
|
status = bmp085_get_temperature(data, &temperature); |
|
if (status < 0) |
|
return status; |
|
else |
|
return sprintf(buf, "%d\n", temperature); |
|
} |
|
static DEVICE_ATTR(temp0_input, S_IRUGO, show_temperature, NULL); |
|
|
|
|
|
static ssize_t show_pressure(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
int pressure; |
|
int status; |
|
struct bmp085_data *data = dev_get_drvdata(dev); |
|
|
|
status = bmp085_get_pressure(data, &pressure); |
|
if (status < 0) |
|
return status; |
|
else |
|
return sprintf(buf, "%d\n", pressure); |
|
} |
|
static DEVICE_ATTR(pressure0_input, S_IRUGO, show_pressure, NULL); |
|
|
|
|
|
static struct attribute *bmp085_attributes[] = { |
|
&dev_attr_temp0_input.attr, |
|
&dev_attr_pressure0_input.attr, |
|
&dev_attr_oversampling.attr, |
|
NULL |
|
}; |
|
|
|
static const struct attribute_group bmp085_attr_group = { |
|
.attrs = bmp085_attributes, |
|
}; |
|
|
|
int bmp085_detect(struct device *dev) |
|
{ |
|
struct bmp085_data *data = dev_get_drvdata(dev); |
|
unsigned int id; |
|
int ret; |
|
|
|
ret = regmap_read(data->regmap, BMP085_CHIP_ID_REG, &id); |
|
if (ret < 0) |
|
return ret; |
|
|
|
if (id != data->chip_id) |
|
return -ENODEV; |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL_GPL(bmp085_detect); |
|
|
|
static void bmp085_get_of_properties(struct bmp085_data *data) |
|
{ |
|
#ifdef CONFIG_OF |
|
struct device_node *np = data->dev->of_node; |
|
u32 prop; |
|
|
|
if (!np) |
|
return; |
|
|
|
if (!of_property_read_u32(np, "chip-id", &prop)) |
|
data->chip_id = prop & 0xff; |
|
|
|
if (!of_property_read_u32(np, "temp-measurement-period", &prop)) |
|
data->temp_measurement_period = (prop/100)*HZ; |
|
|
|
if (!of_property_read_u32(np, "default-oversampling", &prop)) |
|
data->oversampling_setting = prop & 0xff; |
|
#endif |
|
} |
|
|
|
static int bmp085_init_client(struct bmp085_data *data) |
|
{ |
|
int status = bmp085_read_calibration_data(data); |
|
|
|
if (status < 0) |
|
return status; |
|
|
|
/* default settings */ |
|
data->chip_id = BMP085_CHIP_ID; |
|
data->last_temp_measurement = 0; |
|
data->temp_measurement_period = 1*HZ; |
|
data->oversampling_setting = 3; |
|
|
|
bmp085_get_of_properties(data); |
|
|
|
mutex_init(&data->lock); |
|
|
|
return 0; |
|
} |
|
|
|
struct regmap_config bmp085_regmap_config = { |
|
.reg_bits = 8, |
|
.val_bits = 8 |
|
}; |
|
EXPORT_SYMBOL_GPL(bmp085_regmap_config); |
|
|
|
int bmp085_probe(struct device *dev, struct regmap *regmap, int irq) |
|
{ |
|
struct bmp085_data *data; |
|
int err = 0; |
|
|
|
data = kzalloc(sizeof(struct bmp085_data), GFP_KERNEL); |
|
if (!data) { |
|
err = -ENOMEM; |
|
goto exit; |
|
} |
|
|
|
dev_set_drvdata(dev, data); |
|
data->dev = dev; |
|
data->regmap = regmap; |
|
data->irq = irq; |
|
|
|
if (data->irq > 0) { |
|
err = devm_request_irq(dev, data->irq, bmp085_eoc_isr, |
|
IRQF_TRIGGER_RISING, "bmp085", |
|
data); |
|
if (err < 0) |
|
goto exit_free; |
|
} |
|
|
|
/* Initialize the BMP085 chip */ |
|
err = bmp085_init_client(data); |
|
if (err < 0) |
|
goto exit_free; |
|
|
|
err = bmp085_detect(dev); |
|
if (err < 0) { |
|
dev_err(dev, "%s: chip_id failed!\n", BMP085_NAME); |
|
goto exit_free; |
|
} |
|
|
|
/* Register sysfs hooks */ |
|
err = sysfs_create_group(&dev->kobj, &bmp085_attr_group); |
|
if (err) |
|
goto exit_free; |
|
|
|
dev_info(dev, "Successfully initialized %s!\n", BMP085_NAME); |
|
|
|
return 0; |
|
|
|
exit_free: |
|
kfree(data); |
|
exit: |
|
return err; |
|
} |
|
EXPORT_SYMBOL_GPL(bmp085_probe); |
|
|
|
int bmp085_remove(struct device *dev) |
|
{ |
|
struct bmp085_data *data = dev_get_drvdata(dev); |
|
|
|
sysfs_remove_group(&data->dev->kobj, &bmp085_attr_group); |
|
kfree(data); |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL_GPL(bmp085_remove); |
|
|
|
MODULE_AUTHOR("Christoph Mair <christoph.mair@gmail.com>"); |
|
MODULE_DESCRIPTION("BMP085 driver"); |
|
MODULE_LICENSE("GPL");
|
|
|