Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Nuno Sá | 1842 | 100.00% | 1 | 100.00% |
Total | 1842 | 1 |
// SPDX-License-Identifier: GPL-2.0 /* * Fan Control HDL CORE driver * * Copyright 2019 Analog Devices Inc. */ #include <linux/bits.h> #include <linux/clk.h> #include <linux/fpga/adi-axi-common.h> #include <linux/hwmon.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> #define ADI_AXI_PCORE_VER_MAJOR(version) (((version) >> 16) & 0xff) #define ADI_AXI_PCORE_VER_MINOR(version) (((version) >> 8) & 0xff) #define ADI_AXI_PCORE_VER_PATCH(version) ((version) & 0xff) /* register map */ #define ADI_REG_RSTN 0x0080 #define ADI_REG_PWM_WIDTH 0x0084 #define ADI_REG_TACH_PERIOD 0x0088 #define ADI_REG_TACH_TOLERANCE 0x008c #define ADI_REG_PWM_PERIOD 0x00c0 #define ADI_REG_TACH_MEASUR 0x00c4 #define ADI_REG_TEMPERATURE 0x00c8 #define ADI_REG_IRQ_MASK 0x0040 #define ADI_REG_IRQ_PENDING 0x0044 #define ADI_REG_IRQ_SRC 0x0048 /* IRQ sources */ #define ADI_IRQ_SRC_PWM_CHANGED BIT(0) #define ADI_IRQ_SRC_TACH_ERR BIT(1) #define ADI_IRQ_SRC_TEMP_INCREASE BIT(2) #define ADI_IRQ_SRC_NEW_MEASUR BIT(3) #define ADI_IRQ_SRC_MASK GENMASK(3, 0) #define ADI_IRQ_MASK_OUT_ALL 0xFFFFFFFFU #define SYSFS_PWM_MAX 255 struct axi_fan_control_data { void __iomem *base; struct device *hdev; unsigned long clk_rate; int irq; /* pulses per revolution */ u32 ppr; bool hw_pwm_req; bool update_tacho_params; u8 fan_fault; }; static inline void axi_iowrite(const u32 val, const u32 reg, const struct axi_fan_control_data *ctl) { iowrite32(val, ctl->base + reg); } static inline u32 axi_ioread(const u32 reg, const struct axi_fan_control_data *ctl) { return ioread32(ctl->base + reg); } static long axi_fan_control_get_pwm_duty(const struct axi_fan_control_data *ctl) { u32 pwm_width = axi_ioread(ADI_REG_PWM_WIDTH, ctl); u32 pwm_period = axi_ioread(ADI_REG_PWM_PERIOD, ctl); /* * PWM_PERIOD is a RO register set by the core. It should never be 0. * For now we are trusting the HW... */ return DIV_ROUND_CLOSEST(pwm_width * SYSFS_PWM_MAX, pwm_period); } static int axi_fan_control_set_pwm_duty(const long val, struct axi_fan_control_data *ctl) { u32 pwm_period = axi_ioread(ADI_REG_PWM_PERIOD, ctl); u32 new_width; long __val = clamp_val(val, 0, SYSFS_PWM_MAX); new_width = DIV_ROUND_CLOSEST(__val * pwm_period, SYSFS_PWM_MAX); axi_iowrite(new_width, ADI_REG_PWM_WIDTH, ctl); return 0; } static long axi_fan_control_get_fan_rpm(const struct axi_fan_control_data *ctl) { const u32 tach = axi_ioread(ADI_REG_TACH_MEASUR, ctl); if (tach == 0) /* should we return error, EAGAIN maybe? */ return 0; /* * The tacho period should be: * TACH = 60/(ppr * rpm), where rpm is revolutions per second * and ppr is pulses per revolution. * Given the tacho period, we can multiply it by the input clock * so that we know how many clocks we need to have this period. * From this, we can derive the RPM value. */ return DIV_ROUND_CLOSEST(60 * ctl->clk_rate, ctl->ppr * tach); } static int axi_fan_control_read_temp(struct device *dev, u32 attr, long *val) { struct axi_fan_control_data *ctl = dev_get_drvdata(dev); long raw_temp; switch (attr) { case hwmon_temp_input: raw_temp = axi_ioread(ADI_REG_TEMPERATURE, ctl); /* * The formula for the temperature is: * T = (ADC * 501.3743 / 2^bits) - 273.6777 * It's multiplied by 1000 to have millidegrees as * specified by the hwmon sysfs interface. */ *val = ((raw_temp * 501374) >> 16) - 273677; return 0; default: return -ENOTSUPP; } } static int axi_fan_control_read_fan(struct device *dev, u32 attr, long *val) { struct axi_fan_control_data *ctl = dev_get_drvdata(dev); switch (attr) { case hwmon_fan_fault: *val = ctl->fan_fault; /* clear it now */ ctl->fan_fault = 0; return 0; case hwmon_fan_input: *val = axi_fan_control_get_fan_rpm(ctl); return 0; default: return -ENOTSUPP; } } static int axi_fan_control_read_pwm(struct device *dev, u32 attr, long *val) { struct axi_fan_control_data *ctl = dev_get_drvdata(dev); switch (attr) { case hwmon_pwm_input: *val = axi_fan_control_get_pwm_duty(ctl); return 0; default: return -ENOTSUPP; } } static int axi_fan_control_write_pwm(struct device *dev, u32 attr, long val) { struct axi_fan_control_data *ctl = dev_get_drvdata(dev); switch (attr) { case hwmon_pwm_input: return axi_fan_control_set_pwm_duty(val, ctl); default: return -ENOTSUPP; } } static int axi_fan_control_read_labels(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, const char **str) { switch (type) { case hwmon_fan: *str = "FAN"; return 0; case hwmon_temp: *str = "SYSMON4"; return 0; default: return -ENOTSUPP; } } static int axi_fan_control_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { switch (type) { case hwmon_fan: return axi_fan_control_read_fan(dev, attr, val); case hwmon_pwm: return axi_fan_control_read_pwm(dev, attr, val); case hwmon_temp: return axi_fan_control_read_temp(dev, attr, val); default: return -ENOTSUPP; } } static int axi_fan_control_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { switch (type) { case hwmon_pwm: return axi_fan_control_write_pwm(dev, attr, val); default: return -ENOTSUPP; } } static umode_t axi_fan_control_fan_is_visible(const u32 attr) { switch (attr) { case hwmon_fan_input: case hwmon_fan_fault: case hwmon_fan_label: return 0444; default: return 0; } } static umode_t axi_fan_control_pwm_is_visible(const u32 attr) { switch (attr) { case hwmon_pwm_input: return 0644; default: return 0; } } static umode_t axi_fan_control_temp_is_visible(const u32 attr) { switch (attr) { case hwmon_temp_input: case hwmon_temp_label: return 0444; default: return 0; } } static umode_t axi_fan_control_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { switch (type) { case hwmon_fan: return axi_fan_control_fan_is_visible(attr); case hwmon_pwm: return axi_fan_control_pwm_is_visible(attr); case hwmon_temp: return axi_fan_control_temp_is_visible(attr); default: return 0; } } /* * This core has two main ways of changing the PWM duty cycle. It is done, * either by a request from userspace (writing on pwm1_input) or by the * core itself. When the change is done by the core, it will use predefined * parameters to evaluate the tach signal and, on that case we cannot set them. * On the other hand, when the request is done by the user, with some arbitrary * value that the core does not now about, we have to provide the tach * parameters so that, the core can evaluate the signal. On the IRQ handler we * distinguish this by using the ADI_IRQ_SRC_TEMP_INCREASE interrupt. This tell * us that the CORE requested a new duty cycle. After this, there is 5s delay * on which the core waits for the fan rotation speed to stabilize. After this * we get ADI_IRQ_SRC_PWM_CHANGED irq where we will decide if we need to set * the tach parameters or not on the next tach measurement cycle (corresponding * already to the ney duty cycle) based on the %ctl->hw_pwm_req flag. */ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data) { struct axi_fan_control_data *ctl = (struct axi_fan_control_data *)data; u32 irq_pending = axi_ioread(ADI_REG_IRQ_PENDING, ctl); u32 clear_mask; if (irq_pending & ADI_IRQ_SRC_NEW_MEASUR) { if (ctl->update_tacho_params) { u32 new_tach = axi_ioread(ADI_REG_TACH_MEASUR, ctl); /* get 25% tolerance */ u32 tach_tol = DIV_ROUND_CLOSEST(new_tach * 25, 100); /* set new tacho parameters */ axi_iowrite(new_tach, ADI_REG_TACH_PERIOD, ctl); axi_iowrite(tach_tol, ADI_REG_TACH_TOLERANCE, ctl); ctl->update_tacho_params = false; } } if (irq_pending & ADI_IRQ_SRC_PWM_CHANGED) { /* * if the pwm changes on behalf of software, * we need to provide new tacho parameters to the core. * Wait for the next measurement for that... */ if (!ctl->hw_pwm_req) { ctl->update_tacho_params = true; } else { ctl->hw_pwm_req = false; sysfs_notify(&ctl->hdev->kobj, NULL, "pwm1"); } } if (irq_pending & ADI_IRQ_SRC_TEMP_INCREASE) /* hardware requested a new pwm */ ctl->hw_pwm_req = true; if (irq_pending & ADI_IRQ_SRC_TACH_ERR) ctl->fan_fault = 1; /* clear all interrupts */ clear_mask = irq_pending & ADI_IRQ_SRC_MASK; axi_iowrite(clear_mask, ADI_REG_IRQ_PENDING, ctl); return IRQ_HANDLED; } static int axi_fan_control_init(struct axi_fan_control_data *ctl, const struct device_node *np) { int ret; /* get fan pulses per revolution */ ret = of_property_read_u32(np, "pulses-per-revolution", &ctl->ppr); if (ret) return ret; /* 1, 2 and 4 are the typical and accepted values */ if (ctl->ppr != 1 && ctl->ppr != 2 && ctl->ppr != 4) return -EINVAL; /* * Enable all IRQs */ axi_iowrite(ADI_IRQ_MASK_OUT_ALL & ~(ADI_IRQ_SRC_NEW_MEASUR | ADI_IRQ_SRC_TACH_ERR | ADI_IRQ_SRC_PWM_CHANGED | ADI_IRQ_SRC_TEMP_INCREASE), ADI_REG_IRQ_MASK, ctl); /* bring the device out of reset */ axi_iowrite(0x01, ADI_REG_RSTN, ctl); return ret; } static const struct hwmon_channel_info *axi_fan_control_info[] = { HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT), HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_LABEL), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), NULL }; static const struct hwmon_ops axi_fan_control_hwmon_ops = { .is_visible = axi_fan_control_is_visible, .read = axi_fan_control_read, .write = axi_fan_control_write, .read_string = axi_fan_control_read_labels, }; static const struct hwmon_chip_info axi_chip_info = { .ops = &axi_fan_control_hwmon_ops, .info = axi_fan_control_info, }; static const u32 version_1_0_0 = ADI_AXI_PCORE_VER(1, 0, 'a'); static const struct of_device_id axi_fan_control_of_match[] = { { .compatible = "adi,axi-fan-control-1.00.a", .data = (void *)&version_1_0_0}, {}, }; MODULE_DEVICE_TABLE(of, axi_fan_control_of_match); static int axi_fan_control_probe(struct platform_device *pdev) { struct axi_fan_control_data *ctl; struct clk *clk; const struct of_device_id *id; const char *name = "axi_fan_control"; u32 version; int ret; id = of_match_node(axi_fan_control_of_match, pdev->dev.of_node); if (!id) return -EINVAL; ctl = devm_kzalloc(&pdev->dev, sizeof(*ctl), GFP_KERNEL); if (!ctl) return -ENOMEM; ctl->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(ctl->base)) return PTR_ERR(ctl->base); clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(clk)) { dev_err(&pdev->dev, "clk_get failed with %ld\n", PTR_ERR(clk)); return PTR_ERR(clk); } ctl->clk_rate = clk_get_rate(clk); if (!ctl->clk_rate) return -EINVAL; version = axi_ioread(ADI_AXI_REG_VERSION, ctl); if (ADI_AXI_PCORE_VER_MAJOR(version) != ADI_AXI_PCORE_VER_MAJOR((*(u32 *)id->data))) { dev_err(&pdev->dev, "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", ADI_AXI_PCORE_VER_MAJOR((*(u32 *)id->data)), ADI_AXI_PCORE_VER_MINOR((*(u32 *)id->data)), ADI_AXI_PCORE_VER_PATCH((*(u32 *)id->data)), ADI_AXI_PCORE_VER_MAJOR(version), ADI_AXI_PCORE_VER_MINOR(version), ADI_AXI_PCORE_VER_PATCH(version)); return -ENODEV; } ctl->irq = platform_get_irq(pdev, 0); if (ctl->irq < 0) return ctl->irq; ret = devm_request_threaded_irq(&pdev->dev, ctl->irq, NULL, axi_fan_control_irq_handler, IRQF_ONESHOT | IRQF_TRIGGER_HIGH, pdev->driver_override, ctl); if (ret) { dev_err(&pdev->dev, "failed to request an irq, %d", ret); return ret; } ret = axi_fan_control_init(ctl, pdev->dev.of_node); if (ret) { dev_err(&pdev->dev, "Failed to initialize device\n"); return ret; } ctl->hdev = devm_hwmon_device_register_with_info(&pdev->dev, name, ctl, &axi_chip_info, NULL); return PTR_ERR_OR_ZERO(ctl->hdev); } static struct platform_driver axi_fan_control_driver = { .driver = { .name = "axi_fan_control_driver", .of_match_table = axi_fan_control_of_match, }, .probe = axi_fan_control_probe, }; module_platform_driver(axi_fan_control_driver); MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>"); MODULE_DESCRIPTION("Analog Devices Fan Control HDL CORE driver"); 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