Merge tag 'nvme-5.13-2021-05-05' of git://git.infradead.org/nvme into block-5.13
[linux-2.6-microblaze.git] / drivers / hwmon / corsair-psu.c
index 9949405..3a5807e 100644 (file)
 #define CMD_TIMEOUT_MS         250
 #define SECONDS_PER_HOUR       (60 * 60)
 #define SECONDS_PER_DAY                (SECONDS_PER_HOUR * 24)
+#define RAIL_COUNT             3 /* 3v3 + 5v + 12v */
+#define TEMP_COUNT             2
 
 #define PSU_CMD_SELECT_RAIL    0x00 /* expects length 2 */
-#define PSU_CMD_IN_VOLTS       0x88 /* the rest of the commands expect length 3 */
+#define PSU_CMD_RAIL_VOLTS_HCRIT 0x40 /* the rest of the commands expect length 3 */
+#define PSU_CMD_RAIL_VOLTS_LCRIT 0x44
+#define PSU_CMD_RAIL_AMPS_HCRIT        0x46
+#define PSU_CMD_TEMP_HCRIT     0x4F
+#define PSU_CMD_IN_VOLTS       0x88
 #define PSU_CMD_IN_AMPS                0x89
-#define PSU_CMD_RAIL_OUT_VOLTS 0x8B
+#define PSU_CMD_RAIL_VOLTS     0x8B
 #define PSU_CMD_RAIL_AMPS      0x8C
 #define PSU_CMD_TEMP0          0x8D
 #define PSU_CMD_TEMP1          0x8E
@@ -116,30 +122,25 @@ struct corsairpsu_data {
        u8 *cmd_buffer;
        char vendor[REPLY_SIZE];
        char product[REPLY_SIZE];
+       long temp_crit[TEMP_COUNT];
+       long in_crit[RAIL_COUNT];
+       long in_lcrit[RAIL_COUNT];
+       long curr_crit[RAIL_COUNT];
+       u8 temp_crit_support;
+       u8 in_crit_support;
+       u8 in_lcrit_support;
+       u8 curr_crit_support;
+       bool in_curr_cmd_support; /* not all commands are supported on every PSU */
 };
 
 /* some values are SMBus LINEAR11 data which need a conversion */
-static int corsairpsu_linear11_to_int(const int val)
+static int corsairpsu_linear11_to_int(const u16 val, const int scale)
 {
-       int exp = (val & 0xFFFF) >> 0x0B;
-       int mant = val & 0x7FF;
-       int i;
-
-       if (exp > 0x0F)
-               exp -= 0x20;
-       if (mant > 0x3FF)
-               mant -= 0x800;
-       if ((mant & 0x01) == 1)
-               ++mant;
-       if (exp < 0) {
-               for (i = 0; i < -exp; ++i)
-                       mant /= 2;
-       } else {
-               for (i = 0; i < exp; ++i)
-                       mant *= 2;
-       }
+       const int exp = ((s16)val) >> 11;
+       const int mant = (((s16)(val & 0x7ff)) << 5) >> 5;
+       const int result = mant * scale;
 
-       return mant;
+       return (exp >= 0) ? (result << exp) : (result >> -exp);
 }
 
 static int corsairpsu_usb_cmd(struct corsairpsu_data *priv, u8 p0, u8 p1, u8 p2, void *data)
@@ -207,7 +208,10 @@ static int corsairpsu_request(struct corsairpsu_data *priv, u8 cmd, u8 rail, voi
 
        mutex_lock(&priv->lock);
        switch (cmd) {
-       case PSU_CMD_RAIL_OUT_VOLTS:
+       case PSU_CMD_RAIL_VOLTS_HCRIT:
+       case PSU_CMD_RAIL_VOLTS_LCRIT:
+       case PSU_CMD_RAIL_AMPS_HCRIT:
+       case PSU_CMD_RAIL_VOLTS:
        case PSU_CMD_RAIL_AMPS:
        case PSU_CMD_RAIL_WATTS:
                ret = corsairpsu_usb_cmd(priv, 2, PSU_CMD_SELECT_RAIL, rail, NULL);
@@ -243,20 +247,24 @@ static int corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8 rail, l
         */
        tmp = ((long)data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0];
        switch (cmd) {
+       case PSU_CMD_RAIL_VOLTS_HCRIT:
+       case PSU_CMD_RAIL_VOLTS_LCRIT:
+       case PSU_CMD_RAIL_AMPS_HCRIT:
+       case PSU_CMD_TEMP_HCRIT:
        case PSU_CMD_IN_VOLTS:
        case PSU_CMD_IN_AMPS:
-       case PSU_CMD_RAIL_OUT_VOLTS:
+       case PSU_CMD_RAIL_VOLTS:
        case PSU_CMD_RAIL_AMPS:
        case PSU_CMD_TEMP0:
        case PSU_CMD_TEMP1:
-               *val = corsairpsu_linear11_to_int(tmp & 0xFFFF) * 1000;
+               *val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1000);
                break;
        case PSU_CMD_FAN:
-               *val = corsairpsu_linear11_to_int(tmp & 0xFFFF);
+               *val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1);
                break;
        case PSU_CMD_RAIL_WATTS:
        case PSU_CMD_TOTAL_WATTS:
-               *val = corsairpsu_linear11_to_int(tmp & 0xFFFF) * 1000000;
+               *val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1000000);
                break;
        case PSU_CMD_TOTAL_UPTIME:
        case PSU_CMD_UPTIME:
@@ -270,75 +278,265 @@ static int corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8 rail, l
        return ret;
 }
 
-static umode_t corsairpsu_hwmon_ops_is_visible(const void *data, enum hwmon_sensor_types type,
-                                              u32 attr, int channel)
+static void corsairpsu_get_criticals(struct corsairpsu_data *priv)
 {
-       if (type == hwmon_temp && (attr == hwmon_temp_input || attr == hwmon_temp_label))
-               return 0444;
-       else if (type == hwmon_fan && (attr == hwmon_fan_input || attr == hwmon_fan_label))
-               return 0444;
-       else if (type == hwmon_power && (attr == hwmon_power_input || attr == hwmon_power_label))
-               return 0444;
-       else if (type == hwmon_in && (attr == hwmon_in_input || attr == hwmon_in_label))
+       long tmp;
+       int rail;
+
+       for (rail = 0; rail < TEMP_COUNT; ++rail) {
+               if (!corsairpsu_get_value(priv, PSU_CMD_TEMP_HCRIT, rail, &tmp)) {
+                       priv->temp_crit_support |= BIT(rail);
+                       priv->temp_crit[rail] = tmp;
+               }
+       }
+
+       for (rail = 0; rail < RAIL_COUNT; ++rail) {
+               if (!corsairpsu_get_value(priv, PSU_CMD_RAIL_VOLTS_HCRIT, rail, &tmp)) {
+                       priv->in_crit_support |= BIT(rail);
+                       priv->in_crit[rail] = tmp;
+               }
+
+               if (!corsairpsu_get_value(priv, PSU_CMD_RAIL_VOLTS_LCRIT, rail, &tmp)) {
+                       priv->in_lcrit_support |= BIT(rail);
+                       priv->in_lcrit[rail] = tmp;
+               }
+
+               if (!corsairpsu_get_value(priv, PSU_CMD_RAIL_AMPS_HCRIT, rail, &tmp)) {
+                       priv->curr_crit_support |= BIT(rail);
+                       priv->curr_crit[rail] = tmp;
+               }
+       }
+}
+
+static void corsairpsu_check_cmd_support(struct corsairpsu_data *priv)
+{
+       long tmp;
+
+       priv->in_curr_cmd_support = !corsairpsu_get_value(priv, PSU_CMD_IN_AMPS, 0, &tmp);
+}
+
+static umode_t corsairpsu_hwmon_temp_is_visible(const struct corsairpsu_data *priv, u32 attr,
+                                               int channel)
+{
+       umode_t res = 0444;
+
+       switch (attr) {
+       case hwmon_temp_input:
+       case hwmon_temp_label:
+       case hwmon_temp_crit:
+               if (channel > 0 && !(priv->temp_crit_support & BIT(channel - 1)))
+                       res = 0;
+               break;
+       default:
+               break;
+       }
+
+       return res;
+}
+
+static umode_t corsairpsu_hwmon_fan_is_visible(const struct corsairpsu_data *priv, u32 attr,
+                                              int channel)
+{
+       switch (attr) {
+       case hwmon_fan_input:
+       case hwmon_fan_label:
                return 0444;
-       else if (type == hwmon_curr && (attr == hwmon_curr_input || attr == hwmon_curr_label))
+       default:
+               return 0;
+       }
+}
+
+static umode_t corsairpsu_hwmon_power_is_visible(const struct corsairpsu_data *priv, u32 attr,
+                                                int channel)
+{
+       switch (attr) {
+       case hwmon_power_input:
+       case hwmon_power_label:
                return 0444;
+       default:
+               return 0;
+       };
+}
 
-       return 0;
+static umode_t corsairpsu_hwmon_in_is_visible(const struct corsairpsu_data *priv, u32 attr,
+                                             int channel)
+{
+       umode_t res = 0444;
+
+       switch (attr) {
+       case hwmon_in_input:
+       case hwmon_in_label:
+       case hwmon_in_crit:
+               if (channel > 0 && !(priv->in_crit_support & BIT(channel - 1)))
+                       res = 0;
+               break;
+       case hwmon_in_lcrit:
+               if (channel > 0 && !(priv->in_lcrit_support & BIT(channel - 1)))
+                       res = 0;
+               break;
+       default:
+               break;
+       };
+
+       return res;
 }
 
-static int corsairpsu_hwmon_ops_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
-                                    int channel, long *val)
+static umode_t corsairpsu_hwmon_curr_is_visible(const struct corsairpsu_data *priv, u32 attr,
+                                               int channel)
 {
-       struct corsairpsu_data *priv = dev_get_drvdata(dev);
-       int ret;
+       umode_t res = 0444;
+
+       switch (attr) {
+       case hwmon_curr_input:
+               if (channel == 0 && !priv->in_curr_cmd_support)
+                       res = 0;
+               break;
+       case hwmon_curr_label:
+       case hwmon_curr_crit:
+               if (channel > 0 && !(priv->curr_crit_support & BIT(channel - 1)))
+                       res = 0;
+               break;
+       default:
+               break;
+       }
+
+       return res;
+}
+
+static umode_t corsairpsu_hwmon_ops_is_visible(const void *data, enum hwmon_sensor_types type,
+                                              u32 attr, int channel)
+{
+       const struct corsairpsu_data *priv = data;
+
+       switch (type) {
+       case hwmon_temp:
+               return corsairpsu_hwmon_temp_is_visible(priv, attr, channel);
+       case hwmon_fan:
+               return corsairpsu_hwmon_fan_is_visible(priv, attr, channel);
+       case hwmon_power:
+               return corsairpsu_hwmon_power_is_visible(priv, attr, channel);
+       case hwmon_in:
+               return corsairpsu_hwmon_in_is_visible(priv, attr, channel);
+       case hwmon_curr:
+               return corsairpsu_hwmon_curr_is_visible(priv, attr, channel);
+       default:
+               return 0;
+       }
+}
+
+static int corsairpsu_hwmon_temp_read(struct corsairpsu_data *priv, u32 attr, int channel,
+                                     long *val)
+{
+       int err = -EOPNOTSUPP;
+
+       switch (attr) {
+       case hwmon_temp_input:
+               return corsairpsu_get_value(priv, channel ? PSU_CMD_TEMP1 : PSU_CMD_TEMP0,
+                                           channel, val);
+       case hwmon_temp_crit:
+               *val = priv->temp_crit[channel];
+               err = 0;
+               break;
+       default:
+               break;
+       }
 
-       if (type == hwmon_temp && attr == hwmon_temp_input && channel < 2) {
-               ret = corsairpsu_get_value(priv, channel ? PSU_CMD_TEMP1 : PSU_CMD_TEMP0, channel,
-                                          val);
-       } else if (type == hwmon_fan && attr == hwmon_fan_input) {
-               ret = corsairpsu_get_value(priv, PSU_CMD_FAN, 0, val);
-       } else if (type == hwmon_power && attr == hwmon_power_input) {
+       return err;
+}
+
+static int corsairpsu_hwmon_power_read(struct corsairpsu_data *priv, u32 attr, int channel,
+                                      long *val)
+{
+       if (attr == hwmon_power_input) {
                switch (channel) {
                case 0:
-                       ret = corsairpsu_get_value(priv, PSU_CMD_TOTAL_WATTS, 0, val);
-                       break;
+                       return corsairpsu_get_value(priv, PSU_CMD_TOTAL_WATTS, 0, val);
                case 1 ... 3:
-                       ret = corsairpsu_get_value(priv, PSU_CMD_RAIL_WATTS, channel - 1, val);
-                       break;
+                       return corsairpsu_get_value(priv, PSU_CMD_RAIL_WATTS, channel - 1, val);
                default:
-                       return -EOPNOTSUPP;
+                       break;
                }
-       } else if (type == hwmon_in && attr == hwmon_in_input) {
+       }
+
+       return -EOPNOTSUPP;
+}
+
+static int corsairpsu_hwmon_in_read(struct corsairpsu_data *priv, u32 attr, int channel, long *val)
+{
+       int err = -EOPNOTSUPP;
+
+       switch (attr) {
+       case hwmon_in_input:
                switch (channel) {
                case 0:
-                       ret = corsairpsu_get_value(priv, PSU_CMD_IN_VOLTS, 0, val);
-                       break;
+                       return corsairpsu_get_value(priv, PSU_CMD_IN_VOLTS, 0, val);
                case 1 ... 3:
-                       ret = corsairpsu_get_value(priv, PSU_CMD_RAIL_OUT_VOLTS, channel - 1, val);
-                       break;
+                       return corsairpsu_get_value(priv, PSU_CMD_RAIL_VOLTS, channel - 1, val);
                default:
-                       return -EOPNOTSUPP;
+                       break;
                }
-       } else if (type == hwmon_curr && attr == hwmon_curr_input) {
+               break;
+       case hwmon_in_crit:
+               *val = priv->in_crit[channel - 1];
+               err = 0;
+               break;
+       case hwmon_in_lcrit:
+               *val = priv->in_lcrit[channel - 1];
+               err = 0;
+               break;
+       }
+
+       return err;
+}
+
+static int corsairpsu_hwmon_curr_read(struct corsairpsu_data *priv, u32 attr, int channel,
+                                     long *val)
+{
+       int err = -EOPNOTSUPP;
+
+       switch (attr) {
+       case hwmon_curr_input:
                switch (channel) {
                case 0:
-                       ret = corsairpsu_get_value(priv, PSU_CMD_IN_AMPS, 0, val);
-                       break;
+                       return corsairpsu_get_value(priv, PSU_CMD_IN_AMPS, 0, val);
                case 1 ... 3:
-                       ret = corsairpsu_get_value(priv, PSU_CMD_RAIL_AMPS, channel - 1, val);
-                       break;
+                       return corsairpsu_get_value(priv, PSU_CMD_RAIL_AMPS, channel - 1, val);
                default:
-                       return -EOPNOTSUPP;
+                       break;
                }
-       } else {
-               return -EOPNOTSUPP;
+               break;
+       case hwmon_curr_crit:
+               *val = priv->curr_crit[channel - 1];
+               err = 0;
+               break;
+       default:
+               break;
        }
 
-       if (ret < 0)
-               return ret;
+       return err;
+}
 
-       return 0;
+static int corsairpsu_hwmon_ops_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+                                    int channel, long *val)
+{
+       struct corsairpsu_data *priv = dev_get_drvdata(dev);
+
+       switch (type) {
+       case hwmon_temp:
+               return corsairpsu_hwmon_temp_read(priv, attr, channel, val);
+       case hwmon_fan:
+               if (attr == hwmon_fan_input)
+                       return corsairpsu_get_value(priv, PSU_CMD_FAN, 0, val);
+               return -EOPNOTSUPP;
+       case hwmon_power:
+               return corsairpsu_hwmon_power_read(priv, attr, channel, val);
+       case hwmon_in:
+               return corsairpsu_hwmon_in_read(priv, attr, channel, val);
+       case hwmon_curr:
+               return corsairpsu_hwmon_curr_read(priv, attr, channel, val);
+       default:
+               return -EOPNOTSUPP;
+       }
 }
 
 static int corsairpsu_hwmon_ops_read_string(struct device *dev, enum hwmon_sensor_types type,
@@ -374,8 +572,8 @@ static const struct hwmon_channel_info *corsairpsu_info[] = {
        HWMON_CHANNEL_INFO(chip,
                           HWMON_C_REGISTER_TZ),
        HWMON_CHANNEL_INFO(temp,
-                          HWMON_T_INPUT | HWMON_T_LABEL,
-                          HWMON_T_INPUT | HWMON_T_LABEL),
+                          HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_CRIT,
+                          HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_CRIT),
        HWMON_CHANNEL_INFO(fan,
                           HWMON_F_INPUT | HWMON_F_LABEL),
        HWMON_CHANNEL_INFO(power,
@@ -385,14 +583,14 @@ static const struct hwmon_channel_info *corsairpsu_info[] = {
                           HWMON_P_INPUT | HWMON_P_LABEL),
        HWMON_CHANNEL_INFO(in,
                           HWMON_I_INPUT | HWMON_I_LABEL,
-                          HWMON_I_INPUT | HWMON_I_LABEL,
-                          HWMON_I_INPUT | HWMON_I_LABEL,
-                          HWMON_I_INPUT | HWMON_I_LABEL),
+                          HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_LCRIT | HWMON_I_CRIT,
+                          HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_LCRIT | HWMON_I_CRIT,
+                          HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_LCRIT | HWMON_I_CRIT),
        HWMON_CHANNEL_INFO(curr,
                           HWMON_C_INPUT | HWMON_C_LABEL,
-                          HWMON_C_INPUT | HWMON_C_LABEL,
-                          HWMON_C_INPUT | HWMON_C_LABEL,
-                          HWMON_C_INPUT | HWMON_C_LABEL),
+                          HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_CRIT,
+                          HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_CRIT,
+                          HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_CRIT),
        NULL
 };
 
@@ -527,6 +725,9 @@ static int corsairpsu_probe(struct hid_device *hdev, const struct hid_device_id
                goto fail_and_stop;
        }
 
+       corsairpsu_get_criticals(priv);
+       corsairpsu_check_cmd_support(priv);
+
        priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsairpsu", priv,
                                                          &corsairpsu_chip_info, 0);