| /* |
| * ./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"); |