18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// Copyright (c) 2018 Mellanox Technologies. All rights reserved. 48c2ecf20Sopenharmony_ci// Copyright (c) 2018 Vadim Pasternak <vadimp@mellanox.com> 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/bitops.h> 78c2ecf20Sopenharmony_ci#include <linux/device.h> 88c2ecf20Sopenharmony_ci#include <linux/hwmon.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/platform_data/mlxreg.h> 118c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 128c2ecf20Sopenharmony_ci#include <linux/regmap.h> 138c2ecf20Sopenharmony_ci#include <linux/thermal.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#define MLXREG_FAN_MAX_TACHO 12 168c2ecf20Sopenharmony_ci#define MLXREG_FAN_MAX_STATE 10 178c2ecf20Sopenharmony_ci#define MLXREG_FAN_MIN_DUTY 51 /* 20% */ 188c2ecf20Sopenharmony_ci#define MLXREG_FAN_MAX_DUTY 255 /* 100% */ 198c2ecf20Sopenharmony_ci/* 208c2ecf20Sopenharmony_ci * Minimum and maximum FAN allowed speed in percent: from 20% to 100%. Values 218c2ecf20Sopenharmony_ci * MLXREG_FAN_MAX_STATE + x, where x is between 2 and 10 are used for 228c2ecf20Sopenharmony_ci * setting FAN speed dynamic minimum. For example, if value is set to 14 (40%) 238c2ecf20Sopenharmony_ci * cooling levels vector will be set to 4, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10 to 248c2ecf20Sopenharmony_ci * introduce PWM speed in percent: 40, 40, 40, 40, 40, 50, 60. 70, 80, 90, 100. 258c2ecf20Sopenharmony_ci */ 268c2ecf20Sopenharmony_ci#define MLXREG_FAN_SPEED_MIN (MLXREG_FAN_MAX_STATE + 2) 278c2ecf20Sopenharmony_ci#define MLXREG_FAN_SPEED_MAX (MLXREG_FAN_MAX_STATE * 2) 288c2ecf20Sopenharmony_ci#define MLXREG_FAN_SPEED_MIN_LEVEL 2 /* 20 percent */ 298c2ecf20Sopenharmony_ci#define MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF 44 308c2ecf20Sopenharmony_ci#define MLXREG_FAN_TACHO_DIV_MIN 283 318c2ecf20Sopenharmony_ci#define MLXREG_FAN_TACHO_DIV_DEF (MLXREG_FAN_TACHO_DIV_MIN * 4) 328c2ecf20Sopenharmony_ci#define MLXREG_FAN_TACHO_DIV_SCALE_MAX 64 338c2ecf20Sopenharmony_ci/* 348c2ecf20Sopenharmony_ci * FAN datasheet defines the formula for RPM calculations as RPM = 15/t-high. 358c2ecf20Sopenharmony_ci * The logic in a programmable device measures the time t-high by sampling the 368c2ecf20Sopenharmony_ci * tachometer every t-sample (with the default value 11.32 uS) and increment 378c2ecf20Sopenharmony_ci * a counter (N) as long as the pulse has not change: 388c2ecf20Sopenharmony_ci * RPM = 15 / (t-sample * (K + Regval)), where: 398c2ecf20Sopenharmony_ci * Regval: is the value read from the programmable device register; 408c2ecf20Sopenharmony_ci * - 0xff - represents tachometer fault; 418c2ecf20Sopenharmony_ci * - 0xfe - represents tachometer minimum value , which is 4444 RPM; 428c2ecf20Sopenharmony_ci * - 0x00 - represents tachometer maximum value , which is 300000 RPM; 438c2ecf20Sopenharmony_ci * K: is 44 and it represents the minimum allowed samples per pulse; 448c2ecf20Sopenharmony_ci * N: is equal K + Regval; 458c2ecf20Sopenharmony_ci * In order to calculate RPM from the register value the following formula is 468c2ecf20Sopenharmony_ci * used: RPM = 15 / ((Regval + K) * 11.32) * 10^(-6)), which in the 478c2ecf20Sopenharmony_ci * default case is modified to: 488c2ecf20Sopenharmony_ci * RPM = 15000000 * 100 / ((Regval + 44) * 1132); 498c2ecf20Sopenharmony_ci * - for Regval 0x00, RPM will be 15000000 * 100 / (44 * 1132) = 30115; 508c2ecf20Sopenharmony_ci * - for Regval 0xfe, RPM will be 15000000 * 100 / ((254 + 44) * 1132) = 4446; 518c2ecf20Sopenharmony_ci * In common case the formula is modified to: 528c2ecf20Sopenharmony_ci * RPM = 15000000 * 100 / ((Regval + samples) * divider). 538c2ecf20Sopenharmony_ci */ 548c2ecf20Sopenharmony_ci#define MLXREG_FAN_GET_RPM(rval, d, s) (DIV_ROUND_CLOSEST(15000000 * 100, \ 558c2ecf20Sopenharmony_ci ((rval) + (s)) * (d))) 568c2ecf20Sopenharmony_ci#define MLXREG_FAN_GET_FAULT(val, mask) ((val) == (mask)) 578c2ecf20Sopenharmony_ci#define MLXREG_FAN_PWM_DUTY2STATE(duty) (DIV_ROUND_CLOSEST((duty) * \ 588c2ecf20Sopenharmony_ci MLXREG_FAN_MAX_STATE, \ 598c2ecf20Sopenharmony_ci MLXREG_FAN_MAX_DUTY)) 608c2ecf20Sopenharmony_ci#define MLXREG_FAN_PWM_STATE2DUTY(stat) (DIV_ROUND_CLOSEST((stat) * \ 618c2ecf20Sopenharmony_ci MLXREG_FAN_MAX_DUTY, \ 628c2ecf20Sopenharmony_ci MLXREG_FAN_MAX_STATE)) 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci/* 658c2ecf20Sopenharmony_ci * struct mlxreg_fan_tacho - tachometer data (internal use): 668c2ecf20Sopenharmony_ci * 678c2ecf20Sopenharmony_ci * @connected: indicates if tachometer is connected; 688c2ecf20Sopenharmony_ci * @reg: register offset; 698c2ecf20Sopenharmony_ci * @mask: fault mask; 708c2ecf20Sopenharmony_ci */ 718c2ecf20Sopenharmony_cistruct mlxreg_fan_tacho { 728c2ecf20Sopenharmony_ci bool connected; 738c2ecf20Sopenharmony_ci u32 reg; 748c2ecf20Sopenharmony_ci u32 mask; 758c2ecf20Sopenharmony_ci}; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci/* 788c2ecf20Sopenharmony_ci * struct mlxreg_fan_pwm - PWM data (internal use): 798c2ecf20Sopenharmony_ci * 808c2ecf20Sopenharmony_ci * @connected: indicates if PWM is connected; 818c2ecf20Sopenharmony_ci * @reg: register offset; 828c2ecf20Sopenharmony_ci */ 838c2ecf20Sopenharmony_cistruct mlxreg_fan_pwm { 848c2ecf20Sopenharmony_ci bool connected; 858c2ecf20Sopenharmony_ci u32 reg; 868c2ecf20Sopenharmony_ci}; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci/* 898c2ecf20Sopenharmony_ci * struct mlxreg_fan - private data (internal use): 908c2ecf20Sopenharmony_ci * 918c2ecf20Sopenharmony_ci * @dev: basic device; 928c2ecf20Sopenharmony_ci * @regmap: register map of parent device; 938c2ecf20Sopenharmony_ci * @tacho: tachometer data; 948c2ecf20Sopenharmony_ci * @pwm: PWM data; 958c2ecf20Sopenharmony_ci * @samples: minimum allowed samples per pulse; 968c2ecf20Sopenharmony_ci * @divider: divider value for tachometer RPM calculation; 978c2ecf20Sopenharmony_ci * @cooling: cooling device levels; 988c2ecf20Sopenharmony_ci * @cdev: cooling device; 998c2ecf20Sopenharmony_ci */ 1008c2ecf20Sopenharmony_cistruct mlxreg_fan { 1018c2ecf20Sopenharmony_ci struct device *dev; 1028c2ecf20Sopenharmony_ci void *regmap; 1038c2ecf20Sopenharmony_ci struct mlxreg_core_platform_data *pdata; 1048c2ecf20Sopenharmony_ci struct mlxreg_fan_tacho tacho[MLXREG_FAN_MAX_TACHO]; 1058c2ecf20Sopenharmony_ci struct mlxreg_fan_pwm pwm; 1068c2ecf20Sopenharmony_ci int samples; 1078c2ecf20Sopenharmony_ci int divider; 1088c2ecf20Sopenharmony_ci u8 cooling_levels[MLXREG_FAN_MAX_STATE + 1]; 1098c2ecf20Sopenharmony_ci struct thermal_cooling_device *cdev; 1108c2ecf20Sopenharmony_ci}; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic int 1138c2ecf20Sopenharmony_cimlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, 1148c2ecf20Sopenharmony_ci int channel, long *val) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci struct mlxreg_fan *fan = dev_get_drvdata(dev); 1178c2ecf20Sopenharmony_ci struct mlxreg_fan_tacho *tacho; 1188c2ecf20Sopenharmony_ci u32 regval; 1198c2ecf20Sopenharmony_ci int err; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci switch (type) { 1228c2ecf20Sopenharmony_ci case hwmon_fan: 1238c2ecf20Sopenharmony_ci tacho = &fan->tacho[channel]; 1248c2ecf20Sopenharmony_ci switch (attr) { 1258c2ecf20Sopenharmony_ci case hwmon_fan_input: 1268c2ecf20Sopenharmony_ci err = regmap_read(fan->regmap, tacho->reg, ®val); 1278c2ecf20Sopenharmony_ci if (err) 1288c2ecf20Sopenharmony_ci return err; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci if (MLXREG_FAN_GET_FAULT(regval, tacho->mask)) { 1318c2ecf20Sopenharmony_ci /* FAN is broken - return zero for FAN speed. */ 1328c2ecf20Sopenharmony_ci *val = 0; 1338c2ecf20Sopenharmony_ci return 0; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci *val = MLXREG_FAN_GET_RPM(regval, fan->divider, 1378c2ecf20Sopenharmony_ci fan->samples); 1388c2ecf20Sopenharmony_ci break; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci case hwmon_fan_fault: 1418c2ecf20Sopenharmony_ci err = regmap_read(fan->regmap, tacho->reg, ®val); 1428c2ecf20Sopenharmony_ci if (err) 1438c2ecf20Sopenharmony_ci return err; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci *val = MLXREG_FAN_GET_FAULT(regval, tacho->mask); 1468c2ecf20Sopenharmony_ci break; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci default: 1498c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci break; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci case hwmon_pwm: 1548c2ecf20Sopenharmony_ci switch (attr) { 1558c2ecf20Sopenharmony_ci case hwmon_pwm_input: 1568c2ecf20Sopenharmony_ci err = regmap_read(fan->regmap, fan->pwm.reg, ®val); 1578c2ecf20Sopenharmony_ci if (err) 1588c2ecf20Sopenharmony_ci return err; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci *val = regval; 1618c2ecf20Sopenharmony_ci break; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci default: 1648c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci break; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci default: 1698c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci return 0; 1738c2ecf20Sopenharmony_ci} 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_cistatic int 1768c2ecf20Sopenharmony_cimlxreg_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, 1778c2ecf20Sopenharmony_ci int channel, long val) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci struct mlxreg_fan *fan = dev_get_drvdata(dev); 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci switch (type) { 1828c2ecf20Sopenharmony_ci case hwmon_pwm: 1838c2ecf20Sopenharmony_ci switch (attr) { 1848c2ecf20Sopenharmony_ci case hwmon_pwm_input: 1858c2ecf20Sopenharmony_ci if (val < MLXREG_FAN_MIN_DUTY || 1868c2ecf20Sopenharmony_ci val > MLXREG_FAN_MAX_DUTY) 1878c2ecf20Sopenharmony_ci return -EINVAL; 1888c2ecf20Sopenharmony_ci return regmap_write(fan->regmap, fan->pwm.reg, val); 1898c2ecf20Sopenharmony_ci default: 1908c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci break; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci default: 1958c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1968c2ecf20Sopenharmony_ci } 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1998c2ecf20Sopenharmony_ci} 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_cistatic umode_t 2028c2ecf20Sopenharmony_cimlxreg_fan_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, 2038c2ecf20Sopenharmony_ci int channel) 2048c2ecf20Sopenharmony_ci{ 2058c2ecf20Sopenharmony_ci switch (type) { 2068c2ecf20Sopenharmony_ci case hwmon_fan: 2078c2ecf20Sopenharmony_ci if (!(((struct mlxreg_fan *)data)->tacho[channel].connected)) 2088c2ecf20Sopenharmony_ci return 0; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci switch (attr) { 2118c2ecf20Sopenharmony_ci case hwmon_fan_input: 2128c2ecf20Sopenharmony_ci case hwmon_fan_fault: 2138c2ecf20Sopenharmony_ci return 0444; 2148c2ecf20Sopenharmony_ci default: 2158c2ecf20Sopenharmony_ci break; 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci break; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci case hwmon_pwm: 2208c2ecf20Sopenharmony_ci if (!(((struct mlxreg_fan *)data)->pwm.connected)) 2218c2ecf20Sopenharmony_ci return 0; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci switch (attr) { 2248c2ecf20Sopenharmony_ci case hwmon_pwm_input: 2258c2ecf20Sopenharmony_ci return 0644; 2268c2ecf20Sopenharmony_ci default: 2278c2ecf20Sopenharmony_ci break; 2288c2ecf20Sopenharmony_ci } 2298c2ecf20Sopenharmony_ci break; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci default: 2328c2ecf20Sopenharmony_ci break; 2338c2ecf20Sopenharmony_ci } 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci return 0; 2368c2ecf20Sopenharmony_ci} 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_cistatic const struct hwmon_channel_info *mlxreg_fan_hwmon_info[] = { 2398c2ecf20Sopenharmony_ci HWMON_CHANNEL_INFO(fan, 2408c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_FAULT, 2418c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_FAULT, 2428c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_FAULT, 2438c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_FAULT, 2448c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_FAULT, 2458c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_FAULT, 2468c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_FAULT, 2478c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_FAULT, 2488c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_FAULT, 2498c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_FAULT, 2508c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_FAULT, 2518c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_FAULT), 2528c2ecf20Sopenharmony_ci HWMON_CHANNEL_INFO(pwm, 2538c2ecf20Sopenharmony_ci HWMON_PWM_INPUT), 2548c2ecf20Sopenharmony_ci NULL 2558c2ecf20Sopenharmony_ci}; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_cistatic const struct hwmon_ops mlxreg_fan_hwmon_hwmon_ops = { 2588c2ecf20Sopenharmony_ci .is_visible = mlxreg_fan_is_visible, 2598c2ecf20Sopenharmony_ci .read = mlxreg_fan_read, 2608c2ecf20Sopenharmony_ci .write = mlxreg_fan_write, 2618c2ecf20Sopenharmony_ci}; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_cistatic const struct hwmon_chip_info mlxreg_fan_hwmon_chip_info = { 2648c2ecf20Sopenharmony_ci .ops = &mlxreg_fan_hwmon_hwmon_ops, 2658c2ecf20Sopenharmony_ci .info = mlxreg_fan_hwmon_info, 2668c2ecf20Sopenharmony_ci}; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_cistatic int mlxreg_fan_get_max_state(struct thermal_cooling_device *cdev, 2698c2ecf20Sopenharmony_ci unsigned long *state) 2708c2ecf20Sopenharmony_ci{ 2718c2ecf20Sopenharmony_ci *state = MLXREG_FAN_MAX_STATE; 2728c2ecf20Sopenharmony_ci return 0; 2738c2ecf20Sopenharmony_ci} 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_cistatic int mlxreg_fan_get_cur_state(struct thermal_cooling_device *cdev, 2768c2ecf20Sopenharmony_ci unsigned long *state) 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci{ 2798c2ecf20Sopenharmony_ci struct mlxreg_fan *fan = cdev->devdata; 2808c2ecf20Sopenharmony_ci u32 regval; 2818c2ecf20Sopenharmony_ci int err; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci err = regmap_read(fan->regmap, fan->pwm.reg, ®val); 2848c2ecf20Sopenharmony_ci if (err) { 2858c2ecf20Sopenharmony_ci dev_err(fan->dev, "Failed to query PWM duty\n"); 2868c2ecf20Sopenharmony_ci return err; 2878c2ecf20Sopenharmony_ci } 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci *state = MLXREG_FAN_PWM_DUTY2STATE(regval); 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci return 0; 2928c2ecf20Sopenharmony_ci} 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_cistatic int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, 2958c2ecf20Sopenharmony_ci unsigned long state) 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci{ 2988c2ecf20Sopenharmony_ci struct mlxreg_fan *fan = cdev->devdata; 2998c2ecf20Sopenharmony_ci unsigned long cur_state; 3008c2ecf20Sopenharmony_ci int i, config = 0; 3018c2ecf20Sopenharmony_ci u32 regval; 3028c2ecf20Sopenharmony_ci int err; 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci /* 3058c2ecf20Sopenharmony_ci * Verify if this request is for changing allowed FAN dynamical 3068c2ecf20Sopenharmony_ci * minimum. If it is - update cooling levels accordingly and update 3078c2ecf20Sopenharmony_ci * state, if current state is below the newly requested minimum state. 3088c2ecf20Sopenharmony_ci * For example, if current state is 5, and minimal state is to be 3098c2ecf20Sopenharmony_ci * changed from 4 to 6, fan->cooling_levels[0 to 5] will be changed all 3108c2ecf20Sopenharmony_ci * from 4 to 6. And state 5 (fan->cooling_levels[4]) should be 3118c2ecf20Sopenharmony_ci * overwritten. 3128c2ecf20Sopenharmony_ci */ 3138c2ecf20Sopenharmony_ci if (state >= MLXREG_FAN_SPEED_MIN && state <= MLXREG_FAN_SPEED_MAX) { 3148c2ecf20Sopenharmony_ci /* 3158c2ecf20Sopenharmony_ci * This is configuration change, which is only supported through sysfs. 3168c2ecf20Sopenharmony_ci * For configuration non-zero value is to be returned to avoid thermal 3178c2ecf20Sopenharmony_ci * statistics update. 3188c2ecf20Sopenharmony_ci */ 3198c2ecf20Sopenharmony_ci config = 1; 3208c2ecf20Sopenharmony_ci state -= MLXREG_FAN_MAX_STATE; 3218c2ecf20Sopenharmony_ci for (i = 0; i < state; i++) 3228c2ecf20Sopenharmony_ci fan->cooling_levels[i] = state; 3238c2ecf20Sopenharmony_ci for (i = state; i <= MLXREG_FAN_MAX_STATE; i++) 3248c2ecf20Sopenharmony_ci fan->cooling_levels[i] = i; 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci err = regmap_read(fan->regmap, fan->pwm.reg, ®val); 3278c2ecf20Sopenharmony_ci if (err) { 3288c2ecf20Sopenharmony_ci dev_err(fan->dev, "Failed to query PWM duty\n"); 3298c2ecf20Sopenharmony_ci return err; 3308c2ecf20Sopenharmony_ci } 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci cur_state = MLXREG_FAN_PWM_DUTY2STATE(regval); 3338c2ecf20Sopenharmony_ci if (state < cur_state) 3348c2ecf20Sopenharmony_ci return config; 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci state = cur_state; 3378c2ecf20Sopenharmony_ci } 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci if (state > MLXREG_FAN_MAX_STATE) 3408c2ecf20Sopenharmony_ci return -EINVAL; 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci /* Normalize the state to the valid speed range. */ 3438c2ecf20Sopenharmony_ci state = fan->cooling_levels[state]; 3448c2ecf20Sopenharmony_ci err = regmap_write(fan->regmap, fan->pwm.reg, 3458c2ecf20Sopenharmony_ci MLXREG_FAN_PWM_STATE2DUTY(state)); 3468c2ecf20Sopenharmony_ci if (err) { 3478c2ecf20Sopenharmony_ci dev_err(fan->dev, "Failed to write PWM duty\n"); 3488c2ecf20Sopenharmony_ci return err; 3498c2ecf20Sopenharmony_ci } 3508c2ecf20Sopenharmony_ci return config; 3518c2ecf20Sopenharmony_ci} 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_cistatic const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = { 3548c2ecf20Sopenharmony_ci .get_max_state = mlxreg_fan_get_max_state, 3558c2ecf20Sopenharmony_ci .get_cur_state = mlxreg_fan_get_cur_state, 3568c2ecf20Sopenharmony_ci .set_cur_state = mlxreg_fan_set_cur_state, 3578c2ecf20Sopenharmony_ci}; 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_cistatic int mlxreg_fan_connect_verify(struct mlxreg_fan *fan, 3608c2ecf20Sopenharmony_ci struct mlxreg_core_data *data) 3618c2ecf20Sopenharmony_ci{ 3628c2ecf20Sopenharmony_ci u32 regval; 3638c2ecf20Sopenharmony_ci int err; 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci err = regmap_read(fan->regmap, data->capability, ®val); 3668c2ecf20Sopenharmony_ci if (err) { 3678c2ecf20Sopenharmony_ci dev_err(fan->dev, "Failed to query capability register 0x%08x\n", 3688c2ecf20Sopenharmony_ci data->capability); 3698c2ecf20Sopenharmony_ci return err; 3708c2ecf20Sopenharmony_ci } 3718c2ecf20Sopenharmony_ci 3728c2ecf20Sopenharmony_ci return !!(regval & data->bit); 3738c2ecf20Sopenharmony_ci} 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_cistatic int mlxreg_fan_speed_divider_get(struct mlxreg_fan *fan, 3768c2ecf20Sopenharmony_ci struct mlxreg_core_data *data) 3778c2ecf20Sopenharmony_ci{ 3788c2ecf20Sopenharmony_ci u32 regval; 3798c2ecf20Sopenharmony_ci int err; 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci err = regmap_read(fan->regmap, data->capability, ®val); 3828c2ecf20Sopenharmony_ci if (err) { 3838c2ecf20Sopenharmony_ci dev_err(fan->dev, "Failed to query capability register 0x%08x\n", 3848c2ecf20Sopenharmony_ci data->capability); 3858c2ecf20Sopenharmony_ci return err; 3868c2ecf20Sopenharmony_ci } 3878c2ecf20Sopenharmony_ci 3888c2ecf20Sopenharmony_ci /* 3898c2ecf20Sopenharmony_ci * Set divider value according to the capability register, in case it 3908c2ecf20Sopenharmony_ci * contains valid value. Otherwise use default value. The purpose of 3918c2ecf20Sopenharmony_ci * this validation is to protect against the old hardware, in which 3928c2ecf20Sopenharmony_ci * this register can return zero. 3938c2ecf20Sopenharmony_ci */ 3948c2ecf20Sopenharmony_ci if (regval > 0 && regval <= MLXREG_FAN_TACHO_DIV_SCALE_MAX) 3958c2ecf20Sopenharmony_ci fan->divider = regval * MLXREG_FAN_TACHO_DIV_MIN; 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci return 0; 3988c2ecf20Sopenharmony_ci} 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_cistatic int mlxreg_fan_config(struct mlxreg_fan *fan, 4018c2ecf20Sopenharmony_ci struct mlxreg_core_platform_data *pdata) 4028c2ecf20Sopenharmony_ci{ 4038c2ecf20Sopenharmony_ci struct mlxreg_core_data *data = pdata->data; 4048c2ecf20Sopenharmony_ci bool configured = false; 4058c2ecf20Sopenharmony_ci int tacho_num = 0, i; 4068c2ecf20Sopenharmony_ci int err; 4078c2ecf20Sopenharmony_ci 4088c2ecf20Sopenharmony_ci fan->samples = MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF; 4098c2ecf20Sopenharmony_ci fan->divider = MLXREG_FAN_TACHO_DIV_DEF; 4108c2ecf20Sopenharmony_ci for (i = 0; i < pdata->counter; i++, data++) { 4118c2ecf20Sopenharmony_ci if (strnstr(data->label, "tacho", sizeof(data->label))) { 4128c2ecf20Sopenharmony_ci if (tacho_num == MLXREG_FAN_MAX_TACHO) { 4138c2ecf20Sopenharmony_ci dev_err(fan->dev, "too many tacho entries: %s\n", 4148c2ecf20Sopenharmony_ci data->label); 4158c2ecf20Sopenharmony_ci return -EINVAL; 4168c2ecf20Sopenharmony_ci } 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_ci if (data->capability) { 4198c2ecf20Sopenharmony_ci err = mlxreg_fan_connect_verify(fan, data); 4208c2ecf20Sopenharmony_ci if (err < 0) 4218c2ecf20Sopenharmony_ci return err; 4228c2ecf20Sopenharmony_ci else if (!err) { 4238c2ecf20Sopenharmony_ci tacho_num++; 4248c2ecf20Sopenharmony_ci continue; 4258c2ecf20Sopenharmony_ci } 4268c2ecf20Sopenharmony_ci } 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci fan->tacho[tacho_num].reg = data->reg; 4298c2ecf20Sopenharmony_ci fan->tacho[tacho_num].mask = data->mask; 4308c2ecf20Sopenharmony_ci fan->tacho[tacho_num++].connected = true; 4318c2ecf20Sopenharmony_ci } else if (strnstr(data->label, "pwm", sizeof(data->label))) { 4328c2ecf20Sopenharmony_ci if (fan->pwm.connected) { 4338c2ecf20Sopenharmony_ci dev_err(fan->dev, "duplicate pwm entry: %s\n", 4348c2ecf20Sopenharmony_ci data->label); 4358c2ecf20Sopenharmony_ci return -EINVAL; 4368c2ecf20Sopenharmony_ci } 4378c2ecf20Sopenharmony_ci fan->pwm.reg = data->reg; 4388c2ecf20Sopenharmony_ci fan->pwm.connected = true; 4398c2ecf20Sopenharmony_ci } else if (strnstr(data->label, "conf", sizeof(data->label))) { 4408c2ecf20Sopenharmony_ci if (configured) { 4418c2ecf20Sopenharmony_ci dev_err(fan->dev, "duplicate conf entry: %s\n", 4428c2ecf20Sopenharmony_ci data->label); 4438c2ecf20Sopenharmony_ci return -EINVAL; 4448c2ecf20Sopenharmony_ci } 4458c2ecf20Sopenharmony_ci /* Validate that conf parameters are not zeros. */ 4468c2ecf20Sopenharmony_ci if (!data->mask && !data->bit && !data->capability) { 4478c2ecf20Sopenharmony_ci dev_err(fan->dev, "invalid conf entry params: %s\n", 4488c2ecf20Sopenharmony_ci data->label); 4498c2ecf20Sopenharmony_ci return -EINVAL; 4508c2ecf20Sopenharmony_ci } 4518c2ecf20Sopenharmony_ci if (data->capability) { 4528c2ecf20Sopenharmony_ci err = mlxreg_fan_speed_divider_get(fan, data); 4538c2ecf20Sopenharmony_ci if (err) 4548c2ecf20Sopenharmony_ci return err; 4558c2ecf20Sopenharmony_ci } else { 4568c2ecf20Sopenharmony_ci if (data->mask) 4578c2ecf20Sopenharmony_ci fan->samples = data->mask; 4588c2ecf20Sopenharmony_ci if (data->bit) 4598c2ecf20Sopenharmony_ci fan->divider = data->bit; 4608c2ecf20Sopenharmony_ci } 4618c2ecf20Sopenharmony_ci configured = true; 4628c2ecf20Sopenharmony_ci } else { 4638c2ecf20Sopenharmony_ci dev_err(fan->dev, "invalid label: %s\n", data->label); 4648c2ecf20Sopenharmony_ci return -EINVAL; 4658c2ecf20Sopenharmony_ci } 4668c2ecf20Sopenharmony_ci } 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_ci /* Init cooling levels per PWM state. */ 4698c2ecf20Sopenharmony_ci for (i = 0; i < MLXREG_FAN_SPEED_MIN_LEVEL; i++) 4708c2ecf20Sopenharmony_ci fan->cooling_levels[i] = MLXREG_FAN_SPEED_MIN_LEVEL; 4718c2ecf20Sopenharmony_ci for (i = MLXREG_FAN_SPEED_MIN_LEVEL; i <= MLXREG_FAN_MAX_STATE; i++) 4728c2ecf20Sopenharmony_ci fan->cooling_levels[i] = i; 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_ci return 0; 4758c2ecf20Sopenharmony_ci} 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_cistatic int mlxreg_fan_probe(struct platform_device *pdev) 4788c2ecf20Sopenharmony_ci{ 4798c2ecf20Sopenharmony_ci struct mlxreg_core_platform_data *pdata; 4808c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 4818c2ecf20Sopenharmony_ci struct mlxreg_fan *fan; 4828c2ecf20Sopenharmony_ci struct device *hwm; 4838c2ecf20Sopenharmony_ci int err; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci pdata = dev_get_platdata(dev); 4868c2ecf20Sopenharmony_ci if (!pdata) { 4878c2ecf20Sopenharmony_ci dev_err(dev, "Failed to get platform data.\n"); 4888c2ecf20Sopenharmony_ci return -EINVAL; 4898c2ecf20Sopenharmony_ci } 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_ci fan = devm_kzalloc(dev, sizeof(*fan), GFP_KERNEL); 4928c2ecf20Sopenharmony_ci if (!fan) 4938c2ecf20Sopenharmony_ci return -ENOMEM; 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ci fan->dev = dev; 4968c2ecf20Sopenharmony_ci fan->regmap = pdata->regmap; 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci err = mlxreg_fan_config(fan, pdata); 4998c2ecf20Sopenharmony_ci if (err) 5008c2ecf20Sopenharmony_ci return err; 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci hwm = devm_hwmon_device_register_with_info(dev, "mlxreg_fan", 5038c2ecf20Sopenharmony_ci fan, 5048c2ecf20Sopenharmony_ci &mlxreg_fan_hwmon_chip_info, 5058c2ecf20Sopenharmony_ci NULL); 5068c2ecf20Sopenharmony_ci if (IS_ERR(hwm)) { 5078c2ecf20Sopenharmony_ci dev_err(dev, "Failed to register hwmon device\n"); 5088c2ecf20Sopenharmony_ci return PTR_ERR(hwm); 5098c2ecf20Sopenharmony_ci } 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci if (IS_REACHABLE(CONFIG_THERMAL)) { 5128c2ecf20Sopenharmony_ci fan->cdev = devm_thermal_of_cooling_device_register(dev, 5138c2ecf20Sopenharmony_ci NULL, "mlxreg_fan", fan, &mlxreg_fan_cooling_ops); 5148c2ecf20Sopenharmony_ci if (IS_ERR(fan->cdev)) { 5158c2ecf20Sopenharmony_ci dev_err(dev, "Failed to register cooling device\n"); 5168c2ecf20Sopenharmony_ci return PTR_ERR(fan->cdev); 5178c2ecf20Sopenharmony_ci } 5188c2ecf20Sopenharmony_ci } 5198c2ecf20Sopenharmony_ci 5208c2ecf20Sopenharmony_ci return 0; 5218c2ecf20Sopenharmony_ci} 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_cistatic struct platform_driver mlxreg_fan_driver = { 5248c2ecf20Sopenharmony_ci .driver = { 5258c2ecf20Sopenharmony_ci .name = "mlxreg-fan", 5268c2ecf20Sopenharmony_ci }, 5278c2ecf20Sopenharmony_ci .probe = mlxreg_fan_probe, 5288c2ecf20Sopenharmony_ci}; 5298c2ecf20Sopenharmony_ci 5308c2ecf20Sopenharmony_cimodule_platform_driver(mlxreg_fan_driver); 5318c2ecf20Sopenharmony_ci 5328c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); 5338c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Mellanox FAN driver"); 5348c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 5358c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:mlxreg-fan"); 536