diff options
Diffstat (limited to 'drivers/hwmon/powr1220.c')
-rw-r--r-- | drivers/hwmon/powr1220.c | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/drivers/hwmon/powr1220.c b/drivers/hwmon/powr1220.c new file mode 100644 index 000000000..f77dc6db3 --- /dev/null +++ b/drivers/hwmon/powr1220.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * powr1220.c - Driver for the Lattice POWR1220 programmable power supply + * and monitor. Users can read all ADC inputs along with their labels + * using the sysfs nodes. + * + * Copyright (c) 2014 Echo360 https://www.echo360.com + * Scott Kanowitz <skanowitz@echo360.com> <scott.kanowitz@gmail.com> + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/delay.h> + +#define ADC_STEP_MV 2 +#define ADC_MAX_LOW_MEASUREMENT_MV 2000 + +enum powr1xxx_chips { powr1014, powr1220 }; + +enum powr1220_regs { + VMON_STATUS0, + VMON_STATUS1, + VMON_STATUS2, + OUTPUT_STATUS0, + OUTPUT_STATUS1, + OUTPUT_STATUS2, + INPUT_STATUS, + ADC_VALUE_LOW, + ADC_VALUE_HIGH, + ADC_MUX, + UES_BYTE0, + UES_BYTE1, + UES_BYTE2, + UES_BYTE3, + GP_OUTPUT1, + GP_OUTPUT2, + GP_OUTPUT3, + INPUT_VALUE, + RESET, + TRIM1_TRIM, + TRIM2_TRIM, + TRIM3_TRIM, + TRIM4_TRIM, + TRIM5_TRIM, + TRIM6_TRIM, + TRIM7_TRIM, + TRIM8_TRIM, + MAX_POWR1220_REGS +}; + +enum powr1220_adc_values { + VMON1, + VMON2, + VMON3, + VMON4, + VMON5, + VMON6, + VMON7, + VMON8, + VMON9, + VMON10, + VMON11, + VMON12, + VCCA, + VCCINP, + MAX_POWR1220_ADC_VALUES +}; + +struct powr1220_data { + struct i2c_client *client; + struct mutex update_lock; + u8 max_channels; + bool adc_valid[MAX_POWR1220_ADC_VALUES]; + /* the next value is in jiffies */ + unsigned long adc_last_updated[MAX_POWR1220_ADC_VALUES]; + + /* values */ + int adc_maxes[MAX_POWR1220_ADC_VALUES]; + int adc_values[MAX_POWR1220_ADC_VALUES]; +}; + +static const char * const input_names[] = { + [VMON1] = "vmon1", + [VMON2] = "vmon2", + [VMON3] = "vmon3", + [VMON4] = "vmon4", + [VMON5] = "vmon5", + [VMON6] = "vmon6", + [VMON7] = "vmon7", + [VMON8] = "vmon8", + [VMON9] = "vmon9", + [VMON10] = "vmon10", + [VMON11] = "vmon11", + [VMON12] = "vmon12", + [VCCA] = "vcca", + [VCCINP] = "vccinp", +}; + +/* Reads the specified ADC channel */ +static int powr1220_read_adc(struct device *dev, int ch_num) +{ + struct powr1220_data *data = dev_get_drvdata(dev); + int reading; + int result; + int adc_range = 0; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->adc_last_updated[ch_num] + HZ) || + !data->adc_valid[ch_num]) { + /* + * figure out if we need to use the attenuator for + * high inputs or inputs that we don't yet have a measurement + * for. We dynamically set the attenuator depending on the + * max reading. + */ + if (data->adc_maxes[ch_num] > ADC_MAX_LOW_MEASUREMENT_MV || + data->adc_maxes[ch_num] == 0) + adc_range = 1 << 4; + + /* set the attenuator and mux */ + result = i2c_smbus_write_byte_data(data->client, ADC_MUX, + adc_range | ch_num); + if (result) + goto exit; + + /* + * wait at least Tconvert time (200 us) for the + * conversion to complete + */ + udelay(200); + + /* get the ADC reading */ + result = i2c_smbus_read_byte_data(data->client, ADC_VALUE_LOW); + if (result < 0) + goto exit; + + reading = result >> 4; + + /* get the upper half of the reading */ + result = i2c_smbus_read_byte_data(data->client, ADC_VALUE_HIGH); + if (result < 0) + goto exit; + + reading |= result << 4; + + /* now convert the reading to a voltage */ + reading *= ADC_STEP_MV; + data->adc_values[ch_num] = reading; + data->adc_valid[ch_num] = true; + data->adc_last_updated[ch_num] = jiffies; + result = reading; + + if (reading > data->adc_maxes[ch_num]) + data->adc_maxes[ch_num] = reading; + } else { + result = data->adc_values[ch_num]; + } + +exit: + mutex_unlock(&data->update_lock); + + return result; +} + +static umode_t +powr1220_is_visible(const void *data, enum hwmon_sensor_types type, u32 + attr, int channel) +{ + struct powr1220_data *chip_data = (struct powr1220_data *)data; + + if (channel >= chip_data->max_channels) + return 0; + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_highest: + case hwmon_in_label: + return 0444; + default: + break; + } + break; + default: + break; + } + + return 0; +} + +static int +powr1220_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_label: + *str = input_names[channel]; + return 0; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return -EOPNOTSUPP; +} + +static int +powr1220_read(struct device *dev, enum hwmon_sensor_types type, u32 + attr, int channel, long *val) +{ + struct powr1220_data *data = dev_get_drvdata(dev); + int ret; + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + ret = powr1220_read_adc(dev, channel); + if (ret < 0) + return ret; + *val = ret; + break; + case hwmon_in_highest: + *val = data->adc_maxes[channel]; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; +} + + return 0; +} + +static const struct hwmon_channel_info *powr1220_info[] = { + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL), + + NULL +}; + +static const struct hwmon_ops powr1220_hwmon_ops = { + .read = powr1220_read, + .read_string = powr1220_read_string, + .is_visible = powr1220_is_visible, +}; + +static const struct hwmon_chip_info powr1220_chip_info = { + .ops = &powr1220_hwmon_ops, + .info = powr1220_info, +}; + +static const struct i2c_device_id powr1220_ids[]; + +static int powr1220_probe(struct i2c_client *client) +{ + struct powr1220_data *data; + struct device *hwmon_dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + switch (i2c_match_id(powr1220_ids, client)->driver_data) { + case powr1014: + data->max_channels = 10; + break; + default: + data->max_channels = 12; + break; + } + + mutex_init(&data->update_lock); + data->client = client; + + hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, + client->name, + data, + &powr1220_chip_info, + NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id powr1220_ids[] = { + { "powr1014", powr1014, }, + { "powr1220", powr1220, }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, powr1220_ids); + +static struct i2c_driver powr1220_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "powr1220", + }, + .probe_new = powr1220_probe, + .id_table = powr1220_ids, +}; + +module_i2c_driver(powr1220_driver); + +MODULE_AUTHOR("Scott Kanowitz"); +MODULE_DESCRIPTION("POWR1220 driver"); +MODULE_LICENSE("GPL"); |