Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Ibrahim Tilki | 2596 | 100.00% | 1 | 100.00% |
Total | 2596 | 1 |
// SPDX-License-Identifier: GPL-2.0-only #include <linux/bitfield.h> #include <linux/bits.h> #include <linux/err.h> #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> #include <linux/i2c.h> #include <linux/regmap.h> #include <linux/util_macros.h> #define REG_CR1 0x00 #define CR1_HYST BIT(5) #define CR1_DRV GENMASK(4, 3) #define CR1_TEMP_SRC GENMASK(1, 0) #define REG_CR2 0x01 #define CR2_STBY BIT(7) #define CR2_ALERTS BIT(6) #define CR2_DFC BIT(0) #define REG_CR3 0x02 #define REG_PWMR 0x50 #define REG_PWMV 0x51 #define REG_STATUS 0x5A #define STATUS_ALARM_CRIT(ch) BIT(2 + 2 * (ch)) #define STATUS_ALARM_MAX(ch) BIT(3 + 2 * (ch)) #define STATUS_RDFA BIT(6) #define REG_TACH(ch) (0x52 + (ch) * 2) #define REG_TEMP_INPUT(ch) (0x56 + (ch) * 2) #define REG_TEMP_MAX(ch) (0x06 + (ch) * 2) #define REG_TEMP_CRIT(ch) (0x0A + (ch) * 2) #define TEMP11_FROM_REG(reg) ((reg) / 32 * 125) #define TEMP11_TO_REG(val) (DIV_ROUND_CLOSEST(clamp_val((val), -128000, \ 127875), 125) * 32) #define LUT_SIZE 48 #define REG_LUT(index) (0x20 + (index)) struct max31760_state { struct regmap *regmap; struct lut_attribute { char name[24]; struct sensor_device_attribute sda; } lut[LUT_SIZE]; struct attribute *attrs[LUT_SIZE + 2]; struct attribute_group group; const struct attribute_group *groups[2]; }; static bool max31760_volatile_reg(struct device *dev, unsigned int reg) { return reg > 0x50; } static const struct regmap_config regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = 0x5B, .cache_type = REGCACHE_RBTREE, .volatile_reg = max31760_volatile_reg, }; static const int max31760_pwm_freq[] = {33, 150, 1500, 25000}; static int tach_to_rpm(u16 tach) { if (tach == 0) tach = 1; return 60 * 100000 / tach / 2; } static int max31760_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { struct max31760_state *state = dev_get_drvdata(dev); unsigned int regval; unsigned int reg_temp; s16 temp; u8 reg[2]; int ret; switch (type) { case hwmon_temp: switch (attr) { case hwmon_temp_fault: ret = regmap_read(state->regmap, REG_STATUS, ®val); if (ret) return ret; *val = FIELD_GET(STATUS_RDFA, regval); return 0; case hwmon_temp_max_alarm: ret = regmap_read(state->regmap, REG_STATUS, ®val); if (ret) return ret; if (channel) *val = FIELD_GET(STATUS_ALARM_MAX(1), regval); else *val = FIELD_GET(STATUS_ALARM_MAX(0), regval); return 0; case hwmon_temp_crit_alarm: ret = regmap_read(state->regmap, REG_STATUS, ®val); if (ret) return ret; if (channel) *val = FIELD_GET(STATUS_ALARM_CRIT(1), regval); else *val = FIELD_GET(STATUS_ALARM_CRIT(0), regval); return 0; case hwmon_temp_input: reg_temp = REG_TEMP_INPUT(channel); break; case hwmon_temp_max: reg_temp = REG_TEMP_MAX(channel); break; case hwmon_temp_crit: reg_temp = REG_TEMP_CRIT(channel); break; default: return -EOPNOTSUPP; } ret = regmap_bulk_read(state->regmap, reg_temp, reg, 2); if (ret) return ret; temp = (reg[0] << 8) | reg[1]; *val = TEMP11_FROM_REG(temp); return 0; case hwmon_fan: switch (attr) { case hwmon_fan_input: ret = regmap_bulk_read(state->regmap, REG_TACH(channel), reg, 2); if (ret) return ret; *val = tach_to_rpm(reg[0] * 256 + reg[1]); return 0; case hwmon_fan_fault: ret = regmap_read(state->regmap, REG_STATUS, ®val); if (ret) return ret; if (channel) *val = FIELD_GET(BIT(1), regval); else *val = FIELD_GET(BIT(0), regval); return 0; case hwmon_fan_enable: ret = regmap_read(state->regmap, REG_CR3, ®val); if (ret) return ret; if (channel) *val = FIELD_GET(BIT(1), regval); else *val = FIELD_GET(BIT(0), regval); return 0; default: return -EOPNOTSUPP; } case hwmon_pwm: switch (attr) { case hwmon_pwm_input: ret = regmap_read(state->regmap, REG_PWMV, ®val); if (ret) return ret; *val = regval; return 0; case hwmon_pwm_freq: ret = regmap_read(state->regmap, REG_CR1, ®val); if (ret) return ret; regval = FIELD_GET(CR1_DRV, regval); if (regval >= ARRAY_SIZE(max31760_pwm_freq)) return -EINVAL; *val = max31760_pwm_freq[regval]; return 0; case hwmon_pwm_enable: ret = regmap_read(state->regmap, REG_CR2, ®val); if (ret) return ret; *val = 2 - FIELD_GET(CR2_DFC, regval); return 0; case hwmon_pwm_auto_channels_temp: ret = regmap_read(state->regmap, REG_CR1, ®val); if (ret) return ret; switch (FIELD_GET(CR1_TEMP_SRC, regval)) { case 0: *val = 2; break; case 1: *val = 1; break; case 2: case 3: *val = 3; break; default: return -EINVAL; } return 0; default: return -EOPNOTSUPP; } default: return -EOPNOTSUPP; } } static int max31760_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { struct max31760_state *state = dev_get_drvdata(dev); unsigned int pwm_index; unsigned int reg_temp; int temp; u8 reg_val[2]; switch (type) { case hwmon_temp: switch (attr) { case hwmon_temp_max: reg_temp = REG_TEMP_MAX(channel); break; case hwmon_temp_crit: reg_temp = REG_TEMP_CRIT(channel); break; default: return -EOPNOTSUPP; } temp = TEMP11_TO_REG(val); reg_val[0] = temp >> 8; reg_val[1] = temp & 0xFF; return regmap_bulk_write(state->regmap, reg_temp, reg_val, 2); case hwmon_fan: switch (attr) { case hwmon_fan_enable: if (val == 0) return regmap_clear_bits(state->regmap, REG_CR3, BIT(channel)); if (val == 1) return regmap_set_bits(state->regmap, REG_CR3, BIT(channel)); return -EINVAL; default: return -EOPNOTSUPP; } case hwmon_pwm: switch (attr) { case hwmon_pwm_input: if (val < 0 || val > 255) return -EINVAL; return regmap_write(state->regmap, REG_PWMR, val); case hwmon_pwm_enable: if (val == 1) return regmap_set_bits(state->regmap, REG_CR2, CR2_DFC); if (val == 2) return regmap_clear_bits(state->regmap, REG_CR2, CR2_DFC); return -EINVAL; case hwmon_pwm_freq: pwm_index = find_closest(val, max31760_pwm_freq, ARRAY_SIZE(max31760_pwm_freq)); return regmap_update_bits(state->regmap, REG_CR1, CR1_DRV, FIELD_PREP(CR1_DRV, pwm_index)); case hwmon_pwm_auto_channels_temp: switch (val) { case 1: break; case 2: val = 0; break; case 3: val = 2; break; default: return -EINVAL; } return regmap_update_bits(state->regmap, REG_CR1, CR1_TEMP_SRC, val); default: return -EOPNOTSUPP; } default: return -EOPNOTSUPP; } } static const struct hwmon_channel_info *max31760_info[] = { HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_ENABLE, HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_ENABLE), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_LABEL), HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_INPUT | HWMON_PWM_AUTO_CHANNELS_TEMP), NULL }; static umode_t max31760_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { switch (type) { case hwmon_temp: switch (attr) { case hwmon_temp_input: case hwmon_temp_max_alarm: case hwmon_temp_crit_alarm: case hwmon_temp_fault: case hwmon_temp_label: return 0444; case hwmon_temp_max: case hwmon_temp_crit: return 0644; default: return 0; } case hwmon_fan: switch (attr) { case hwmon_fan_input: case hwmon_fan_fault: return 0444; case hwmon_fan_enable: return 0644; default: return 0; } case hwmon_pwm: switch (attr) { case hwmon_pwm_enable: case hwmon_pwm_input: case hwmon_pwm_freq: case hwmon_pwm_auto_channels_temp: return 0644; default: return 0; } default: return 0; } } static int max31760_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, const char **str) { switch (type) { case hwmon_temp: if (attr != hwmon_temp_label) return -EOPNOTSUPP; *str = channel ? "local" : "remote"; return 0; default: return -EOPNOTSUPP; } } static const struct hwmon_ops max31760_hwmon_ops = { .is_visible = max31760_is_visible, .read = max31760_read, .write = max31760_write, .read_string = max31760_read_string }; static const struct hwmon_chip_info max31760_chip_info = { .ops = &max31760_hwmon_ops, .info = max31760_info, }; static ssize_t lut_show(struct device *dev, struct device_attribute *devattr, char *buf) { struct sensor_device_attribute *sda = to_sensor_dev_attr(devattr); struct max31760_state *state = dev_get_drvdata(dev); int ret; unsigned int regval; ret = regmap_read(state->regmap, REG_LUT(sda->index), ®val); if (ret) return ret; return sysfs_emit(buf, "%d\n", regval); } static ssize_t lut_store(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct sensor_device_attribute *sda = to_sensor_dev_attr(devattr); struct max31760_state *state = dev_get_drvdata(dev); int ret; u8 pwm; ret = kstrtou8(buf, 10, &pwm); if (ret) return ret; ret = regmap_write(state->regmap, REG_LUT(sda->index), pwm); if (ret) return ret; return count; } static ssize_t pwm1_auto_point_temp_hyst_show(struct device *dev, struct device_attribute *attr, char *buf) { struct max31760_state *state = dev_get_drvdata(dev); unsigned int regval; int ret; ret = regmap_read(state->regmap, REG_CR1, ®val); if (ret) return ret; return sysfs_emit(buf, "%d\n", (1 + (int)FIELD_GET(CR1_HYST, regval)) * 2000); } static ssize_t pwm1_auto_point_temp_hyst_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct max31760_state *state = dev_get_drvdata(dev); unsigned int hyst; int ret; ret = kstrtou32(buf, 10, &hyst); if (ret) return ret; if (hyst < 3000) ret = regmap_clear_bits(state->regmap, REG_CR1, CR1_HYST); else ret = regmap_set_bits(state->regmap, REG_CR1, CR1_HYST); if (ret) return ret; return count; } static DEVICE_ATTR_RW(pwm1_auto_point_temp_hyst); static void max31760_create_lut_nodes(struct max31760_state *state) { int i; struct sensor_device_attribute *sda; struct lut_attribute *lut; for (i = 0; i < LUT_SIZE; ++i) { lut = &state->lut[i]; sda = &lut->sda; snprintf(lut->name, sizeof(lut->name), "pwm1_auto_point%d_pwm", i + 1); sda->dev_attr.attr.mode = 0644; sda->index = i; sda->dev_attr.show = lut_show; sda->dev_attr.store = lut_store; sda->dev_attr.attr.name = lut->name; sysfs_attr_init(&sda->dev_attr.attr); state->attrs[i] = &sda->dev_attr.attr; } state->attrs[i] = &dev_attr_pwm1_auto_point_temp_hyst.attr; state->group.attrs = state->attrs; state->groups[0] = &state->group; } static int max31760_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct max31760_state *state; struct device *hwmon_dev; int ret; state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); if (!state) return -ENOMEM; state->regmap = devm_regmap_init_i2c(client, ®map_config); if (IS_ERR(state->regmap)) return dev_err_probe(dev, PTR_ERR(state->regmap), "regmap initialization failed\n"); dev_set_drvdata(dev, state); /* Set alert output to comparator mode */ ret = regmap_set_bits(state->regmap, REG_CR2, CR2_ALERTS); if (ret) return dev_err_probe(dev, ret, "cannot write register\n"); max31760_create_lut_nodes(state); hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, state, &max31760_chip_info, state->groups); return PTR_ERR_OR_ZERO(hwmon_dev); } static const struct of_device_id max31760_of_match[] = { {.compatible = "adi,max31760"}, { } }; MODULE_DEVICE_TABLE(of, max31760_of_match); static const struct i2c_device_id max31760_id[] = { {"max31760"}, { } }; MODULE_DEVICE_TABLE(i2c, max31760_id); static int max31760_suspend(struct device *dev) { struct max31760_state *state = dev_get_drvdata(dev); return regmap_set_bits(state->regmap, REG_CR2, CR2_STBY); } static int max31760_resume(struct device *dev) { struct max31760_state *state = dev_get_drvdata(dev); return regmap_clear_bits(state->regmap, REG_CR2, CR2_STBY); } static DEFINE_SIMPLE_DEV_PM_OPS(max31760_pm_ops, max31760_suspend, max31760_resume); static struct i2c_driver max31760_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = "max31760", .of_match_table = max31760_of_match, .pm = pm_ptr(&max31760_pm_ops) }, .probe_new = max31760_probe, .id_table = max31760_id }; module_i2c_driver(max31760_driver); MODULE_AUTHOR("Ibrahim Tilki <Ibrahim.Tilki@analog.com>"); MODULE_DESCRIPTION("Analog Devices MAX31760 Fan Speed Controller"); MODULE_SOFTDEP("pre: regmap_i2c"); MODULE_VERSION("1.0"); MODULE_LICENSE("GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1