The Gateworks System Controller has a hwmon sub-component that exposes up to 16 ADC's, some of which are temperature sensors, others which are voltage inputs. The ADC configuration (register mapping and name) is configured via device-tree and varies board to board. Signed-off-by: Tim Harvey <tharvey@xxxxxxxxxxxxx> --- drivers/hwmon/Kconfig | 6 + drivers/hwmon/Makefile | 1 + drivers/hwmon/gsc-hwmon.c | 299 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 drivers/hwmon/gsc-hwmon.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 7ad0176..9cdc3cb 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -475,6 +475,12 @@ config SENSORS_F75375S This driver can also be built as a module. If so, the module will be called f75375s. +config SENSORS_GSC + tristate "Gateworks System Controller ADC" + depends on MFD_GSC + help + Support for the Gateworks System Controller A/D converters. + config SENSORS_MC13783_ADC tristate "Freescale MC13783/MC13892 ADC" depends on MFD_MC13XXX diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 0fe489f..835a536 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_SENSORS_G760A) += g760a.o obj-$(CONFIG_SENSORS_G762) += g762.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o +obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o diff --git a/drivers/hwmon/gsc-hwmon.c b/drivers/hwmon/gsc-hwmon.c new file mode 100644 index 0000000..3e14bea --- /dev/null +++ b/drivers/hwmon/gsc-hwmon.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Gateworks Corporation + */ +#define DEBUG + +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/mfd/gsc.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +/* map channel to channel info */ +struct gsc_hwmon_ch { + u8 reg; + char name[32]; +}; +static struct gsc_hwmon_ch gsc_temp_ch[16]; +static struct gsc_hwmon_ch gsc_in_ch[16]; +static struct gsc_hwmon_ch gsc_fan_ch[5]; + +static int +gsc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int ch, long *val) +{ + struct gsc_dev *gsc = dev_get_drvdata(dev); + int sz, ret; + u8 reg; + u8 buf[3]; + + dev_dbg(dev, "%s type=%d attr=%d ch=%d\n", __func__, type, attr, ch); + switch (type) { + case hwmon_in: + sz = 3; + reg = gsc_in_ch[ch].reg; + break; + case hwmon_temp: + sz = 2; + reg = gsc_temp_ch[ch].reg; + break; + default: + return -EOPNOTSUPP; + } + + ret = regmap_bulk_read(gsc->regmap_hwmon, reg, &buf, sz); + if (!ret) { + *val = 0; + while (sz-- > 0) + *val |= (buf[sz] << (8*sz)); + if ((type == hwmon_temp) && *val > 0x8000) + *val -= 0xffff; + } + + return ret; +} + +static int +gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int ch, const char **buf) +{ + dev_dbg(dev, "%s type=%d attr=%d ch=%d\n", __func__, type, attr, ch); + switch (type) { + case hwmon_in: + case hwmon_temp: + case hwmon_fan: + switch (attr) { + case hwmon_in_label: + *buf = gsc_in_ch[ch].name; + return 0; + break; + case hwmon_temp_label: + *buf = gsc_temp_ch[ch].name; + return 0; + break; + case hwmon_fan_label: + *buf = gsc_fan_ch[ch].name; + return 0; + break; + default: + break; + } + break; + default: + break; + } + + return -ENOTSUPP; +} + +static int +gsc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int ch, long val) +{ + struct gsc_dev *gsc = dev_get_drvdata(dev); + int ret; + u8 reg; + u8 buf[3]; + + dev_dbg(dev, "%s type=%d attr=%d ch=%d\n", __func__, type, attr, ch); + switch (type) { + case hwmon_fan: + buf[0] = val & 0xff; + buf[1] = (val >> 8) & 0xff; + reg = gsc_fan_ch[ch].reg; + ret = regmap_bulk_write(gsc->regmap_hwmon, reg, &buf, 2); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +static umode_t +gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr, + int ch) +{ + const struct gsc_dev *gsc = _data; + struct device *dev = gsc->dev; + umode_t mode = 0; + + switch (type) { + case hwmon_fan: + if (attr == hwmon_fan_input) + mode = (S_IRUGO | S_IWUSR); + break; + case hwmon_temp: + case hwmon_in: + mode = S_IRUGO; + break; + default: + break; + } + dev_dbg(dev, "%s type=%d attr=%d ch=%d mode=0x%x\n", __func__, type, + attr, ch, mode); + + return mode; +} + +static u32 gsc_in_config[] = { + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + 0 +}; +static const struct hwmon_channel_info gsc_in = { + .type = hwmon_in, + .config = gsc_in_config, +}; + +static u32 gsc_temp_config[] = { + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + 0 +}; +static const struct hwmon_channel_info gsc_temp = { + .type = hwmon_temp, + .config = gsc_temp_config, +}; + +static u32 gsc_fan_config[] = { + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + 0, +}; +static const struct hwmon_channel_info gsc_fan = { + .type = hwmon_fan, + .config = gsc_fan_config, +}; + +static const struct hwmon_channel_info *gsc_info[] = { + &gsc_temp, + &gsc_in, + &gsc_fan, + NULL +}; + +static const struct hwmon_ops gsc_hwmon_ops = { + .is_visible = gsc_hwmon_is_visible, + .read = gsc_hwmon_read, + .read_string = gsc_hwmon_read_string, + .write = gsc_hwmon_write, +}; + +static const struct hwmon_chip_info gsc_chip_info = { + .ops = &gsc_hwmon_ops, + .info = gsc_info, +}; + +static int gsc_hwmon_probe(struct platform_device *pdev) +{ + struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent); + struct device_node *np; + struct device *hwmon; + int temp_count = 0; + int in_count = 0; + int fan_count = 0; + int ret; + + dev_dbg(&pdev->dev, "%s\n", __func__); + np = of_get_next_child(pdev->dev.of_node, NULL); + while (np) { + u32 reg, type; + const char *label; + + of_property_read_u32(np, "reg", ®); + of_property_read_u32(np, "type", &type); + label = of_get_property(np, "label", NULL); + switch(type) { + case 0: /* temperature sensor */ + gsc_temp_config[temp_count] = HWMON_T_INPUT | + HWMON_T_LABEL; + gsc_temp_ch[temp_count].reg = reg; + strncpy(gsc_temp_ch[temp_count].name, label, 32); + if (temp_count < ARRAY_SIZE(gsc_temp_config)) + temp_count++; + break; + case 1: /* voltage sensor */ + gsc_in_config[in_count] = HWMON_I_INPUT | + HWMON_I_LABEL; + gsc_in_ch[in_count].reg = reg; + strncpy(gsc_in_ch[in_count].name, label, 32); + if (in_count < ARRAY_SIZE(gsc_in_config)) + in_count++; + break; + case 2: /* fan controller setpoint */ + gsc_fan_config[fan_count] = HWMON_F_INPUT | + HWMON_F_LABEL; + gsc_fan_ch[fan_count].reg = reg; + strncpy(gsc_fan_ch[fan_count].name, label, 32); + if (fan_count < ARRAY_SIZE(gsc_fan_config)) + fan_count++; + break; + } + np = of_get_next_child(pdev->dev.of_node, np); + } + /* terminate list */ + gsc_in_config[in_count] = 0; + gsc_temp_config[temp_count] = 0; + gsc_fan_config[fan_count] = 0; + + hwmon = devm_hwmon_device_register_with_info(&pdev->dev, + KBUILD_MODNAME, gsc, + &gsc_chip_info, NULL); + if (IS_ERR(hwmon)) { + ret = PTR_ERR(hwmon); + dev_err(&pdev->dev, "Unable to register hwmon device: %d\n", + ret); + return ret; + } + + return 0; +} + +static const struct of_device_id gsc_hwmon_of_match[] = { + { .compatible = "gw,gsc-hwmon", }, + {} +}; + +static struct platform_driver gsc_hwmon_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = gsc_hwmon_of_match, + }, + .probe = gsc_hwmon_probe, +}; + +module_platform_driver(gsc_hwmon_driver); + +MODULE_AUTHOR("Tim Harvey <tharvey@xxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("GSC hardware monitor driver"); +MODULE_LICENSE("GPL v2"); -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html