18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Fan Control HDL CORE driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2019 Analog Devices Inc. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci#include <linux/bits.h> 88c2ecf20Sopenharmony_ci#include <linux/clk.h> 98c2ecf20Sopenharmony_ci#include <linux/fpga/adi-axi-common.h> 108c2ecf20Sopenharmony_ci#include <linux/hwmon.h> 118c2ecf20Sopenharmony_ci#include <linux/hwmon-sysfs.h> 128c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 138c2ecf20Sopenharmony_ci#include <linux/io.h> 148c2ecf20Sopenharmony_ci#include <linux/kernel.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/of.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci/* register map */ 208c2ecf20Sopenharmony_ci#define ADI_REG_RSTN 0x0080 218c2ecf20Sopenharmony_ci#define ADI_REG_PWM_WIDTH 0x0084 228c2ecf20Sopenharmony_ci#define ADI_REG_TACH_PERIOD 0x0088 238c2ecf20Sopenharmony_ci#define ADI_REG_TACH_TOLERANCE 0x008c 248c2ecf20Sopenharmony_ci#define ADI_REG_PWM_PERIOD 0x00c0 258c2ecf20Sopenharmony_ci#define ADI_REG_TACH_MEASUR 0x00c4 268c2ecf20Sopenharmony_ci#define ADI_REG_TEMPERATURE 0x00c8 278c2ecf20Sopenharmony_ci#define ADI_REG_TEMP_00_H 0x0100 288c2ecf20Sopenharmony_ci#define ADI_REG_TEMP_25_L 0x0104 298c2ecf20Sopenharmony_ci#define ADI_REG_TEMP_25_H 0x0108 308c2ecf20Sopenharmony_ci#define ADI_REG_TEMP_50_L 0x010c 318c2ecf20Sopenharmony_ci#define ADI_REG_TEMP_50_H 0x0110 328c2ecf20Sopenharmony_ci#define ADI_REG_TEMP_75_L 0x0114 338c2ecf20Sopenharmony_ci#define ADI_REG_TEMP_75_H 0x0118 348c2ecf20Sopenharmony_ci#define ADI_REG_TEMP_100_L 0x011c 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci#define ADI_REG_IRQ_MASK 0x0040 378c2ecf20Sopenharmony_ci#define ADI_REG_IRQ_PENDING 0x0044 388c2ecf20Sopenharmony_ci#define ADI_REG_IRQ_SRC 0x0048 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci/* IRQ sources */ 418c2ecf20Sopenharmony_ci#define ADI_IRQ_SRC_PWM_CHANGED BIT(0) 428c2ecf20Sopenharmony_ci#define ADI_IRQ_SRC_TACH_ERR BIT(1) 438c2ecf20Sopenharmony_ci#define ADI_IRQ_SRC_TEMP_INCREASE BIT(2) 448c2ecf20Sopenharmony_ci#define ADI_IRQ_SRC_NEW_MEASUR BIT(3) 458c2ecf20Sopenharmony_ci#define ADI_IRQ_SRC_MASK GENMASK(3, 0) 468c2ecf20Sopenharmony_ci#define ADI_IRQ_MASK_OUT_ALL 0xFFFFFFFFU 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci#define SYSFS_PWM_MAX 255 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistruct axi_fan_control_data { 518c2ecf20Sopenharmony_ci void __iomem *base; 528c2ecf20Sopenharmony_ci struct device *hdev; 538c2ecf20Sopenharmony_ci unsigned long clk_rate; 548c2ecf20Sopenharmony_ci int irq; 558c2ecf20Sopenharmony_ci /* pulses per revolution */ 568c2ecf20Sopenharmony_ci u32 ppr; 578c2ecf20Sopenharmony_ci bool hw_pwm_req; 588c2ecf20Sopenharmony_ci bool update_tacho_params; 598c2ecf20Sopenharmony_ci u8 fan_fault; 608c2ecf20Sopenharmony_ci}; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic inline void axi_iowrite(const u32 val, const u32 reg, 638c2ecf20Sopenharmony_ci const struct axi_fan_control_data *ctl) 648c2ecf20Sopenharmony_ci{ 658c2ecf20Sopenharmony_ci iowrite32(val, ctl->base + reg); 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic inline u32 axi_ioread(const u32 reg, 698c2ecf20Sopenharmony_ci const struct axi_fan_control_data *ctl) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci return ioread32(ctl->base + reg); 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci/* 758c2ecf20Sopenharmony_ci * The core calculates the temperature as: 768c2ecf20Sopenharmony_ci * T = /raw * 509.3140064 / 65535) - 280.2308787 778c2ecf20Sopenharmony_ci */ 788c2ecf20Sopenharmony_cistatic ssize_t axi_fan_control_show(struct device *dev, struct device_attribute *da, char *buf) 798c2ecf20Sopenharmony_ci{ 808c2ecf20Sopenharmony_ci struct axi_fan_control_data *ctl = dev_get_drvdata(dev); 818c2ecf20Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(da); 828c2ecf20Sopenharmony_ci u32 temp = axi_ioread(attr->index, ctl); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci temp = DIV_ROUND_CLOSEST_ULL(temp * 509314ULL, 65535) - 280230; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci return sprintf(buf, "%u\n", temp); 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic ssize_t axi_fan_control_store(struct device *dev, struct device_attribute *da, 908c2ecf20Sopenharmony_ci const char *buf, size_t count) 918c2ecf20Sopenharmony_ci{ 928c2ecf20Sopenharmony_ci struct axi_fan_control_data *ctl = dev_get_drvdata(dev); 938c2ecf20Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(da); 948c2ecf20Sopenharmony_ci u32 temp; 958c2ecf20Sopenharmony_ci int ret; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci ret = kstrtou32(buf, 10, &temp); 988c2ecf20Sopenharmony_ci if (ret) 998c2ecf20Sopenharmony_ci return ret; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci temp = DIV_ROUND_CLOSEST_ULL((temp + 280230) * 65535ULL, 509314); 1028c2ecf20Sopenharmony_ci axi_iowrite(temp, attr->index, ctl); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci return count; 1058c2ecf20Sopenharmony_ci} 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_cistatic long axi_fan_control_get_pwm_duty(const struct axi_fan_control_data *ctl) 1088c2ecf20Sopenharmony_ci{ 1098c2ecf20Sopenharmony_ci u32 pwm_width = axi_ioread(ADI_REG_PWM_WIDTH, ctl); 1108c2ecf20Sopenharmony_ci u32 pwm_period = axi_ioread(ADI_REG_PWM_PERIOD, ctl); 1118c2ecf20Sopenharmony_ci /* 1128c2ecf20Sopenharmony_ci * PWM_PERIOD is a RO register set by the core. It should never be 0. 1138c2ecf20Sopenharmony_ci * For now we are trusting the HW... 1148c2ecf20Sopenharmony_ci */ 1158c2ecf20Sopenharmony_ci return DIV_ROUND_CLOSEST(pwm_width * SYSFS_PWM_MAX, pwm_period); 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic int axi_fan_control_set_pwm_duty(const long val, 1198c2ecf20Sopenharmony_ci struct axi_fan_control_data *ctl) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci u32 pwm_period = axi_ioread(ADI_REG_PWM_PERIOD, ctl); 1228c2ecf20Sopenharmony_ci u32 new_width; 1238c2ecf20Sopenharmony_ci long __val = clamp_val(val, 0, SYSFS_PWM_MAX); 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci new_width = DIV_ROUND_CLOSEST(__val * pwm_period, SYSFS_PWM_MAX); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci axi_iowrite(new_width, ADI_REG_PWM_WIDTH, ctl); 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci return 0; 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic long axi_fan_control_get_fan_rpm(const struct axi_fan_control_data *ctl) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci const u32 tach = axi_ioread(ADI_REG_TACH_MEASUR, ctl); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci if (tach == 0) 1378c2ecf20Sopenharmony_ci /* should we return error, EAGAIN maybe? */ 1388c2ecf20Sopenharmony_ci return 0; 1398c2ecf20Sopenharmony_ci /* 1408c2ecf20Sopenharmony_ci * The tacho period should be: 1418c2ecf20Sopenharmony_ci * TACH = 60/(ppr * rpm), where rpm is revolutions per second 1428c2ecf20Sopenharmony_ci * and ppr is pulses per revolution. 1438c2ecf20Sopenharmony_ci * Given the tacho period, we can multiply it by the input clock 1448c2ecf20Sopenharmony_ci * so that we know how many clocks we need to have this period. 1458c2ecf20Sopenharmony_ci * From this, we can derive the RPM value. 1468c2ecf20Sopenharmony_ci */ 1478c2ecf20Sopenharmony_ci return DIV_ROUND_CLOSEST(60 * ctl->clk_rate, ctl->ppr * tach); 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_cistatic int axi_fan_control_read_temp(struct device *dev, u32 attr, long *val) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci struct axi_fan_control_data *ctl = dev_get_drvdata(dev); 1538c2ecf20Sopenharmony_ci long raw_temp; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci switch (attr) { 1568c2ecf20Sopenharmony_ci case hwmon_temp_input: 1578c2ecf20Sopenharmony_ci raw_temp = axi_ioread(ADI_REG_TEMPERATURE, ctl); 1588c2ecf20Sopenharmony_ci /* 1598c2ecf20Sopenharmony_ci * The formula for the temperature is: 1608c2ecf20Sopenharmony_ci * T = (ADC * 501.3743 / 2^bits) - 273.6777 1618c2ecf20Sopenharmony_ci * It's multiplied by 1000 to have millidegrees as 1628c2ecf20Sopenharmony_ci * specified by the hwmon sysfs interface. 1638c2ecf20Sopenharmony_ci */ 1648c2ecf20Sopenharmony_ci *val = ((raw_temp * 501374) >> 16) - 273677; 1658c2ecf20Sopenharmony_ci return 0; 1668c2ecf20Sopenharmony_ci default: 1678c2ecf20Sopenharmony_ci return -ENOTSUPP; 1688c2ecf20Sopenharmony_ci } 1698c2ecf20Sopenharmony_ci} 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_cistatic int axi_fan_control_read_fan(struct device *dev, u32 attr, long *val) 1728c2ecf20Sopenharmony_ci{ 1738c2ecf20Sopenharmony_ci struct axi_fan_control_data *ctl = dev_get_drvdata(dev); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci switch (attr) { 1768c2ecf20Sopenharmony_ci case hwmon_fan_fault: 1778c2ecf20Sopenharmony_ci *val = ctl->fan_fault; 1788c2ecf20Sopenharmony_ci /* clear it now */ 1798c2ecf20Sopenharmony_ci ctl->fan_fault = 0; 1808c2ecf20Sopenharmony_ci return 0; 1818c2ecf20Sopenharmony_ci case hwmon_fan_input: 1828c2ecf20Sopenharmony_ci *val = axi_fan_control_get_fan_rpm(ctl); 1838c2ecf20Sopenharmony_ci return 0; 1848c2ecf20Sopenharmony_ci default: 1858c2ecf20Sopenharmony_ci return -ENOTSUPP; 1868c2ecf20Sopenharmony_ci } 1878c2ecf20Sopenharmony_ci} 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_cistatic int axi_fan_control_read_pwm(struct device *dev, u32 attr, long *val) 1908c2ecf20Sopenharmony_ci{ 1918c2ecf20Sopenharmony_ci struct axi_fan_control_data *ctl = dev_get_drvdata(dev); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci switch (attr) { 1948c2ecf20Sopenharmony_ci case hwmon_pwm_input: 1958c2ecf20Sopenharmony_ci *val = axi_fan_control_get_pwm_duty(ctl); 1968c2ecf20Sopenharmony_ci return 0; 1978c2ecf20Sopenharmony_ci default: 1988c2ecf20Sopenharmony_ci return -ENOTSUPP; 1998c2ecf20Sopenharmony_ci } 2008c2ecf20Sopenharmony_ci} 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic int axi_fan_control_write_pwm(struct device *dev, u32 attr, long val) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci struct axi_fan_control_data *ctl = dev_get_drvdata(dev); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci switch (attr) { 2078c2ecf20Sopenharmony_ci case hwmon_pwm_input: 2088c2ecf20Sopenharmony_ci return axi_fan_control_set_pwm_duty(val, ctl); 2098c2ecf20Sopenharmony_ci default: 2108c2ecf20Sopenharmony_ci return -ENOTSUPP; 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci} 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_cistatic int axi_fan_control_read_labels(struct device *dev, 2158c2ecf20Sopenharmony_ci enum hwmon_sensor_types type, 2168c2ecf20Sopenharmony_ci u32 attr, int channel, const char **str) 2178c2ecf20Sopenharmony_ci{ 2188c2ecf20Sopenharmony_ci switch (type) { 2198c2ecf20Sopenharmony_ci case hwmon_fan: 2208c2ecf20Sopenharmony_ci *str = "FAN"; 2218c2ecf20Sopenharmony_ci return 0; 2228c2ecf20Sopenharmony_ci case hwmon_temp: 2238c2ecf20Sopenharmony_ci *str = "SYSMON4"; 2248c2ecf20Sopenharmony_ci return 0; 2258c2ecf20Sopenharmony_ci default: 2268c2ecf20Sopenharmony_ci return -ENOTSUPP; 2278c2ecf20Sopenharmony_ci } 2288c2ecf20Sopenharmony_ci} 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_cistatic int axi_fan_control_read(struct device *dev, 2318c2ecf20Sopenharmony_ci enum hwmon_sensor_types type, 2328c2ecf20Sopenharmony_ci u32 attr, int channel, long *val) 2338c2ecf20Sopenharmony_ci{ 2348c2ecf20Sopenharmony_ci switch (type) { 2358c2ecf20Sopenharmony_ci case hwmon_fan: 2368c2ecf20Sopenharmony_ci return axi_fan_control_read_fan(dev, attr, val); 2378c2ecf20Sopenharmony_ci case hwmon_pwm: 2388c2ecf20Sopenharmony_ci return axi_fan_control_read_pwm(dev, attr, val); 2398c2ecf20Sopenharmony_ci case hwmon_temp: 2408c2ecf20Sopenharmony_ci return axi_fan_control_read_temp(dev, attr, val); 2418c2ecf20Sopenharmony_ci default: 2428c2ecf20Sopenharmony_ci return -ENOTSUPP; 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cistatic int axi_fan_control_write(struct device *dev, 2478c2ecf20Sopenharmony_ci enum hwmon_sensor_types type, 2488c2ecf20Sopenharmony_ci u32 attr, int channel, long val) 2498c2ecf20Sopenharmony_ci{ 2508c2ecf20Sopenharmony_ci switch (type) { 2518c2ecf20Sopenharmony_ci case hwmon_pwm: 2528c2ecf20Sopenharmony_ci return axi_fan_control_write_pwm(dev, attr, val); 2538c2ecf20Sopenharmony_ci default: 2548c2ecf20Sopenharmony_ci return -ENOTSUPP; 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci} 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_cistatic umode_t axi_fan_control_fan_is_visible(const u32 attr) 2598c2ecf20Sopenharmony_ci{ 2608c2ecf20Sopenharmony_ci switch (attr) { 2618c2ecf20Sopenharmony_ci case hwmon_fan_input: 2628c2ecf20Sopenharmony_ci case hwmon_fan_fault: 2638c2ecf20Sopenharmony_ci case hwmon_fan_label: 2648c2ecf20Sopenharmony_ci return 0444; 2658c2ecf20Sopenharmony_ci default: 2668c2ecf20Sopenharmony_ci return 0; 2678c2ecf20Sopenharmony_ci } 2688c2ecf20Sopenharmony_ci} 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_cistatic umode_t axi_fan_control_pwm_is_visible(const u32 attr) 2718c2ecf20Sopenharmony_ci{ 2728c2ecf20Sopenharmony_ci switch (attr) { 2738c2ecf20Sopenharmony_ci case hwmon_pwm_input: 2748c2ecf20Sopenharmony_ci return 0644; 2758c2ecf20Sopenharmony_ci default: 2768c2ecf20Sopenharmony_ci return 0; 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci} 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_cistatic umode_t axi_fan_control_temp_is_visible(const u32 attr) 2818c2ecf20Sopenharmony_ci{ 2828c2ecf20Sopenharmony_ci switch (attr) { 2838c2ecf20Sopenharmony_ci case hwmon_temp_input: 2848c2ecf20Sopenharmony_ci case hwmon_temp_label: 2858c2ecf20Sopenharmony_ci return 0444; 2868c2ecf20Sopenharmony_ci default: 2878c2ecf20Sopenharmony_ci return 0; 2888c2ecf20Sopenharmony_ci } 2898c2ecf20Sopenharmony_ci} 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_cistatic umode_t axi_fan_control_is_visible(const void *data, 2928c2ecf20Sopenharmony_ci enum hwmon_sensor_types type, 2938c2ecf20Sopenharmony_ci u32 attr, int channel) 2948c2ecf20Sopenharmony_ci{ 2958c2ecf20Sopenharmony_ci switch (type) { 2968c2ecf20Sopenharmony_ci case hwmon_fan: 2978c2ecf20Sopenharmony_ci return axi_fan_control_fan_is_visible(attr); 2988c2ecf20Sopenharmony_ci case hwmon_pwm: 2998c2ecf20Sopenharmony_ci return axi_fan_control_pwm_is_visible(attr); 3008c2ecf20Sopenharmony_ci case hwmon_temp: 3018c2ecf20Sopenharmony_ci return axi_fan_control_temp_is_visible(attr); 3028c2ecf20Sopenharmony_ci default: 3038c2ecf20Sopenharmony_ci return 0; 3048c2ecf20Sopenharmony_ci } 3058c2ecf20Sopenharmony_ci} 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci/* 3088c2ecf20Sopenharmony_ci * This core has two main ways of changing the PWM duty cycle. It is done, 3098c2ecf20Sopenharmony_ci * either by a request from userspace (writing on pwm1_input) or by the 3108c2ecf20Sopenharmony_ci * core itself. When the change is done by the core, it will use predefined 3118c2ecf20Sopenharmony_ci * parameters to evaluate the tach signal and, on that case we cannot set them. 3128c2ecf20Sopenharmony_ci * On the other hand, when the request is done by the user, with some arbitrary 3138c2ecf20Sopenharmony_ci * value that the core does not now about, we have to provide the tach 3148c2ecf20Sopenharmony_ci * parameters so that, the core can evaluate the signal. On the IRQ handler we 3158c2ecf20Sopenharmony_ci * distinguish this by using the ADI_IRQ_SRC_TEMP_INCREASE interrupt. This tell 3168c2ecf20Sopenharmony_ci * us that the CORE requested a new duty cycle. After this, there is 5s delay 3178c2ecf20Sopenharmony_ci * on which the core waits for the fan rotation speed to stabilize. After this 3188c2ecf20Sopenharmony_ci * we get ADI_IRQ_SRC_PWM_CHANGED irq where we will decide if we need to set 3198c2ecf20Sopenharmony_ci * the tach parameters or not on the next tach measurement cycle (corresponding 3208c2ecf20Sopenharmony_ci * already to the ney duty cycle) based on the %ctl->hw_pwm_req flag. 3218c2ecf20Sopenharmony_ci */ 3228c2ecf20Sopenharmony_cistatic irqreturn_t axi_fan_control_irq_handler(int irq, void *data) 3238c2ecf20Sopenharmony_ci{ 3248c2ecf20Sopenharmony_ci struct axi_fan_control_data *ctl = (struct axi_fan_control_data *)data; 3258c2ecf20Sopenharmony_ci u32 irq_pending = axi_ioread(ADI_REG_IRQ_PENDING, ctl); 3268c2ecf20Sopenharmony_ci u32 clear_mask; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci if (irq_pending & ADI_IRQ_SRC_NEW_MEASUR) { 3298c2ecf20Sopenharmony_ci if (ctl->update_tacho_params) { 3308c2ecf20Sopenharmony_ci u32 new_tach = axi_ioread(ADI_REG_TACH_MEASUR, ctl); 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci /* get 25% tolerance */ 3338c2ecf20Sopenharmony_ci u32 tach_tol = DIV_ROUND_CLOSEST(new_tach * 25, 100); 3348c2ecf20Sopenharmony_ci /* set new tacho parameters */ 3358c2ecf20Sopenharmony_ci axi_iowrite(new_tach, ADI_REG_TACH_PERIOD, ctl); 3368c2ecf20Sopenharmony_ci axi_iowrite(tach_tol, ADI_REG_TACH_TOLERANCE, ctl); 3378c2ecf20Sopenharmony_ci ctl->update_tacho_params = false; 3388c2ecf20Sopenharmony_ci } 3398c2ecf20Sopenharmony_ci } 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci if (irq_pending & ADI_IRQ_SRC_PWM_CHANGED) { 3428c2ecf20Sopenharmony_ci /* 3438c2ecf20Sopenharmony_ci * if the pwm changes on behalf of software, 3448c2ecf20Sopenharmony_ci * we need to provide new tacho parameters to the core. 3458c2ecf20Sopenharmony_ci * Wait for the next measurement for that... 3468c2ecf20Sopenharmony_ci */ 3478c2ecf20Sopenharmony_ci if (!ctl->hw_pwm_req) { 3488c2ecf20Sopenharmony_ci ctl->update_tacho_params = true; 3498c2ecf20Sopenharmony_ci } else { 3508c2ecf20Sopenharmony_ci ctl->hw_pwm_req = false; 3518c2ecf20Sopenharmony_ci sysfs_notify(&ctl->hdev->kobj, NULL, "pwm1"); 3528c2ecf20Sopenharmony_ci } 3538c2ecf20Sopenharmony_ci } 3548c2ecf20Sopenharmony_ci 3558c2ecf20Sopenharmony_ci if (irq_pending & ADI_IRQ_SRC_TEMP_INCREASE) 3568c2ecf20Sopenharmony_ci /* hardware requested a new pwm */ 3578c2ecf20Sopenharmony_ci ctl->hw_pwm_req = true; 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci if (irq_pending & ADI_IRQ_SRC_TACH_ERR) 3608c2ecf20Sopenharmony_ci ctl->fan_fault = 1; 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci /* clear all interrupts */ 3638c2ecf20Sopenharmony_ci clear_mask = irq_pending & ADI_IRQ_SRC_MASK; 3648c2ecf20Sopenharmony_ci axi_iowrite(clear_mask, ADI_REG_IRQ_PENDING, ctl); 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci return IRQ_HANDLED; 3678c2ecf20Sopenharmony_ci} 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_cistatic int axi_fan_control_init(struct axi_fan_control_data *ctl, 3708c2ecf20Sopenharmony_ci const struct device_node *np) 3718c2ecf20Sopenharmony_ci{ 3728c2ecf20Sopenharmony_ci int ret; 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci /* get fan pulses per revolution */ 3758c2ecf20Sopenharmony_ci ret = of_property_read_u32(np, "pulses-per-revolution", &ctl->ppr); 3768c2ecf20Sopenharmony_ci if (ret) 3778c2ecf20Sopenharmony_ci return ret; 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_ci /* 1, 2 and 4 are the typical and accepted values */ 3808c2ecf20Sopenharmony_ci if (ctl->ppr != 1 && ctl->ppr != 2 && ctl->ppr != 4) 3818c2ecf20Sopenharmony_ci return -EINVAL; 3828c2ecf20Sopenharmony_ci /* 3838c2ecf20Sopenharmony_ci * Enable all IRQs 3848c2ecf20Sopenharmony_ci */ 3858c2ecf20Sopenharmony_ci axi_iowrite(ADI_IRQ_MASK_OUT_ALL & 3868c2ecf20Sopenharmony_ci ~(ADI_IRQ_SRC_NEW_MEASUR | ADI_IRQ_SRC_TACH_ERR | 3878c2ecf20Sopenharmony_ci ADI_IRQ_SRC_PWM_CHANGED | ADI_IRQ_SRC_TEMP_INCREASE), 3888c2ecf20Sopenharmony_ci ADI_REG_IRQ_MASK, ctl); 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci /* bring the device out of reset */ 3918c2ecf20Sopenharmony_ci axi_iowrite(0x01, ADI_REG_RSTN, ctl); 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_ci return ret; 3948c2ecf20Sopenharmony_ci} 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_cistatic const struct hwmon_channel_info *axi_fan_control_info[] = { 3978c2ecf20Sopenharmony_ci HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT), 3988c2ecf20Sopenharmony_ci HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_LABEL), 3998c2ecf20Sopenharmony_ci HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), 4008c2ecf20Sopenharmony_ci NULL 4018c2ecf20Sopenharmony_ci}; 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_cistatic const struct hwmon_ops axi_fan_control_hwmon_ops = { 4048c2ecf20Sopenharmony_ci .is_visible = axi_fan_control_is_visible, 4058c2ecf20Sopenharmony_ci .read = axi_fan_control_read, 4068c2ecf20Sopenharmony_ci .write = axi_fan_control_write, 4078c2ecf20Sopenharmony_ci .read_string = axi_fan_control_read_labels, 4088c2ecf20Sopenharmony_ci}; 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_cistatic const struct hwmon_chip_info axi_chip_info = { 4118c2ecf20Sopenharmony_ci .ops = &axi_fan_control_hwmon_ops, 4128c2ecf20Sopenharmony_ci .info = axi_fan_control_info, 4138c2ecf20Sopenharmony_ci}; 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_ci/* temperature threshold below which PWM should be 0% */ 4168c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp_hyst, axi_fan_control, ADI_REG_TEMP_00_H); 4178c2ecf20Sopenharmony_ci/* temperature threshold above which PWM should be 25% */ 4188c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, axi_fan_control, ADI_REG_TEMP_25_L); 4198c2ecf20Sopenharmony_ci/* temperature threshold below which PWM should be 25% */ 4208c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp_hyst, axi_fan_control, ADI_REG_TEMP_25_H); 4218c2ecf20Sopenharmony_ci/* temperature threshold above which PWM should be 50% */ 4228c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, axi_fan_control, ADI_REG_TEMP_50_L); 4238c2ecf20Sopenharmony_ci/* temperature threshold below which PWM should be 50% */ 4248c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp_hyst, axi_fan_control, ADI_REG_TEMP_50_H); 4258c2ecf20Sopenharmony_ci/* temperature threshold above which PWM should be 75% */ 4268c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, axi_fan_control, ADI_REG_TEMP_75_L); 4278c2ecf20Sopenharmony_ci/* temperature threshold below which PWM should be 75% */ 4288c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp_hyst, axi_fan_control, ADI_REG_TEMP_75_H); 4298c2ecf20Sopenharmony_ci/* temperature threshold above which PWM should be 100% */ 4308c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, axi_fan_control, ADI_REG_TEMP_100_L); 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_cistatic struct attribute *axi_fan_control_attrs[] = { 4338c2ecf20Sopenharmony_ci &sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr, 4348c2ecf20Sopenharmony_ci &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, 4358c2ecf20Sopenharmony_ci &sensor_dev_attr_pwm1_auto_point2_temp_hyst.dev_attr.attr, 4368c2ecf20Sopenharmony_ci &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, 4378c2ecf20Sopenharmony_ci &sensor_dev_attr_pwm1_auto_point3_temp_hyst.dev_attr.attr, 4388c2ecf20Sopenharmony_ci &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, 4398c2ecf20Sopenharmony_ci &sensor_dev_attr_pwm1_auto_point4_temp_hyst.dev_attr.attr, 4408c2ecf20Sopenharmony_ci &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, 4418c2ecf20Sopenharmony_ci NULL, 4428c2ecf20Sopenharmony_ci}; 4438c2ecf20Sopenharmony_ciATTRIBUTE_GROUPS(axi_fan_control); 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_cistatic const u32 version_1_0_0 = ADI_AXI_PCORE_VER(1, 0, 'a'); 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_cistatic const struct of_device_id axi_fan_control_of_match[] = { 4488c2ecf20Sopenharmony_ci { .compatible = "adi,axi-fan-control-1.00.a", 4498c2ecf20Sopenharmony_ci .data = (void *)&version_1_0_0}, 4508c2ecf20Sopenharmony_ci {}, 4518c2ecf20Sopenharmony_ci}; 4528c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, axi_fan_control_of_match); 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_cistatic int axi_fan_control_probe(struct platform_device *pdev) 4558c2ecf20Sopenharmony_ci{ 4568c2ecf20Sopenharmony_ci struct axi_fan_control_data *ctl; 4578c2ecf20Sopenharmony_ci struct clk *clk; 4588c2ecf20Sopenharmony_ci const struct of_device_id *id; 4598c2ecf20Sopenharmony_ci const char *name = "axi_fan_control"; 4608c2ecf20Sopenharmony_ci u32 version; 4618c2ecf20Sopenharmony_ci int ret; 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci id = of_match_node(axi_fan_control_of_match, pdev->dev.of_node); 4648c2ecf20Sopenharmony_ci if (!id) 4658c2ecf20Sopenharmony_ci return -EINVAL; 4668c2ecf20Sopenharmony_ci 4678c2ecf20Sopenharmony_ci ctl = devm_kzalloc(&pdev->dev, sizeof(*ctl), GFP_KERNEL); 4688c2ecf20Sopenharmony_ci if (!ctl) 4698c2ecf20Sopenharmony_ci return -ENOMEM; 4708c2ecf20Sopenharmony_ci 4718c2ecf20Sopenharmony_ci ctl->base = devm_platform_ioremap_resource(pdev, 0); 4728c2ecf20Sopenharmony_ci if (IS_ERR(ctl->base)) 4738c2ecf20Sopenharmony_ci return PTR_ERR(ctl->base); 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci clk = devm_clk_get(&pdev->dev, NULL); 4768c2ecf20Sopenharmony_ci if (IS_ERR(clk)) { 4778c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "clk_get failed with %ld\n", PTR_ERR(clk)); 4788c2ecf20Sopenharmony_ci return PTR_ERR(clk); 4798c2ecf20Sopenharmony_ci } 4808c2ecf20Sopenharmony_ci 4818c2ecf20Sopenharmony_ci ctl->clk_rate = clk_get_rate(clk); 4828c2ecf20Sopenharmony_ci if (!ctl->clk_rate) 4838c2ecf20Sopenharmony_ci return -EINVAL; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci version = axi_ioread(ADI_AXI_REG_VERSION, ctl); 4868c2ecf20Sopenharmony_ci if (ADI_AXI_PCORE_VER_MAJOR(version) != 4878c2ecf20Sopenharmony_ci ADI_AXI_PCORE_VER_MAJOR((*(u32 *)id->data))) { 4888c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", 4898c2ecf20Sopenharmony_ci ADI_AXI_PCORE_VER_MAJOR((*(u32 *)id->data)), 4908c2ecf20Sopenharmony_ci ADI_AXI_PCORE_VER_MINOR((*(u32 *)id->data)), 4918c2ecf20Sopenharmony_ci ADI_AXI_PCORE_VER_PATCH((*(u32 *)id->data)), 4928c2ecf20Sopenharmony_ci ADI_AXI_PCORE_VER_MAJOR(version), 4938c2ecf20Sopenharmony_ci ADI_AXI_PCORE_VER_MINOR(version), 4948c2ecf20Sopenharmony_ci ADI_AXI_PCORE_VER_PATCH(version)); 4958c2ecf20Sopenharmony_ci return -ENODEV; 4968c2ecf20Sopenharmony_ci } 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci ret = axi_fan_control_init(ctl, pdev->dev.of_node); 4998c2ecf20Sopenharmony_ci if (ret) { 5008c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed to initialize device\n"); 5018c2ecf20Sopenharmony_ci return ret; 5028c2ecf20Sopenharmony_ci } 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci ctl->hdev = devm_hwmon_device_register_with_info(&pdev->dev, 5058c2ecf20Sopenharmony_ci name, 5068c2ecf20Sopenharmony_ci ctl, 5078c2ecf20Sopenharmony_ci &axi_chip_info, 5088c2ecf20Sopenharmony_ci axi_fan_control_groups); 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci if (IS_ERR(ctl->hdev)) 5118c2ecf20Sopenharmony_ci return PTR_ERR(ctl->hdev); 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ci ctl->irq = platform_get_irq(pdev, 0); 5148c2ecf20Sopenharmony_ci if (ctl->irq < 0) 5158c2ecf20Sopenharmony_ci return ctl->irq; 5168c2ecf20Sopenharmony_ci 5178c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(&pdev->dev, ctl->irq, NULL, 5188c2ecf20Sopenharmony_ci axi_fan_control_irq_handler, 5198c2ecf20Sopenharmony_ci IRQF_ONESHOT | IRQF_TRIGGER_HIGH, 5208c2ecf20Sopenharmony_ci pdev->driver_override, ctl); 5218c2ecf20Sopenharmony_ci if (ret) { 5228c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to request an irq, %d", ret); 5238c2ecf20Sopenharmony_ci return ret; 5248c2ecf20Sopenharmony_ci } 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_ci return 0; 5278c2ecf20Sopenharmony_ci} 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_cistatic struct platform_driver axi_fan_control_driver = { 5308c2ecf20Sopenharmony_ci .driver = { 5318c2ecf20Sopenharmony_ci .name = "axi_fan_control_driver", 5328c2ecf20Sopenharmony_ci .of_match_table = axi_fan_control_of_match, 5338c2ecf20Sopenharmony_ci }, 5348c2ecf20Sopenharmony_ci .probe = axi_fan_control_probe, 5358c2ecf20Sopenharmony_ci}; 5368c2ecf20Sopenharmony_cimodule_platform_driver(axi_fan_control_driver); 5378c2ecf20Sopenharmony_ci 5388c2ecf20Sopenharmony_ciMODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>"); 5398c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Analog Devices Fan Control HDL CORE driver"); 5408c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 541