* [PATCH] add Enermax DigiFanless HID/hwmon driver
@ 2015-08-18 6:38 Frank Schaefer
2015-08-25 15:51 ` [lm-sensors] " Guenter Roeck
0 siblings, 1 reply; 2+ messages in thread
From: Frank Schaefer @ 2015-08-18 6:38 UTC (permalink / raw)
To: lm-sensors, linux-input
This driver adds support for the monitoring logic in the Enermax DigiFanless
550W (EDF550AWN) power supply. It spans the HID and hwmon subsystems, so I
decided it needs review from both maintainer teams. (I'm still aiming for it
to filter through the hwmon linux-staging repo).
Signed-off-by: Frank Schaefer <kelledin@gmail.com>
---
Documentation/hwmon/zdpms | 107 ++++++
drivers/hid/Kconfig | 16 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-core.c | 1 +
drivers/hid/hid-ids.h | 1 +
drivers/hid/hid-zdpms.c | 828 ++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 954 insertions(+)
create mode 100644 Documentation/hwmon/zdpms
create mode 100644 drivers/hid/hid-zdpms.c
diff --git a/Documentation/hwmon/zdpms b/Documentation/hwmon/zdpms
new file mode 100644
index 0000000..98a4cdc
--- /dev/null
+++ b/Documentation/hwmon/zdpms
@@ -0,0 +1,107 @@
+Kernel driver hid-zdpms
+=======================
+
+Supported chips:
+ * Enermax DigiFanless 550W PIC (Microchip PIC32MX230F064D)
+ Prefix: 'zdpms'
+ Addresses scanned: USB HID space
+ Datasheet: Still not published
+
+Authors:
+ Frank Schaefer <kelledin@gmail.com>
+
+Module Parameters
+-----------------
+
+zdpms_timeout=timeout Set the ZDPMS transaction timeout in milliseconds
+ (default 100 ms). This is clamped to a minimum of 20
+ milliseconds, because accuracy is spotty with anything
+ less.
+ Example: 'modprobe hid-zdpms zdpms_timeout=500'
+zdpms_12v_config=<int> Set the 12V rail configuration (1 for single rail, 2
+ for dual rail). By default the rail configuration is
+ left untouched.
+ Example: 'modprobe hid-zdpms zdpms_12v_config=2'
+
+Description
+-----------
+
+The Enermax DigiFanless 550W power supply has an integrated Microchip
+32-bit peripheral interface controller (PIC), running a specialized
+application to monitor internal power-supply sensors and handle some
+incidental user-controllable configuration (mainly the 12V rail
+configuration). This controller application communicates with the host
+via USB raw Human Interface Device (HID) protocol, using a custom OEM
+usage table and failing to disclose the required report descriptors via
+standard HID enumeration. This controller is the foundation of the
+Enermax Zero-Delay Power Monitoring System (ZDPMS), which is apparently
+supported by Enermax only through a proprietary Windows application.
+
+The Microchip PIC application implements voltage and current sensors for
+all internal DC rails (12V1, 12V2, 5V, and 3.3V), an internal temperature
+sensor, and an AC input voltage sensor, all of which we are able to query.
+There are also supposedly shutdown thresholds for overvoltage protection
+and overtemp protection, but as near as i can tell, they are hardcoded and
+firmly out of software's reach.
+
+Sensor values are reported by the PIC in an odd format similar to (but
+not quite identical to) IEEE 754 half-precision floating-point. They
+seem to have a 5-bit exponent and no sign field, and the leading '1' bit
+implicit in IEEE 754 mantissa fields is apparently explicit here. The
+exponent also appears to be a fixed value for each sensor, implying that
+each sensor has its own fixed range and precision. The apparent lack of
+a sign bit implies that all sensor readings are lower-bounded at zero.
+
+Voltage sensors (also known as IN sensors) report their values in volts;
+current sensors (known as CURR sensors) report their values in amperes.
+The only known temperature sensor reports its value in degrees Celcius.
+Applications such as the Enermax OEM application are supposedly expected
+to calculate watts in software using the information supplied in voltage
+and amperage sensors (although we currently lack enough sensor readings
+to calculate all AC input properties).
+
+Supposedly, if any sensor reading crosses its hardcoded alarm threshold,
+the power supply will shut down, presumably powering down the embedded
+Microchip PIC as well (although we have yet to confirm this empirically).
+So any software alarms are probably superfluous.
+
+It is unknown how often the Microchip PIC will measure internal sensor
+readings, although the "zero delay" terminology implies that it is near
+instantaneous (which seems appropriate for overvoltage protection).
+The driver queries the PIC every time userland reads a sensor token via
+sysfs but does no internal monitoring of its own.
+
+Known Issues
+------------
+
+This driver identifies the Enermax PIC controller application by USB
+vendor and product ID. Unfortunately, it is specifically advertising a
+Microchip vendor ID rather than an Enermax vendor ID, and the product ID
+is an otherwise unknown value. Even worse, it does not advertise any
+vendor, product, or serial number string through HID enumeration, so I
+have some concerns about how well Enermax has ensured uniqueness of
+enumerated IDs. There's also an open question as to whether Enermax has
+incorporated similar technology in any of their other power supplies, how
+those other products enumerate via HID, and how the driver will respond
+upon seeing one of these other devices.
+
+Also, since pretty much all my knowledge of this hardware is based on
+dissecting USB traffic generated by the Enermax OEM application, I'm not
+entirely certain of some aspects, such as the sensor-reading format. For
+example, I've never seen either of the two high bits of the exponent unset,
+or the high bit of the mantissa set--so for all I know, these bits may not
+mean what I think they mean.
+
+Furthermore, although the OEM application is able to determine the apparent
+power usage (volt-amperes), the actual power usage (watts), and the power
+factor for the AC inputs, I haven't learned how it determines any of those
+values.
+
+Finally, there is a possibility that upon non-fatal "alarm" conditions,
+the controller may notify the host via an unexpected HID event. I have no
+idea what this will look like, and if there's a host-initiated query or
+command in flight, any such unexpected event may interfere with the expected
+response. To figure out how to deal with this (or even see what it looks
+like without vendor documentation), it may be necessary to construct a
+benchtop load setup specifically to trigger such alarm conditions. This is
+difficult and somewhat dangerous, for obvious reasons.
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index cc4c664..de01050 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -849,6 +849,22 @@ config HID_XINMO
standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here
if you have a Xin-Mo Dual Arcade controller.
+config HID_ZDPMS
+ tristate "Enermax ZDPMS support (EXPERIMENTAL)"
+ depends on HID && HWMON
+ default n
+ ---help---
+ Support for monitoring sensors and software-configurable settings
+ embedded in the Enermax DigiFanless 550W power supply. This driver
+ registers as a hwmon driver to expose these sensors for use by
+ libsensors-based applications.
+
+ This driver is largely based on dissecting captures of USB traffic
+ generated by the Enermax ZDPMS OEM Windows application. At least
+ two sensors are not supported (yet), and it's uncertain how this
+ driver will behave in the presence of other Enermax products with
+ similar (but not quite identical) functionality.
+
config HID_ZEROPLUS
tristate "Zeroplus based game controller support"
depends on HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 2f8a41d..dd538dd 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o
obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o
obj-$(CONFIG_HID_UCLOGIC) += hid-uclogic.o
obj-$(CONFIG_HID_XINMO) += hid-xinmo.o
+obj-$(CONFIG_HID_ZDPMS) += hid-zdpms.o
obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o
obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index e6fce23..5d4062b 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1895,6 +1895,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_ZDPMS) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_MOUSE_4500) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K) },
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index b3b225b..a9eedea 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -650,6 +650,7 @@
#define USB_DEVICE_ID_PICOLCD 0xc002
#define USB_DEVICE_ID_PICOLCD_BOOTLOADER 0xf002
#define USB_DEVICE_ID_PICK16F1454 0x0042
+#define USB_DEVICE_ID_ZDPMS 0xf590
#define USB_VENDOR_ID_MICROSOFT 0x045e
#define USB_DEVICE_ID_SIDEWINDER_GV 0x003b
diff --git a/drivers/hid/hid-zdpms.c b/drivers/hid/hid-zdpms.c
new file mode 100644
index 0000000..905ef6a
--- /dev/null
+++ b/drivers/hid/hid-zdpms.c
@@ -0,0 +1,828 @@
+/*
+ * Linux HID/hwmon driver for Enermax ZDPMS controller
+ *
+ * Copyright (c) 2015 Frank Schaefer <kelledin@gmail.com>
+ *
+ * The name "Enermax" (and possibly "DigiFanless" and "ZDPMS") are trademark
+ * Enermax Inc (http://www.enermax.com/).
+ */
+
+/*
+ * 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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/hid.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/mutex.h>
+#include <linux/jiffies.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/atomic.h>
+#include "hid-ids.h"
+
+#define ZDPMS_VERSION "0.1.1"
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Enermax ZDPMS controller");
+MODULE_AUTHOR("Frank Schaefer <kelledin@gmail.com>");
+MODULE_VERSION(ZDPMS_VERSION);
+
+static const struct hid_device_id zdpms_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_ZDPMS) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, zdpms_devices);
+
+static int zdpms_timeout = 100;
+module_param(zdpms_timeout, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(zdpms_timeout,
+ "Transaction timeout in milliseconds (minimum 20)");
+
+static int zdpms_12v_config;
+module_param(zdpms_12v_config, int, S_IRUGO);
+MODULE_PARM_DESC(zdpms_12v_config,
+ "Set 12V rail configuration (1: single rail, 2: dual rail)");
+
+enum {
+ /* Raw size for HID messages, excluding the URB header */
+ ZDPMS_MSG_SIZE = 64,
+
+ /*
+ * No idea what these mean, but they are part of the initialization
+ * sequence used by the Enermax ZDPMS OEM application, with the
+ * single byte value 0x5 passed to each one. I suspect they set
+ * something, I just don't know what.
+ */
+ ZDPMS_INIT1 = 0x25,
+ ZDPMS_INIT2 = 0x24,
+ ZDPMS_INIT3 = 0x23,
+
+ /*
+ * 12V rail configuration setting. Passing byte value 0x1 sets
+ * single-rail mode, while passing byte value 0x2 sets dual-rail
+ * mode. Some PSU models may support more values for more rail
+ * configurations, but this is what we get on our one sample.
+ */
+ ZDPMS_12V_CONFIG = 0x26,
+
+ /* Sensor values, as near as we can tell. */
+ ZDPMS_SENSOR_MIN = 0x27, /* Minimum sensor value */
+ ZDPMS_AC_V = 0x27, /* AC input voltage level */
+ ZDPMS_DC_12V1 = 0x28, /*
+ * One of the 12V rail voltage levels
+ * (probably 12V1)
+ */
+ ZDPMS_DC_5V = 0x29, /* DC 5V rail voltage level */
+ ZDPMS_DC_3V = 0x2a, /* DC 3.3V rail voltage level */
+ ZDPMS_DC_12V2 = 0x2b, /*
+ * One of the 21V rail voltage levels.
+ * (probably 12V1)
+ */
+ ZDPMS_DC_12V1_CURRENT = 0x2c, /* 12V1 current level in amperes */
+ ZDPMS_DC_5V_CURRENT = 0x2d, /* 5V current level in amperes */
+ ZDPMS_DC_3V_CURRENT = 0x2e, /* 3.3V current level in amperes */
+ ZDPMS_DC_12V2_CURRENT = 0x2f, /* 12V2 current level in amperes */
+ ZDPMS_TEMP = 0x30, /* PSU temperature in degrees Celcius */
+ ZDPMS_SENSOR_MAX = 0x30, /* Maximum sensor value */
+
+ ZDPMS_MODEL_NUMBER = 0x32, /*
+ * PSU model number string length
+ * (variable length)
+ */
+
+ /* Message types (a.k.a. HID report IDs) that we've seen. */
+ ZDPMS_SET_CONFIG = 0x82, /* Used for setting configuration. */
+ ZDPMS_READ_SENSOR = 0x83, /* Used for reading values. */
+ ZDPMS_WTF_1 = 0x90, /* No idea at all. Doesn't get a valid
+ * response.
+ */
+ ZDPMS_WTF_2 = 0x91, /* As above, no idea at all. */
+};
+
+static const char * const zdpms_labels[] = {
+ "AC input voltage",
+ "DC 12V1",
+ "DC 5V",
+ "DC 3.3V",
+ "DC 12V2",
+ "DC 12V1 current",
+ "DC 5V current",
+ "DC 3.3V current",
+ "DC 12V2 current",
+ "PSU temp",
+};
+
+struct zdpms_device {
+ /* The hid_device we belong to. */
+ struct hid_device *hdev;
+
+ /* The hwmon device pointer. */
+ struct device *hwmon_dev;
+
+ /* The wait queue used to track transaction replies. */
+ wait_queue_head_t io_wait;
+
+ /*
+ * The atomic condition variable to indicate that a message is
+ * received.
+ */
+ atomic_t msg_received;
+
+ /*
+ * The last data read by the raw event handler. This is expected to be
+ * a command or query response (up to ZDPMS_MSG_SIZE bytes), and to the
+ * best of my knowledge, only one pending command or query is supported
+ * at any time.
+ */
+ uint8_t data[ZDPMS_MSG_SIZE];
+ unsigned int sz;
+
+ /*
+ * The ZDPMS model number (i.e. "EDF550AWN" for the Enermax DigiFanless
+ * 550W). We don't expect this to be more than 32 bytes.
+ */
+ char model[32];
+
+ /*
+ * Transaction lock mutex. This keeps us from launching a second
+ * command/query while one is still pending.
+ */
+ struct mutex io_lock;
+
+};
+
+/**
+ * Perform a command/response or query/response exchange with an Enermax
+ * ZDPMS controller. The command is launched immediately through the
+ * controller's interrupt endpoint; the response is copied back for the
+ * caller to perform further analysis. A minimum response timeout
+ * (maintained in the zdpms_timeout variable) is implicit to avoid
+ * potential deadlocks. No validation is performed on the response
+ * content.
+ *
+ * Note that commands, queries, and responses are generally zero padded to
+ * ZDPMS_MSG_SIZE bytes. The first byte of any message must be the report
+ * ID (typically either ZDPMS_SET_CONFIG or ZDPMS_READ_SENSOR); the second
+ * byte is supposed to be the length of the message payload, minus the first
+ * two bytes and any zero padding. For successful commands, the ZDPMS
+ * controller appears to copy the report ID to the end of the message; for
+ * unsupported commands, the controller appears to set the length byte to
+ * 0xFF in the response.
+ *
+ * @param hdev
+ * The hid_device to which we're sending the command/query
+ *
+ * @param msg
+ * The command/query to send to the ZDPMS controller, starting with the
+ * report ID and message-length byte
+ *
+ * @param sz
+ * The size of the command/query in bytes, including the report ID and
+ * message-length byte
+ *
+ * @param resp
+ * A user-supplied buffer to store the response. This MUST be at least
+ * ZDPMS_MSG_SIZE bytes in order to avoid potential buffer overflows.
+ *
+ * @return
+ * Returns the number of bytes read on success (usually ZDPMS_MSG_SIZE),
+ * or a negative errno value on failure.
+ */
+static int zdpms_xchg(struct hid_device *hdev,
+ const uint8_t *data,
+ unsigned int sz,
+ uint8_t *resp)
+{
+ uint8_t msg[ZDPMS_MSG_SIZE];
+ struct zdpms_device *dev;
+ unsigned long last_jiffy;
+ int ret;
+
+ dev = hid_get_drvdata(hdev);
+
+ /* Copy the message and clear any padding. */
+ memcpy(msg, data, sz);
+
+ if (sz < sizeof(msg))
+ memset(msg + sz, 0, sizeof(msg) - sz);
+
+ mutex_lock(&(dev->io_lock));
+
+ /*
+ * Send the command/query. We use hid_hw_output_report() specifically
+ * because it will send the raw message to an interrupt endpoint.
+ * NOTE: No return type (WTF really?). If it fails, we'll supposedly
+ * find out when we time out waiting for the response...
+ */
+ hid_hw_output_report(hdev, msg, sizeof(msg));
+
+ /*
+ * Clamp the timeout to 20ms. Any smaller and it's dicey to represent
+ * it in jiffies...
+ */
+ if (zdpms_timeout < 20)
+ zdpms_timeout = 20;
+
+ /*
+ * We track the last allowable jiffy, in order to avoid our timeout
+ * getting mistakenly prolonged by a signal storm or similar event.
+ */
+ last_jiffy = jiffies + msecs_to_jiffies(zdpms_timeout);
+
+ do {
+ ret = wait_event_interruptible_timeout(
+ dev->io_wait,
+ atomic_read(&(dev->msg_received)),
+ last_jiffy - jiffies);
+
+ if (ret == -ERESTARTSYS)
+ /* Interrupted by a signal. Keep trying... */
+ continue;
+
+ if (ret < 0)
+ /* Unspecified error. Punt! */
+ goto fail_mutex_locked;
+
+ if (ret == 0) {
+ /* Timed out! */
+ ret = -ETIMEDOUT;
+ goto fail_mutex_locked;
+ }
+
+ if (ret > 0)
+ /* Message is received. */
+ break;
+ } while (time_before(jiffies, last_jiffy));
+
+ /*
+ * Now see about copying the received data to the user-supplied buffer.
+ */
+ memcpy(resp, dev->data, dev->sz);
+ ret = dev->sz;
+ atomic_set(&(dev->msg_received), 0);
+
+fail_mutex_locked:
+ mutex_unlock(&(dev->io_lock));
+ return ret;
+}
+
+/**
+ * Sets a specific ZDPMS configuration item. Currently-known configuration
+ * commands only take a one-byte configuration value. Response validation
+ * is performed.
+ *
+ * @param hdev
+ * Pointer to the hid_device structure to configure
+ *
+ * @param cmd
+ * Device setting to configure
+ *
+ * @param val
+ * Configuration setting to apply
+ *
+ * @return
+ * Returns the size of the return message on success, or a negative
+ * errno value on failure
+ */
+static int zdpms_set(struct hid_device *hdev, uint8_t cmd, uint8_t val)
+{
+ uint8_t msg[8], resp[ZDPMS_MSG_SIZE];
+ int result;
+
+ msg[0] = ZDPMS_SET_CONFIG;
+ msg[1] = 0x03; /* 3-byte message */
+ msg[2] = 0x40; /* no idea what this is supposed to mean */
+ msg[3] = cmd; /* Command value */
+ msg[4] = val;
+
+ result = zdpms_xchg(hdev, msg, 5, resp);
+
+ if (result < 0)
+ return result;
+
+ if (result < 6)
+ return -EMSGSIZE;
+
+ if (resp[1] == 0xff)
+ /* Unsupported command? */
+ return -EINVAL;
+
+ if (resp[0] != msg[0] || resp[1] != 4 || resp[2] != msg[2] ||
+ resp[3] != msg[3] || resp[4] != msg[4] || resp[5] != resp[0])
+ return -EPROTO;
+
+ return result;
+}
+
+/**
+ * Reads the ASCII model number string from the ZDPMS controller and stores it
+ * in the caller-supplied buffer and appends a NULL terminator if possible. If
+ * the buffer is not large enough to hold the entire string, it will be
+ * truncated to fit (but no NULL terminator will be added).
+ *
+ * @param hdev
+ * The hid_device we're attempting to query
+ *
+ * @param buf
+ * The caller-supplied buffer to store the model number string
+ *
+ * @param sz
+ * The maximum buffer size (in bytes)
+ *
+ * @return
+ * Returns the length of the model-number string in bytes (not counting
+ * the NULL terminator), or a negative errno value on failure. The
+ * returned string length may be larger than the supplied buffer size.
+ *
+ */
+static int zdpms_read_model(struct hid_device *hdev, char *buf, size_t sz)
+{
+ uint8_t msg[8], resp[ZDPMS_MSG_SIZE];
+ int result;
+
+ msg[0] = ZDPMS_READ_SENSOR;
+ msg[1] = 0x2; /* 2-byte message */
+ msg[2] = 0x40; /* nope, still no idea what this is for. */
+ msg[3] = ZDPMS_MODEL_NUMBER;
+ result = zdpms_xchg(hdev, msg, 4, resp);
+
+ if (result < 0)
+ return result;
+
+ if (result < 6)
+ return -EMSGSIZE;
+
+ if (resp[1] == 0xff)
+ return -EINVAL;
+
+ if (resp[1] + 2 > ZDPMS_MSG_SIZE || resp[0] != msg[0] ||
+ resp[2] != msg[2] || resp[3] != msg[3] ||
+ resp[resp[1]+1] != resp[0])
+ return -EPROTO;
+
+ result = resp[1] - 3;
+
+ if (result < sz) {
+ /*
+ * Good news, we can fit the entire model name in the supplied
+ * buffer, complete with a NULL terminator.
+ */
+ memcpy(buf, resp + 4, result);
+ buf[result] = '\0';
+ } else
+ /* Whoops, we can only fit in part of it. */
+ memcpy(buf, resp + 4, sz);
+
+ return result;
+}
+
+/**
+ * Query a sensor reading from a ZDPMS controller. This reading is reported
+ * in millivolts, milliamperes, or millidegrees Celcius, in order to conform
+ * with the Linux hwmon subsystem expectations.
+ *
+ * @param hdev
+ * The hid_device we're attempting to query
+ *
+ * @param sensor
+ * The sensor index we're attempting to query
+ *
+ * @return
+ * Returns the sensor value (currently always >=0) on success, or a
+ * negative errno value on failure.
+ *
+ */
+static int zdpms_read_sensor(struct hid_device *hdev, uint8_t sensor)
+{
+ uint8_t msg[8], resp[ZDPMS_MSG_SIZE];
+ int result;
+ uint32_t val;
+ unsigned int exponent;
+
+ msg[0] = ZDPMS_READ_SENSOR;
+ msg[1] = 0x2; /* 2-byte message */
+ msg[2] = 0x40; /* no idea what this is supposed to mean */
+ msg[3] = sensor; /* Sensor value */
+ result = zdpms_xchg(hdev, msg, 4, resp);
+
+ if (result < 4)
+ return result;
+
+ if (result < 7)
+ return -EMSGSIZE;
+
+ if (resp[1] == 0xff)
+ /* Nonexistent sensor? */
+ return -ENODEV;
+
+ if (resp[0] != msg[0] || resp[1] != 5 || resp[2] != msg[2] ||
+ resp[3] != msg[3] || resp[6] != resp[0])
+ return -EPROTO;
+
+ /*
+ * The sensor value is encoded as little-endian quasi-IEEE 754, in
+ * bytes 4 and 5.
+ */
+ val = ((resp[5] & 7) << 8) | resp[4];
+ exponent = resp[5] >> 3;
+
+ /*
+ * All hwmon sensors we support are supposed to be reported in
+ * milliunits (millivolts, millidegrees Celcius, or milliamperes).
+ * Given that the maximum possible value for the mantissa is 2^11
+ * (2048), we'll remain well within 32-bit integer range even after
+ * applying this multiplier.
+ */
+ val *= 1000;
+
+ /*
+ * Apply the exponent shift *after* applying the units multiplier, so we
+ * preserve as much accuracy as possible.
+ */
+ val >>= (32 - exponent);
+ return val;
+}
+
+/**
+ * Callback for reporting ZDPMS sensor labels via sysfs, in accordance with
+ * standard hwmon conventions.
+ *
+ * @param dev
+ * hwmon device pointer. The corresponding zdpms_device structure is
+ * referenced by the dev_get_drvdata() result and contains a pointer to
+ * the associated hid_device structure of the ZDPMS controller.
+ *
+ * @param attr
+ * sysfs device attribute pointer. The corresponding sensor device
+ * attribute can be deduced from this and supplies the desired ZDPMS
+ * sensor ID.
+ *
+ * @param buf
+ * buffer for storing a string representation of the sensor reading.
+ *
+ * @return
+ * Returns the size of the string representation stored in the buffer,
+ * not including the NULL terminator, or a negative errno value on
+ * failure.
+ *
+ */
+static ssize_t zdpms_show_label(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sensor_device_attribute *sens_attr;
+ struct zdpms_device *zdev;
+ struct hid_device *hdev;
+ unsigned int idx;
+
+ sens_attr = to_sensor_dev_attr(attr);
+ zdev = dev_get_drvdata(dev);
+ hdev = zdev->hdev;
+ idx = sens_attr->index;
+
+ /* We use a straight sequential array as a lookup table. */
+ idx -= ZDPMS_SENSOR_MIN;
+
+ return sprintf(buf, "%s\n", zdpms_labels[idx]);
+}
+
+/**
+ * Callback for reporting ZDPMS sensor values via sysfs, in accordance with
+ * standard hwmon conventions.
+ *
+ * @param dev
+ * hwmon device pointer. The corresponding zdpms_device structure is
+ * referenced by the dev_get_drvdata() result and contains a pointer to
+ * the associated hid_device structure of the ZDPMS controller.
+ *
+ * @param attr
+ * sysfs device attribute pointer. The corresponding sensor device
+ * attribute can be deduced from this and supplies the desired ZDPMS
+ * sensor ID.
+ *
+ * @param buf
+ * buffer for storing a string representation of the sensor reading.
+ *
+ * @return
+ * Returns the size of the string representation stored in the buffer,
+ * not including the NULL terminator, or a negative errno value on
+ * failure.
+ *
+ */
+static ssize_t zdpms_show_sensor(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sensor_device_attribute *sens_attr;
+ struct zdpms_device *zdev;
+ struct hid_device *hdev;
+ unsigned int idx;
+ int val;
+
+ sens_attr = to_sensor_dev_attr(attr);
+ zdev = dev_get_drvdata(dev);
+ hdev = zdev->hdev;
+ idx = sens_attr->index;
+ val = zdpms_read_sensor(hdev, idx);
+
+ if (val < 0)
+ return val;
+
+ return sprintf(buf, "%d\n", val);
+}
+
+/**
+ * Callback for reporting raw HID events from a ZDPMS controller. ZDPMS
+ * controllers generally only send HID events in response to commands or
+ * queries sent to their interrupt endpoints; generally only one such
+ * command or query is expected to be pending at any time.
+ *
+ * @param hdev
+ * Pointer to the HID device (ZDPMS controller) responsible for this
+ * HID event
+ *
+ * @param report
+ * Pointer to the HID report structure for this event. This is
+ * generally ignored, since we prefer to parse raw messages.
+ *
+ * @param data
+ * Pointer to the HID data payload from this event (not including any
+ * headers such as the USB Request Block (URB)
+ *
+ * @param sz
+ * Size of the HID data payload from this event (usually ZDPMS_MSG_SIZE
+ * bytes).
+ *
+ * @return
+ * In accordance with the kernel HID API, returns 0 when the event has
+ * been successfully parsed, or -1 if a parse error occurs.
+ *
+ */
+static int zdpms_raw_event(struct hid_device *hdev,
+ struct hid_report *report,
+ u8 *data,
+ int sz)
+{
+ struct zdpms_device *dev;
+
+ dev = hid_get_drvdata(hdev);
+
+ /*
+ * We shouldn't receive any messages larger than ZDPMS_MSG_SIZE bytes.
+ * If we do, we need to clip it so it fits in our buffer.
+ */
+ if (sz > ZDPMS_MSG_SIZE) {
+ hid_warn(hdev,
+ "truncated oversize %d-byte event message!\n",
+ sz);
+
+ sz = ZDPMS_MSG_SIZE;
+ }
+
+ memcpy(dev->data, data, sz);
+ dev->sz = sz;
+
+ if (atomic_inc_return(&(dev->msg_received)) > 1)
+ /*
+ * This implies we already had a message pending, and we just
+ * overwrote it. D'oh!
+ */
+ hid_warn(hdev, "overwriting unacknowledged message\n");
+
+ /* Now wake up anyone waiting on a transaction to complete. */
+ wake_up_interruptible(&(dev->io_wait));
+ return 0;
+}
+
+static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, zdpms_show_sensor, NULL,
+ ZDPMS_DC_12V1);
+static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, zdpms_show_label, NULL,
+ ZDPMS_DC_12V1);
+static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, zdpms_show_sensor, NULL,
+ ZDPMS_DC_5V);
+static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, zdpms_show_label, NULL,
+ ZDPMS_DC_5V);
+static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, zdpms_show_sensor, NULL,
+ ZDPMS_DC_3V);
+static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, zdpms_show_label, NULL,
+ ZDPMS_DC_3V);
+static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, zdpms_show_sensor, NULL,
+ ZDPMS_DC_12V2);
+static SENSOR_DEVICE_ATTR(in4_label, S_IRUGO, zdpms_show_label, NULL,
+ ZDPMS_DC_12V2);
+static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, zdpms_show_sensor, NULL,
+ ZDPMS_AC_V);
+static SENSOR_DEVICE_ATTR(in5_label, S_IRUGO, zdpms_show_label, NULL,
+ ZDPMS_AC_V);
+static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, zdpms_show_sensor, NULL,
+ ZDPMS_DC_12V1_CURRENT);
+static SENSOR_DEVICE_ATTR(curr1_label, S_IRUGO, zdpms_show_label, NULL,
+ ZDPMS_DC_12V1_CURRENT);
+static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO, zdpms_show_sensor, NULL,
+ ZDPMS_DC_5V_CURRENT);
+static SENSOR_DEVICE_ATTR(curr2_label, S_IRUGO, zdpms_show_label, NULL,
+ ZDPMS_DC_5V_CURRENT);
+static SENSOR_DEVICE_ATTR(curr3_input, S_IRUGO, zdpms_show_sensor, NULL,
+ ZDPMS_DC_3V_CURRENT);
+static SENSOR_DEVICE_ATTR(curr3_label, S_IRUGO, zdpms_show_label, NULL,
+ ZDPMS_DC_3V_CURRENT);
+static SENSOR_DEVICE_ATTR(curr4_input, S_IRUGO, zdpms_show_sensor, NULL,
+ ZDPMS_DC_12V2_CURRENT);
+static SENSOR_DEVICE_ATTR(curr4_label, S_IRUGO, zdpms_show_label, NULL,
+ ZDPMS_DC_12V2_CURRENT);
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, zdpms_show_sensor, NULL,
+ ZDPMS_TEMP);
+static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, zdpms_show_label, NULL,
+ ZDPMS_TEMP);
+
+static struct attribute *zdpms_attrs[] = {
+ &sensor_dev_attr_in1_label.dev_attr.attr,
+ &sensor_dev_attr_in1_input.dev_attr.attr,
+ &sensor_dev_attr_in2_label.dev_attr.attr,
+ &sensor_dev_attr_in2_input.dev_attr.attr,
+ &sensor_dev_attr_in3_label.dev_attr.attr,
+ &sensor_dev_attr_in3_input.dev_attr.attr,
+ &sensor_dev_attr_in4_label.dev_attr.attr,
+ &sensor_dev_attr_in4_input.dev_attr.attr,
+ &sensor_dev_attr_in5_label.dev_attr.attr,
+ &sensor_dev_attr_in5_input.dev_attr.attr,
+ &sensor_dev_attr_curr1_label.dev_attr.attr,
+ &sensor_dev_attr_curr1_input.dev_attr.attr,
+ &sensor_dev_attr_curr2_label.dev_attr.attr,
+ &sensor_dev_attr_curr2_input.dev_attr.attr,
+ &sensor_dev_attr_curr3_label.dev_attr.attr,
+ &sensor_dev_attr_curr3_input.dev_attr.attr,
+ &sensor_dev_attr_curr4_label.dev_attr.attr,
+ &sensor_dev_attr_curr4_input.dev_attr.attr,
+ &sensor_dev_attr_temp1_label.dev_attr.attr,
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(zdpms);
+
+static int zdpms_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct zdpms_device *dev;
+ int ret;
+
+ ret = hid_parse(hdev);
+
+ if (ret < 0) {
+ hid_err(hdev, "parse failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+
+ if (ret < 0) {
+ hid_err(hdev, "hw start failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_open(hdev);
+
+ if (ret < 0) {
+ hid_err(hdev, "hw open failed\n");
+ goto fail_hid_started;
+ }
+
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+
+ if (ret < 0) {
+ hid_err(hdev, "power management error: %d\n", ret);
+ goto fail_hid_open;
+ }
+
+ dev = devm_kzalloc(&(hdev->dev),
+ sizeof(struct zdpms_device),
+ GFP_KERNEL);
+
+ if (dev == NULL) {
+ ret = -ENOMEM;
+ goto fail_hid_power_on;
+ }
+
+ /* Initialize the private device data structure. */
+ dev->hdev = hdev;
+ init_waitqueue_head(&(dev->io_wait));
+ atomic_set(&(dev->msg_received), 0);
+ mutex_init(&(dev->io_lock));
+ hid_set_drvdata(hdev, dev);
+
+ /* Allow event processing during probe. */
+ hid_device_io_start(hdev);
+
+ /* Perform the initialization sequence */
+ ret = zdpms_set(hdev, ZDPMS_INIT1, 0x5);
+
+ if (ret >= 0) {
+ ret = zdpms_set(hdev, ZDPMS_INIT2, 0x5);
+
+ if (ret >= 0)
+ ret = zdpms_set(hdev, ZDPMS_INIT3, 0x5);
+ }
+
+ if (ret < 0) {
+ hid_err(hdev,
+ "Primary initialization sequence FAILED (error %d)\n",
+ ret);
+
+ goto fail_io_started;
+ }
+
+ if (zdpms_12v_config > 0 && zdpms_12v_config < 3) {
+ hid_info(hdev, "Setting 12V %s-rail configuration...",
+ zdpms_12v_config == 1 ? "single" : "dual");
+
+ ret = zdpms_set(hdev, ZDPMS_12V_CONFIG, zdpms_12v_config);
+
+ if (ret < 0) {
+ hid_err(hdev,
+ "12V rail configuration FAILED (error %d)\n",
+ ret);
+
+ goto fail_io_started;
+ }
+ }
+
+ ret = zdpms_read_model(hdev, dev->model, sizeof(dev->model));
+
+ if (ret < 0) {
+ hid_err(hdev, "Model number query FAILED (error %d)\n", ret);
+ goto fail_io_started;
+ }
+
+ if (ret >= sizeof(dev->model)) {
+ hid_warn(hdev,
+ "model number truncated to %ld bytes (full size %d bytes)\n",
+ sizeof(dev->model) - 1, ret);
+
+ dev->model[sizeof(dev->model) - 1] = '\0';
+ } else
+ dev->model[ret] = '\0';
+
+ hid_info(hdev, "Detected model number: %s\n", dev->model);
+
+ /* Undo hid_device_io_start(). */
+ hid_device_io_stop(hdev);
+
+ /* Now register this as an hwmon device. */
+ dev->hwmon_dev = devm_hwmon_device_register_with_groups(&(hdev->dev),
+ "zdpms",
+ dev,
+ zdpms_groups);
+
+ ret = PTR_ERR_OR_ZERO(dev->hwmon_dev);
+
+ if (ret < 0) {
+ hid_err(hdev, "hwmon device registration FAILED (error %d)\n",
+ ret);
+
+ goto fail_data_alloced;
+ }
+
+ return 0;
+
+fail_io_started:
+ hid_device_io_stop(hdev);
+fail_data_alloced:
+ hid_set_drvdata(hdev, NULL);
+ devm_kfree(&(hdev->dev), dev);
+fail_hid_power_on:
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+fail_hid_open:
+ hid_hw_close(hdev);
+fail_hid_started:
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void zdpms_remove(struct hid_device *hdev)
+{
+ struct zdpms_device *dev;
+
+ dev = hid_get_drvdata(hdev);
+ mutex_destroy(&(dev->io_lock));
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static struct hid_driver zdpms_driver = {
+ .name = "zdpms",
+ .id_table = zdpms_devices,
+ .probe = zdpms_probe,
+ .remove = zdpms_remove,
+ .raw_event = zdpms_raw_event,
+};
+
+module_hid_driver(zdpms_driver);
+
--
2.1.4
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [lm-sensors] [PATCH] add Enermax DigiFanless HID/hwmon driver
2015-08-18 6:38 [PATCH] add Enermax DigiFanless HID/hwmon driver Frank Schaefer
@ 2015-08-25 15:51 ` Guenter Roeck
0 siblings, 0 replies; 2+ messages in thread
From: Guenter Roeck @ 2015-08-25 15:51 UTC (permalink / raw)
To: Frank Schaefer; +Cc: lm-sensors, linux-input
On Tue, Aug 18, 2015 at 01:38:40AM -0500, Frank Schaefer wrote:
> This driver adds support for the monitoring logic in the Enermax DigiFanless
> 550W (EDF550AWN) power supply. It spans the HID and hwmon subsystems, so I
> decided it needs review from both maintainer teams. (I'm still aiming for it
> to filter through the hwmon linux-staging repo).
>
Not sure what you mean with that last sentence. Either case, this and the review
comment should probably not be in the commit log and thus be after the '---'.
Since this driver will go into drivers/hid, it should go through the hid tree.
Given its experimental nature, I wonder if it should reside in drivers/staging
for a while.
> Signed-off-by: Frank Schaefer <kelledin@gmail.com>
> ---
> Documentation/hwmon/zdpms | 107 ++++++
> drivers/hid/Kconfig | 16 +
> drivers/hid/Makefile | 1 +
> drivers/hid/hid-core.c | 1 +
> drivers/hid/hid-ids.h | 1 +
> drivers/hid/hid-zdpms.c | 828 ++++++++++++++++++++++++++++++++++++++++++++++
Care to add yourself as maintainer ?
> 6 files changed, 954 insertions(+)
> create mode 100644 Documentation/hwmon/zdpms
> create mode 100644 drivers/hid/hid-zdpms.c
>
> diff --git a/Documentation/hwmon/zdpms b/Documentation/hwmon/zdpms
> new file mode 100644
> index 0000000..98a4cdc
> --- /dev/null
> +++ b/Documentation/hwmon/zdpms
> @@ -0,0 +1,107 @@
> +Kernel driver hid-zdpms
> +=======================
> +
> +Supported chips:
> + * Enermax DigiFanless 550W PIC (Microchip PIC32MX230F064D)
> + Prefix: 'zdpms'
> + Addresses scanned: USB HID space
> + Datasheet: Still not published
> +
> +Authors:
> + Frank Schaefer <kelledin@gmail.com>
> +
> +Module Parameters
> +-----------------
> +
> +zdpms_timeout=timeout Set the ZDPMS transaction timeout in milliseconds
> + (default 100 ms). This is clamped to a minimum of 20
> + milliseconds, because accuracy is spotty with anything
> + less.
> + Example: 'modprobe hid-zdpms zdpms_timeout=500'
> +zdpms_12v_config=<int> Set the 12V rail configuration (1 for single rail, 2
> + for dual rail). By default the rail configuration is
> + left untouched.
> + Example: 'modprobe hid-zdpms zdpms_12v_config=2'
Is this a good idea ? Normally such parameters are determined by the hardware,
and changing it from the OS may be harmful.
> +
> +Description
> +-----------
> +
> +The Enermax DigiFanless 550W power supply has an integrated Microchip
> +32-bit peripheral interface controller (PIC), running a specialized
> +application to monitor internal power-supply sensors and handle some
> +incidental user-controllable configuration (mainly the 12V rail
> +configuration). This controller application communicates with the host
> +via USB raw Human Interface Device (HID) protocol, using a custom OEM
> +usage table and failing to disclose the required report descriptors via
> +standard HID enumeration. This controller is the foundation of the
> +Enermax Zero-Delay Power Monitoring System (ZDPMS), which is apparently
> +supported by Enermax only through a proprietary Windows application.
> +
> +The Microchip PIC application implements voltage and current sensors for
> +all internal DC rails (12V1, 12V2, 5V, and 3.3V), an internal temperature
> +sensor, and an AC input voltage sensor, all of which we are able to query.
> +There are also supposedly shutdown thresholds for overvoltage protection
> +and overtemp protection, but as near as i can tell, they are hardcoded and
> +firmly out of software's reach.
> +
> +Sensor values are reported by the PIC in an odd format similar to (but
> +not quite identical to) IEEE 754 half-precision floating-point. They
> +seem to have a 5-bit exponent and no sign field, and the leading '1' bit
> +implicit in IEEE 754 mantissa fields is apparently explicit here. The
> +exponent also appears to be a fixed value for each sensor, implying that
> +each sensor has its own fixed range and precision. The apparent lack of
> +a sign bit implies that all sensor readings are lower-bounded at zero.
> +
> +Voltage sensors (also known as IN sensors) report their values in volts;
> +current sensors (known as CURR sensors) report their values in amperes.
> +The only known temperature sensor reports its value in degrees Celcius.
> +Applications such as the Enermax OEM application are supposedly expected
> +to calculate watts in software using the information supplied in voltage
> +and amperage sensors (although we currently lack enough sensor readings
> +to calculate all AC input properties).
> +
> +Supposedly, if any sensor reading crosses its hardcoded alarm threshold,
> +the power supply will shut down, presumably powering down the embedded
> +Microchip PIC as well (although we have yet to confirm this empirically).
> +So any software alarms are probably superfluous.
> +
> +It is unknown how often the Microchip PIC will measure internal sensor
> +readings, although the "zero delay" terminology implies that it is near
> +instantaneous (which seems appropriate for overvoltage protection).
> +The driver queries the PIC every time userland reads a sensor token via
> +sysfs but does no internal monitoring of its own.
> +
> +Known Issues
> +------------
> +
> +This driver identifies the Enermax PIC controller application by USB
> +vendor and product ID. Unfortunately, it is specifically advertising a
> +Microchip vendor ID rather than an Enermax vendor ID, and the product ID
> +is an otherwise unknown value. Even worse, it does not advertise any
> +vendor, product, or serial number string through HID enumeration, so I
> +have some concerns about how well Enermax has ensured uniqueness of
> +enumerated IDs. There's also an open question as to whether Enermax has
> +incorporated similar technology in any of their other power supplies, how
> +those other products enumerate via HID, and how the driver will respond
> +upon seeing one of these other devices.
> +
> +Also, since pretty much all my knowledge of this hardware is based on
> +dissecting USB traffic generated by the Enermax OEM application, I'm not
> +entirely certain of some aspects, such as the sensor-reading format. For
> +example, I've never seen either of the two high bits of the exponent unset,
> +or the high bit of the mantissa set--so for all I know, these bits may not
> +mean what I think they mean.
> +
> +Furthermore, although the OEM application is able to determine the apparent
> +power usage (volt-amperes), the actual power usage (watts), and the power
> +factor for the AC inputs, I haven't learned how it determines any of those
> +values.
> +
> +Finally, there is a possibility that upon non-fatal "alarm" conditions,
> +the controller may notify the host via an unexpected HID event. I have no
> +idea what this will look like, and if there's a host-initiated query or
> +command in flight, any such unexpected event may interfere with the expected
> +response. To figure out how to deal with this (or even see what it looks
> +like without vendor documentation), it may be necessary to construct a
> +benchtop load setup specifically to trigger such alarm conditions. This is
> +difficult and somewhat dangerous, for obvious reasons.
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index cc4c664..de01050 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -849,6 +849,22 @@ config HID_XINMO
> standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here
> if you have a Xin-Mo Dual Arcade controller.
>
> +config HID_ZDPMS
> + tristate "Enermax ZDPMS support (EXPERIMENTAL)"
> + depends on HID && HWMON
> + default n
> + ---help---
> + Support for monitoring sensors and software-configurable settings
> + embedded in the Enermax DigiFanless 550W power supply. This driver
> + registers as a hwmon driver to expose these sensors for use by
> + libsensors-based applications.
> +
> + This driver is largely based on dissecting captures of USB traffic
> + generated by the Enermax ZDPMS OEM Windows application. At least
> + two sensors are not supported (yet), and it's uncertain how this
> + driver will behave in the presence of other Enermax products with
> + similar (but not quite identical) functionality.
> +
"If unsure, say 'n'." seems appropriate here.
> config HID_ZEROPLUS
> tristate "Zeroplus based game controller support"
> depends on HID
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index 2f8a41d..dd538dd 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -92,6 +92,7 @@ obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o
> obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o
> obj-$(CONFIG_HID_UCLOGIC) += hid-uclogic.o
> obj-$(CONFIG_HID_XINMO) += hid-xinmo.o
> +obj-$(CONFIG_HID_ZDPMS) += hid-zdpms.o
> obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o
> obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o
>
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index e6fce23..5d4062b 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -1895,6 +1895,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
> { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR) },
> { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) },
> { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_ZDPMS) },
> { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_MOUSE_4500) },
> { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV) },
> { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K) },
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index b3b225b..a9eedea 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -650,6 +650,7 @@
> #define USB_DEVICE_ID_PICOLCD 0xc002
> #define USB_DEVICE_ID_PICOLCD_BOOTLOADER 0xf002
> #define USB_DEVICE_ID_PICK16F1454 0x0042
> +#define USB_DEVICE_ID_ZDPMS 0xf590
>
> #define USB_VENDOR_ID_MICROSOFT 0x045e
> #define USB_DEVICE_ID_SIDEWINDER_GV 0x003b
> diff --git a/drivers/hid/hid-zdpms.c b/drivers/hid/hid-zdpms.c
> new file mode 100644
> index 0000000..905ef6a
> --- /dev/null
> +++ b/drivers/hid/hid-zdpms.c
> @@ -0,0 +1,828 @@
> +/*
> + * Linux HID/hwmon driver for Enermax ZDPMS controller
> + *
> + * Copyright (c) 2015 Frank Schaefer <kelledin@gmail.com>
> + *
> + * The name "Enermax" (and possibly "DigiFanless" and "ZDPMS") are trademark
> + * Enermax Inc (http://www.enermax.com/).
> + */
> +
> +/*
Comment split unnecessary ?
> + * 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.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/hid.h>
> +#include <linux/delay.h>
> +#include <linux/sched.h>
> +#include <linux/wait.h>
> +#include <linux/mutex.h>
> +#include <linux/jiffies.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/atomic.h>
> +#include "hid-ids.h"
> +
> +#define ZDPMS_VERSION "0.1.1"
I would suggest to drop the version number. It tends to be never updated.
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Enermax ZDPMS controller");
> +MODULE_AUTHOR("Frank Schaefer <kelledin@gmail.com>");
> +MODULE_VERSION(ZDPMS_VERSION);
> +
> +static const struct hid_device_id zdpms_devices[] = {
> + { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_ZDPMS) },
> + { }
> +};
> +MODULE_DEVICE_TABLE(hid, zdpms_devices);
> +
> +static int zdpms_timeout = 100;
> +module_param(zdpms_timeout, int, S_IRUGO | S_IWUSR);
> +MODULE_PARM_DESC(zdpms_timeout,
> + "Transaction timeout in milliseconds (minimum 20)");
> +
> +static int zdpms_12v_config;
> +module_param(zdpms_12v_config, int, S_IRUGO);
> +MODULE_PARM_DESC(zdpms_12v_config,
> + "Set 12V rail configuration (1: single rail, 2: dual rail)");
> +
> +enum {
> + /* Raw size for HID messages, excluding the URB header */
> + ZDPMS_MSG_SIZE = 64,
> +
> + /*
> + * No idea what these mean, but they are part of the initialization
> + * sequence used by the Enermax ZDPMS OEM application, with the
> + * single byte value 0x5 passed to each one. I suspect they set
> + * something, I just don't know what.
> + */
> + ZDPMS_INIT1 = 0x25,
> + ZDPMS_INIT2 = 0x24,
> + ZDPMS_INIT3 = 0x23,
Why the odd numbering ?
> +
> + /*
> + * 12V rail configuration setting. Passing byte value 0x1 sets
> + * single-rail mode, while passing byte value 0x2 sets dual-rail
> + * mode. Some PSU models may support more values for more rail
> + * configurations, but this is what we get on our one sample.
> + */
> + ZDPMS_12V_CONFIG = 0x26,
> +
> + /* Sensor values, as near as we can tell. */
> + ZDPMS_SENSOR_MIN = 0x27, /* Minimum sensor value */
> + ZDPMS_AC_V = 0x27, /* AC input voltage level */
> + ZDPMS_DC_12V1 = 0x28, /*
> + * One of the 12V rail voltage levels
> + * (probably 12V1)
> + */
> + ZDPMS_DC_5V = 0x29, /* DC 5V rail voltage level */
> + ZDPMS_DC_3V = 0x2a, /* DC 3.3V rail voltage level */
> + ZDPMS_DC_12V2 = 0x2b, /*
> + * One of the 21V rail voltage levels.
> + * (probably 12V1)
> + */
> + ZDPMS_DC_12V1_CURRENT = 0x2c, /* 12V1 current level in amperes */
> + ZDPMS_DC_5V_CURRENT = 0x2d, /* 5V current level in amperes */
> + ZDPMS_DC_3V_CURRENT = 0x2e, /* 3.3V current level in amperes */
> + ZDPMS_DC_12V2_CURRENT = 0x2f, /* 12V2 current level in amperes */
> + ZDPMS_TEMP = 0x30, /* PSU temperature in degrees Celcius */
> + ZDPMS_SENSOR_MAX = 0x30, /* Maximum sensor value */
> +
> + ZDPMS_MODEL_NUMBER = 0x32, /*
> + * PSU model number string length
> + * (variable length)
> + */
> +
> + /* Message types (a.k.a. HID report IDs) that we've seen. */
> + ZDPMS_SET_CONFIG = 0x82, /* Used for setting configuration. */
> + ZDPMS_READ_SENSOR = 0x83, /* Used for reading values. */
> + ZDPMS_WTF_1 = 0x90, /* No idea at all. Doesn't get a valid
> + * response.
> + */
> + ZDPMS_WTF_2 = 0x91, /* As above, no idea at all. */
> +};
> +
> +static const char * const zdpms_labels[] = {
> + "AC input voltage",
> + "DC 12V1",
> + "DC 5V",
> + "DC 3.3V",
> + "DC 12V2",
> + "DC 12V1 current",
> + "DC 5V current",
> + "DC 3.3V current",
> + "DC 12V2 current",
> + "PSU temp",
> +};
> +
> +struct zdpms_device {
> + /* The hid_device we belong to. */
> + struct hid_device *hdev;
> +
> + /* The hwmon device pointer. */
> + struct device *hwmon_dev;
> +
> + /* The wait queue used to track transaction replies. */
> + wait_queue_head_t io_wait;
> +
> + /*
> + * The atomic condition variable to indicate that a message is
> + * received.
> + */
> + atomic_t msg_received;
> +
> + /*
> + * The last data read by the raw event handler. This is expected to be
> + * a command or query response (up to ZDPMS_MSG_SIZE bytes), and to the
> + * best of my knowledge, only one pending command or query is supported
> + * at any time.
> + */
> + uint8_t data[ZDPMS_MSG_SIZE];
> + unsigned int sz;
> +
> + /*
> + * The ZDPMS model number (i.e. "EDF550AWN" for the Enermax DigiFanless
> + * 550W). We don't expect this to be more than 32 bytes.
> + */
> + char model[32];
> +
> + /*
> + * Transaction lock mutex. This keeps us from launching a second
> + * command/query while one is still pending.
> + */
> + struct mutex io_lock;
> +
> +};
> +
> +/**
> + * Perform a command/response or query/response exchange with an Enermax
> + * ZDPMS controller. The command is launched immediately through the
> + * controller's interrupt endpoint; the response is copied back for the
> + * caller to perform further analysis. A minimum response timeout
> + * (maintained in the zdpms_timeout variable) is implicit to avoid
> + * potential deadlocks. No validation is performed on the response
> + * content.
> + *
> + * Note that commands, queries, and responses are generally zero padded to
> + * ZDPMS_MSG_SIZE bytes. The first byte of any message must be the report
> + * ID (typically either ZDPMS_SET_CONFIG or ZDPMS_READ_SENSOR); the second
> + * byte is supposed to be the length of the message payload, minus the first
> + * two bytes and any zero padding. For successful commands, the ZDPMS
> + * controller appears to copy the report ID to the end of the message; for
> + * unsupported commands, the controller appears to set the length byte to
> + * 0xFF in the response.
> + *
> + * @param hdev
> + * The hid_device to which we're sending the command/query
> + *
> + * @param msg
> + * The command/query to send to the ZDPMS controller, starting with the
> + * report ID and message-length byte
> + *
> + * @param sz
> + * The size of the command/query in bytes, including the report ID and
> + * message-length byte
> + *
> + * @param resp
> + * A user-supplied buffer to store the response. This MUST be at least
> + * ZDPMS_MSG_SIZE bytes in order to avoid potential buffer overflows.
> + *
> + * @return
> + * Returns the number of bytes read on success (usually ZDPMS_MSG_SIZE),
> + * or a negative errno value on failure.
> + */
> +static int zdpms_xchg(struct hid_device *hdev,
> + const uint8_t *data,
> + unsigned int sz,
> + uint8_t *resp)
Continuation line alignment is a bit odd here. Please
use a consistent alignment, and don't unnecessarily split lines.
> +{
> + uint8_t msg[ZDPMS_MSG_SIZE];
> + struct zdpms_device *dev;
> + unsigned long last_jiffy;
> + int ret;
> +
> + dev = hid_get_drvdata(hdev);
> +
> + /* Copy the message and clear any padding. */
> + memcpy(msg, data, sz);
> +
> + if (sz < sizeof(msg))
> + memset(msg + sz, 0, sizeof(msg) - sz);
> +
> + mutex_lock(&(dev->io_lock));
Unnecessary ( ). See checkpatch --strict.
> +
> + /*
> + * Send the command/query. We use hid_hw_output_report() specifically
> + * because it will send the raw message to an interrupt endpoint.
> + * NOTE: No return type (WTF really?). If it fails, we'll supposedly
> + * find out when we time out waiting for the response...
> + */
> + hid_hw_output_report(hdev, msg, sizeof(msg));
> +
> + /*
> + * Clamp the timeout to 20ms. Any smaller and it's dicey to represent
> + * it in jiffies...
> + */
> + if (zdpms_timeout < 20)
> + zdpms_timeout = 20;
> +
> + /*
> + * We track the last allowable jiffy, in order to avoid our timeout
> + * getting mistakenly prolonged by a signal storm or similar event.
> + */
> + last_jiffy = jiffies + msecs_to_jiffies(zdpms_timeout);
> +
> + do {
> + ret = wait_event_interruptible_timeout(
> + dev->io_wait,
> + atomic_read(&(dev->msg_received)),
Unnecessary ( ).
> + last_jiffy - jiffies);
> +
> + if (ret == -ERESTARTSYS)
> + /* Interrupted by a signal. Keep trying... */
> + continue;
> +
> + if (ret < 0)
> + /* Unspecified error. Punt! */
> + goto fail_mutex_locked;
> +
> + if (ret == 0) {
> + /* Timed out! */
> + ret = -ETIMEDOUT;
> + goto fail_mutex_locked;
> + }
> +
> + if (ret > 0)
> + /* Message is received. */
> + break;
> + } while (time_before(jiffies, last_jiffy));
> +
> + /*
> + * Now see about copying the received data to the user-supplied buffer.
> + */
> + memcpy(resp, dev->data, dev->sz);
> + ret = dev->sz;
> + atomic_set(&(dev->msg_received), 0);
Unnecessary (). Around many & in the code, as it looks like.
> +
> +fail_mutex_locked:
> + mutex_unlock(&(dev->io_lock));
> + return ret;
> +}
> +
> +/**
> + * Sets a specific ZDPMS configuration item. Currently-known configuration
> + * commands only take a one-byte configuration value. Response validation
> + * is performed.
> + *
> + * @param hdev
> + * Pointer to the hid_device structure to configure
> + *
> + * @param cmd
> + * Device setting to configure
> + *
> + * @param val
> + * Configuration setting to apply
> + *
> + * @return
> + * Returns the size of the return message on success, or a negative
> + * errno value on failure
> + */
> +static int zdpms_set(struct hid_device *hdev, uint8_t cmd, uint8_t val)
> +{
> + uint8_t msg[8], resp[ZDPMS_MSG_SIZE];
> + int result;
> +
> + msg[0] = ZDPMS_SET_CONFIG;
> + msg[1] = 0x03; /* 3-byte message */
> + msg[2] = 0x40; /* no idea what this is supposed to mean */
> + msg[3] = cmd; /* Command value */
> + msg[4] = val;
> +
> + result = zdpms_xchg(hdev, msg, 5, resp);
> +
> + if (result < 0)
> + return result;
> +
> + if (result < 6)
> + return -EMSGSIZE;
> +
> + if (resp[1] == 0xff)
> + /* Unsupported command? */
> + return -EINVAL;
> +
> + if (resp[0] != msg[0] || resp[1] != 4 || resp[2] != msg[2] ||
> + resp[3] != msg[3] || resp[4] != msg[4] || resp[5] != resp[0])
> + return -EPROTO;
> +
> + return result;
> +}
> +
> +/**
> + * Reads the ASCII model number string from the ZDPMS controller and stores it
> + * in the caller-supplied buffer and appends a NULL terminator if possible. If
> + * the buffer is not large enough to hold the entire string, it will be
> + * truncated to fit (but no NULL terminator will be added).
> + *
> + * @param hdev
> + * The hid_device we're attempting to query
> + *
> + * @param buf
> + * The caller-supplied buffer to store the model number string
> + *
> + * @param sz
> + * The maximum buffer size (in bytes)
> + *
> + * @return
> + * Returns the length of the model-number string in bytes (not counting
> + * the NULL terminator), or a negative errno value on failure. The
> + * returned string length may be larger than the supplied buffer size.
> + *
> + */
> +static int zdpms_read_model(struct hid_device *hdev, char *buf, size_t sz)
> +{
> + uint8_t msg[8], resp[ZDPMS_MSG_SIZE];
> + int result;
> +
> + msg[0] = ZDPMS_READ_SENSOR;
> + msg[1] = 0x2; /* 2-byte message */
> + msg[2] = 0x40; /* nope, still no idea what this is for. */
> + msg[3] = ZDPMS_MODEL_NUMBER;
> + result = zdpms_xchg(hdev, msg, 4, resp);
> +
> + if (result < 0)
> + return result;
> +
> + if (result < 6)
> + return -EMSGSIZE;
> +
> + if (resp[1] == 0xff)
> + return -EINVAL;
> +
> + if (resp[1] + 2 > ZDPMS_MSG_SIZE || resp[0] != msg[0] ||
> + resp[2] != msg[2] || resp[3] != msg[3] ||
> + resp[resp[1]+1] != resp[0])
> + return -EPROTO;
> +
> + result = resp[1] - 3;
> +
> + if (result < sz) {
> + /*
> + * Good news, we can fit the entire model name in the supplied
> + * buffer, complete with a NULL terminator.
> + */
> + memcpy(buf, resp + 4, result);
> + buf[result] = '\0';
You are setting the terminating 0 several times, here and in the calling code.
Also, in the calling code, you dump a warning message if the
result is too large.
Given that,
memcpy(buf, resp + 4, min(result, sz));
would be much simpler.
> + } else
> + /* Whoops, we can only fit in part of it. */
> + memcpy(buf, resp + 4, sz);
> +
checkpatch should complain here. Yes, it does, with --strict,
and --strict points out a few other problems. Please fix.
> + return result;
> +}
> +
> +/**
> + * Query a sensor reading from a ZDPMS controller. This reading is reported
> + * in millivolts, milliamperes, or millidegrees Celcius, in order to conform
> + * with the Linux hwmon subsystem expectations.
> + *
> + * @param hdev
> + * The hid_device we're attempting to query
> + *
> + * @param sensor
> + * The sensor index we're attempting to query
> + *
> + * @return
> + * Returns the sensor value (currently always >=0) on success, or a
> + * negative errno value on failure.
> + *
> + */
> +static int zdpms_read_sensor(struct hid_device *hdev, uint8_t sensor)
> +{
> + uint8_t msg[8], resp[ZDPMS_MSG_SIZE];
> + int result;
> + uint32_t val;
> + unsigned int exponent;
> +
> + msg[0] = ZDPMS_READ_SENSOR;
> + msg[1] = 0x2; /* 2-byte message */
> + msg[2] = 0x40; /* no idea what this is supposed to mean */
> + msg[3] = sensor; /* Sensor value */
> + result = zdpms_xchg(hdev, msg, 4, resp);
> +
> + if (result < 4)
> + return result;
So 0..3 are acceptable return values and indicate a valid sensor value ?
> +
> + if (result < 7)
> + return -EMSGSIZE;
> +
> + if (resp[1] == 0xff)
> + /* Nonexistent sensor? */
> + return -ENODEV;
> +
> + if (resp[0] != msg[0] || resp[1] != 5 || resp[2] != msg[2] ||
> + resp[3] != msg[3] || resp[6] != resp[0])
> + return -EPROTO;
> +
> + /*
> + * The sensor value is encoded as little-endian quasi-IEEE 754, in
> + * bytes 4 and 5.
> + */
> + val = ((resp[5] & 7) << 8) | resp[4];
> + exponent = resp[5] >> 3;
> +
> + /*
> + * All hwmon sensors we support are supposed to be reported in
> + * milliunits (millivolts, millidegrees Celcius, or milliamperes).
> + * Given that the maximum possible value for the mantissa is 2^11
> + * (2048), we'll remain well within 32-bit integer range even after
> + * applying this multiplier.
> + */
> + val *= 1000;
> +
> + /*
> + * Apply the exponent shift *after* applying the units multiplier, so we
> + * preserve as much accuracy as possible.
> + */
> + val >>= (32 - exponent);
> + return val;
> +}
> +
> +/**
> + * Callback for reporting ZDPMS sensor labels via sysfs, in accordance with
> + * standard hwmon conventions.
> + *
> + * @param dev
> + * hwmon device pointer. The corresponding zdpms_device structure is
> + * referenced by the dev_get_drvdata() result and contains a pointer to
> + * the associated hid_device structure of the ZDPMS controller.
> + *
> + * @param attr
> + * sysfs device attribute pointer. The corresponding sensor device
> + * attribute can be deduced from this and supplies the desired ZDPMS
> + * sensor ID.
> + *
> + * @param buf
> + * buffer for storing a string representation of the sensor reading.
> + *
> + * @return
> + * Returns the size of the string representation stored in the buffer,
> + * not including the NULL terminator, or a negative errno value on
> + * failure.
> + *
> + */
> +static ssize_t zdpms_show_label(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct sensor_device_attribute *sens_attr;
> + struct zdpms_device *zdev;
> + struct hid_device *hdev;
> + unsigned int idx;
I am personally not a friend of aligned variable names.
Either case, you use it inconsistently. I would suggest to drop
the alignment in variable declarations. Change the longest one later,
and you end up with a merge mess.
> +
> + sens_attr = to_sensor_dev_attr(attr);
> + zdev = dev_get_drvdata(dev);
> + hdev = zdev->hdev;
> + idx = sens_attr->index;
> +
> + /* We use a straight sequential array as a lookup table. */
> + idx -= ZDPMS_SENSOR_MIN;
> +
> + return sprintf(buf, "%s\n", zdpms_labels[idx]);
> +}
> +
> +/**
> + * Callback for reporting ZDPMS sensor values via sysfs, in accordance with
> + * standard hwmon conventions.
> + *
> + * @param dev
> + * hwmon device pointer. The corresponding zdpms_device structure is
> + * referenced by the dev_get_drvdata() result and contains a pointer to
> + * the associated hid_device structure of the ZDPMS controller.
> + *
> + * @param attr
> + * sysfs device attribute pointer. The corresponding sensor device
> + * attribute can be deduced from this and supplies the desired ZDPMS
> + * sensor ID.
> + *
> + * @param buf
> + * buffer for storing a string representation of the sensor reading.
> + *
> + * @return
> + * Returns the size of the string representation stored in the buffer,
> + * not including the NULL terminator, or a negative errno value on
> + * failure.
> + *
> + */
> +static ssize_t zdpms_show_sensor(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct sensor_device_attribute *sens_attr;
> + struct zdpms_device *zdev;
> + struct hid_device *hdev;
> + unsigned int idx;
> + int val;
> +
> + sens_attr = to_sensor_dev_attr(attr);
> + zdev = dev_get_drvdata(dev);
> + hdev = zdev->hdev;
> + idx = sens_attr->index;
> + val = zdpms_read_sensor(hdev, idx);
> +
> + if (val < 0)
> + return val;
> +
> + return sprintf(buf, "%d\n", val);
> +}
> +
> +/**
> + * Callback for reporting raw HID events from a ZDPMS controller. ZDPMS
> + * controllers generally only send HID events in response to commands or
> + * queries sent to their interrupt endpoints; generally only one such
> + * command or query is expected to be pending at any time.
> + *
> + * @param hdev
> + * Pointer to the HID device (ZDPMS controller) responsible for this
> + * HID event
> + *
> + * @param report
> + * Pointer to the HID report structure for this event. This is
> + * generally ignored, since we prefer to parse raw messages.
> + *
> + * @param data
> + * Pointer to the HID data payload from this event (not including any
> + * headers such as the USB Request Block (URB)
> + *
> + * @param sz
> + * Size of the HID data payload from this event (usually ZDPMS_MSG_SIZE
> + * bytes).
> + *
> + * @return
> + * In accordance with the kernel HID API, returns 0 when the event has
> + * been successfully parsed, or -1 if a parse error occurs.
> + *
> + */
> +static int zdpms_raw_event(struct hid_device *hdev,
> + struct hid_report *report,
> + u8 *data,
> + int sz)
> +{
> + struct zdpms_device *dev;
> +
> + dev = hid_get_drvdata(hdev);
> +
> + /*
> + * We shouldn't receive any messages larger than ZDPMS_MSG_SIZE bytes.
> + * If we do, we need to clip it so it fits in our buffer.
> + */
> + if (sz > ZDPMS_MSG_SIZE) {
> + hid_warn(hdev,
> + "truncated oversize %d-byte event message!\n",
> + sz);
> +
> + sz = ZDPMS_MSG_SIZE;
> + }
> +
> + memcpy(dev->data, data, sz);
> + dev->sz = sz;
> +
> + if (atomic_inc_return(&(dev->msg_received)) > 1)
> + /*
> + * This implies we already had a message pending, and we just
> + * overwrote it. D'oh!
> + */
> + hid_warn(hdev, "overwriting unacknowledged message\n");
> +
> + /* Now wake up anyone waiting on a transaction to complete. */
> + wake_up_interruptible(&(dev->io_wait));
> + return 0;
> +}
> +
> +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, zdpms_show_sensor, NULL,
> + ZDPMS_DC_12V1);
> +static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, zdpms_show_label, NULL,
> + ZDPMS_DC_12V1);
> +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, zdpms_show_sensor, NULL,
> + ZDPMS_DC_5V);
> +static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, zdpms_show_label, NULL,
> + ZDPMS_DC_5V);
> +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, zdpms_show_sensor, NULL,
> + ZDPMS_DC_3V);
> +static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, zdpms_show_label, NULL,
> + ZDPMS_DC_3V);
> +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, zdpms_show_sensor, NULL,
> + ZDPMS_DC_12V2);
> +static SENSOR_DEVICE_ATTR(in4_label, S_IRUGO, zdpms_show_label, NULL,
> + ZDPMS_DC_12V2);
> +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, zdpms_show_sensor, NULL,
> + ZDPMS_AC_V);
> +static SENSOR_DEVICE_ATTR(in5_label, S_IRUGO, zdpms_show_label, NULL,
> + ZDPMS_AC_V);
> +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, zdpms_show_sensor, NULL,
> + ZDPMS_DC_12V1_CURRENT);
> +static SENSOR_DEVICE_ATTR(curr1_label, S_IRUGO, zdpms_show_label, NULL,
> + ZDPMS_DC_12V1_CURRENT);
> +static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO, zdpms_show_sensor, NULL,
> + ZDPMS_DC_5V_CURRENT);
> +static SENSOR_DEVICE_ATTR(curr2_label, S_IRUGO, zdpms_show_label, NULL,
> + ZDPMS_DC_5V_CURRENT);
> +static SENSOR_DEVICE_ATTR(curr3_input, S_IRUGO, zdpms_show_sensor, NULL,
> + ZDPMS_DC_3V_CURRENT);
> +static SENSOR_DEVICE_ATTR(curr3_label, S_IRUGO, zdpms_show_label, NULL,
> + ZDPMS_DC_3V_CURRENT);
> +static SENSOR_DEVICE_ATTR(curr4_input, S_IRUGO, zdpms_show_sensor, NULL,
> + ZDPMS_DC_12V2_CURRENT);
> +static SENSOR_DEVICE_ATTR(curr4_label, S_IRUGO, zdpms_show_label, NULL,
> + ZDPMS_DC_12V2_CURRENT);
> +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, zdpms_show_sensor, NULL,
> + ZDPMS_TEMP);
> +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, zdpms_show_label, NULL,
> + ZDPMS_TEMP);
> +
> +static struct attribute *zdpms_attrs[] = {
> + &sensor_dev_attr_in1_label.dev_attr.attr,
> + &sensor_dev_attr_in1_input.dev_attr.attr,
> + &sensor_dev_attr_in2_label.dev_attr.attr,
> + &sensor_dev_attr_in2_input.dev_attr.attr,
> + &sensor_dev_attr_in3_label.dev_attr.attr,
> + &sensor_dev_attr_in3_input.dev_attr.attr,
> + &sensor_dev_attr_in4_label.dev_attr.attr,
> + &sensor_dev_attr_in4_input.dev_attr.attr,
> + &sensor_dev_attr_in5_label.dev_attr.attr,
> + &sensor_dev_attr_in5_input.dev_attr.attr,
> + &sensor_dev_attr_curr1_label.dev_attr.attr,
> + &sensor_dev_attr_curr1_input.dev_attr.attr,
> + &sensor_dev_attr_curr2_label.dev_attr.attr,
> + &sensor_dev_attr_curr2_input.dev_attr.attr,
> + &sensor_dev_attr_curr3_label.dev_attr.attr,
> + &sensor_dev_attr_curr3_input.dev_attr.attr,
> + &sensor_dev_attr_curr4_label.dev_attr.attr,
> + &sensor_dev_attr_curr4_input.dev_attr.attr,
> + &sensor_dev_attr_temp1_label.dev_attr.attr,
> + &sensor_dev_attr_temp1_input.dev_attr.attr,
> + NULL,
> +};
> +
> +ATTRIBUTE_GROUPS(zdpms);
> +
> +static int zdpms_probe(struct hid_device *hdev, const struct hid_device_id *id)
> +{
> + struct zdpms_device *dev;
> + int ret;
> +
> + ret = hid_parse(hdev);
> +
> + if (ret < 0) {
> + hid_err(hdev, "parse failed\n");
> + return ret;
> + }
> +
> + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
> +
> + if (ret < 0) {
> + hid_err(hdev, "hw start failed\n");
> + return ret;
> + }
> +
> + ret = hid_hw_open(hdev);
> +
> + if (ret < 0) {
> + hid_err(hdev, "hw open failed\n");
> + goto fail_hid_started;
> + }
> +
> + ret = hid_hw_power(hdev, PM_HINT_FULLON);
> +
> + if (ret < 0) {
> + hid_err(hdev, "power management error: %d\n", ret);
> + goto fail_hid_open;
> + }
> +
> + dev = devm_kzalloc(&(hdev->dev),
> + sizeof(struct zdpms_device),
> + GFP_KERNEL);
> +
> + if (dev == NULL) {
> + ret = -ENOMEM;
> + goto fail_hid_power_on;
> + }
> +
> + /* Initialize the private device data structure. */
> + dev->hdev = hdev;
> + init_waitqueue_head(&(dev->io_wait));
> + atomic_set(&(dev->msg_received), 0);
> + mutex_init(&(dev->io_lock));
> + hid_set_drvdata(hdev, dev);
> +
> + /* Allow event processing during probe. */
> + hid_device_io_start(hdev);
> +
> + /* Perform the initialization sequence */
> + ret = zdpms_set(hdev, ZDPMS_INIT1, 0x5);
> +
> + if (ret >= 0) {
> + ret = zdpms_set(hdev, ZDPMS_INIT2, 0x5);
> +
> + if (ret >= 0)
> + ret = zdpms_set(hdev, ZDPMS_INIT3, 0x5);
> + }
> +
> + if (ret < 0) {
> + hid_err(hdev,
> + "Primary initialization sequence FAILED (error %d)\n",
> + ret);
> +
> + goto fail_io_started;
> + }
> +
> + if (zdpms_12v_config > 0 && zdpms_12v_config < 3) {
> + hid_info(hdev, "Setting 12V %s-rail configuration...",
> + zdpms_12v_config == 1 ? "single" : "dual");
> +
> + ret = zdpms_set(hdev, ZDPMS_12V_CONFIG, zdpms_12v_config);
> +
> + if (ret < 0) {
> + hid_err(hdev,
> + "12V rail configuration FAILED (error %d)\n",
> + ret);
> +
> + goto fail_io_started;
> + }
> + }
> +
> + ret = zdpms_read_model(hdev, dev->model, sizeof(dev->model));
> +
> + if (ret < 0) {
> + hid_err(hdev, "Model number query FAILED (error %d)\n", ret);
> + goto fail_io_started;
> + }
> +
> + if (ret >= sizeof(dev->model)) {
> + hid_warn(hdev,
> + "model number truncated to %ld bytes (full size %d bytes)\n",
> + sizeof(dev->model) - 1, ret);
Is this really worth a warning ?
> +
> + dev->model[sizeof(dev->model) - 1] = '\0';
> + } else
> + dev->model[ret] = '\0';
> +
> + hid_info(hdev, "Detected model number: %s\n", dev->model);
> +
> + /* Undo hid_device_io_start(). */
> + hid_device_io_stop(hdev);
> +
> + /* Now register this as an hwmon device. */
> + dev->hwmon_dev = devm_hwmon_device_register_with_groups(&(hdev->dev),
> + "zdpms",
> + dev,
> + zdpms_groups);
> +
> + ret = PTR_ERR_OR_ZERO(dev->hwmon_dev);
> +
> + if (ret < 0) {
> + hid_err(hdev, "hwmon device registration FAILED (error %d)\n",
> + ret);
> +
> + goto fail_data_alloced;
> + }
> +
> + return 0;
> +
> +fail_io_started:
> + hid_device_io_stop(hdev);
> +fail_data_alloced:
> + hid_set_drvdata(hdev, NULL);
Is this necessary ?
> + devm_kfree(&(hdev->dev), dev);
Unnecessary.
> +fail_hid_power_on:
> + hid_hw_power(hdev, PM_HINT_NORMAL);
> +fail_hid_open:
> + hid_hw_close(hdev);
> +fail_hid_started:
> + hid_hw_stop(hdev);
> + return ret;
> +}
> +
> +static void zdpms_remove(struct hid_device *hdev)
> +{
> + struct zdpms_device *dev;
> +
> + dev = hid_get_drvdata(hdev);
> + mutex_destroy(&(dev->io_lock));
> + hid_hw_power(hdev, PM_HINT_NORMAL);
> + hid_hw_close(hdev);
> + hid_hw_stop(hdev);
> +}
> +
> +static struct hid_driver zdpms_driver = {
> + .name = "zdpms",
> + .id_table = zdpms_devices,
> + .probe = zdpms_probe,
> + .remove = zdpms_remove,
> + .raw_event = zdpms_raw_event,
> +};
> +
> +module_hid_driver(zdpms_driver);
> +
> --
> 2.1.4
>
> _______________________________________________
> lm-sensors mailing list
> lm-sensors@lm-sensors.org
> http://lists.lm-sensors.org/mailman/listinfo/lm-sensors
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2015-08-25 15:51 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-08-18 6:38 [PATCH] add Enermax DigiFanless HID/hwmon driver Frank Schaefer
2015-08-25 15:51 ` [lm-sensors] " Guenter Roeck
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).