Initial commit of ADS7924 driver
Change-Id: If74b8c4f6bbc0aec67e2dca6c0162c3d15e08ae8
diff --git a/qcom-adc/ads7924-driver.c b/qcom-adc/ads7924-driver.c
new file mode 100644
index 0000000..356b23a
--- /dev/null
+++ b/qcom-adc/ads7924-driver.c
@@ -0,0 +1,403 @@
+/*
+ * ./kernel_modules/qcom-adc/ads7924-driver.c
+ *
+ * ads7924-driver.c - driver for TI ADS7924 4-channel A/D converter
+ *
+ * Author: Cradlepoint Technology, Inc. <source@cradlepoint.com>
+ * Kyle Swenson <kswenson@cradlepoint.com>
+ * Derived heavily from similar hwmon-based drivers in the linux source tree.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+#include <linux/timer.h>
+#include <linux/slab.h>
+
+#include "ads7924-regdefs.h"
+#include "adc-averaging.h"
+
+#define ADS7924_NCH 4 /* Number of ADC Channels for the ADS7924 */
+#define ADS7924_SAMPLE_INTERVAL 10 /*In ms -> 100 Hz default */
+#define ADS7924_ENABLE_DEFAULT 0 /*Automatic sampling disabled by default */
+#define ADS7924_DEFAULT_BUFFER_SIZE 16 /*Default length for each channel's buffer */
+#define ADS7924_VMAX 5000 /* Reference voltage, maximum */
+
+/* ADS7924-specific data */
+struct ads7924_data {
+ struct mutex update_lock; /* Mutex protecting updates */
+ struct timer_list sample_timer;
+ struct work_struct workq;
+
+ struct device *hwmon_dev; /* result of register the struct i2c_client->dev with hwmon */
+ struct i2c_client * client;
+ struct gpio_desc * reset;
+ unsigned long last_updated; /* Last updated time (in jiffies) */
+ unsigned enabled; /* */
+ unsigned update_interval; /* in ms */
+ bool valid; /* Validity flag, for first data resquest */
+ unsigned sample_length; /*Number of samples stored for the average */
+ struct adc_channel *channel[ADS7924_NCH];
+};
+
+
+void ads7924_timer_irq(unsigned long arg) {
+ struct ads7924_data *data = (struct ads7924_data *)arg;
+ if (data->enabled) {
+ mod_timer(&data->sample_timer, jiffies + msecs_to_jiffies(data->update_interval));
+ schedule_work(&data->workq);
+ }
+}
+
+void ads7924_start_sampler(struct ads7924_data *data)
+{
+ setup_timer(&data->sample_timer, ads7924_timer_irq, (unsigned long) data);
+ mod_timer(&data->sample_timer, jiffies + msecs_to_jiffies(data->update_interval));
+}
+void ads7924_stop_sampler(struct ads7924_data *data)
+{
+ del_timer(&data->sample_timer);
+}
+
+static uint16_t ads7924_sample_channel(struct i2c_client *client, uint8_t channel)
+{
+ int err;
+ int lb, hb, hb2;
+ uint16_t adc_count = 0;
+ err = i2c_smbus_write_byte_data(client, ADS7924_REG_MODECNTRL, (ADS7924_MODE_MANUAL_SINGLE << 2) | (channel & 0x03));
+ if (err < 0) {
+ dev_warn(&client->dev, "Failed to i2c write");
+ }
+ hb = i2c_smbus_read_byte_data(client, ADS7924_REG_DATA0_U + (channel << 1));
+ lb = i2c_smbus_read_byte_data(client, ADS7924_REG_DATA0_L + (channel << 1));
+ hb2 = i2c_smbus_read_byte_data(client, ADS7924_REG_DATA0_U + (channel << 1));
+ if (hb != hb2)
+ dev_warn(&client->dev, "Warning hb reading inconsistent!\n");
+ if (hb < 0 || lb < 0) {
+ dev_warn(&client->dev, "Failed to i2c read");
+ return (uint16_t) 0xFFFF; /* return a value > 4096, upper layers should check */
+ }
+ adc_count = (uint16_t)((((hb & 0x00FF) << 8) | (lb & 0x00FF)) >> 4);
+
+ return adc_count;
+}
+
+static struct ads7924_data *ads7924_update_device(struct i2c_client *client)
+{
+ struct ads7924_data *data = i2c_get_clientdata(client);
+ uint16_t sample;
+ int i = 0;
+
+ mutex_lock(&data->update_lock);
+
+ for(i=0; i<ADS7924_NCH; i++) {
+ sample = ads7924_sample_channel(client, i);
+ if (!(sample & 0xF000))
+ add_sample(data->channel[i], sample);
+ else
+ dev_info(&client->dev, "Ch%d sample 0x%04x out of range\n", i, sample);
+
+ }
+ data->last_updated = jiffies;
+ data->valid = 1;
+ mutex_unlock(&data->update_lock);
+ return data;
+}
+
+void ads7924_work(struct work_struct *work)
+{
+ struct ads7924_data *data = container_of(work, struct ads7924_data, workq);
+ ads7924_update_device(data->client);
+}
+
+/*
+ Number of samples used to compute the average
+*/
+static ssize_t sample_length_store(struct device *dev, struct device_attribute *da, const char *buf, size_t len)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ads7924_data *data = i2c_get_clientdata(client);
+ int i;
+ unsigned long length;
+ int err = kstrtoul(buf, 10, &length);
+ if (err)
+ return err;
+
+ if (length >= MAX_SAMPLE_LENGTH) {
+ return -EINVAL;
+ }
+ mutex_lock(&data->update_lock);
+ data->sample_length = length;
+ for (i = 0; i < ADS7924_NCH; i++) {
+ free_adc_channel(data->channel[i]);
+ data->channel[i] = create_adc_channel(data->sample_length);
+ if (!data->channel[i]) {
+ dev_warn(&client->dev, "Failed to resize the channel buffer\n");
+ /*TODO: If this happens, we're pretty screwed. Like, we should unload ourselves. */
+ }
+ }
+ mutex_unlock(&data->update_lock);
+ return len;
+}
+static ssize_t sample_length_show(struct device *dev, struct device_attribute *da, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ads7924_data *data = i2c_get_clientdata(client);
+ return sprintf(buf, "%u", data->sample_length);
+}
+static SENSOR_DEVICE_ATTR(sample_length, S_IRUGO|S_IWUSR|S_IWGRP, sample_length_show, sample_length_store, 0);
+
+
+/*
+ Enable/disable the automatic sample & average functionality. Disabled by default
+*/
+static ssize_t enable_store(struct device *dev, struct device_attribute *da, const char *buf, size_t length)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ads7924_data *data = i2c_get_clientdata(client);
+ unsigned long enabled;
+ int err = kstrtoul(buf, 10, &enabled);
+ if (err)
+ return err;
+
+ mutex_lock(&data->update_lock);
+ if (enabled)
+ data->enabled = 1;
+ else
+ data->enabled = 0;
+ mutex_unlock(&data->update_lock);
+ ads7924_timer_irq((unsigned long) data);
+ return length;
+}
+static ssize_t enable_show(struct device *dev, struct device_attribute *da, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ads7924_data *data = i2c_get_clientdata(client);
+ return sprintf(buf, "%u", data->enabled);
+}
+static SENSOR_DEVICE_ATTR(enable, S_IRUGO|S_IWUSR|S_IWGRP, enable_show, enable_store, 0);
+
+/*
+ update interval : minimium time between sample requests
+*/
+static ssize_t update_interval_store(struct device *dev, struct device_attribute *da, const char *buf, size_t length)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ads7924_data *data = i2c_get_clientdata(client);
+ unsigned long update_interval;
+ int err = kstrtoul(buf, 10, &update_interval);
+ if (err)
+ return err;
+ mutex_lock(&data->update_lock);
+ data->update_interval = update_interval;
+ mutex_unlock(&data->update_lock);
+ return length;
+}
+static ssize_t update_interval_show(struct device *dev, struct device_attribute *da, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ads7924_data *data = i2c_get_clientdata(client);
+ return sprintf(buf, "%u", data->update_interval);
+}
+static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO|S_IWUSR|S_IWGRP, update_interval_show, update_interval_store, 0);
+
+/*
+ Show the voltage on the specified channel.
+
+ Note that this causes the ADC to sample the specified channel,
+ independently of any averaging that might be going on. Also note that if there
+ is averaging going on, this particular average will not be included in the
+ sample
+
+*/
+static ssize_t ads7924_show_in(struct device *dev, struct device_attribute *da, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+ struct ads7924_data *data = i2c_get_clientdata(client);
+ uint16_t value;
+ mutex_lock(&data->update_lock);
+ /* TODO RATE LIMITE THIS*/
+ value = ads7924_sample_channel(client, attr->index);
+ mutex_unlock(&data->update_lock);
+ return sprintf(buf, "%d", value);
+}
+
+static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ads7924_show_in, NULL, 0);
+static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ads7924_show_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ads7924_show_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, ads7924_show_in, NULL, 3);
+
+static ssize_t ads7924_show_average(struct device *dev, struct device_attribute *da, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+ struct ads7924_data *data = ads7924_update_device(client);
+ uint16_t value;
+ if (data) {
+ value = compute_average(data->channel[attr->index]);
+ return sprintf(buf, "%d", value);
+ }
+ return -EAGAIN;
+}
+static SENSOR_DEVICE_ATTR(in0_average, S_IRUGO, ads7924_show_average, NULL, 0);
+static SENSOR_DEVICE_ATTR(in1_average, S_IRUGO, ads7924_show_average, NULL, 1);
+static SENSOR_DEVICE_ATTR(in2_average, S_IRUGO, ads7924_show_average, NULL, 2);
+static SENSOR_DEVICE_ATTR(in3_average, S_IRUGO, ads7924_show_average, NULL, 3);
+
+static struct attribute *ads7924_attrs[] = {
+ &sensor_dev_attr_update_interval.dev_attr.attr, /* Interval between device updates*/
+ &sensor_dev_attr_sample_length.dev_attr.attr, /* Number of samples used to average*/
+ &sensor_dev_attr_enable.dev_attr.attr, /* Enable/disable the timer-based sampleing*/
+ &sensor_dev_attr_in0_average.dev_attr.attr,
+ &sensor_dev_attr_in1_average.dev_attr.attr,
+ &sensor_dev_attr_in2_average.dev_attr.attr,
+ &sensor_dev_attr_in3_average.dev_attr.attr,
+ &sensor_dev_attr_in0_input.dev_attr.attr,
+ &sensor_dev_attr_in1_input.dev_attr.attr,
+ &sensor_dev_attr_in2_input.dev_attr.attr,
+ &sensor_dev_attr_in3_input.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group ads7924_group = {
+ .attrs = ads7924_attrs,
+};
+
+static int ads7924_remove(struct i2c_client *client)
+{
+ struct ads7924_data *data = i2c_get_clientdata(client);
+ int i;
+
+ ads7924_stop_sampler(data);
+ mutex_lock(&data->update_lock);
+ for (i = 0; i < ADS7924_NCH; i++)
+ {
+ if (data->channel[i]) {
+ free_adc_channel(data->channel[i]);
+ }
+ data->channel[i] = NULL;
+ }
+
+ hwmon_device_unregister(data->hwmon_dev);
+ sysfs_remove_group(&client->dev.kobj, &ads7924_group);
+ mutex_unlock(&data->update_lock);
+ return 0;
+}
+
+static int ads7924_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct ads7924_data *data;
+ int i;
+ uint8_t dev_id;
+ int err;
+ int smbus_err;
+ dev_info(&client->dev, "ads7924 probe begin\n");
+ data = devm_kzalloc(&client->dev, sizeof(struct ads7924_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->valid = 0;
+ data->update_interval = ADS7924_SAMPLE_INTERVAL;
+ data->sample_length = ADS7924_DEFAULT_BUFFER_SIZE;
+ data->enabled = ADS7924_ENABLE_DEFAULT;
+ data->client = client;
+ data->reset = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(data->reset)){
+ dev_warn(&client->dev, "Failed to request the reset gpio, error=%ld\n", PTR_ERR(data->reset));
+ data->reset = NULL;
+ } else {
+ dev_info(&client->dev, "Successfully claimed the reset gpio (gpio %d), value is %d\n",desc_to_gpio(data->reset), gpiod_get_value(data->reset));
+ }
+
+ mdelay(10);
+ smbus_err = i2c_smbus_read_byte_data(client, ADS7924_REG_RESET);
+ if (smbus_err < 0)
+ dev_warn(&client->dev, "failed to read device id register 0x%02X, err=%d\n", ADS7924_REG_RESET, smbus_err);
+
+ dev_id = (uint8_t) (smbus_err & 0xFF);
+ if (dev_id != 0x19) { /* This is my device's ID, at least. */
+ dev_warn(&client->dev, "Device id of 0x%02x doesn't match the expected 0x19\n", dev_id);
+ err = -ENODEV;
+ goto err_free;
+ }
+
+ for (i = 0; i < ADS7924_NCH; i++) {
+ data->channel[i] = create_adc_channel(data->sample_length);
+ if (!data->channel[i]) {
+ dev_warn(&client->dev, "Failed to allocate memeory for adc_channel %d\n", i);
+ return -ENOMEM;
+ }
+ }
+
+ INIT_WORK(&data->workq, ads7924_work);
+ mutex_init(&data->update_lock);
+
+ err = sysfs_create_group(&client->dev.kobj, &ads7924_group);
+ if (err < 0)
+ return err;
+
+ i2c_set_clientdata(client, data);
+
+ data->hwmon_dev = hwmon_device_register(&client->dev);
+
+ if (IS_ERR(data->hwmon_dev)) {
+ err = PTR_ERR(data->hwmon_dev);
+ dev_err(&client->dev, "Failed to register with hwmon, error=%d\n", err);
+ goto err_remove;
+ }
+ ads7924_start_sampler(data);
+ return 0;
+
+
+err_remove:
+ sysfs_remove_group(&client->dev.kobj, &ads7924_group);
+err_free:
+ devm_kfree(&client->dev, data);
+ return err;
+
+}
+
+static const struct i2c_device_id ads7924_device_ids[] = {
+ { "ads7924", 0},
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, ads7924_device_ids);
+
+static struct i2c_driver ads7924_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "ads7924",
+ },
+ .id_table = ads7924_device_ids,
+ .probe = ads7924_probe,
+ .remove = ads7924_remove,
+};
+
+module_i2c_driver(ads7924_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kyle Swenson <kswenson@cradlepoint.com>");
+MODULE_DESCRIPTION("Driver for TI ADS7924 A/D converter and compatibles");