Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Michael Walle | 1557 | 99.87% | 1 | 33.33% |
Christophe Jaillet | 1 | 0.06% | 1 | 33.33% |
Krzysztof Kozlowski | 1 | 0.06% | 1 | 33.33% |
Total | 1559 | 3 |
// SPDX-License-Identifier: GPL-2.0-only #include <linux/bitfield.h> #include <linux/clk.h> #include <linux/hwmon.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/mod_devicetable.h> #include <linux/platform_device.h> #include <linux/polynomial.h> #include <linux/regmap.h> /* * The original translation formulae of the temperature (in degrees of Celsius) * are as follows: * * T = -3.4627e-11*(N^4) + 1.1023e-7*(N^3) + -1.9165e-4*(N^2) + * 3.0604e-1*(N^1) + -5.6197e1 * * where [-56.197, 136.402]C and N = [0, 1023]. * * They must be accordingly altered to be suitable for the integer arithmetics. * The technique is called 'factor redistribution', which just makes sure the * multiplications and divisions are made so to have a result of the operations * within the integer numbers limit. In addition we need to translate the * formulae to accept millidegrees of Celsius. Here what it looks like after * the alterations: * * T = -34627e-12*(N^4) + 110230e-9*(N^3) + -191650e-6*(N^2) + * 306040e-3*(N^1) + -56197 * * where T = [-56197, 136402]mC and N = [0, 1023]. */ static const struct polynomial poly_N_to_temp = { .terms = { {4, -34627, 1000, 1}, {3, 110230, 1000, 1}, {2, -191650, 1000, 1}, {1, 306040, 1000, 1}, {0, -56197, 1, 1} } }; #define PVT_SENSOR_CTRL 0x0 /* unused */ #define PVT_SENSOR_CFG 0x4 #define SENSOR_CFG_CLK_CFG GENMASK(27, 20) #define SENSOR_CFG_TRIM_VAL GENMASK(13, 9) #define SENSOR_CFG_SAMPLE_ENA BIT(8) #define SENSOR_CFG_START_CAPTURE BIT(7) #define SENSOR_CFG_CONTINIOUS_MODE BIT(6) #define SENSOR_CFG_PSAMPLE_ENA GENMASK(1, 0) #define PVT_SENSOR_STAT 0x8 #define SENSOR_STAT_DATA_VALID BIT(10) #define SENSOR_STAT_DATA GENMASK(9, 0) #define FAN_CFG 0x0 #define FAN_CFG_DUTY_CYCLE GENMASK(23, 16) #define INV_POL BIT(3) #define GATE_ENA BIT(2) #define PWM_OPEN_COL_ENA BIT(1) #define FAN_STAT_CFG BIT(0) #define FAN_PWM_FREQ 0x4 #define FAN_PWM_CYC_10US GENMASK(25, 15) #define FAN_PWM_FREQ_FREQ GENMASK(14, 0) #define FAN_CNT 0xc #define FAN_CNT_DATA GENMASK(15, 0) #define LAN966X_PVT_CLK 1200000 /* 1.2 MHz */ struct lan966x_hwmon { struct regmap *regmap_pvt; struct regmap *regmap_fan; struct clk *clk; unsigned long clk_rate; }; static int lan966x_hwmon_read_temp(struct device *dev, long *val) { struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); unsigned int data; int ret; ret = regmap_read(hwmon->regmap_pvt, PVT_SENSOR_STAT, &data); if (ret < 0) return ret; if (!(data & SENSOR_STAT_DATA_VALID)) return -ENODATA; *val = polynomial_calc(&poly_N_to_temp, FIELD_GET(SENSOR_STAT_DATA, data)); return 0; } static int lan966x_hwmon_read_fan(struct device *dev, long *val) { struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); unsigned int data; int ret; ret = regmap_read(hwmon->regmap_fan, FAN_CNT, &data); if (ret < 0) return ret; /* * Data is given in pulses per second. Assume two pulses * per revolution. */ *val = FIELD_GET(FAN_CNT_DATA, data) * 60 / 2; return 0; } static int lan966x_hwmon_read_pwm(struct device *dev, long *val) { struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); unsigned int data; int ret; ret = regmap_read(hwmon->regmap_fan, FAN_CFG, &data); if (ret < 0) return ret; *val = FIELD_GET(FAN_CFG_DUTY_CYCLE, data); return 0; } static int lan966x_hwmon_read_pwm_freq(struct device *dev, long *val) { struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); unsigned long tmp; unsigned int data; int ret; ret = regmap_read(hwmon->regmap_fan, FAN_PWM_FREQ, &data); if (ret < 0) return ret; /* * Datasheet says it is sys_clk / 256 / pwm_freq. But in reality * it is sys_clk / 256 / (pwm_freq + 1). */ data = FIELD_GET(FAN_PWM_FREQ_FREQ, data) + 1; tmp = DIV_ROUND_CLOSEST(hwmon->clk_rate, 256); *val = DIV_ROUND_CLOSEST(tmp, data); return 0; } static int lan966x_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { switch (type) { case hwmon_temp: return lan966x_hwmon_read_temp(dev, val); case hwmon_fan: return lan966x_hwmon_read_fan(dev, val); case hwmon_pwm: switch (attr) { case hwmon_pwm_input: return lan966x_hwmon_read_pwm(dev, val); case hwmon_pwm_freq: return lan966x_hwmon_read_pwm_freq(dev, val); default: return -EOPNOTSUPP; } default: return -EOPNOTSUPP; } } static int lan966x_hwmon_write_pwm(struct device *dev, long val) { struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); if (val < 0 || val > 255) return -EINVAL; return regmap_update_bits(hwmon->regmap_fan, FAN_CFG, FAN_CFG_DUTY_CYCLE, FIELD_PREP(FAN_CFG_DUTY_CYCLE, val)); } static int lan966x_hwmon_write_pwm_freq(struct device *dev, long val) { struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); if (val <= 0) return -EINVAL; val = DIV_ROUND_CLOSEST(hwmon->clk_rate, val); val = DIV_ROUND_CLOSEST(val, 256) - 1; val = clamp_val(val, 0, FAN_PWM_FREQ_FREQ); return regmap_update_bits(hwmon->regmap_fan, FAN_PWM_FREQ, FAN_PWM_FREQ_FREQ, FIELD_PREP(FAN_PWM_FREQ_FREQ, val)); } static int lan966x_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { switch (type) { case hwmon_pwm: switch (attr) { case hwmon_pwm_input: return lan966x_hwmon_write_pwm(dev, val); case hwmon_pwm_freq: return lan966x_hwmon_write_pwm_freq(dev, val); default: return -EOPNOTSUPP; } default: return -EOPNOTSUPP; } } static umode_t lan966x_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { umode_t mode = 0; switch (type) { case hwmon_temp: switch (attr) { case hwmon_temp_input: mode = 0444; break; default: break; } break; case hwmon_fan: switch (attr) { case hwmon_fan_input: mode = 0444; break; default: break; } break; case hwmon_pwm: switch (attr) { case hwmon_pwm_input: case hwmon_pwm_freq: mode = 0644; break; default: break; } break; default: break; } return mode; } static const struct hwmon_channel_info * const lan966x_hwmon_info[] = { HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_FREQ), NULL }; static const struct hwmon_ops lan966x_hwmon_ops = { .is_visible = lan966x_hwmon_is_visible, .read = lan966x_hwmon_read, .write = lan966x_hwmon_write, }; static const struct hwmon_chip_info lan966x_hwmon_chip_info = { .ops = &lan966x_hwmon_ops, .info = lan966x_hwmon_info, }; static void lan966x_hwmon_disable(void *data) { struct lan966x_hwmon *hwmon = data; regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG, SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE, 0); } static int lan966x_hwmon_enable(struct device *dev, struct lan966x_hwmon *hwmon) { unsigned int mask = SENSOR_CFG_CLK_CFG | SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_START_CAPTURE | SENSOR_CFG_CONTINIOUS_MODE | SENSOR_CFG_PSAMPLE_ENA; unsigned int val; unsigned int div; int ret; /* enable continuous mode */ val = SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE; /* set PVT clock to be between 1.15 and 1.25 MHz */ div = DIV_ROUND_CLOSEST(hwmon->clk_rate, LAN966X_PVT_CLK); val |= FIELD_PREP(SENSOR_CFG_CLK_CFG, div); ret = regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG, mask, val); if (ret) return ret; return devm_add_action_or_reset(dev, lan966x_hwmon_disable, hwmon); } static struct regmap *lan966x_init_regmap(struct platform_device *pdev, const char *name) { struct regmap_config regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, }; void __iomem *base; base = devm_platform_ioremap_resource_byname(pdev, name); if (IS_ERR(base)) return ERR_CAST(base); regmap_config.name = name; return devm_regmap_init_mmio(&pdev->dev, base, ®map_config); } static int lan966x_hwmon_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct lan966x_hwmon *hwmon; struct device *hwmon_dev; int ret; hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); if (!hwmon) return -ENOMEM; hwmon->clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(hwmon->clk)) return dev_err_probe(dev, PTR_ERR(hwmon->clk), "failed to get clock\n"); hwmon->clk_rate = clk_get_rate(hwmon->clk); hwmon->regmap_pvt = lan966x_init_regmap(pdev, "pvt"); if (IS_ERR(hwmon->regmap_pvt)) return dev_err_probe(dev, PTR_ERR(hwmon->regmap_pvt), "failed to get regmap for PVT registers\n"); hwmon->regmap_fan = lan966x_init_regmap(pdev, "fan"); if (IS_ERR(hwmon->regmap_fan)) return dev_err_probe(dev, PTR_ERR(hwmon->regmap_fan), "failed to get regmap for fan registers\n"); ret = lan966x_hwmon_enable(dev, hwmon); if (ret) return dev_err_probe(dev, ret, "failed to enable sensor\n"); hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "lan966x_hwmon", hwmon, &lan966x_hwmon_chip_info, NULL); if (IS_ERR(hwmon_dev)) return dev_err_probe(dev, PTR_ERR(hwmon_dev), "failed to register hwmon device\n"); return 0; } static const struct of_device_id lan966x_hwmon_of_match[] = { { .compatible = "microchip,lan9668-hwmon" }, {} }; MODULE_DEVICE_TABLE(of, lan966x_hwmon_of_match); static struct platform_driver lan966x_hwmon_driver = { .probe = lan966x_hwmon_probe, .driver = { .name = "lan966x-hwmon", .of_match_table = lan966x_hwmon_of_match, }, }; module_platform_driver(lan966x_hwmon_driver); MODULE_DESCRIPTION("LAN966x Hardware Monitoring Driver"); MODULE_AUTHOR("Michael Walle <michael@walle.cc>"); 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