0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011 #include <linux/delay.h>
0012 #include <linux/hwmon.h>
0013 #include <linux/hwmon-sysfs.h>
0014 #include <linux/i2c.h>
0015 #include <linux/math64.h>
0016 #include <linux/mfd/lochnagar.h>
0017 #include <linux/mfd/lochnagar2_regs.h>
0018 #include <linux/module.h>
0019 #include <linux/of.h>
0020 #include <linux/of_device.h>
0021 #include <linux/platform_device.h>
0022 #include <linux/regmap.h>
0023
0024 #define LN2_MAX_NSAMPLE 1023
0025 #define LN2_SAMPLE_US 1670
0026
0027 #define LN2_CURR_UNITS 1000
0028 #define LN2_VOLT_UNITS 1000
0029 #define LN2_TEMP_UNITS 1000
0030 #define LN2_PWR_UNITS 1000000
0031
0032 static const char * const lochnagar_chan_names[] = {
0033 "DBVDD1",
0034 "1V8 DSP",
0035 "1V8 CDC",
0036 "VDDCORE DSP",
0037 "AVDD 1V8",
0038 "SYSVDD",
0039 "VDDCORE CDC",
0040 "MICVDD",
0041 };
0042
0043 struct lochnagar_hwmon {
0044 struct regmap *regmap;
0045
0046 long power_nsamples[ARRAY_SIZE(lochnagar_chan_names)];
0047
0048
0049 struct mutex sensor_lock;
0050 };
0051
0052 enum lochnagar_measure_mode {
0053 LN2_CURR = 0,
0054 LN2_VOLT,
0055 LN2_TEMP,
0056 };
0057
0058
0059
0060
0061
0062
0063
0064
0065
0066
0067
0068
0069
0070
0071
0072 static long float_to_long(u32 data, u32 precision)
0073 {
0074 u64 man = data & 0x007FFFFF;
0075 int exp = ((data & 0x7F800000) >> 23) - 127 - 23;
0076 bool negative = data & 0x80000000;
0077 long result;
0078
0079 man = (man + (1 << 23)) * precision;
0080
0081 if (fls64(man) + exp > (int)sizeof(long) * 8 - 1)
0082 result = LONG_MAX;
0083 else if (exp < 0)
0084 result = (man + (1ull << (-exp - 1))) >> -exp;
0085 else
0086 result = man << exp;
0087
0088 return negative ? -result : result;
0089 }
0090
0091 static int do_measurement(struct regmap *regmap, int chan,
0092 enum lochnagar_measure_mode mode, int nsamples)
0093 {
0094 unsigned int val;
0095 int ret;
0096
0097 chan = 1 << (chan + LOCHNAGAR2_IMON_MEASURED_CHANNELS_SHIFT);
0098
0099 ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL1,
0100 LOCHNAGAR2_IMON_ENA_MASK | chan | mode);
0101 if (ret < 0)
0102 return ret;
0103
0104 ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL2, nsamples);
0105 if (ret < 0)
0106 return ret;
0107
0108 ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL3,
0109 LOCHNAGAR2_IMON_CONFIGURE_MASK);
0110 if (ret < 0)
0111 return ret;
0112
0113 ret = regmap_read_poll_timeout(regmap, LOCHNAGAR2_IMON_CTRL3, val,
0114 val & LOCHNAGAR2_IMON_DONE_MASK,
0115 1000, 10000);
0116 if (ret < 0)
0117 return ret;
0118
0119 ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL3,
0120 LOCHNAGAR2_IMON_MEASURE_MASK);
0121 if (ret < 0)
0122 return ret;
0123
0124
0125
0126
0127
0128
0129
0130
0131 msleep((nsamples * 3) / 2);
0132
0133 ret = regmap_read_poll_timeout(regmap, LOCHNAGAR2_IMON_CTRL3, val,
0134 val & LOCHNAGAR2_IMON_DONE_MASK,
0135 5000, 200000);
0136 if (ret < 0)
0137 return ret;
0138
0139 return regmap_write(regmap, LOCHNAGAR2_IMON_CTRL3, 0);
0140 }
0141
0142 static int request_data(struct regmap *regmap, int chan, u32 *data)
0143 {
0144 unsigned int val;
0145 int ret;
0146
0147 ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL4,
0148 LOCHNAGAR2_IMON_DATA_REQ_MASK |
0149 chan << LOCHNAGAR2_IMON_CH_SEL_SHIFT);
0150 if (ret < 0)
0151 return ret;
0152
0153 ret = regmap_read_poll_timeout(regmap, LOCHNAGAR2_IMON_CTRL4, val,
0154 val & LOCHNAGAR2_IMON_DATA_RDY_MASK,
0155 1000, 10000);
0156 if (ret < 0)
0157 return ret;
0158
0159 ret = regmap_read(regmap, LOCHNAGAR2_IMON_DATA1, &val);
0160 if (ret < 0)
0161 return ret;
0162
0163 *data = val << 16;
0164
0165 ret = regmap_read(regmap, LOCHNAGAR2_IMON_DATA2, &val);
0166 if (ret < 0)
0167 return ret;
0168
0169 *data |= val;
0170
0171 return regmap_write(regmap, LOCHNAGAR2_IMON_CTRL4, 0);
0172 }
0173
0174 static int read_sensor(struct device *dev, int chan,
0175 enum lochnagar_measure_mode mode, int nsamples,
0176 unsigned int precision, long *val)
0177 {
0178 struct lochnagar_hwmon *priv = dev_get_drvdata(dev);
0179 struct regmap *regmap = priv->regmap;
0180 u32 data;
0181 int ret;
0182
0183 mutex_lock(&priv->sensor_lock);
0184
0185 ret = do_measurement(regmap, chan, mode, nsamples);
0186 if (ret < 0) {
0187 dev_err(dev, "Failed to perform measurement: %d\n", ret);
0188 goto error;
0189 }
0190
0191 ret = request_data(regmap, chan, &data);
0192 if (ret < 0) {
0193 dev_err(dev, "Failed to read measurement: %d\n", ret);
0194 goto error;
0195 }
0196
0197 *val = float_to_long(data, precision);
0198
0199 error:
0200 mutex_unlock(&priv->sensor_lock);
0201
0202 return ret;
0203 }
0204
0205 static int read_power(struct device *dev, int chan, long *val)
0206 {
0207 struct lochnagar_hwmon *priv = dev_get_drvdata(dev);
0208 int nsamples = priv->power_nsamples[chan];
0209 u64 power;
0210 int ret;
0211
0212 if (!strcmp("SYSVDD", lochnagar_chan_names[chan])) {
0213 power = 5 * LN2_PWR_UNITS;
0214 } else {
0215 ret = read_sensor(dev, chan, LN2_VOLT, 1, LN2_PWR_UNITS, val);
0216 if (ret < 0)
0217 return ret;
0218
0219 power = abs(*val);
0220 }
0221
0222 ret = read_sensor(dev, chan, LN2_CURR, nsamples, LN2_PWR_UNITS, val);
0223 if (ret < 0)
0224 return ret;
0225
0226 power *= abs(*val);
0227 power = DIV_ROUND_CLOSEST_ULL(power, LN2_PWR_UNITS);
0228
0229 if (power > LONG_MAX)
0230 *val = LONG_MAX;
0231 else
0232 *val = power;
0233
0234 return 0;
0235 }
0236
0237 static umode_t lochnagar_is_visible(const void *drvdata,
0238 enum hwmon_sensor_types type,
0239 u32 attr, int chan)
0240 {
0241 switch (type) {
0242 case hwmon_in:
0243 if (!strcmp("SYSVDD", lochnagar_chan_names[chan]))
0244 return 0;
0245 break;
0246 case hwmon_power:
0247 if (attr == hwmon_power_average_interval)
0248 return 0644;
0249 break;
0250 default:
0251 break;
0252 }
0253
0254 return 0444;
0255 }
0256
0257 static int lochnagar_read(struct device *dev, enum hwmon_sensor_types type,
0258 u32 attr, int chan, long *val)
0259 {
0260 struct lochnagar_hwmon *priv = dev_get_drvdata(dev);
0261 int interval;
0262
0263 switch (type) {
0264 case hwmon_in:
0265 return read_sensor(dev, chan, LN2_VOLT, 1, LN2_VOLT_UNITS, val);
0266 case hwmon_curr:
0267 return read_sensor(dev, chan, LN2_CURR, 1, LN2_CURR_UNITS, val);
0268 case hwmon_temp:
0269 return read_sensor(dev, chan, LN2_TEMP, 1, LN2_TEMP_UNITS, val);
0270 case hwmon_power:
0271 switch (attr) {
0272 case hwmon_power_average:
0273 return read_power(dev, chan, val);
0274 case hwmon_power_average_interval:
0275 interval = priv->power_nsamples[chan] * LN2_SAMPLE_US;
0276 *val = DIV_ROUND_CLOSEST(interval, 1000);
0277 return 0;
0278 default:
0279 return -EOPNOTSUPP;
0280 }
0281 default:
0282 return -EOPNOTSUPP;
0283 }
0284 }
0285
0286 static int lochnagar_read_string(struct device *dev,
0287 enum hwmon_sensor_types type, u32 attr,
0288 int chan, const char **str)
0289 {
0290 switch (type) {
0291 case hwmon_in:
0292 case hwmon_curr:
0293 case hwmon_power:
0294 *str = lochnagar_chan_names[chan];
0295 return 0;
0296 default:
0297 return -EOPNOTSUPP;
0298 }
0299 }
0300
0301 static int lochnagar_write(struct device *dev, enum hwmon_sensor_types type,
0302 u32 attr, int chan, long val)
0303 {
0304 struct lochnagar_hwmon *priv = dev_get_drvdata(dev);
0305
0306 if (type != hwmon_power || attr != hwmon_power_average_interval)
0307 return -EOPNOTSUPP;
0308
0309 val = clamp_t(long, val, 1, (LN2_MAX_NSAMPLE * LN2_SAMPLE_US) / 1000);
0310 val = DIV_ROUND_CLOSEST(val * 1000, LN2_SAMPLE_US);
0311
0312 priv->power_nsamples[chan] = val;
0313
0314 return 0;
0315 }
0316
0317 static const struct hwmon_ops lochnagar_ops = {
0318 .is_visible = lochnagar_is_visible,
0319 .read = lochnagar_read,
0320 .read_string = lochnagar_read_string,
0321 .write = lochnagar_write,
0322 };
0323
0324 static const struct hwmon_channel_info *lochnagar_info[] = {
0325 HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
0326 HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL,
0327 HWMON_I_INPUT | HWMON_I_LABEL,
0328 HWMON_I_INPUT | HWMON_I_LABEL,
0329 HWMON_I_INPUT | HWMON_I_LABEL,
0330 HWMON_I_INPUT | HWMON_I_LABEL,
0331 HWMON_I_INPUT | HWMON_I_LABEL,
0332 HWMON_I_INPUT | HWMON_I_LABEL,
0333 HWMON_I_INPUT | HWMON_I_LABEL),
0334 HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL,
0335 HWMON_C_INPUT | HWMON_C_LABEL,
0336 HWMON_C_INPUT | HWMON_C_LABEL,
0337 HWMON_C_INPUT | HWMON_C_LABEL,
0338 HWMON_C_INPUT | HWMON_C_LABEL,
0339 HWMON_C_INPUT | HWMON_C_LABEL,
0340 HWMON_C_INPUT | HWMON_C_LABEL,
0341 HWMON_C_INPUT | HWMON_C_LABEL),
0342 HWMON_CHANNEL_INFO(power, HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
0343 HWMON_P_LABEL,
0344 HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
0345 HWMON_P_LABEL,
0346 HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
0347 HWMON_P_LABEL,
0348 HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
0349 HWMON_P_LABEL,
0350 HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
0351 HWMON_P_LABEL,
0352 HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
0353 HWMON_P_LABEL,
0354 HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
0355 HWMON_P_LABEL,
0356 HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
0357 HWMON_P_LABEL),
0358 NULL
0359 };
0360
0361 static const struct hwmon_chip_info lochnagar_chip_info = {
0362 .ops = &lochnagar_ops,
0363 .info = lochnagar_info,
0364 };
0365
0366 static const struct of_device_id lochnagar_of_match[] = {
0367 { .compatible = "cirrus,lochnagar2-hwmon" },
0368 {}
0369 };
0370 MODULE_DEVICE_TABLE(of, lochnagar_of_match);
0371
0372 static int lochnagar_hwmon_probe(struct platform_device *pdev)
0373 {
0374 struct device *dev = &pdev->dev;
0375 struct device *hwmon_dev;
0376 struct lochnagar_hwmon *priv;
0377 int i;
0378
0379 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
0380 if (!priv)
0381 return -ENOMEM;
0382
0383 mutex_init(&priv->sensor_lock);
0384
0385 priv->regmap = dev_get_regmap(dev->parent, NULL);
0386 if (!priv->regmap) {
0387 dev_err(dev, "No register map found\n");
0388 return -EINVAL;
0389 }
0390
0391 for (i = 0; i < ARRAY_SIZE(priv->power_nsamples); i++)
0392 priv->power_nsamples[i] = 96;
0393
0394 hwmon_dev = devm_hwmon_device_register_with_info(dev, "Lochnagar", priv,
0395 &lochnagar_chip_info,
0396 NULL);
0397
0398 return PTR_ERR_OR_ZERO(hwmon_dev);
0399 }
0400
0401 static struct platform_driver lochnagar_hwmon_driver = {
0402 .driver = {
0403 .name = "lochnagar-hwmon",
0404 .of_match_table = lochnagar_of_match,
0405 },
0406 .probe = lochnagar_hwmon_probe,
0407 };
0408 module_platform_driver(lochnagar_hwmon_driver);
0409
0410 MODULE_AUTHOR("Lucas Tanure <tanureal@opensource.cirrus.com>");
0411 MODULE_DESCRIPTION("Lochnagar hardware monitoring features");
0412 MODULE_LICENSE("GPL");