Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  *  power_supply_hwmon.c - power supply hwmon support.
0004  */
0005 
0006 #include <linux/err.h>
0007 #include <linux/hwmon.h>
0008 #include <linux/power_supply.h>
0009 #include <linux/slab.h>
0010 
0011 struct power_supply_hwmon {
0012     struct power_supply *psy;
0013     unsigned long *props;
0014 };
0015 
0016 static const char *const ps_temp_label[] = {
0017     "temp",
0018     "ambient temp",
0019 };
0020 
0021 static int power_supply_hwmon_in_to_property(u32 attr)
0022 {
0023     switch (attr) {
0024     case hwmon_in_average:
0025         return POWER_SUPPLY_PROP_VOLTAGE_AVG;
0026     case hwmon_in_min:
0027         return POWER_SUPPLY_PROP_VOLTAGE_MIN;
0028     case hwmon_in_max:
0029         return POWER_SUPPLY_PROP_VOLTAGE_MAX;
0030     case hwmon_in_input:
0031         return POWER_SUPPLY_PROP_VOLTAGE_NOW;
0032     default:
0033         return -EINVAL;
0034     }
0035 }
0036 
0037 static int power_supply_hwmon_curr_to_property(u32 attr)
0038 {
0039     switch (attr) {
0040     case hwmon_curr_average:
0041         return POWER_SUPPLY_PROP_CURRENT_AVG;
0042     case hwmon_curr_max:
0043         return POWER_SUPPLY_PROP_CURRENT_MAX;
0044     case hwmon_curr_input:
0045         return POWER_SUPPLY_PROP_CURRENT_NOW;
0046     default:
0047         return -EINVAL;
0048     }
0049 }
0050 
0051 static int power_supply_hwmon_temp_to_property(u32 attr, int channel)
0052 {
0053     if (channel) {
0054         switch (attr) {
0055         case hwmon_temp_input:
0056             return POWER_SUPPLY_PROP_TEMP_AMBIENT;
0057         case hwmon_temp_min_alarm:
0058             return POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN;
0059         case hwmon_temp_max_alarm:
0060             return POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX;
0061         default:
0062             break;
0063         }
0064     } else {
0065         switch (attr) {
0066         case hwmon_temp_input:
0067             return POWER_SUPPLY_PROP_TEMP;
0068         case hwmon_temp_max:
0069             return POWER_SUPPLY_PROP_TEMP_MAX;
0070         case hwmon_temp_min:
0071             return POWER_SUPPLY_PROP_TEMP_MIN;
0072         case hwmon_temp_min_alarm:
0073             return POWER_SUPPLY_PROP_TEMP_ALERT_MIN;
0074         case hwmon_temp_max_alarm:
0075             return POWER_SUPPLY_PROP_TEMP_ALERT_MAX;
0076         default:
0077             break;
0078         }
0079     }
0080 
0081     return -EINVAL;
0082 }
0083 
0084 static int
0085 power_supply_hwmon_to_property(enum hwmon_sensor_types type,
0086                    u32 attr, int channel)
0087 {
0088     switch (type) {
0089     case hwmon_in:
0090         return power_supply_hwmon_in_to_property(attr);
0091     case hwmon_curr:
0092         return power_supply_hwmon_curr_to_property(attr);
0093     case hwmon_temp:
0094         return power_supply_hwmon_temp_to_property(attr, channel);
0095     default:
0096         return -EINVAL;
0097     }
0098 }
0099 
0100 static bool power_supply_hwmon_is_a_label(enum hwmon_sensor_types type,
0101                        u32 attr)
0102 {
0103     return type == hwmon_temp && attr == hwmon_temp_label;
0104 }
0105 
0106 struct hwmon_type_attr_list {
0107     const u32 *attrs;
0108     size_t n_attrs;
0109 };
0110 
0111 static const u32 ps_temp_attrs[] = {
0112     hwmon_temp_input,
0113     hwmon_temp_min, hwmon_temp_max,
0114     hwmon_temp_min_alarm, hwmon_temp_max_alarm,
0115 };
0116 
0117 static const struct hwmon_type_attr_list ps_type_attrs[hwmon_max] = {
0118     [hwmon_temp] = { ps_temp_attrs, ARRAY_SIZE(ps_temp_attrs) },
0119 };
0120 
0121 static bool power_supply_hwmon_has_input(
0122     const struct power_supply_hwmon *psyhw,
0123     enum hwmon_sensor_types type, int channel)
0124 {
0125     const struct hwmon_type_attr_list *attr_list = &ps_type_attrs[type];
0126     size_t i;
0127 
0128     for (i = 0; i < attr_list->n_attrs; ++i) {
0129         int prop = power_supply_hwmon_to_property(type,
0130             attr_list->attrs[i], channel);
0131 
0132         if (prop >= 0 && test_bit(prop, psyhw->props))
0133             return true;
0134     }
0135 
0136     return false;
0137 }
0138 
0139 static bool power_supply_hwmon_is_writable(enum hwmon_sensor_types type,
0140                        u32 attr)
0141 {
0142     switch (type) {
0143     case hwmon_in:
0144         return attr == hwmon_in_min ||
0145                attr == hwmon_in_max;
0146     case hwmon_curr:
0147         return attr == hwmon_curr_max;
0148     case hwmon_temp:
0149         return attr == hwmon_temp_max ||
0150                attr == hwmon_temp_min ||
0151                attr == hwmon_temp_min_alarm ||
0152                attr == hwmon_temp_max_alarm;
0153     default:
0154         return false;
0155     }
0156 }
0157 
0158 static umode_t power_supply_hwmon_is_visible(const void *data,
0159                          enum hwmon_sensor_types type,
0160                          u32 attr, int channel)
0161 {
0162     const struct power_supply_hwmon *psyhw = data;
0163     int prop;
0164 
0165     if (power_supply_hwmon_is_a_label(type, attr)) {
0166         if (power_supply_hwmon_has_input(psyhw, type, channel))
0167             return 0444;
0168         else
0169             return 0;
0170     }
0171 
0172     prop = power_supply_hwmon_to_property(type, attr, channel);
0173     if (prop < 0 || !test_bit(prop, psyhw->props))
0174         return 0;
0175 
0176     if (power_supply_property_is_writeable(psyhw->psy, prop) > 0 &&
0177         power_supply_hwmon_is_writable(type, attr))
0178         return 0644;
0179 
0180     return 0444;
0181 }
0182 
0183 static int power_supply_hwmon_read_string(struct device *dev,
0184                       enum hwmon_sensor_types type,
0185                       u32 attr, int channel,
0186                       const char **str)
0187 {
0188     switch (type) {
0189     case hwmon_temp:
0190         *str = ps_temp_label[channel];
0191         break;
0192     default:
0193         /* unreachable, but see:
0194          * gcc bug #51513 [1] and clang bug #978 [2]
0195          *
0196          * [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51513
0197          * [2] https://github.com/ClangBuiltLinux/linux/issues/978
0198          */
0199         break;
0200     }
0201 
0202     return 0;
0203 }
0204 
0205 static int
0206 power_supply_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
0207             u32 attr, int channel, long *val)
0208 {
0209     struct power_supply_hwmon *psyhw = dev_get_drvdata(dev);
0210     struct power_supply *psy = psyhw->psy;
0211     union power_supply_propval pspval;
0212     int ret, prop;
0213 
0214     prop = power_supply_hwmon_to_property(type, attr, channel);
0215     if (prop < 0)
0216         return prop;
0217 
0218     ret  = power_supply_get_property(psy, prop, &pspval);
0219     if (ret)
0220         return ret;
0221 
0222     switch (type) {
0223     /*
0224      * Both voltage and current is reported in units of
0225      * microvolts/microamps, so we need to adjust it to
0226      * milliamps(volts)
0227      */
0228     case hwmon_curr:
0229     case hwmon_in:
0230         pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 1000);
0231         break;
0232     /*
0233      * Temp needs to be converted from 1/10 C to milli-C
0234      */
0235     case hwmon_temp:
0236         if (check_mul_overflow(pspval.intval, 100,
0237                        &pspval.intval))
0238             return -EOVERFLOW;
0239         break;
0240     default:
0241         return -EINVAL;
0242     }
0243 
0244     *val = pspval.intval;
0245 
0246     return 0;
0247 }
0248 
0249 static int
0250 power_supply_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
0251              u32 attr, int channel, long val)
0252 {
0253     struct power_supply_hwmon *psyhw = dev_get_drvdata(dev);
0254     struct power_supply *psy = psyhw->psy;
0255     union power_supply_propval pspval;
0256     int prop;
0257 
0258     prop = power_supply_hwmon_to_property(type, attr, channel);
0259     if (prop < 0)
0260         return prop;
0261 
0262     pspval.intval = val;
0263 
0264     switch (type) {
0265     /*
0266      * Both voltage and current is reported in units of
0267      * microvolts/microamps, so we need to adjust it to
0268      * milliamps(volts)
0269      */
0270     case hwmon_curr:
0271     case hwmon_in:
0272         if (check_mul_overflow(pspval.intval, 1000,
0273                        &pspval.intval))
0274             return -EOVERFLOW;
0275         break;
0276     /*
0277      * Temp needs to be converted from 1/10 C to milli-C
0278      */
0279     case hwmon_temp:
0280         pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 100);
0281         break;
0282     default:
0283         return -EINVAL;
0284     }
0285 
0286     return power_supply_set_property(psy, prop, &pspval);
0287 }
0288 
0289 static const struct hwmon_ops power_supply_hwmon_ops = {
0290     .is_visible = power_supply_hwmon_is_visible,
0291     .read       = power_supply_hwmon_read,
0292     .write      = power_supply_hwmon_write,
0293     .read_string    = power_supply_hwmon_read_string,
0294 };
0295 
0296 static const struct hwmon_channel_info *power_supply_hwmon_info[] = {
0297     HWMON_CHANNEL_INFO(temp,
0298                HWMON_T_LABEL     |
0299                HWMON_T_INPUT     |
0300                HWMON_T_MAX       |
0301                HWMON_T_MIN       |
0302                HWMON_T_MIN_ALARM,
0303 
0304                HWMON_T_LABEL     |
0305                HWMON_T_INPUT     |
0306                HWMON_T_MIN_ALARM |
0307                HWMON_T_MAX_ALARM),
0308 
0309     HWMON_CHANNEL_INFO(curr,
0310                HWMON_C_AVERAGE |
0311                HWMON_C_MAX     |
0312                HWMON_C_INPUT),
0313 
0314     HWMON_CHANNEL_INFO(in,
0315                HWMON_I_AVERAGE |
0316                HWMON_I_MIN     |
0317                HWMON_I_MAX     |
0318                HWMON_I_INPUT),
0319     NULL
0320 };
0321 
0322 static const struct hwmon_chip_info power_supply_hwmon_chip_info = {
0323     .ops = &power_supply_hwmon_ops,
0324     .info = power_supply_hwmon_info,
0325 };
0326 
0327 int power_supply_add_hwmon_sysfs(struct power_supply *psy)
0328 {
0329     const struct power_supply_desc *desc = psy->desc;
0330     struct power_supply_hwmon *psyhw;
0331     struct device *dev = &psy->dev;
0332     struct device *hwmon;
0333     int ret, i;
0334     const char *name;
0335 
0336     if (!devres_open_group(dev, power_supply_add_hwmon_sysfs,
0337                    GFP_KERNEL))
0338         return -ENOMEM;
0339 
0340     psyhw = devm_kzalloc(dev, sizeof(*psyhw), GFP_KERNEL);
0341     if (!psyhw) {
0342         ret = -ENOMEM;
0343         goto error;
0344     }
0345 
0346     psyhw->psy = psy;
0347     psyhw->props = devm_bitmap_zalloc(dev,
0348                       POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1,
0349                       GFP_KERNEL);
0350     if (!psyhw->props) {
0351         ret = -ENOMEM;
0352         goto error;
0353     }
0354 
0355     for (i = 0; i < desc->num_properties; i++) {
0356         const enum power_supply_property prop = desc->properties[i];
0357 
0358         switch (prop) {
0359         case POWER_SUPPLY_PROP_CURRENT_AVG:
0360         case POWER_SUPPLY_PROP_CURRENT_MAX:
0361         case POWER_SUPPLY_PROP_CURRENT_NOW:
0362         case POWER_SUPPLY_PROP_TEMP:
0363         case POWER_SUPPLY_PROP_TEMP_MAX:
0364         case POWER_SUPPLY_PROP_TEMP_MIN:
0365         case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
0366         case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
0367         case POWER_SUPPLY_PROP_TEMP_AMBIENT:
0368         case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN:
0369         case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX:
0370         case POWER_SUPPLY_PROP_VOLTAGE_AVG:
0371         case POWER_SUPPLY_PROP_VOLTAGE_MIN:
0372         case POWER_SUPPLY_PROP_VOLTAGE_MAX:
0373         case POWER_SUPPLY_PROP_VOLTAGE_NOW:
0374             set_bit(prop, psyhw->props);
0375             break;
0376         default:
0377             break;
0378         }
0379     }
0380 
0381     name = psy->desc->name;
0382     if (strchr(name, '-')) {
0383         char *new_name;
0384 
0385         new_name = devm_kstrdup(dev, name, GFP_KERNEL);
0386         if (!new_name) {
0387             ret = -ENOMEM;
0388             goto error;
0389         }
0390         strreplace(new_name, '-', '_');
0391         name = new_name;
0392     }
0393     hwmon = devm_hwmon_device_register_with_info(dev, name,
0394                         psyhw,
0395                         &power_supply_hwmon_chip_info,
0396                         NULL);
0397     ret = PTR_ERR_OR_ZERO(hwmon);
0398     if (ret)
0399         goto error;
0400 
0401     devres_close_group(dev, power_supply_add_hwmon_sysfs);
0402     return 0;
0403 error:
0404     devres_release_group(dev, NULL);
0405     return ret;
0406 }
0407 
0408 void power_supply_remove_hwmon_sysfs(struct power_supply *psy)
0409 {
0410     devres_release_group(&psy->dev, power_supply_add_hwmon_sysfs);
0411 }