From 62a909ee81b0c10f8bec3d44d9903ff69e59910c Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 3 Apr 2020 19:25:25 -0600 Subject: [PATCH] Improve fan curve - Implement fan cooldown and heatup periods - Add fan get/set commands - Fix compilation with logging level lower than debug --- src/board/system76/darp5/peci.c | 64 +++++++++++++++++++++++----- src/board/system76/darp5/power.c | 7 ++- src/board/system76/darp5/smfi.c | 33 ++++++++++++++ src/board/system76/galp3-c/peci.c | 64 +++++++++++++++++++++++----- src/board/system76/galp3-c/power.c | 7 ++- src/board/system76/galp3-c/smfi.c | 33 ++++++++++++++ src/board/system76/lemp9/peci.c | 64 +++++++++++++++++++++++----- src/board/system76/lemp9/power.c | 7 ++- src/board/system76/lemp9/smfi.c | 33 ++++++++++++++ src/common/include/common/command.h | 4 ++ tool/src/.ec.rs.swp | Bin 0 -> 16384 bytes tool/src/ec.rs | 22 ++++++++-- tool/src/main.rs | 58 +++++++++++++++++++++++++ 13 files changed, 350 insertions(+), 46 deletions(-) create mode 100644 tool/src/.ec.rs.swp diff --git a/src/board/system76/darp5/peci.c b/src/board/system76/darp5/peci.c index c7991b1..93d8e0d 100644 --- a/src/board/system76/darp5/peci.c +++ b/src/board/system76/darp5/peci.c @@ -7,6 +7,12 @@ #include #include +// Fan speed is the lowest requested over HEATUP seconds +#define HEATUP 5 + +// Fan speed is the highest HEATUP speed over COOLDOWN seconds +#define COOLDOWN 10 + // Tjunction = 100C for i7-8565U (and probably the same for all WHL-U) #define T_JUNCTION 100 @@ -30,7 +36,7 @@ struct FanPoint __code FAN_POINTS[] = { FAN_POINT(70, 45), FAN_POINT(75, 55), FAN_POINT(80, 75), - FAN_POINT(84, 100), + FAN_POINT(84, 100) }; // Get duty cycle based on temperature, adapted from @@ -51,12 +57,14 @@ uint8_t fan_duty(int16_t temp) { // If in between current temp and previous temp, interpolate if (temp > prev->temp) { - int16_t dtemp = (cur->temp - prev->temp); - int16_t dduty = ((int16_t)cur->duty) - ((int16_t)prev->duty); - return (uint8_t)( - ((int16_t)prev->duty) + - ((temp - prev->temp) * dduty) / dtemp - ); + return prev->duty; + + // int16_t dtemp = (cur->temp - prev->temp); + // int16_t dduty = ((int16_t)cur->duty) - ((int16_t)prev->duty); + // return (uint8_t)( + // ((int16_t)prev->duty) + + // ((temp - prev->temp) * dduty) / dtemp + // ); } } } @@ -66,6 +74,40 @@ uint8_t fan_duty(int16_t temp) { return PWM_DUTY(100); } +uint8_t fan_heatup(uint8_t duty) { + static uint8_t history[HEATUP] = { 0 }; + uint8_t lowest = duty; + + int i; + for (i = 0; (i + 1) < ARRAY_SIZE(history); i++) { + uint8_t value = history[i + 1]; + if (value < lowest) { + lowest = value; + } + history[i] = value; + } + history[i] = duty; + + return lowest; +} + +uint8_t fan_cooldown(uint8_t duty) { + static uint8_t history[COOLDOWN] = { 0 }; + uint8_t highest = duty; + + int i; + for (i = 0; (i + 1) < ARRAY_SIZE(history); i++) { + uint8_t value = history[i + 1]; + if (value > highest) { + highest = value; + } + history[i] = value; + } + history[i] = duty; + + return highest; +} + void peci_init(void) { // Allow PECI pin to be used GCR2 |= (1 << 4); @@ -123,8 +165,10 @@ void peci_event(void) { peci_duty = PWM_DUTY(0); } - if (peci_duty != DCR2) { - DCR2 = peci_duty; - DEBUG("PECI offset=%d, temp=%d = %d\n", peci_offset, peci_temp, peci_duty); + uint8_t heatup_duty = fan_heatup(peci_duty); + uint8_t cooldown_duty = fan_cooldown(heatup_duty); + if (cooldown_duty != DCR2) { + DCR2 = cooldown_duty; + DEBUG("PECI offset=%d, temp=%d = %d\n", peci_offset, peci_temp, cooldown_duty); } } diff --git a/src/board/system76/darp5/power.c b/src/board/system76/darp5/power.c index 121a0d9..683db64 100644 --- a/src/board/system76/darp5/power.c +++ b/src/board/system76/darp5/power.c @@ -386,11 +386,10 @@ void power_event(void) { // state is S3 static bool ack_last = false; bool ack_new = gpio_get(&SUSWARN_N); - if (ack_new && !ack_last) { - DEBUG("%02X: SUSPWRDNACK asserted\n", main_cycle); - } #if LEVEL >= LEVEL_DEBUG - else if (!ack_new && ack_last) { + if (ack_new && !ack_last) { + DEBUG("%02X: SUSPWRDNACK asserted\n", main_cycle); + } else if (!ack_new && ack_last) { DEBUG("%02X: SUSPWRDNACK de-asserted\n", main_cycle); } #endif diff --git a/src/board/system76/darp5/smfi.c b/src/board/system76/darp5/smfi.c index b4dac23..ae2bc03 100644 --- a/src/board/system76/darp5/smfi.c +++ b/src/board/system76/darp5/smfi.c @@ -9,6 +9,7 @@ #include #include #include +#include // Shared memory host semaphore volatile uint8_t __xdata __at(0x1022) SMHSR; @@ -143,6 +144,30 @@ static enum Result cmd_reset(void) { return RES_ERR; } +static enum Result cmd_fan_get(void) { + // If setting fan 0 + if (smfi_cmd[2] == 0) { + // Get duty of fan 0 + smfi_cmd[3] = DCR2; + return RES_OK; + } + + // Failed if fan not found + return RES_ERR; +} + +static enum Result cmd_fan_set(void) { + // If setting fan 0 + if (smfi_cmd[2] == 0) { + // Set duty cycle of fan 0 + DCR2 = smfi_cmd[3]; + return RES_OK; + } + + // Failed if fan not found + return RES_ERR; +} + void smfi_event(void) { if (smfi_cmd[0]) { switch (smfi_cmd[0]) { @@ -174,6 +199,14 @@ void smfi_event(void) { case CMD_RESET: smfi_cmd[1] = cmd_reset(); break; +#ifndef __SCRATCH__ + case CMD_FAN_GET: + smfi_cmd[1] = cmd_fan_get(); + break; + case CMD_FAN_SET: + smfi_cmd[1] = cmd_fan_set(); + break; +#endif // __SCRATCH__ default: // Command not found smfi_cmd[1] = RES_ERR; diff --git a/src/board/system76/galp3-c/peci.c b/src/board/system76/galp3-c/peci.c index c7991b1..93d8e0d 100644 --- a/src/board/system76/galp3-c/peci.c +++ b/src/board/system76/galp3-c/peci.c @@ -7,6 +7,12 @@ #include #include +// Fan speed is the lowest requested over HEATUP seconds +#define HEATUP 5 + +// Fan speed is the highest HEATUP speed over COOLDOWN seconds +#define COOLDOWN 10 + // Tjunction = 100C for i7-8565U (and probably the same for all WHL-U) #define T_JUNCTION 100 @@ -30,7 +36,7 @@ struct FanPoint __code FAN_POINTS[] = { FAN_POINT(70, 45), FAN_POINT(75, 55), FAN_POINT(80, 75), - FAN_POINT(84, 100), + FAN_POINT(84, 100) }; // Get duty cycle based on temperature, adapted from @@ -51,12 +57,14 @@ uint8_t fan_duty(int16_t temp) { // If in between current temp and previous temp, interpolate if (temp > prev->temp) { - int16_t dtemp = (cur->temp - prev->temp); - int16_t dduty = ((int16_t)cur->duty) - ((int16_t)prev->duty); - return (uint8_t)( - ((int16_t)prev->duty) + - ((temp - prev->temp) * dduty) / dtemp - ); + return prev->duty; + + // int16_t dtemp = (cur->temp - prev->temp); + // int16_t dduty = ((int16_t)cur->duty) - ((int16_t)prev->duty); + // return (uint8_t)( + // ((int16_t)prev->duty) + + // ((temp - prev->temp) * dduty) / dtemp + // ); } } } @@ -66,6 +74,40 @@ uint8_t fan_duty(int16_t temp) { return PWM_DUTY(100); } +uint8_t fan_heatup(uint8_t duty) { + static uint8_t history[HEATUP] = { 0 }; + uint8_t lowest = duty; + + int i; + for (i = 0; (i + 1) < ARRAY_SIZE(history); i++) { + uint8_t value = history[i + 1]; + if (value < lowest) { + lowest = value; + } + history[i] = value; + } + history[i] = duty; + + return lowest; +} + +uint8_t fan_cooldown(uint8_t duty) { + static uint8_t history[COOLDOWN] = { 0 }; + uint8_t highest = duty; + + int i; + for (i = 0; (i + 1) < ARRAY_SIZE(history); i++) { + uint8_t value = history[i + 1]; + if (value > highest) { + highest = value; + } + history[i] = value; + } + history[i] = duty; + + return highest; +} + void peci_init(void) { // Allow PECI pin to be used GCR2 |= (1 << 4); @@ -123,8 +165,10 @@ void peci_event(void) { peci_duty = PWM_DUTY(0); } - if (peci_duty != DCR2) { - DCR2 = peci_duty; - DEBUG("PECI offset=%d, temp=%d = %d\n", peci_offset, peci_temp, peci_duty); + uint8_t heatup_duty = fan_heatup(peci_duty); + uint8_t cooldown_duty = fan_cooldown(heatup_duty); + if (cooldown_duty != DCR2) { + DCR2 = cooldown_duty; + DEBUG("PECI offset=%d, temp=%d = %d\n", peci_offset, peci_temp, cooldown_duty); } } diff --git a/src/board/system76/galp3-c/power.c b/src/board/system76/galp3-c/power.c index 0f9db81..0440459 100644 --- a/src/board/system76/galp3-c/power.c +++ b/src/board/system76/galp3-c/power.c @@ -383,11 +383,10 @@ void power_event(void) { // state is S3 static bool ack_last = false; bool ack_new = gpio_get(&SUSWARN_N); - if (ack_new && !ack_last) { - DEBUG("%02X: SUSPWRDNACK asserted\n", main_cycle); - } #if LEVEL >= LEVEL_DEBUG - else if (!ack_new && ack_last) { + if (ack_new && !ack_last) { + DEBUG("%02X: SUSPWRDNACK asserted\n", main_cycle); + } else if (!ack_new && ack_last) { DEBUG("%02X: SUSPWRDNACK de-asserted\n", main_cycle); } #endif diff --git a/src/board/system76/galp3-c/smfi.c b/src/board/system76/galp3-c/smfi.c index b4dac23..ae2bc03 100644 --- a/src/board/system76/galp3-c/smfi.c +++ b/src/board/system76/galp3-c/smfi.c @@ -9,6 +9,7 @@ #include #include #include +#include // Shared memory host semaphore volatile uint8_t __xdata __at(0x1022) SMHSR; @@ -143,6 +144,30 @@ static enum Result cmd_reset(void) { return RES_ERR; } +static enum Result cmd_fan_get(void) { + // If setting fan 0 + if (smfi_cmd[2] == 0) { + // Get duty of fan 0 + smfi_cmd[3] = DCR2; + return RES_OK; + } + + // Failed if fan not found + return RES_ERR; +} + +static enum Result cmd_fan_set(void) { + // If setting fan 0 + if (smfi_cmd[2] == 0) { + // Set duty cycle of fan 0 + DCR2 = smfi_cmd[3]; + return RES_OK; + } + + // Failed if fan not found + return RES_ERR; +} + void smfi_event(void) { if (smfi_cmd[0]) { switch (smfi_cmd[0]) { @@ -174,6 +199,14 @@ void smfi_event(void) { case CMD_RESET: smfi_cmd[1] = cmd_reset(); break; +#ifndef __SCRATCH__ + case CMD_FAN_GET: + smfi_cmd[1] = cmd_fan_get(); + break; + case CMD_FAN_SET: + smfi_cmd[1] = cmd_fan_set(); + break; +#endif // __SCRATCH__ default: // Command not found smfi_cmd[1] = RES_ERR; diff --git a/src/board/system76/lemp9/peci.c b/src/board/system76/lemp9/peci.c index c7991b1..93d8e0d 100644 --- a/src/board/system76/lemp9/peci.c +++ b/src/board/system76/lemp9/peci.c @@ -7,6 +7,12 @@ #include #include +// Fan speed is the lowest requested over HEATUP seconds +#define HEATUP 5 + +// Fan speed is the highest HEATUP speed over COOLDOWN seconds +#define COOLDOWN 10 + // Tjunction = 100C for i7-8565U (and probably the same for all WHL-U) #define T_JUNCTION 100 @@ -30,7 +36,7 @@ struct FanPoint __code FAN_POINTS[] = { FAN_POINT(70, 45), FAN_POINT(75, 55), FAN_POINT(80, 75), - FAN_POINT(84, 100), + FAN_POINT(84, 100) }; // Get duty cycle based on temperature, adapted from @@ -51,12 +57,14 @@ uint8_t fan_duty(int16_t temp) { // If in between current temp and previous temp, interpolate if (temp > prev->temp) { - int16_t dtemp = (cur->temp - prev->temp); - int16_t dduty = ((int16_t)cur->duty) - ((int16_t)prev->duty); - return (uint8_t)( - ((int16_t)prev->duty) + - ((temp - prev->temp) * dduty) / dtemp - ); + return prev->duty; + + // int16_t dtemp = (cur->temp - prev->temp); + // int16_t dduty = ((int16_t)cur->duty) - ((int16_t)prev->duty); + // return (uint8_t)( + // ((int16_t)prev->duty) + + // ((temp - prev->temp) * dduty) / dtemp + // ); } } } @@ -66,6 +74,40 @@ uint8_t fan_duty(int16_t temp) { return PWM_DUTY(100); } +uint8_t fan_heatup(uint8_t duty) { + static uint8_t history[HEATUP] = { 0 }; + uint8_t lowest = duty; + + int i; + for (i = 0; (i + 1) < ARRAY_SIZE(history); i++) { + uint8_t value = history[i + 1]; + if (value < lowest) { + lowest = value; + } + history[i] = value; + } + history[i] = duty; + + return lowest; +} + +uint8_t fan_cooldown(uint8_t duty) { + static uint8_t history[COOLDOWN] = { 0 }; + uint8_t highest = duty; + + int i; + for (i = 0; (i + 1) < ARRAY_SIZE(history); i++) { + uint8_t value = history[i + 1]; + if (value > highest) { + highest = value; + } + history[i] = value; + } + history[i] = duty; + + return highest; +} + void peci_init(void) { // Allow PECI pin to be used GCR2 |= (1 << 4); @@ -123,8 +165,10 @@ void peci_event(void) { peci_duty = PWM_DUTY(0); } - if (peci_duty != DCR2) { - DCR2 = peci_duty; - DEBUG("PECI offset=%d, temp=%d = %d\n", peci_offset, peci_temp, peci_duty); + uint8_t heatup_duty = fan_heatup(peci_duty); + uint8_t cooldown_duty = fan_cooldown(heatup_duty); + if (cooldown_duty != DCR2) { + DCR2 = cooldown_duty; + DEBUG("PECI offset=%d, temp=%d = %d\n", peci_offset, peci_temp, cooldown_duty); } } diff --git a/src/board/system76/lemp9/power.c b/src/board/system76/lemp9/power.c index 0f9db81..0440459 100644 --- a/src/board/system76/lemp9/power.c +++ b/src/board/system76/lemp9/power.c @@ -383,11 +383,10 @@ void power_event(void) { // state is S3 static bool ack_last = false; bool ack_new = gpio_get(&SUSWARN_N); - if (ack_new && !ack_last) { - DEBUG("%02X: SUSPWRDNACK asserted\n", main_cycle); - } #if LEVEL >= LEVEL_DEBUG - else if (!ack_new && ack_last) { + if (ack_new && !ack_last) { + DEBUG("%02X: SUSPWRDNACK asserted\n", main_cycle); + } else if (!ack_new && ack_last) { DEBUG("%02X: SUSPWRDNACK de-asserted\n", main_cycle); } #endif diff --git a/src/board/system76/lemp9/smfi.c b/src/board/system76/lemp9/smfi.c index b4dac23..ae2bc03 100644 --- a/src/board/system76/lemp9/smfi.c +++ b/src/board/system76/lemp9/smfi.c @@ -9,6 +9,7 @@ #include #include #include +#include // Shared memory host semaphore volatile uint8_t __xdata __at(0x1022) SMHSR; @@ -143,6 +144,30 @@ static enum Result cmd_reset(void) { return RES_ERR; } +static enum Result cmd_fan_get(void) { + // If setting fan 0 + if (smfi_cmd[2] == 0) { + // Get duty of fan 0 + smfi_cmd[3] = DCR2; + return RES_OK; + } + + // Failed if fan not found + return RES_ERR; +} + +static enum Result cmd_fan_set(void) { + // If setting fan 0 + if (smfi_cmd[2] == 0) { + // Set duty cycle of fan 0 + DCR2 = smfi_cmd[3]; + return RES_OK; + } + + // Failed if fan not found + return RES_ERR; +} + void smfi_event(void) { if (smfi_cmd[0]) { switch (smfi_cmd[0]) { @@ -174,6 +199,14 @@ void smfi_event(void) { case CMD_RESET: smfi_cmd[1] = cmd_reset(); break; +#ifndef __SCRATCH__ + case CMD_FAN_GET: + smfi_cmd[1] = cmd_fan_get(); + break; + case CMD_FAN_SET: + smfi_cmd[1] = cmd_fan_set(); + break; +#endif // __SCRATCH__ default: // Command not found smfi_cmd[1] = RES_ERR; diff --git a/src/common/include/common/command.h b/src/common/include/common/command.h index 36966f7..0f36400 100644 --- a/src/common/include/common/command.h +++ b/src/common/include/common/command.h @@ -16,6 +16,10 @@ enum Command { CMD_SPI = 5, // Reset EC CMD_RESET = 6, + // Get fan speeds + CMD_FAN_GET = 7, + // Set fan speeds + CMD_FAN_SET = 8, //TODO }; diff --git a/tool/src/.ec.rs.swp b/tool/src/.ec.rs.swp new file mode 100644 index 0000000000000000000000000000000000000000..3ef60735b9bc54e1f01b6c43512d3361f715a2dd GIT binary patch literal 16384 zcmeI2OKjXk7{><+lvkmYa-kp%52aqQNjBT2SuLBOWRuc}7D5szsFdKfXA_fM+w!AH zDFwvyzy%QEPy|9C!HEkX6_7x@B%pwJ3oeKQTmS(A2_Y`P{~OzDJ58VhBtRNVzwFM# z-+c3*nQ!dbG)h;F@8Ff5K89;8V-MeP*R^j=yX^848M|J1qP>?2{`iXObuT#He0NKC z1IJyy{!LNE-U-jSUKoMjD~#U2aaz5;XW)B}=P!Ss%2f&|1^%W2VbJWZtYMqVeI>GV z-nr-S)7M?`H;t;>l>$lurGQdEDWDWk3Md7X0!o4ZlLCReirox{kB*(bH~u_o$>(qJ zb!(h|-;(lQ;8K9mAV0i}RaKq;UUPzopolmbctrGQdEDWDYi7ZlJ< z#?D8(x6=Ute*aJ2|Gznvv9G~r-~;d`cpf|n9s&1*d%-=x15L0KjDsq;0GtU<0>9!= z;Ct{nco)0_-Ug3?5NrcyfeqldHH`fLz5pMB_rZJMAh;h~4SGQjI2-(UGVFo3!E@jN zV1TQ^RbU8ofm6UQCo%RN_!fK$J^^olSHR2QAz*>aK@n^KXMpwK2mC+8v)~c14+t;= zt_3x)8hn2OV~4%ev}0ycvl&<)mtmoSeW1T>#+qd9d17mu%&2zWaTxWxx~ z=|Yxd`=ZtCF`RZ=w@t0yHmlVuh38w2UAQQ!ZtylL}2QK8mFrs&=xH}ycT za+$F!tn4oE?jb%Y{IC_&Lf^VU6#1CvIo=T8CmZeU?M1O}CZ>4PbJ~2Y&SdHSj$@b2 zYgVC?O7l4`i(fFv`_fKOUcLt9G(4ft=POxF>FzFTHy?xyu}f5$F6xOal0ECe?WU%o zEnY5g-REJYkXl=qvswa<)Z|?~En#Z~-qppE`ZZqe-;%L^#PW|gx@RsgeuIks3F)Pw zm4wd=iLuk<_*JV@)~u}up(ivy`Bun`5Em_9WO_`BVi4mQlUAgBQX?+Fbd+;Ys=Ya* zFvhCW4vm&pS{&F?;u8`v;steJz zY(ZNOjJb?3eOdXE)#wS|b!-e^V{afhf&mYNA4nI`j^i;hPgho#nFZYnv_ujrg}I%` zllR5q@xb9t%eMSEMm6Y?HWoZ95L&6ojkbB%@*+D_&J!c#7Lzw8_s1nO#+)$bmpU=r zYH`;Sdn_mP6B|DF=bW%*@&-bgbV!)eWIn_tW1b0d@UY4e9JAx96_y#-$xs|Y zKo_8qFYI8c|M|(8nUy<9*^!oG%%^_uUm+x5c6Nr6Ryl1!a`~4Ew9w@)bg5%w8dA`W z*ZSdIjdVKlzD^1RFWblZ;Ar)Tx?70+O5LRyDE&1Xv)C9k-84PvX5W^y(uCzytF^E) zRIS=#Aqpw^IcADRIGe_cA}2dl+F@w-?DV;_SIRvym=j#y$bDBg#L}TD^h7I)cCpl# zj>ELd{<_1F>5kT^!^!?nuJ4nhN=`>>7YmtwVenrtji1^Dal254!RrzO7xdr=jqmrS()dQ=6M>;%-T5Y#gn}(SvSY!7! zYSZ-m$|Akfb|R(ZV50v~N)Dk+k%xZZg+?GPWDLR-6&l!a176=TIx{sfKC^ZE@WnGD z!}Uw9n4nOAuJ@5vn=V|b?o@qpc)GqVTe-Yq<qJfM}QHFf*P7P7SrK%S(HA+DiY&y#lHuurs8%tYl$u+hz`t+YDROK9 zXp7$eU&MR&I=rXT`#-+Q>tm!}2QPrf0lo7-3?2e5pm+VP;Cw*u`Rf7MI1Q*vDWDWk z3Md7X0!jg;fKosypcGIFC { cmd: u16, @@ -181,6 +183,18 @@ impl Ec { pub unsafe fn reset(&mut self) -> Result<(), Error> { self.command(Cmd::Reset) } + + pub unsafe fn fan_get(&mut self, index: u8) -> Result { + self.write(2, index); + self.command(Cmd::FanGet)?; + Ok(self.read(3)) + } + + pub unsafe fn fan_set(&mut self, index: u8, duty: u8) -> Result<(), Error> { + self.write(2, index); + self.write(3, duty); + self.command(Cmd::FanSet) + } } pub struct EcSpi<'a, T: Timeout> { diff --git a/tool/src/main.rs b/tool/src/main.rs index 1ef633e..7f48a36 100644 --- a/tool/src/main.rs +++ b/tool/src/main.rs @@ -274,9 +274,33 @@ unsafe fn print(message: &[u8]) -> Result<(), Error> { Ok(()) } +unsafe fn fan_get(index: u8) -> Result<(), Error> { + iopl(); + + let mut ec = Ec::new( + StdTimeout::new(Duration::new(1, 0)), + )?; + + let duty = ec.fan_get(index)?; + eprintln!("{}", duty); + + Ok(()) +} + +unsafe fn fan_set(index: u8, duty: u8) -> Result<(), Error> { + iopl(); + + let mut ec = Ec::new( + StdTimeout::new(Duration::new(1, 0)), + )?; + + ec.fan_set(index, duty) +} + fn usage() { eprintln!(" console"); eprintln!(" flash [file]"); + eprintln!(" fan [index] "); eprintln!(" info"); eprintln!(" print [message]"); } @@ -293,6 +317,40 @@ fn main() { process::exit(1); }, }, + "fan" => match args.next() { + Some(index_str) => match index_str.parse::() { + Ok(index) => match args.next() { + Some(duty_str) => match duty_str.parse::() { + Ok(duty) => match unsafe { fan_set(index, duty) } { + Ok(()) => (), + Err(err) => { + eprintln!("failed to set fan {} to {}: {:X?}", index, duty, err); + process::exit(1); + }, + }, + Err(err) => { + eprintln!("failed to parse '{}': {:X?}", duty_str, err); + process::exit(1); + }, + }, + None => match unsafe { fan_get(index) } { + Ok(()) => (), + Err(err) => { + eprintln!("failed to get fan {}: {:X?}", index, err); + process::exit(1); + }, + }, + }, + Err(err) => { + eprintln!("failed to parse '{}': {:X?}", index_str, err); + process::exit(1); + }, + }, + None => { + eprintln!("no index provided"); + process::exit(1); + }, + }, "flash" => match args.next() { Some(path) => match unsafe { flash(&path) } { Ok(()) => (),