1 
2 /*
3  * Simple interface library for fan control operations
4  * This file provides interface functions to support pwmtachtool.
5  * Copyright (C) <2019>  <American Megatrends International LLC>
6  *
7  */
8 
9 #include <stdio.h>
10 #include <unistd.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <fcntl.h>
14 #include <sys/ioctl.h>
15 #include <errno.h>
16 #include <string.h>
17 #include "libpwmtach.h"
18 #include "pwmtach_ioctl.h"
19 #include "EINTR_wrappers.h"
20 #include <stdlib.h>
21 
select_sleep(time_t sec,suseconds_t usec)22 void select_sleep(time_t sec,suseconds_t usec)
23 {
24 	struct timeval tv;
25 
26 	tv.tv_sec = sec;
27 	tv.tv_usec = usec;
28 
29 	while(sigwrap_select(0, NULL, NULL, NULL, &tv) < 0);
30 }
31 
32 //support acessing driver using sysfs device file
33 static char DevNodeFileName[50];
34 #define HWMON_DIR "/sys/class/hwmon"
35 
36 //build the pwm and tach access device node file name, and mapping pwm/tach number starting from 1.
37 #define BUILD_PWM_NODE_NAME(buffer,DEV_ID,PWM_NUM)	    snprintf(buffer, sizeof(buffer), "%s%d%s%d", HWMON_DIR "/hwmon",DEV_ID, "/pwm", PWM_NUM+1)
38 #define BUILD_TACH_NODE_NAME(buffer,DEV_ID,TACH_NUM)	snprintf(buffer, sizeof(buffer), "%s%d%s%d%s", HWMON_DIR "/hwmon",DEV_ID, "/fan", TACH_NUM+1,"_input")
39 #define BUILD_FAN_REG_NAME(buffer,DEV_ID,FAN_NUM)	snprintf(buffer, sizeof(buffer), "%s%d%s%d%s", HWMON_DIR "/hwmon",DEV_ID, "/of_node/fan@", FAN_NUM,"/reg")
40 
41 //predefine FAN RPM range, must defined at some where for configuration.
42 #define RPM_MAX         38600
43 #define RPM_MIN         7500
44 #define COUNTERRES_DEF  100
45 
46 /* Check hwmon if exist or not */
pwmtach_directory_check(void)47 static int pwmtach_directory_check(void)
48 {
49 	int retval = 0;
50 	struct stat sb;
51 	if (!(stat("/sys/class/hwmon", &sb) == 0 && S_ISDIR(sb.st_mode)))
52 	{
53 		printf("\"/sys/class/hwmon\" not exist!\n");
54 		retval = -1;
55 	}
56 	return retval;
57 }
58 //Notice: dutycycle_value is one byte (0-255)
SET_PWM_DUTYCYCLE_VALUE(pwmtach_ioctl_data * ppwmtach_arg)59 static int SET_PWM_DUTYCYCLE_VALUE ( pwmtach_ioctl_data  *ppwmtach_arg )
60 {
61 	int retval = 0;
62 	unsigned char dutycycle_value;
63 	int fd;
64 	char duty_num[5];
65 
66 	retval = pwmtach_directory_check();
67 	if(retval != 0)
68 	{
69 		return retval;
70 	}
71 
72 	dutycycle_value = ppwmtach_arg->dutycycle;
73 	BUILD_PWM_NODE_NAME(DevNodeFileName,ppwmtach_arg->dev_id,ppwmtach_arg->pwmnumber);
74 	retval = access(DevNodeFileName,F_OK);
75 	if(retval != 0)
76 	{
77 		return retval;
78 	}
79 
80 	fd = sigwrap_open(DevNodeFileName, O_WRONLY);
81 	if (fd < 0) {
82 		return fd;
83 	}
84 
85 	snprintf(duty_num,5, "%d", dutycycle_value);
86 
87 	if ( write(fd, duty_num, strlen (duty_num)) != (ssize_t )strlen(duty_num)){
88 		printf("%s: Error write dutycycle value %d to pwm %d\n",__FUNCTION__,dutycycle_value,ppwmtach_arg->pwmnumber);
89 		retval = -1;
90 	}
91 	(void)sigwrap_close(fd);
92 
93 	return retval;
94 }
95 
96 //Notice: dutycycle_percentage value should be between 1 to 99.
SET_PWM_DUTYCYCLE(pwmtach_ioctl_data * ppwmtach_arg)97 static int SET_PWM_DUTYCYCLE ( pwmtach_ioctl_data  *ppwmtach_arg)
98 {
99 	int retval = 0;
100 	unsigned char dutycycle_value;
101 
102 	if(ppwmtach_arg->dutycycle > 100)
103 	{
104 		return -1;
105 	}
106 
107 	dutycycle_value = (ppwmtach_arg->dutycycle*255)/100;
108 	ppwmtach_arg->dutycycle = dutycycle_value;
109 	retval = SET_PWM_DUTYCYCLE_VALUE(ppwmtach_arg);
110 	return retval;
111 }
112 
GET_PWM_DUTYCYCLE(pwmtach_ioctl_data * ppwmtach_arg)113 static int GET_PWM_DUTYCYCLE ( pwmtach_ioctl_data  *ppwmtach_arg )
114 {
115 	int retval = 0;
116 	int fd;
117 	char duty_num[5];
118 
119 	retval = pwmtach_directory_check();
120 	if(retval != 0)
121 	{//printf("%s,error 0\n",__FUNCTION__);
122 		return retval;
123 	}
124 
125 	BUILD_PWM_NODE_NAME(DevNodeFileName,ppwmtach_arg->dev_id,ppwmtach_arg->pwmnumber);
126 	retval = access(DevNodeFileName,F_OK);
127 	if(retval != 0)
128 	{printf("%s,error 2,%s not exist\n",__FUNCTION__,DevNodeFileName);
129 		return retval;
130 	}
131 	fd = sigwrap_open(DevNodeFileName, O_RDONLY);
132 	if (fd < 0) {printf("%s,error 3\n",__FUNCTION__);
133 		return fd;
134 	}
135 	read(fd, duty_num, 5);
136 	ppwmtach_arg->dutycycle = atoi(duty_num);
137 	printf("%s:dutycycle value %d to pwm %d\n",__FUNCTION__,ppwmtach_arg->dutycycle,ppwmtach_arg->pwmnumber);
138 	(void)sigwrap_close(fd);
139 
140 	return retval;
141 }
GET_TACH_SPEED(pwmtach_ioctl_data * ppwmtach_arg)142 int GET_TACH_SPEED (pwmtach_ioctl_data *ppwmtach_arg )
143 {
144 	int retval = 0;
145 	int fd;
146 	char data[6];
147 
148 	retval = pwmtach_directory_check();
149 	if(retval != 0)
150 	{printf("%s,error 0\n",__FUNCTION__);
151 		return retval;
152 	}
153 	BUILD_TACH_NODE_NAME(DevNodeFileName,ppwmtach_arg->dev_id,ppwmtach_arg->tachnumber);
154 	retval = access(DevNodeFileName,F_OK);
155 	if(retval != 0)
156 	{printf("%s,error 2,%s not exist\n",__FUNCTION__,DevNodeFileName);
157 		return retval;
158 	}
159 
160 	fd = sigwrap_open(DevNodeFileName, O_RDONLY);
161 	if (fd < 0) {printf("%s,error 3\n",__FUNCTION__);
162 		return fd;
163 	}
164 	memset(data, 0, 6);
165 	read(fd, data, 6);
166 	ppwmtach_arg->rpmvalue = atoi(data);
167 	(void)sigwrap_close(fd);
168 	printf("%s:rpm value %d\n",__FUNCTION__,ppwmtach_arg->rpmvalue);
169 	return retval;
170 }
171 //mapping function of fan to tach
172 //using direct mapping as default
173 #define GET_TACH_NUMBER(FAN_NUMBER)     FAN_NUMBER
174 //mapping fan number to pwm number
175 //using information in fan@number reg item, to look up the pwm index
GET_PWM_NUMBER(pwmtach_ioctl_data * ppwmtach_arg)176 static int GET_PWM_NUMBER(pwmtach_ioctl_data *ppwmtach_arg)
177 {
178 	int retval = 0;
179 	int fd;
180 	int reg_val = 0;
181 
182 	retval = pwmtach_directory_check();
183 	if(retval != 0)
184 	{printf("%s,error 0\n",__FUNCTION__);
185 		return retval;
186 	}
187 	BUILD_FAN_REG_NAME(DevNodeFileName,ppwmtach_arg->dev_id,ppwmtach_arg->fannumber);
188 	retval = access(DevNodeFileName,F_OK);
189 	if(retval != 0)
190 	{printf("%s,error 2,%s not exist\n",__FUNCTION__,DevNodeFileName);
191 		return retval;
192 	}
193 
194 	fd = sigwrap_open(DevNodeFileName, O_RDONLY);
195 	if (fd < 0) {printf("%s,error 3\n",__FUNCTION__);
196 		return fd;
197 	}
198 	read(fd, &reg_val, sizeof(int));
199 	if(reg_val < 0)
200 	{
201 		retval = -1;
202 	}else
203 	{
204 		retval = reg_val >> 24; //get the highest byte
205 		printf("%s:fan %d, pwm %d, val 0x%X\n",__FUNCTION__,ppwmtach_arg->fannumber,retval,reg_val);
206 		printf("%s\n",DevNodeFileName);
207 	}
208 	(void)sigwrap_close(fd);
209 	// printf("%s:rpm value %d\n",__FUNCTION__,ppwmtach_arg->rpmvalue);
210 	return retval;
211 }
pwmtach_action(pwmtach_ioctl_data * argp,int command)212 static int pwmtach_action(  pwmtach_ioctl_data* argp, int command )
213 {
214 	int retval = 0;
215 	// printf("%s, Command 0x%X:Dev:%d,Pwm:0x%X,Fan:0x%x,Tach:0x%X\n",__FUNCTION__,command,argp->dev_id,argp->pwmnumber,argp->fannumber,argp->tachnumber);
216 	switch(command)
217 	{
218 		case SET_DUTY_CYCLE_BY_PWM_CHANNEL:
219 			retval = SET_PWM_DUTYCYCLE(argp);
220 			break;
221 		case SET_DUTY_CYCLE_VALUE_BY_PWM_CHANNEL:
222 		case SET_DUTY_CYCLE:
223 			retval = SET_PWM_DUTYCYCLE_VALUE(argp);
224 			break;
225 		case GET_TACH_VALUE_BY_TACH_CHANNEL:
226 			retval = GET_TACH_SPEED(argp);
227 			break;
228 		case GET_TACH_VALUE: //used to get fan speed
229 			argp->tachnumber = GET_TACH_NUMBER(argp->fannumber);
230 			retval = GET_TACH_SPEED(argp);
231 			break;
232 		case GET_DUTY_CYCLE:
233 			retval = GET_PWM_DUTYCYCLE(argp);
234 			break;
235 		case GET_FAN_RPM_RANGE:
236 			argp->max_rpm = RPM_MAX;
237 			argp->min_rpm = RPM_MIN;
238 			break;
239 		case INIT_PWMTACH: //assume that init complete
240 			argp->pwmnumber         = GET_PWM_NUMBER(argp);; //since we don't have the fan to pwm mapping, just using direct map for workarround.
241 			argp->counterresvalue   = COUNTERRES_DEF; //since driver don't support COUNTERRES, just using default value for workarround.
242 			retval = GET_PWM_DUTYCYCLE(argp);
243 			break;
244 		case END_OF_FUNC_TABLE:
245 		default:
246 			printf("%s, Command 0x%X not support!\n",__FUNCTION__,command);
247 			retval = -1;
248 	}
249 	return( retval );
250 }
251 
get_tach_speed(unsigned int dev_id,unsigned char tach_number,unsigned int * rpm_value)252 int get_tach_speed ( unsigned int dev_id, unsigned char tach_number, unsigned int *rpm_value )
253 {
254 	pwmtach_ioctl_data pwmtach_arg;
255 	int retval = 0;
256 
257 	pwmtach_arg.dev_id = dev_id;
258 	pwmtach_arg.tachnumber = tach_number;
259 	retval = pwmtach_action( &pwmtach_arg, GET_TACH_VALUE_BY_TACH_CHANNEL);
260 	if(retval != -1)
261 		*rpm_value = pwmtach_arg.rpmvalue;
262 	return retval;
263 }
264 
265 //Notice: dutycycle_percentage value should be between 1 to 99.
set_pwm_dutycycle(unsigned int dev_id,unsigned char pwm_number,unsigned char dutycycle_percentage)266 int set_pwm_dutycycle ( unsigned int dev_id, unsigned char pwm_number, unsigned char dutycycle_percentage )
267 {
268 	pwmtach_ioctl_data pwmtach_arg;
269 	int retval = 0;
270 
271 	pwmtach_arg.dev_id = dev_id;
272 	pwmtach_arg.pwmnumber = pwm_number;
273 	pwmtach_arg.dutycycle= dutycycle_percentage;
274 	retval = pwmtach_action( &pwmtach_arg, SET_DUTY_CYCLE_BY_PWM_CHANNEL);
275 
276 	return retval;
277 }
278 
279 //Notice: dutycycle_value is one byte (0-255)
set_pwm_dutycycle_value(unsigned int dev_id,unsigned char pwm_number,unsigned char dutycycle_value)280 int set_pwm_dutycycle_value ( unsigned int dev_id, unsigned char pwm_number, unsigned char dutycycle_value )
281 {
282 	pwmtach_ioctl_data pwmtach_arg;
283 	int retval = 0;
284 
285 	pwmtach_arg.dev_id = dev_id;
286 	pwmtach_arg.pwmnumber = pwm_number;
287 	pwmtach_arg.dutycycle= dutycycle_value;
288 	retval = pwmtach_action( &pwmtach_arg, SET_DUTY_CYCLE_VALUE_BY_PWM_CHANNEL);
289 
290 	return retval;
291 }
292 
get_pwm_dutycycle(unsigned int dev_id,unsigned char pwm_number,unsigned char * dutycycle_percentage)293 int get_pwm_dutycycle ( unsigned int dev_id, unsigned char pwm_number, unsigned char *dutycycle_percentage )
294 {
295 	pwmtach_ioctl_data pwmtach_arg;
296 	int retval = 0;
297 
298 	pwmtach_arg.dev_id = dev_id;
299 	pwmtach_arg.pwmnumber = pwm_number;
300 	retval = pwmtach_action(&pwmtach_arg, GET_DUTY_CYCLE);
301 	if(retval != -1)
302 		*dutycycle_percentage = pwmtach_arg.dutycycle;
303 	return retval;
304 
305 }
306 
set_fan_speed(unsigned int dev_id,unsigned char fan_number,unsigned int rpm_value)307 int set_fan_speed ( unsigned int dev_id, unsigned char fan_number, unsigned int rpm_value )
308 {
309 	int retval = 0;
310 	unsigned int retries = 20;
311 	unsigned char firsttime = 1;
312 	unsigned char duty_cycle_increasing = 0;
313 	unsigned char reached90percent = 0;
314 	unsigned char reached5percent = 0;
315 	unsigned long desiredrpm = rpm_value;
316 	pwmtach_ioctl_data          pwmtach_arg;
317 	pwmtach_data_t* indata = (pwmtach_data_t*) &pwmtach_arg;
318 
319 	indata->dev_id = dev_id;
320 	indata->fannumber = fan_number;
321 	indata->rpmvalue = rpm_value;
322 	indata->counterresvalue = 0;
323 	indata->dutycycle = 0;
324 	indata->prevdutycycle = 0;
325 
326 	retval = pwmtach_action( indata, GET_FAN_RPM_RANGE);
327 	if ((rpm_value < indata->min_rpm) || (rpm_value > indata->max_rpm))
328 	{
329 		printf("Out of range Fan Speed value for fan.\n");
330 		return -1;
331 	}
332 	retval = pwmtach_action ( indata, INIT_PWMTACH );
333 
334 	while (retries--)
335 	{
336 		/* Wait for 1 seconds */
337 		select_sleep(0,1*1000*1000);
338 		if ((retval = pwmtach_action( indata, GET_TACH_VALUE )) != 0)
339 		{
340 			indata->dutycycle = indata->prevdutycycle;
341 			retval = pwmtach_action( indata, SET_DUTY_CYCLE);
342 			return -1;
343 		}
344 		else
345 		{
346 			indata->prevdutycycle = indata->dutycycle;
347 			if (indata->rpmvalue > (desiredrpm + 50))
348 			{
349 				if (indata->dutycycle <= ((indata->counterresvalue*10)/100))
350 				{
351 					if (reached5percent == 1)
352 					{
353 						printf("\nSpeed is set to minimum possible speed of %d RPM.\n",indata->rpmvalue);
354 						break;
355 					}
356 					reached5percent = 1;
357 				}
358 				else
359 				{
360 					indata->dutycycle -= ((5 * indata->counterresvalue)/100);
361 				}
362 			}
363 			else if (indata->rpmvalue < (desiredrpm - 50))
364 			{
365 				if (indata->dutycycle >= indata->counterresvalue)
366 				{
367 					if (reached90percent == 1)
368 					{
369 						printf("\nSpeed is set to maximum possible speed of %d RPM.\n",indata->rpmvalue);
370 						break;
371 					}
372 					reached90percent = 1;
373 				}
374 				else
375 				{
376 					indata->dutycycle += ((5 * indata->counterresvalue)/100);
377 				}
378 			}
379 			else
380 			{
381 				break;
382 			}
383 
384 			retval = pwmtach_action (indata, SET_DUTY_CYCLE);
385 			printf("After update: dutycycle=%d, rpmvalue=%d\n", indata->dutycycle, indata->rpmvalue);
386 
387 			if(indata->prevdutycycle < indata->dutycycle)
388 			{       /* Duty Cycle increasing */
389 				if ((firsttime == 0) && (duty_cycle_increasing == 0))
390 				{
391 					indata->dutycycle = indata->prevdutycycle;
392 					retval = pwmtach_action( indata, SET_DUTY_CYCLE);
393 					printf("\n");
394 					return 0;
395 				}
396 				duty_cycle_increasing = 1;
397 			}
398 			else
399 			{       /* Duty Cycle decreasing */
400 				if ((firsttime == 0) && (duty_cycle_increasing == 1))
401 				{
402 					indata->dutycycle = indata->prevdutycycle;
403 					retval = pwmtach_action( indata, SET_DUTY_CYCLE);
404 					printf("\n");
405 					return 0;
406 				}
407 				duty_cycle_increasing = 0;
408 			}
409 			if (firsttime == 1)
410 				firsttime = 0;
411 		}
412 		printf("retry %d : dt=%d, ps=%d, cr=%d\n", retries, indata->dutycycle, indata->prescalervalue, indata->counterresvalue);
413 	}
414 	return 0;
415 
416 }
417 
get_fan_speed(unsigned int dev_id,unsigned char fan_number,unsigned int * rpm_value)418 int get_fan_speed ( unsigned int dev_id, unsigned char fan_number, unsigned int *rpm_value )
419 {
420 	pwmtach_ioctl_data pwmtach_arg;
421 	int retval = 0;
422 
423 	pwmtach_arg.dev_id = dev_id;
424 	pwmtach_arg.fannumber = fan_number;
425 	retval = pwmtach_action( &pwmtach_arg, GET_TACH_VALUE );
426 	if(retval != -1)
427 		*rpm_value = pwmtach_arg.rpmvalue;
428 	return retval;
429 }
430