Home | History | Annotate | Line # | Download | only in emcfanctl
      1 /*	$NetBSD: emcfanctl.c,v 1.1 2025/03/11 13:56:48 brad Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2025 Brad Spencer <brad (at) anduin.eldar.org>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  */
     18 
     19 #include <sys/cdefs.h>
     20 #ifdef __RCSID
     21 __RCSID("$NetBSD: emcfanctl.c,v 1.1 2025/03/11 13:56:48 brad Exp $");
     22 #endif
     23 
     24 /* Main userland program that can interfact with a EMC-210x and EMC-230x
     25  * fan controller using emcfan(4).
     26  */
     27 
     28 #include <stdio.h>
     29 #include <stdint.h>
     30 #include <unistd.h>
     31 #include <stdbool.h>
     32 #include <stdlib.h>
     33 #include <string.h>
     34 #include <inttypes.h>
     35 #include <err.h>
     36 #include <errno.h>
     37 #include <fcntl.h>
     38 #include <sys/ioctl.h>
     39 
     40 #include <dev/i2c/emcfanreg.h>
     41 #include <dev/i2c/emcfaninfo.h>
     42 
     43 #define EXTERN extern
     44 #include "emcfanctl.h"
     45 #include "emcfanctlconst.h"
     46 #include "emcfanctlutil.h"
     47 #include "emcfanctloutputs.h"
     48 
     49 int	valid_cmd(const struct emcfanctlcmd[], long unsigned int, char *);
     50 
     51 static void
     52 usage(void)
     53 {
     54 	const char *p = getprogname();
     55 
     56 	fprintf(stderr, "Usage: %s [-jdh] device cmd args\n\n",
     57 	    p);
     58 
     59 	for(long unsigned int i = 0;i < __arraycount(emcfanctlcmds);i++) {
     60 		switch (emcfanctlcmds[i].id) {
     61 		case EMCFANCTL_REGISTER:
     62 			for(long unsigned int j = 0;j < __arraycount(emcfanctlregistercmds);j++) {
     63 				fprintf(stderr,"%s [-jdh] device %s %s\n",
     64 				    p,
     65 				    emcfanctlcmds[i].cmd,
     66 				    emcfanctlregistercmds[j].helpargs);
     67 			}
     68 			break;
     69 		case EMCFANCTL_FAN:
     70 			for(long unsigned int j = 0;j < __arraycount(emcfanctlfancmds);j++) {
     71 				switch (emcfanctlfancmds[j].id) {
     72 				case EMCFANCTL_FAN_DRIVE:
     73 				case EMCFANCTL_FAN_DIVIDER:
     74 					for(long unsigned int k = 0;k < __arraycount(emcfanctlddcmds);k++) {
     75 						fprintf(stderr,"%s [-jdh] device %s <n> %s %s\n",
     76 						    p,
     77 						    emcfanctlcmds[i].cmd,
     78 						    emcfanctlfancmds[j].cmd,
     79 						    emcfanctlddcmds[k].helpargs);
     80 					}
     81 					break;
     82 				case EMCFANCTL_FAN_MINEXPECTED_RPM:
     83 					for(long unsigned int k = 0;k < __arraycount(emcfanctlddcmds);k++) {
     84 						if (emcfanctlddcmds[k].id != EMCFANCTL_FAN_DD_WRITE) {
     85 							fprintf(stderr,"%s [-jdh] device %s <n> %s %s\n",
     86 							    p,
     87 							    emcfanctlcmds[i].cmd,
     88 							    emcfanctlfancmds[j].cmd,
     89 							    emcfanctlddcmds[k].helpargs);
     90 						} else {
     91 							fprintf(stderr,"%s [-jdh] device %s <n> %s %s ",
     92 							    p,
     93 							    emcfanctlcmds[i].cmd,
     94 							    emcfanctlfancmds[j].cmd,
     95 							    emcfanctlddcmds[k].helpargs);
     96 							for(long unsigned int q = 0;q < __arraycount(fan_minexpectedrpm);q++) {
     97 								fprintf(stderr,"%d|",fan_minexpectedrpm[q].human_int);
     98 							}
     99 							fprintf(stderr,"\n");
    100 						}
    101 					}
    102 					break;
    103 				case EMCFANCTL_FAN_EDGES:
    104 					for(long unsigned int k = 0;k < __arraycount(emcfanctlddcmds);k++) {
    105 						if (emcfanctlddcmds[k].id != EMCFANCTL_FAN_DD_WRITE) {
    106 							fprintf(stderr,"%s [-jdh] device %s <n> %s %s\n",
    107 							    p,
    108 							    emcfanctlcmds[i].cmd,
    109 							    emcfanctlfancmds[j].cmd,
    110 							    emcfanctlddcmds[k].helpargs);
    111 						} else {
    112 							fprintf(stderr,"%s [-jdh] device %s <n> %s %s ",
    113 							    p,
    114 							    emcfanctlcmds[i].cmd,
    115 							    emcfanctlfancmds[j].cmd,
    116 							    emcfanctlddcmds[k].helpargs);
    117 							for(long unsigned int q = 0;q < __arraycount(fan_numedges);q++) {
    118 								fprintf(stderr,"%d|",fan_numedges[q].human_int);
    119 							}
    120 							fprintf(stderr,"\n");
    121 						}
    122 					}
    123 					break;
    124 				case EMCFANCTL_FAN_POLARITY:
    125 					for(long unsigned int k = 0;k < __arraycount(emcfanctlpcmds);k++) {
    126 						fprintf(stderr,"%s [-jdh] device %s <n> %s %s\n",
    127 						    p,
    128 						    emcfanctlcmds[i].cmd,
    129 						    emcfanctlfancmds[j].cmd,
    130 						    emcfanctlpcmds[k].helpargs);
    131 					}
    132 					break;
    133 				case EMCFANCTL_FAN_PWM_BASEFREQ:
    134 					for(long unsigned int k = 0;k < __arraycount(emcfanctlddcmds);k++) {
    135 						if (emcfanctlddcmds[k].id != EMCFANCTL_FAN_DD_WRITE) {
    136 							fprintf(stderr,"%s [-jdh] device %s <n> %s %s\n",
    137 							    p,
    138 							    emcfanctlcmds[i].cmd,
    139 							    emcfanctlfancmds[j].cmd,
    140 							    emcfanctlddcmds[k].helpargs);
    141 						} else {
    142 							fprintf(stderr,"%s [-jdh] device %s <n> %s %s ",
    143 							    p,
    144 							    emcfanctlcmds[i].cmd,
    145 							    emcfanctlfancmds[j].cmd,
    146 							    emcfanctlddcmds[k].helpargs);
    147 							for(long unsigned int q = 0;q < __arraycount(fan_pwm_basefreq);q++) {
    148 								if (fan_pwm_basefreq[q].instance > 0)
    149 									break;
    150 								fprintf(stderr,"%d|",fan_pwm_basefreq[q].human_int);
    151 							}
    152 							fprintf(stderr,"\n");
    153 						}
    154 					}
    155 					break;
    156 				case EMCFANCTL_FAN_PWM_OUTPUTTYPE:
    157 					for(long unsigned int k = 0;k < __arraycount(emcfanctlotcmds);k++) {
    158 						fprintf(stderr,"%s [-jdh] device %s <n> %s %s\n",
    159 						    p,
    160 						    emcfanctlcmds[i].cmd,
    161 						    emcfanctlfancmds[j].cmd,
    162 						    emcfanctlotcmds[k].helpargs);
    163 					}
    164 					break;
    165 				default:
    166 					fprintf(stderr,"%s [-jdh] device %s %s\n",
    167 					    p,
    168 					    emcfanctlcmds[i].cmd,
    169 					    emcfanctlfancmds[j].helpargs);
    170 					break;
    171 				};
    172 			}
    173 			break;
    174 		case EMCFANCTL_APD:
    175 		case EMCFANCTL_SMBUSTO:
    176 			for(long unsigned int j = 0;j < __arraycount(emcfanctlapdsmtocmds);j++) {
    177 				fprintf(stderr,"%s [-jdh] device %s %s\n",
    178 				    p,
    179 				    emcfanctlcmds[i].cmd,
    180 				    emcfanctlapdsmtocmds[j].helpargs);
    181 			}
    182 			break;
    183 		default:
    184 			fprintf(stderr,"%s [-jdh] device %s %s\n",
    185 			    p,emcfanctlcmds[i].cmd,emcfanctlcmds[i].helpargs);
    186 			break;
    187 		};
    188 	}
    189 }
    190 
    191 int
    192 valid_cmd(const struct emcfanctlcmd c[], long unsigned int csize, char *cmdtocheck)
    193 {
    194 	int r = -1;
    195 
    196 	for(long unsigned int i = 0;i < csize;i++) {
    197 		if (strncmp(cmdtocheck,c[i].cmd,16) == 0) {
    198 			r = i;
    199 			break;
    200 		}
    201 	}
    202 
    203 	return r;
    204 }
    205 
    206 int
    207 main(int argc, char *argv[])
    208 {
    209 	int c;
    210 	bool debug = false;
    211 	uint8_t product_id;
    212 	int product_family;
    213 	int fd = -1, valid, error = 0, validsub = -1, validsubsub = -1;
    214 	bool jsonify = false;
    215 	int start_reg = 0xff, end_reg, value, tvalue, instance;
    216 	int the_fan;
    217 
    218 	while ((c = getopt(argc, argv, "djh")) != -1 ) {
    219 		switch (c) {
    220 		case 'd':
    221 			debug = true;
    222 			break;
    223 		case 'j':
    224 			jsonify = true;
    225 			break;
    226 		case 'h':
    227 		default:
    228 			usage();
    229 			exit(0);
    230 		}
    231 	}
    232 
    233 	argc -= optind;
    234 	argv += optind;
    235 
    236 	if (debug) {
    237 		fprintf(stderr,"ARGC: %d\n", argc);
    238 		fprintf(stderr,"ARGV[0]: %s ; ARGV[1]: %s ; ARGV[2]: %s ; ARGV[3]: %s; ARGV[4]: %s; ARGV[5]: %s\n",
    239 		    argv[0],argv[1],argv[2],argv[3],argv[4],argv[5]);
    240 	}
    241 
    242 	if (argc <= 1) {
    243 		usage();
    244 		exit(0);
    245 	}
    246 
    247 	fd = open(argv[0], O_RDWR, 0);
    248 	if (fd == -1) {
    249 		err(EXIT_FAILURE, "open %s", argv[0]);
    250 	}
    251 
    252 	error = emcfan_read_register(fd, EMCFAN_PRODUCT_ID, &product_id, debug);
    253 
    254 	if (error)
    255 		err(EXIT_FAILURE, "read product_id %d", error);
    256 
    257 	int iindex;
    258 	iindex = emcfan_find_info(product_id);
    259 	if (iindex == -1) {
    260 		printf("Unknown info for product_id: %d\n",product_id);
    261 		exit(2);
    262 	}
    263 
    264 	product_family = emcfan_chip_infos[iindex].family;
    265 
    266 	/* Parse out the command line into what the requested action is */
    267 
    268 	valid = valid_cmd(emcfanctlcmds,__arraycount(emcfanctlcmds),argv[1]);
    269 	if (valid != -1) {
    270 		switch (emcfanctlcmds[valid].id) {
    271 		case EMCFANCTL_INFO:
    272 			error = output_emcfan_info(fd, product_id, product_family, jsonify, debug);
    273 			if (error != 0) {
    274 				errno = error;
    275 				err(EXIT_FAILURE, "output info");
    276 			}
    277 			break;
    278 		case EMCFANCTL_APD:
    279 			if (argc >= 3) {
    280 				validsub = valid_cmd(emcfanctlapdsmtocmds,__arraycount(emcfanctlapdsmtocmds),argv[2]);
    281 				if (product_id == EMCFAN_PRODUCT_2103_24 ||
    282 				    product_id == EMCFAN_PRODUCT_2104 ||
    283 				    product_id == EMCFAN_PRODUCT_2106) {
    284 					switch(emcfanctlapdsmtocmds[validsub].id) {
    285 					case EMCFANCTL_APD_READ:
    286 						error = output_emcfan_apd(fd, product_id, product_family, EMCFAN_CHIP_CONFIG, jsonify, debug);
    287 						if (error != 0) {
    288 							fprintf(stderr, "Error output for apd subcommand: %d\n",error);
    289 							exit(1);
    290 						}
    291 						break;
    292 					case EMCFANCTL_APD_ON:
    293 					case EMCFANCTL_APD_OFF:
    294 						tvalue = find_translated_bits_by_str(apd, __arraycount(apd), argv[2]);
    295 						if (tvalue < 0) {
    296 							fprintf(stderr,"Error converting human value: %s\n", argv[4]);
    297 							exit(1);
    298 						}
    299 						error = emcfan_rmw_register(fd, EMCFAN_CHIP_CONFIG, 0, apd, __arraycount(apd), tvalue, debug);
    300 						if (error != 0) {
    301 							errno = error;
    302 							err(EXIT_FAILURE, "read/modify/write register");
    303 						}
    304 						break;
    305 					default:
    306 						fprintf(stderr,"Unhandled subcommand to apd: %s %d\n\n", argv[2], validsub);
    307 						usage();
    308 						exit(1);
    309 						break;
    310 					};
    311 				} else {
    312 					errno = EINVAL;
    313 					err(EXIT_FAILURE, "This chip does not support the APD command");
    314 				}
    315 			} else {
    316 				fprintf(stderr,"Missing arguments to apd command\n\n");
    317 				usage();
    318 				exit(1);
    319 			}
    320 			break;
    321 		case EMCFANCTL_SMBUSTO:
    322 			if (argc >= 3) {
    323 				if (product_id ==  EMCFAN_PRODUCT_2101 ||
    324 				    product_id == EMCFAN_PRODUCT_2101R) {
    325 					start_reg = EMCFAN_2101_CHIP_CONFIG;
    326 					instance = 2101;
    327 				} else {
    328 					if (product_family == EMCFAN_FAMILY_230X) {
    329 						start_reg = EMCFAN_CHIP_CONFIG;
    330 						instance = 2301;
    331 					} else {
    332 						start_reg = EMCFAN_CHIP_CONFIG_2;
    333 						instance = 2103;
    334 					}
    335 				}
    336 
    337 				validsub = valid_cmd(emcfanctlapdsmtocmds,__arraycount(emcfanctlapdsmtocmds),argv[2]);
    338 				switch(emcfanctlapdsmtocmds[validsub].id) {
    339 				case EMCFANCTL_APD_READ:
    340 					error = output_emcfan_smbusto(fd, product_id, product_family, start_reg, instance, jsonify, debug);
    341 					if (error != 0) {
    342 						fprintf(stderr, "Error output for SMBUS timeout subcommand: %d\n",error);
    343 						exit(1);
    344 					}
    345 					break;
    346 				case EMCFANCTL_APD_ON:
    347 				case EMCFANCTL_APD_OFF:
    348 					tvalue = find_translated_bits_by_str_instance(smbus_timeout, __arraycount(smbus_timeout), argv[2], instance);
    349 					if (tvalue < 0) {
    350 						fprintf(stderr,"Error converting human value: %s\n", argv[4]);
    351 						exit(1);
    352 					}
    353 					error = emcfan_rmw_register(fd, start_reg, 0, smbus_timeout, __arraycount(smbus_timeout), tvalue, debug);
    354 					if (error != 0) {
    355 						errno = error;
    356 						err(EXIT_FAILURE, "read/modify/write register");
    357 					}
    358 					break;
    359 				default:
    360 					fprintf(stderr,"Unhandled subcommand to SMBUS timeout: %s %d\n\n", argv[2], validsub);
    361 					usage();
    362 					exit(1);
    363 					break;
    364 				};
    365 			} else {
    366 				fprintf(stderr,"Missing arguments to SMBUS timeout command\n\n");
    367 				usage();
    368 				exit(1);
    369 			}
    370 			break;
    371 		case EMCFANCTL_REGISTER:
    372 			if (argc >= 3) {
    373 				validsub = valid_cmd(emcfanctlregistercmds,__arraycount(emcfanctlregistercmds),argv[2]);
    374 				switch (emcfanctlregistercmds[validsub].id) {
    375 				case EMCFANCTL_REGISTER_LIST:
    376 					output_emcfan_register_list(product_id, product_family, jsonify, debug);
    377 					break;
    378 				case EMCFANCTL_REGISTER_READ:
    379 					start_reg = end_reg = 0x00;
    380 					if (argc >= 4) {
    381 						start_reg = (uint8_t)strtoi(argv[3], NULL, 0, 0, 0xff, &error);
    382 						if (error) {
    383 							start_reg = emcfan_reg_by_name(product_id, product_family, argv[3]);
    384 							if (start_reg == -1) {
    385 								fprintf(stderr,"Bad conversion for read register start: %s\n", argv[3]);
    386 								exit(1);
    387 							}
    388 						}
    389 						end_reg = start_reg;
    390 						if (argc == 5) {
    391 							end_reg = (uint8_t)strtoi(argv[4], NULL, 0, 0, 0xff, &error);
    392 							if (error) {
    393 								end_reg = emcfan_reg_by_name(product_id, product_family, argv[4]);
    394 								if (end_reg == -1) {
    395 									fprintf(stderr,"Bad conversion for read register end: %s\n", argv[4]);
    396 									exit(1);
    397 								}
    398 							}
    399 						}
    400 					} else {
    401 						end_reg = 0xff;
    402 					}
    403 					if (end_reg < start_reg) {
    404 						fprintf(stderr,"Register end can not be less than register start: %d %d\n\n", start_reg, end_reg);
    405 						usage();
    406 						exit(1);
    407 					}
    408 
    409 					if (debug)
    410 						fprintf(stderr,"register read: START_REG: %d 0x%02X, END_REG: %d 0x%02X\n",start_reg, start_reg, end_reg, end_reg);
    411 
    412 					error = output_emcfan_register_read(fd, product_id, product_family, start_reg, end_reg, jsonify, debug);
    413 					if (error != 0) {
    414 						fprintf(stderr, "Error read register: %d\n",error);
    415 						exit(1);
    416 					}
    417 
    418 					break;
    419 				case EMCFANCTL_REGISTER_WRITE:
    420 					if (argc == 5) {
    421 						start_reg = (uint8_t)strtoi(argv[3], NULL, 0, 0, 0xff, &error);
    422 						if (error) {
    423 							start_reg = emcfan_reg_by_name(product_id, product_family, argv[3]);
    424 							if (start_reg == -1) {
    425 								fprintf(stderr,"Bad conversion for write register: %s\n", argv[3]);
    426 								exit(1);
    427 							}
    428 						}
    429 
    430 						value = (uint8_t)strtoi(argv[4], NULL, 0, 0, 0xff, &error);
    431 						if (error) {
    432 							fprintf(stderr,"Could not convert value for register write.\n");
    433 							usage();
    434 							exit(1);
    435 						}
    436 					} else {
    437 						fprintf(stderr,"Not enough arguments for register write.\n");
    438 						usage();
    439 						exit(1);
    440 					}
    441 
    442 					if (debug)
    443 						fprintf(stderr,"register write: START_REG: %d 0x%02X, VALUE: %d 0x%02X\n",start_reg, start_reg, value, value);
    444 
    445 					error = emcfan_write_register(fd, start_reg, value, debug);
    446 					if (error != 0) {
    447 						errno = error;
    448 						err(EXIT_FAILURE, "write register");
    449 					}
    450 
    451 					break;
    452 				default:
    453 					fprintf(stderr,"Unhandled subcommand to register: %s %d\n\n", argv[2], validsub);
    454 					usage();
    455 					exit(1);
    456 					break;
    457 				};
    458 			} else {
    459 				fprintf(stderr,"Missing arguments to register command\n\n");
    460 				usage();
    461 				exit(1);
    462 			}
    463 			break;
    464 		case EMCFANCTL_FAN:
    465 			if (argc >= 4) {
    466 				the_fan = strtoi(argv[2], NULL, 0, 1, emcfan_chip_infos[iindex].num_fans, &error);
    467 				if (error) {
    468 					fprintf(stderr,"Bad conversion for fan number: %s\n", argv[2]);
    469 					exit(1);
    470 				}
    471 				the_fan--;
    472 				validsub = valid_cmd(emcfanctlfancmds,__arraycount(emcfanctlfancmds),argv[3]);
    473 				switch (emcfanctlfancmds[validsub].id) {
    474 				case EMCFANCTL_FAN_STATUS:
    475 					if (product_id != EMCFAN_PRODUCT_2101 &&
    476 					    product_id != EMCFAN_PRODUCT_2101R) {
    477 						if (product_family == EMCFAN_FAMILY_230X) {
    478 							start_reg = EMCFAN_230X_FAN_STATUS;
    479 							end_reg = EMCFAN_230X_FAN_DRIVE_STATUS;
    480 						} else {
    481 							start_reg = EMCFAN_210_346_FAN_STATUS;
    482 							end_reg = 0xff;
    483 						}
    484 					} else {
    485 						errno = EINVAL;
    486 						err(EXIT_FAILURE, "EMC2101 and EMC2101R do not support this subcommand");
    487 					}
    488 					break;
    489 				case EMCFANCTL_FAN_DRIVE:
    490 					start_reg = emcfan_chip_infos[iindex].fan_drive_registers[the_fan];
    491 					break;
    492 				case EMCFANCTL_FAN_DIVIDER:
    493 					start_reg = emcfan_chip_infos[iindex].fan_divider_registers[the_fan];
    494 					break;
    495 				case EMCFANCTL_FAN_MINEXPECTED_RPM:
    496 				case EMCFANCTL_FAN_EDGES:
    497 					if (product_id != EMCFAN_PRODUCT_2101 &&
    498 					    product_id != EMCFAN_PRODUCT_2101R) {
    499 						if (debug)
    500 							fprintf(stderr,"fan subcommand minexpected_rpm / edges: the_fan=%d\n",the_fan);
    501 						if (product_family == EMCFAN_FAMILY_210X) {
    502 							the_fan = strtoi(argv[2], NULL, 0, 1, emcfan_chip_infos[iindex].num_tachs, &error);
    503 							if (error) {
    504 								fprintf(stderr,"Bad conversion for fan number: %s\n", argv[2]);
    505 								exit(1);
    506 							}
    507 							the_fan--;
    508 							switch(the_fan) {
    509 							case 0:
    510 								start_reg = EMCFAN_210_346_CONFIG_1;
    511 								break;
    512 							case 1:
    513 								start_reg = EMCFAN_210_346_CONFIG_2;
    514 								break;
    515 							default:
    516 								start_reg = 0xff;
    517 								break;
    518 							};
    519 						} else {
    520 							switch(the_fan) {
    521 							case 0:
    522 								start_reg = EMCFAN_230X_CONFIG_1;
    523 								break;
    524 							case 1:
    525 								start_reg = EMCFAN_230X_CONFIG_2;
    526 								break;
    527 							case 2:
    528 								start_reg = EMCFAN_230X_CONFIG_3;
    529 								break;
    530 							case 3:
    531 								start_reg = EMCFAN_230X_CONFIG_4;
    532 								break;
    533 							case 4:
    534 								start_reg = EMCFAN_230X_CONFIG_5;
    535 								break;
    536 							default:
    537 								start_reg = 0xff;
    538 								break;
    539 							};
    540 						}
    541 					} else {
    542 						errno = EINVAL;
    543 						err(EXIT_FAILURE, "EMC2101 and EMC2101R do not support this subcommand");
    544 					}
    545 					break;
    546 				case EMCFANCTL_FAN_POLARITY:
    547 					if (product_id != EMCFAN_PRODUCT_2101 &&
    548 					    product_id != EMCFAN_PRODUCT_2101R) {
    549 						start_reg = EMCFAN_POLARITY_CONFIG;
    550 					} else {
    551 						start_reg = EMCFAN_2101_FAN_CONFIG;
    552 					}
    553 					break;
    554 				case EMCFANCTL_FAN_PWM_BASEFREQ:
    555 					if (product_id != EMCFAN_PRODUCT_2101 &&
    556 					    product_id != EMCFAN_PRODUCT_2101R) {
    557 						if (product_family == EMCFAN_FAMILY_210X)
    558 							start_reg = EMCFAN_210_346_PWM_BASEFREQ;
    559 						else
    560 							if (the_fan <= 2)
    561 								start_reg = EMCFAN_230X_BASE_FREQ_123;
    562 							else
    563 								start_reg = EMCFAN_230X_BASE_FREQ_45;
    564 					} else {
    565 						errno = EINVAL;
    566 						err(EXIT_FAILURE, "EMC2101 and EMC2101R do not support this subcommand");
    567 					}
    568 					break;
    569 				case EMCFANCTL_FAN_PWM_OUTPUTTYPE:
    570 					if (product_family == EMCFAN_FAMILY_230X) {
    571 						start_reg = EMCFAN_230X_OUTPUT_CONFIG;
    572 					} else {
    573 						errno = EINVAL;
    574 						err(EXIT_FAILURE, "This subcommand is only supported on the EMC230X family");
    575 					}
    576 					break;
    577 				default:
    578 					fprintf(stderr,"Unhandled subcommand to fan: %s %d\n\n", argv[3], validsub);
    579 					usage();
    580 					exit(1);
    581 					break;
    582 				};
    583 				if (emcfanctlfancmds[validsub].id == EMCFANCTL_FAN_STATUS) {
    584 					error = output_emcfan_fan_status(fd, product_id, product_family, start_reg, end_reg, the_fan, jsonify, debug);
    585 					if (error != 0) {
    586 						fprintf(stderr, "Error fan status for fan subcommand: %d\n",error);
    587 						exit(1);
    588 					}
    589 				}
    590 				if (emcfanctlfancmds[validsub].id == EMCFANCTL_FAN_DRIVE ||
    591 				    emcfanctlfancmds[validsub].id == EMCFANCTL_FAN_DIVIDER) {
    592 					validsubsub = valid_cmd(emcfanctlddcmds,__arraycount(emcfanctlddcmds),argv[4]);
    593 					switch(emcfanctlddcmds[validsubsub].id) {
    594 					case EMCFANCTL_FAN_DD_READ:
    595 						if (emcfanctlfancmds[validsub].id == EMCFANCTL_FAN_DRIVE)
    596 							error = output_emcfan_drive(fd, product_id, product_family, start_reg, jsonify, debug);
    597 						else
    598 							error = output_emcfan_divider(fd, product_id, product_family, start_reg, jsonify, debug);
    599 						if (error != 0) {
    600 							fprintf(stderr, "Error read drive / divider for fan subcommand: %d\n",error);
    601 							exit(1);
    602 						}
    603 						break;
    604 					case EMCFANCTL_FAN_DD_WRITE:
    605 						value = (uint8_t)strtoi(argv[5], NULL, 0, 0, 0xff, &error);
    606 						if (error) {
    607 							fprintf(stderr,"Could not convert value for fan subcommand write.\n");
    608 							usage();
    609 							exit(1);
    610 						}
    611 						error = emcfan_write_register(fd, start_reg, value, debug);
    612 						if (error != 0) {
    613 							errno = error;
    614 							err(EXIT_FAILURE, "write register");
    615 						}
    616 						break;
    617 					default:
    618 						fprintf(stderr,"Unhandled subsubcommand to fan: %s %d\n\n", argv[4], validsubsub);
    619 						usage();
    620 						exit(1);
    621 						break;
    622 					};
    623 				}
    624 				if (emcfanctlfancmds[validsub].id == EMCFANCTL_FAN_MINEXPECTED_RPM) {
    625 					if (start_reg == 0xff) {
    626 						fprintf(stderr,"fan minexpected rpm subcommand, unknown register\n");
    627 						exit(1);
    628 					}
    629 					validsubsub = valid_cmd(emcfanctlddcmds,__arraycount(emcfanctlddcmds),argv[4]);
    630 					switch(emcfanctlddcmds[validsubsub].id) {
    631 					case EMCFANCTL_FAN_DD_READ:
    632 						error = output_emcfan_minexpected_rpm(fd, product_id, product_family, start_reg, jsonify, debug);
    633 						if (error != 0) {
    634 							fprintf(stderr, "Error read minexpected rpm subcommand: %d\n",error);
    635 							exit(1);
    636 						}
    637 						break;
    638 					case EMCFANCTL_FAN_DD_WRITE:
    639 						value = (int)strtoi(argv[5], NULL, 0, 0, 0xffff, &error);
    640 						if (error) {
    641 							fprintf(stderr,"Could not convert value for minexpected rpm subcommand write.\n");
    642 							usage();
    643 							exit(1);
    644 						}
    645 						tvalue = find_translated_bits_by_hint(fan_minexpectedrpm, __arraycount(fan_minexpectedrpm), value);
    646 						if (tvalue < 0) {
    647 							fprintf(stderr,"Error converting human value: %d %d\n",value, tvalue);
    648 							exit(1);
    649 						}
    650 						error = emcfan_rmw_register(fd, start_reg, value, fan_minexpectedrpm, __arraycount(fan_minexpectedrpm), tvalue, debug);
    651 						if (error != 0) {
    652 							errno = error;
    653 							err(EXIT_FAILURE, "read/modify/write register");
    654 						}
    655 						break;
    656 					default:
    657 						fprintf(stderr,"Unhandled subsubcommand to minexpected rpm subcommand: %s %d\n\n", argv[4], validsubsub);
    658 						usage();
    659 						exit(1);
    660 						break;
    661 					};
    662 				}
    663 				if (emcfanctlfancmds[validsub].id == EMCFANCTL_FAN_EDGES) {
    664 					if (start_reg == 0xff) {
    665 						fprintf(stderr,"fan edges subcommand, unknown register\n");
    666 						exit(1);
    667 					}
    668 					validsubsub = valid_cmd(emcfanctlddcmds,__arraycount(emcfanctlddcmds),argv[4]);
    669 					switch(emcfanctlddcmds[validsubsub].id) {
    670 					case EMCFANCTL_FAN_DD_READ:
    671 						error = output_emcfan_edges(fd, product_id, product_family, start_reg, jsonify, debug);
    672 						if (error != 0) {
    673 							fprintf(stderr, "Error read edges subcommand: %d\n",error);
    674 							exit(1);
    675 						}
    676 						break;
    677 					case EMCFANCTL_FAN_DD_WRITE:
    678 						value = (uint8_t)strtoi(argv[5], NULL, 0, 0, 9, &error);
    679 						if (error) {
    680 							fprintf(stderr,"Could not convert value for edges subcommand write.\n");
    681 							usage();
    682 							exit(1);
    683 						}
    684 						tvalue = find_translated_bits_by_hint(fan_numedges, __arraycount(fan_numedges), value);
    685 						if (tvalue < 0) {
    686 							fprintf(stderr,"Error converting human value: %d\n",value);
    687 							exit(1);
    688 						}
    689 						error = emcfan_rmw_register(fd, start_reg, value, fan_numedges, __arraycount(fan_numedges), tvalue, debug);
    690 						if (error != 0) {
    691 							errno = error;
    692 							err(EXIT_FAILURE, "read/modify/write register");
    693 						}
    694 						break;
    695 					default:
    696 						fprintf(stderr,"Unhandled subsubcommand to minexpected rpm subcommand: %s %d\n\n", argv[4], validsubsub);
    697 						usage();
    698 						exit(1);
    699 						break;
    700 					};
    701 				}
    702 				if (emcfanctlfancmds[validsub].id == EMCFANCTL_FAN_POLARITY) {
    703 					if (start_reg == 0xff) {
    704 						fprintf(stderr,"fan polarity subcommand, unknown register\n");
    705 						exit(1);
    706 					}
    707 					validsubsub = valid_cmd(emcfanctlpcmds,__arraycount(emcfanctlpcmds),argv[4]);
    708 					switch(emcfanctlpcmds[validsubsub].id) {
    709 					case EMCFANCTL_FAN_P_READ:
    710 						error = output_emcfan_polarity(fd, product_id, product_family, start_reg, the_fan, jsonify, debug);
    711 						if (error != 0) {
    712 							fprintf(stderr, "Error output for polarity subcommand: %d\n",error);
    713 							exit(1);
    714 						}
    715 						break;
    716 					case EMCFANCTL_FAN_P_INVERTED:
    717 					case EMCFANCTL_FAN_P_NONINVERTED:
    718 						if (product_id == EMCFAN_PRODUCT_2101 ||
    719 						    product_id == EMCFAN_PRODUCT_2101R) {
    720 							tvalue = find_translated_bits_by_str_instance(fan_polarity, __arraycount(fan_polarity), argv[4], 2101);
    721 						} else {
    722 							tvalue = find_translated_bits_by_str_instance(fan_polarity, __arraycount(fan_polarity), argv[4], the_fan);
    723 						}
    724 						if (tvalue < 0) {
    725 							fprintf(stderr,"Error converting human value: %s\n", argv[4]);
    726 							exit(1);
    727 						}
    728 						error = emcfan_rmw_register(fd, start_reg, 0, fan_polarity, __arraycount(fan_polarity), tvalue, debug);
    729 						if (error != 0) {
    730 							errno = error;
    731 							err(EXIT_FAILURE, "read/modify/write register");
    732 						}
    733 						break;
    734 					default:
    735 						fprintf(stderr,"Unhandled subsubcommand to polarity subcommand: %s %d\n\n", argv[4], validsubsub);
    736 						usage();
    737 						exit(1);
    738 						break;
    739 					};
    740 
    741 				}
    742 				if (emcfanctlfancmds[validsub].id == EMCFANCTL_FAN_PWM_BASEFREQ) {
    743 					if (start_reg == 0xff) {
    744 						fprintf(stderr,"fan base_freq subcommand, unknown register\n");
    745 						exit(1);
    746 					}
    747 					validsubsub = valid_cmd(emcfanctlddcmds,__arraycount(emcfanctlddcmds),argv[4]);
    748 					switch(emcfanctlddcmds[validsubsub].id) {
    749 					case EMCFANCTL_FAN_DD_READ:
    750 						if (debug)
    751 							fprintf(stderr,"%s: the_fan=%d\n",__func__,the_fan);
    752 						if (product_id != EMCFAN_PRODUCT_2305)
    753 							error = output_emcfan_pwm_basefreq(fd, product_id, product_family, start_reg, the_fan, jsonify, debug);
    754 						else
    755 							if (the_fan <= 2)
    756 								error = output_emcfan_pwm_basefreq(fd, product_id, product_family, start_reg, the_fan, jsonify, debug);
    757 							else
    758 								error = output_emcfan_pwm_basefreq(fd, product_id, product_family, start_reg,
    759 								    the_fan + 23050, jsonify, debug);
    760 						if (error != 0) {
    761 							fprintf(stderr, "Error read base_freq subcommand: %d\n",error);
    762 							exit(1);
    763 						}
    764 						break;
    765 					case EMCFANCTL_FAN_DD_WRITE:
    766 						value = (int)strtoi(argv[5], NULL, 0, 0, 0xffff, &error);
    767 						if (error) {
    768 							fprintf(stderr,"Could not convert value for base_freq subcommand write.\n");
    769 							usage();
    770 							exit(1);
    771 						}
    772 						if (product_id != EMCFAN_PRODUCT_2305)
    773 							tvalue = find_translated_bits_by_hint_instance(fan_pwm_basefreq, __arraycount(fan_pwm_basefreq), value, the_fan);
    774 						else
    775 							if (the_fan <= 2)
    776 								tvalue = find_translated_bits_by_hint_instance(fan_pwm_basefreq, __arraycount(fan_pwm_basefreq), value, the_fan);
    777 							else
    778 								tvalue = find_translated_bits_by_hint_instance(fan_pwm_basefreq, __arraycount(fan_pwm_basefreq),
    779 								    value, the_fan + 23050);
    780 						if (tvalue < 0) {
    781 							fprintf(stderr,"Error converting human value: %d %d\n",value, tvalue);
    782 							exit(1);
    783 						}
    784 						error = emcfan_rmw_register(fd, start_reg, value, fan_pwm_basefreq, __arraycount(fan_pwm_basefreq), tvalue, debug);
    785 						if (error != 0) {
    786 							errno = error;
    787 							err(EXIT_FAILURE, "read/modify/write register");
    788 						}
    789 						break;
    790 					default:
    791 						fprintf(stderr,"Unhandled subsubcommand to base_freq subcommand: %s %d\n\n", argv[4], validsubsub);
    792 						usage();
    793 						exit(1);
    794 						break;
    795 					};
    796 					break;
    797 				}
    798 				if (emcfanctlfancmds[validsub].id == EMCFANCTL_FAN_PWM_OUTPUTTYPE) {
    799 					if (start_reg == 0xff) {
    800 						fprintf(stderr,"fan pwm_output_type subcommand, unknown register\n");
    801 						exit(1);
    802 					}
    803 					validsubsub = valid_cmd(emcfanctlotcmds,__arraycount(emcfanctlotcmds),argv[4]);
    804 					switch(emcfanctlotcmds[validsubsub].id) {
    805 					case EMCFANCTL_FAN_OT_READ:
    806 						error = output_emcfan_pwm_output_type(fd, product_id, product_family, start_reg, the_fan, jsonify, debug);
    807 						if (error != 0) {
    808 							fprintf(stderr, "Error output for pwm_output_type subcommand: %d\n",error);
    809 							exit(1);
    810 						}
    811 						break;
    812 					case EMCFANCTL_FAN_OT_PUSHPULL:
    813 					case EMCFANCTL_FAN_OT_OPENDRAIN:
    814 						tvalue = find_translated_bits_by_str_instance(fan_pwm_output_type, __arraycount(fan_pwm_output_type), argv[4], the_fan);
    815 						if (tvalue < 0) {
    816 							fprintf(stderr,"Error converting human value: %s\n", argv[4]);
    817 							exit(1);
    818 						}
    819 						error = emcfan_rmw_register(fd, start_reg, 0, fan_pwm_output_type, __arraycount(fan_pwm_output_type), tvalue, debug);
    820 						if (error != 0) {
    821 							errno = error;
    822 							err(EXIT_FAILURE, "read/modify/write register");
    823 						}
    824 						break;
    825 					default:
    826 						fprintf(stderr,"Unhandled subsubcommand to pwm_output_type subcommand: %s %d\n\n", argv[4], validsubsub);
    827 						usage();
    828 						exit(1);
    829 						break;
    830 					};
    831 
    832 				}
    833 			} else {
    834 				fprintf(stderr,"Missing arguments to fan command\n\n");
    835 				usage();
    836 				exit(1);
    837 			}
    838 			break;
    839 		default:
    840 			fprintf(stderr,"Unknown handling of command: %d\n",valid);
    841 			exit(2);
    842 			break;
    843 		}
    844 	} else {
    845 		fprintf(stderr,"Unknown command: %s\n\n",argv[1]);
    846 		usage();
    847 		exit(1);
    848 	}
    849 	close(fd);
    850 	exit(0);
    851 }
    852