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");