The driver was developed by Christoph Mair <christoph.m...@gmail.com> out of tree so far.
Signed-off-by: Stefan Schmidt <ste...@datenfreihafen.org> --- arch/arm/mach-s3c2440/mach-gta02.c | 3 + drivers/i2c/chips/Kconfig | 11 ++ drivers/i2c/chips/Makefile | 1 + drivers/i2c/chips/bmp085.c | 345 ++++++++++++++++++++++++++++++++++++ 4 files changed, 360 insertions(+), 0 deletions(-) create mode 100644 drivers/i2c/chips/bmp085.c diff --git a/arch/arm/mach-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c index 4520d7b..153549e 100644 --- a/arch/arm/mach-s3c2440/mach-gta02.c +++ b/arch/arm/mach-s3c2440/mach-gta02.c @@ -928,6 +928,9 @@ static struct i2c_board_info gta02_i2c_devs[] __initdata = { .irq = GTA02_IRQ_PCF50633, .platform_data = >a02_pcf_pdata, }, + { + I2C_BOARD_INFO("bmp085", 0x77), + }, }; static struct s3c2410_nand_set gta02_nand_sets[] = { diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig index a84179a..e59c8cb 100644 --- a/drivers/i2c/chips/Kconfig +++ b/drivers/i2c/chips/Kconfig @@ -224,4 +224,15 @@ config PCA9632 This driver can also be built as a module. If so, the module will be called pca9632. + +config SENSORS_BMP085 + tristate "Bosch Sensortec BMP085 pressure driver" + depends on I2C + help + If you say yes here you get support for the Bosch Sensortec BMP085 + pressure driver. + + This driver can also be built as a module. If so, the module + will be called bmp085. + endmenu diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile index b0dc12e..17c3107 100644 --- a/drivers/i2c/chips/Makefile +++ b/drivers/i2c/chips/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o obj-$(CONFIG_MCU_MPC8349EMITX) += mcu_mpc8349emitx.o obj-$(CONFIG_SENSORS_TSL256X) += tsl256x.o obj-$(CONFIG_PCA9632) += pca9632.o +obj-$(CONFIG_SENSORS_BMP085) += bmp085.o ifeq ($(CONFIG_I2C_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/i2c/chips/bmp085.c b/drivers/i2c/chips/bmp085.c new file mode 100644 index 0000000..d4523ce --- /dev/null +++ b/drivers/i2c/chips/bmp085.c @@ -0,0 +1,345 @@ +/* Copyright (c) 2009 Christoph Mair <christoph.m...@gmail.com> + + 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/init.h> +#include <linux/i2c.h> +#include <linux/delay.h> + + + +#define BMP085_I2C_ADDRESS 0x77 + +#define BMP085_CALIBRATION_DATA_START 0xAA +#define BMP085_CALIBRATION_DATA_LENGTH 22 + +#define BMP085_CHIP_ID_REG 0xD0 +#define BMP085_VERSION_REG 0xD1 + +#define BMP085_CHIP_ID 0x55 /* 85 */ + + +/* Addresses to scan: 0x77 */ +static const unsigned short normal_i2c[] = { 0x77, I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(bmp085); + +struct bmp085_calibration_data { + s16 AC1, AC2, AC3; + u16 AC4, AC5, AC6; + s16 B1, B2; + s16 MB, MC, MD; +}; + + +/* Each client has this additional data */ +struct bmp085_data { + unsigned char version; + struct bmp085_calibration_data calibration; + unsigned char oversampling_setting; + unsigned long next_temp_measurement; + long b6; // calculated temperature correction coefficient +}; + +static void bmp085_init_client(struct i2c_client *client); + + +static s32 bmp085_get_calibration_data(struct i2c_client *client) +{ + u8 tmp[BMP085_CALIBRATION_DATA_LENGTH]; + struct bmp085_data *data = i2c_get_clientdata(client); + struct bmp085_calibration_data *cali = &(data->calibration); + s32 status = i2c_smbus_read_i2c_block_data(client, BMP085_CALIBRATION_DATA_START, + BMP085_CALIBRATION_DATA_LENGTH, tmp); + + cali->AC1 = (tmp[0] <<8) | tmp[1]; + cali->AC2 = (tmp[2] <<8) | tmp[3]; + cali->AC3 = (tmp[4] <<8) | tmp[5]; + cali->AC4 = (tmp[6] <<8) | tmp[7]; + cali->AC5 = (tmp[8] <<8) | tmp[9]; + cali->AC6 = (tmp[10] <<8) | tmp[11]; + + /*parameters B1,B2*/ + cali->B1 = (tmp[12] <<8) | tmp[13]; + cali->B2 = (tmp[14] <<8) | tmp[15]; + + /*parameters MB,MC,MD*/ + cali->MB = (tmp[16] <<8) | tmp[17]; + cali->MC = (tmp[18] <<8) | tmp[19]; + cali->MD = (tmp[20] <<8) | tmp[21]; + return status; +} + + +static s32 bmp085_get_temperature(struct i2c_client *client) +{ + struct bmp085_data *data = i2c_get_clientdata(client); + u16 temperature = 0x00; + u8 tmp[2]; + s32 status = i2c_smbus_write_byte_data(client, 0xF4, 0x2E); + if (status != 0) { + printk(KERN_INFO "bmp085: Error while requesting temperature measurement.\n"); + return status; + } + msleep(5); + + i2c_smbus_read_i2c_block_data(client, 0xF6, 2, tmp); + data->next_temp_measurement = jiffies + 1;/**HZ;*/ /* next temperature measurement is needed in one second */ + temperature = (tmp[0] << 8) + tmp[1]; + printk(KERN_INFO "temperature: %u\n", temperature); + return temperature; +} + + +static s32 bmp085_read_pressure(struct i2c_client *client, u32 *pressure) +{ + struct bmp085_data *data = i2c_get_clientdata(client); + u8 tmp[3]; + s32 status = 0; + *pressure = 0; + + status = i2c_smbus_write_byte_data(client, 0xF4, 0x34 + (data->oversampling_setting<<6)); + if (status != 0) { + return status; + } + msleep(2+(3 << data->oversampling_setting<<1)); /* wait for the end of conversion */ + + status = i2c_smbus_read_i2c_block_data(client, 0xF6, 0x03, tmp); + /* swap positions to correct the MSB/LSB positions*/ + *pressure = (tmp[0] << 16) | (tmp[1] << 8) | tmp[2]; + *pressure = *pressure >> (8-data->oversampling_setting); + printk(KERN_INFO "pressure: %u\n", *pressure); + return status; +} + + +/* sysfs callbacks */ +static ssize_t set_oversampling(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bmp085_data *data = i2c_get_clientdata(client); + data->oversampling_setting = simple_strtoul(buf, NULL, 10); + if (data->oversampling_setting > 3) { + data->oversampling_setting = 3; + } + return count; +} + +static ssize_t show_oversampling(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bmp085_data *data = i2c_get_clientdata(client); + return sprintf(buf, "%u\n", data->oversampling_setting); +} +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) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bmp085_data *data = i2c_get_clientdata(client); + struct bmp085_calibration_data *cali = &data->calibration; + + long raw_temp = bmp085_get_temperature(client); + long x1 = ((raw_temp - cali->AC6) * cali->AC5) >> 15; + long x2 = (cali->MC << 11) / (x1 + cali->MD); + data->b6 = x1 + x2 - 4000; +// printk(KERN_INFO "got raw temp: %ld; AC5: %d, AC6: %d, MC: %d, MD: %d\n", raw_temp, cali->AC5, cali->AC6, cali->MC, cali->MD); + return sprintf(buf, "%ld\n", (x1+x2+8) >> 4); +} +static DEVICE_ATTR(temperature, S_IRUGO, show_temperature, NULL); + + +static ssize_t show_pressure(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bmp085_data *data = i2c_get_clientdata(client); + struct bmp085_calibration_data *cali = &data->calibration; + long x1, x2, x3, b3; + unsigned long b4, b7; + long p; + unsigned int raw_pressure; + if (time_after(jiffies, data->next_temp_measurement)) { + printk(KERN_INFO "new temp required: %lu v.s. %lu\n", jiffies, data->next_temp_measurement); + show_temperature(dev, attr, buf); /* read temperature and calculate coefficients */ + } + + bmp085_read_pressure(client, &raw_pressure); +// printk(KERN_INFO "raw_pressure: %ud, B6: %ld\n", raw_pressure, data->b6); + + x1 = (data->b6 * data->b6) >> 12; + x1 *= cali->B2; + x1 >>= 11; + + x2 = cali->AC2 * data->b6; + x2 >>= 11; + + x3 = x1 + x2; + + b3 = (((((long)cali->AC1) * 4 + x3) << data->oversampling_setting) + 2) >> 2; + +// printk(KERN_INFO "x1: %ld, x2: %ld, x3: %ld, b3: %ld\n", x1, x2, x3, b3); + x1 = (cali->AC3 * data->b6) >> 13; + x2 = (cali->B1 * ((data->b6 * data->b6) >> 12)) >> 16; + x3 = (x1 + x2 + 2) >> 2; + b4 = (cali->AC4 * (unsigned long)(x3 + 32768)) >> 15; + +// printk(KERN_INFO "x1: %ld, x2: %ld, x3: %ld, b4: %ld\n", x1, x2, x3, b4); + b7 = ((unsigned long)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; + return sprintf(buf, "%ld\n", p); +} +static DEVICE_ATTR(pressure, S_IRUGO, show_pressure, NULL); + + +static struct attribute *bmp085_attributes[] = { + &dev_attr_temperature.attr, + &dev_attr_pressure.attr, + &dev_attr_oversampling.attr, + NULL +}; + +static const struct attribute_group bmp085_attr_group = { + .attrs = bmp085_attributes, +}; + + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int bmp085_detect(struct i2c_client *client, int kind, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + const char *client_name; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -ENODEV; + + if (client->addr == BMP085_I2C_ADDRESS) { + if (i2c_smbus_read_byte_data(client, BMP085_CHIP_ID_REG) != BMP085_CHIP_ID) { + goto exit; + } + + /* Chip identified */ + client_name = "bmp085"; + strlcpy(info->type, client_name, I2C_NAME_SIZE); + printk(KERN_INFO "BMP085 found!\n"); + return 0; + } + + exit: + printk(KERN_INFO "BMP085 not found at address %x!\n", client->addr); + return -ENODEV; +} + +static int bmp085_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bmp085_data *data; + int err; + + printk(KERN_INFO "bmp085 probe!\n"); + + data = kzalloc(sizeof(struct bmp085_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + /* default settings after POR */ + data->oversampling_setting = 0x00; + + i2c_set_clientdata(client, data); + + /* Initialize the BMP085 chip */ + bmp085_init_client(client); + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &bmp085_attr_group); + if (err) + goto exit_free; + + printk(KERN_INFO "bmp085 probe succeeded!\n"); + return 0; + + exit_free: + kfree(data); + exit: + return err; +} + +static int bmp085_remove(struct i2c_client *client) +{ + printk(KERN_INFO "bmp085 remove!\n"); + sysfs_remove_group(&client->dev.kobj, &bmp085_attr_group); + kfree(i2c_get_clientdata(client)); + return 0; +} + +/* Called when we have found a new HMC5843. */ +static void bmp085_init_client(struct i2c_client *client) +{ + struct bmp085_data *data = i2c_get_clientdata(client); + bmp085_get_calibration_data(client); + data->version = i2c_smbus_read_byte_data(client, BMP085_VERSION_REG); + data->next_temp_measurement = 0; + data->oversampling_setting = 3; + printk(KERN_INFO "BMP085 ver. %d.%d initialized\n", (data->version & 0x0F), (data->version & 0xF0) >> 4); +} + +static const struct i2c_device_id bmp085_id[] = { + { "bmp085", 0 }, + { } +}; + +static struct i2c_driver bmp085_driver = { + .driver = { + .name = "bmp085", + }, + .probe = bmp085_probe, + .remove = bmp085_remove, + .id_table = bmp085_id, + + .detect = bmp085_detect, + .address_data = &addr_data, +}; + +static int __init bmp085_init(void) +{ + printk(KERN_INFO "init!\n"); + return i2c_add_driver(&bmp085_driver); +} + +static void __exit bmp085_exit(void) +{ + printk(KERN_INFO "exit!\n"); + i2c_del_driver(&bmp085_driver); +} + + +MODULE_AUTHOR("Christoph Mair <christoph.m...@gmail.com"); +MODULE_DESCRIPTION("BMP085 driver"); +MODULE_LICENSE("GPL"); + +module_init(bmp085_init); +module_exit(bmp085_exit); -- 1.7.0.5