blob: 676a7d804a89ae308f6a7e9a9fd8b73660d502dc [file] [log] [blame]
/*
* ./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(struct timer_list *timer)
{
struct ads7924_data *data = from_timer(data, timer, sample_timer);
if (data->enabled) {
mod_timer(&data->sample_timer, jiffies + msecs_to_jiffies(data->update_interval));
schedule_work(&data->workq);
}
}
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 ads7924_data *data = dev_get_drvdata(dev);
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(dev, data->sample_length);
if (!data->channel[i]) {
dev_warn(&data->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 ads7924_data *data = dev_get_drvdata(dev);
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 ads7924_data *data = dev_get_drvdata(dev);
unsigned long enabled;
int err = kstrtoul(buf, 10, &enabled);
if (err)
return err;
mutex_lock(&data->update_lock);
if (enabled != data->enabled) {
data->enabled = enabled;
if (enabled)
mod_timer(&data->sample_timer, 0);
}
mutex_unlock(&data->update_lock);
return length;
}
static ssize_t enable_show(struct device *dev, struct device_attribute *da, char *buf)
{
struct ads7924_data *data = dev_get_drvdata(dev);
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 ads7924_data *data = dev_get_drvdata(dev);
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 ads7924_data *data = dev_get_drvdata(dev);
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 sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct ads7924_data *data = dev_get_drvdata(dev);
uint16_t value;
mutex_lock(&data->update_lock);
/* TODO RATE LIMITE THIS*/
value = ads7924_sample_channel(data->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 sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct ads7924_data *data = dev_get_drvdata(dev);
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
};
ATTRIBUTE_GROUPS(ads7924);
static int ads7924_remove(struct i2c_client *client)
{
struct ads7924_data *data = i2c_get_clientdata(client);
int i;
hwmon_device_unregister(data->hwmon_dev);
del_timer_sync(&data->sample_timer);
cancel_work_sync(&data->workq);
mutex_lock(&data->update_lock);
data->enabled = 0;
for (i = 0; i < ADS7924_NCH; i++)
free_adc_channel(data->channel[i]);
mutex_unlock(&data->update_lock);
mutex_destroy(&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); /* Grab reset GPIO if available and assert reset */
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_cansleep(data->reset));
mdelay(5);
gpiod_set_value_cansleep(data->reset, 0); /* Deassert 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);
return smbus_err;
}
dev_id = (uint8_t) smbus_err;
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);
return -ENODEV;
}
for (i = 0; i < ADS7924_NCH; i++) {
data->channel[i] = create_adc_channel(&client->dev, 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);
i2c_set_clientdata(client, data);
dev_set_drvdata(&client->dev, data);
data->hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, "ads7924", data, ads7924_groups);
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;
}
timer_setup(&data->sample_timer, ads7924_timer_irq, 0);
return 0;
err_remove:
mutex_destroy(&data->update_lock);
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");