/* * ads1015.c - lm_sensors driver for ads1015 12-bit 4-input ADC * (C) Copyright 2010 * Dirk Eibach, Guntermann & Drunck GmbH * * Based on the ads7828 driver by Steve Hardy. * * Datasheet available at: http://focus.ti.com/lit/ds/symlink/ads1015.pdf * * 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 #include #include #include #include #include #include #include #include #include #include /* ADS1015 registers */ enum { ADS1015_CONVERSION = 0, ADS1015_CONFIG = 1, }; /* PGA fullscale voltages in mV */ static const unsigned int fullscale_table[8] = { 6144, 4096, 2048, 1024, 512, 256, 256, 256 }; #define ADS1015_CONFIG_CHANNELS 8 #define ADS1015_DEFAULT_CHANNELS 0xff struct ads1015_data { struct device *hwmon_dev; struct mutex update_lock; /* mutex protect updates */ }; static s32 ads1015_read_reg(struct i2c_client *client, unsigned int reg) { s32 data = i2c_smbus_read_word_data(client, reg); return (data < 0) ? data : swab16(data); } static s32 ads1015_write_reg(struct i2c_client *client, unsigned int reg, u16 val) { return i2c_smbus_write_word_data(client, reg, swab16(val)); } static int ads1015_read_value(struct i2c_client *client, unsigned int channel, int *value) { u16 config; s16 conversion; unsigned int pga; int fullscale; unsigned int k; struct ads1015_data *data = i2c_get_clientdata(client); int res; mutex_lock(&data->update_lock); /* get fullscale voltage */ res = ads1015_read_reg(client, ADS1015_CONFIG); if (res < 0) goto err_unlock; config = res; pga = (config >> 9) & 0x0007; fullscale = fullscale_table[pga]; /* set channel and start single conversion */ config &= ~(0x0007 << 12); config |= (1 << 15) | (1 << 8) | (channel & 0x0007) << 12; /* wait until conversion finished */ res = ads1015_write_reg(client, ADS1015_CONFIG, config); if (res < 0) goto err_unlock; for (k = 0; k < 5; ++k) { msleep(1); res = ads1015_read_reg(client, ADS1015_CONFIG); if (res < 0) goto err_unlock; config = res; if (config & (1 << 15)) break; } if (k == 5) { res = -EIO; goto err_unlock; } res = ads1015_read_reg(client, ADS1015_CONVERSION); if (res < 0) goto err_unlock; conversion = res; mutex_unlock(&data->update_lock); *value = DIV_ROUND_CLOSEST(conversion * fullscale, 0x7ff0); return 0; err_unlock: mutex_unlock(&data->update_lock); return res; } /* sysfs callback function */ static ssize_t show_in(struct device *dev, struct device_attribute *da, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(da); struct i2c_client *client = to_i2c_client(dev); int in; int res; res = ads1015_read_value(client, attr->index, &in); return (res < 0) ? res : sprintf(buf, "%d\n", in); } static const struct sensor_device_attribute ads1015_in[] = { SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0), SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1), SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2), SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3), SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4), SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5), SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6), SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7), }; /* * Driver interface */ static int ads1015_remove(struct i2c_client *client) { struct ads1015_data *data = i2c_get_clientdata(client); int k; hwmon_device_unregister(data->hwmon_dev); for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) device_remove_file(&client->dev, &ads1015_in[k].dev_attr); kfree(data); return 0; } static unsigned int ads1015_get_exported_channels(struct i2c_client *client) { struct ads1015_platform_data *pdata = dev_get_platdata(&client->dev); #ifdef CONFIG_OF struct device_node *np = client->dev.of_node; const __be32 *of_channels; int of_channels_size; #endif /* prefer platform data */ if (pdata) return pdata->exported_channels; #ifdef CONFIG_OF /* fallback on OF */ of_channels = of_get_property(np, "exported-channels", &of_channels_size); if (of_channels && (of_channels_size == sizeof(*of_channels))) return be32_to_cpup(of_channels); #endif /* fallback on default configuration */ return ADS1015_DEFAULT_CHANNELS; } static int ads1015_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ads1015_data *data; int err; unsigned int exported_channels; unsigned int k; data = kzalloc(sizeof(struct ads1015_data), GFP_KERNEL); if (!data) { err = -ENOMEM; goto exit; } i2c_set_clientdata(client, data); mutex_init(&data->update_lock); /* build sysfs attribute group */ exported_channels = ads1015_get_exported_channels(client); for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) { if (!(exported_channels & (1<dev, &ads1015_in[k].dev_attr); if (err) goto exit_free; } data->hwmon_dev = hwmon_device_register(&client->dev); if (IS_ERR(data->hwmon_dev)) { err = PTR_ERR(data->hwmon_dev); goto exit_remove; } return 0; exit_remove: for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) device_remove_file(&client->dev, &ads1015_in[k].dev_attr); exit_free: kfree(data); exit: return err; } static const struct i2c_device_id ads1015_id[] = { { "ads1015", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, ads1015_id); static struct i2c_driver ads1015_driver = { .driver = { .name = "ads1015", }, .probe = ads1015_probe, .remove = ads1015_remove, .id_table = ads1015_id, }; static int __init sensors_ads1015_init(void) { return i2c_add_driver(&ads1015_driver); } static void __exit sensors_ads1015_exit(void) { i2c_del_driver(&ads1015_driver); } MODULE_AUTHOR("Dirk Eibach "); MODULE_DESCRIPTION("ADS1015 driver"); MODULE_LICENSE("GPL"); module_init(sensors_ads1015_init); module_exit(sensors_ads1015_exit);