271 lines
7.1 KiB
C
271 lines
7.1 KiB
C
#include <linux/i2c-dev.h>
|
|
#include <i2c/smbus.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "icx3.h"
|
|
|
|
int icx3_init(struct card_info *card)
|
|
{
|
|
char data[I2C_SMBUS_BLOCK_MAX] = {};
|
|
int read_result;
|
|
struct icx3_info *temp_info;
|
|
|
|
int fd = open(card->i2c_dev_path, O_RDONLY);
|
|
|
|
if (fd == -1)
|
|
return -1;
|
|
|
|
if (ioctl(fd, I2C_SLAVE, ICX3_I2C_ADDR) < 0) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
read_result = i2c_smbus_read_i2c_block_data(fd, ICX3_REG_READINFORMATION, ICX3_READINFORMATION_SIZE, data);
|
|
if (read_result == ICX3_READINFORMATION_SIZE) {
|
|
temp_info = (struct icx3_info*)&data;
|
|
if (temp_info->slave_address == ICX3_I2C_ADDR) {
|
|
card->product_id = temp_info->product_id;
|
|
card->i2c_fd = fd;
|
|
return fd;
|
|
} else {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void print_icx3_fans(struct card_info *card)
|
|
{
|
|
struct icx3_fan_control fans[ICX3_MAX_FANS];
|
|
get_fan_status(fans, card);
|
|
|
|
for (int i=0; i < ICX3_MAX_FANS; i++) {
|
|
printf("%s: %d RPM (%d/%d%%, %s)\n",
|
|
icx3_fan_names[i],
|
|
fans[i].rpm_status,
|
|
fans[i].duty_status,
|
|
fans[i].duty,
|
|
icx3_fan_mode_names[fans[i].fanmode]
|
|
);
|
|
}
|
|
}
|
|
|
|
void print_icx3_fans_oneline(struct card_info *card)
|
|
{
|
|
struct icx3_fan_control fans[ICX3_MAX_FANS];
|
|
get_fan_status(fans, card);
|
|
|
|
printf("FAN");
|
|
for (int i=0; i < ICX3_MAX_FANS; i++) {
|
|
printf(" %3d", fans[i].duty_status);
|
|
}
|
|
printf("%%");
|
|
}
|
|
|
|
void print_icx3_temps(struct card_info *card)
|
|
{
|
|
float temps[ICX3_NUM_TEMP_SENSORS];
|
|
|
|
get_temp_sensors(temps, card);
|
|
|
|
for (int i=0; i<ICX3_NUM_TEMP_SENSORS; i++) {
|
|
printf("%s: %+.1f°C\n",
|
|
icx3_temp_sensor_names[i],
|
|
temps[i]);
|
|
}
|
|
}
|
|
|
|
void print_icx3_temps_oneline(struct card_info *card)
|
|
{
|
|
float temps[ICX3_NUM_TEMP_SENSORS];
|
|
|
|
get_temp_sensors(temps, card);
|
|
|
|
for (int i=0; i<ICX3_NUM_TEMP_SENSORS; i++) {
|
|
if (i == 0 || strncmp(icx3_temp_sensor_names[i], icx3_temp_sensor_names[i-1], 3))
|
|
printf(" %.3s", icx3_temp_sensor_names[i]);
|
|
printf(" %3.0f", temps[i]);
|
|
}
|
|
printf("°C");
|
|
}
|
|
|
|
void get_available_fans(char *fans_avail, struct card_info *card)
|
|
{
|
|
int product_id = card->product_id;
|
|
|
|
/* From ICX3TotalFanCtrl.cs */
|
|
switch(product_id) {
|
|
case ICX3_PRODUCT_E221_E222_XC3:
|
|
case ICX3_PRODUCT_E229_XC3:
|
|
case ICX3_PRODUCT_E222_XC3_LPC54113:
|
|
case ICX3_PRODUCT_E229_XC3_LPC54113:
|
|
case ICX3_PRODUCT_E221_XC3_STM32L431:
|
|
case ICX3_PRODUCT_E229_E237_XC3_STM32L431:
|
|
/* XC3, default plus fan 3 */
|
|
fans_avail[0] = 1;
|
|
fans_avail[1] = 1;
|
|
fans_avail[2] = 1;
|
|
break;
|
|
case ICX3_PRODUCT_E227_E228_FTW3:
|
|
case ICX3_PRODUCT_E230_FTW3:
|
|
case ICX3_PRODUCT_E227_E228_FTW3_LPC54113:
|
|
case ICX3_PRODUCT_E230_FTW3_LPC54113:
|
|
case ICX3_PRODUCT_E230_E238_FTW3_STM32L431:
|
|
case ICX3_PRODUCT_E250_FTW3:
|
|
/* FTW3, default plus fan 3 and external fan */
|
|
fans_avail[0] = 1;
|
|
fans_avail[1] = 1;
|
|
fans_avail[2] = 1;
|
|
fans_avail[3] = 1;
|
|
break;
|
|
case ICX3_PRODUCT_E223_KINGPIN_HYBRID:
|
|
case ICX3_PRODUCT_E227_E228_FTW3_HYBRID:
|
|
case ICX3_PRODUCT_E251_KINGPIN_HYBRID:
|
|
case ICX3_PRODUCT_E227_E228_FTW3_HYBRID_LPC54113:
|
|
case ICX3_PRODUCT_E250_FTW3_HYBRID:
|
|
/* KINGPIN, default and external */
|
|
fans_avail[0] = 1;
|
|
fans_avail[1] = 1;
|
|
fans_avail[3] = 1;
|
|
break;
|
|
case ICX3_PRODUCT_E227_E228_FTW3_HC:
|
|
case ICX3_PRODUCT_E223_KINGPIN_HC:
|
|
case ICX3_PRODUCT_E251_KINGPIN_HC:
|
|
/* External fan only */
|
|
fans_avail[3] = 1;
|
|
break;
|
|
default:
|
|
/* default fans only */
|
|
fans_avail[0] = 1;
|
|
fans_avail[1] = 1;
|
|
break;
|
|
case ICX3_PRODUCT_E221_E222_XC3_HC:
|
|
/* Not sure what this case is for */
|
|
break;
|
|
}
|
|
}
|
|
|
|
void get_fan_status(struct icx3_fan_control *fans, struct card_info *card)
|
|
{
|
|
char data[I2C_SMBUS_BLOCK_MAX] = {};
|
|
int read_result;
|
|
|
|
char fans_avail[ICX3_MAX_FANS] = {0};
|
|
get_available_fans(fans_avail, card);
|
|
|
|
for (int i=0; i < ICX3_MAX_FANS; i++) {
|
|
if (!fans_avail[i]) {
|
|
continue;
|
|
}
|
|
|
|
read_result = i2c_smbus_read_i2c_block_data(card->i2c_fd, ICX3_REG_FANCONTROL + i, ICX3_FANCONTROL_SIZE, data);
|
|
if (read_result != ICX3_FANCONTROL_SIZE) {
|
|
return;
|
|
}
|
|
memcpy(&fans[i], &data, sizeof(struct icx3_fan_control));
|
|
}
|
|
}
|
|
|
|
void get_temp_sensors(float *temps, struct card_info *card)
|
|
{
|
|
char data[I2C_SMBUS_BLOCK_MAX] = {};
|
|
int read_result;
|
|
struct icx3_temp_sensors *temp_sensors;
|
|
|
|
read_result = i2c_smbus_read_i2c_block_data(card->i2c_fd, ICX3_REG_TEMPSENSOR, ICX3_TEMPSENSOR_SIZE, data);
|
|
if (read_result != ICX3_TEMPSENSOR_SIZE)
|
|
return;
|
|
temp_sensors = (struct icx3_temp_sensors*) &data;
|
|
|
|
float cur_temp;
|
|
short cur_data;
|
|
|
|
for (int i=0; i<ICX3_NUM_TEMP_SENSORS; i++) {
|
|
/* endian swap */
|
|
cur_data = (short)(temp_sensors->data[2*i+1] << 8) | (short)(temp_sensors->data[2*i]);
|
|
/* temp is reported in tenths of deg C */
|
|
temps[i] = (float)cur_data/10;
|
|
}
|
|
}
|
|
|
|
void enable_write(int enable, struct card_info *card)
|
|
{
|
|
unsigned char *data;
|
|
if (enable)
|
|
data = icx3_write_enable;
|
|
else
|
|
data = icx3_write_disable;
|
|
/* Enable or disable write */
|
|
i2c_smbus_write_i2c_block_data(card->i2c_fd, ICX3_REG_ENABLEWRITE, ICX3_ENABLEWRITE_SIZE, data);
|
|
|
|
/* Read back the result to verify */
|
|
unsigned char read_result[ICX3_ENABLEWRITE_SIZE];
|
|
int write_ok = 0;
|
|
i2c_smbus_read_i2c_block_data(card->i2c_fd, ICX3_REG_ENABLEWRITE, ICX3_ENABLEWRITE_SIZE, read_result);
|
|
if (enable)
|
|
write_ok = (read_result[1] == 0xFC);
|
|
else
|
|
write_ok = (read_result[1] == 0xFE);
|
|
|
|
if (!write_ok)
|
|
printf("Unable to enable/disable write on %s\n", card->i2c_dev_path);
|
|
}
|
|
|
|
/* Sets a given fan on the GPU according to a string setting
|
|
'auto' resets the fan to default (fans 1-2 go to GPU control, 3-4 to +0 RPM offset from GPU control)
|
|
A number without a sign (e.g. 50) manually sets the fan to a given % duty cycle
|
|
A number with a sign (e.g. +500) sets the fan to run at an RPM offset from GPU control */
|
|
void set_fan(int fan, char *setting, struct card_info *card)
|
|
{
|
|
char fans_avail[ICX3_MAX_FANS] = {0};
|
|
|
|
struct icx3_fan_control fan_control = {};
|
|
struct icx3_fan_control fan_readback = {};
|
|
char reg = ICX3_REG_FANCONTROL + fan;
|
|
|
|
int write_result;
|
|
|
|
/* Check to make sure we're setting a valid fan */
|
|
get_available_fans(fans_avail, card);
|
|
if (fans_avail[fan] == 0) {
|
|
printf("Fan %d does not exist on this card \n", fan);
|
|
return;
|
|
}
|
|
|
|
fan_control.length = ICX3_FANCONTROL_SIZE - 1;
|
|
if (strcmp(setting, "auto") == 0) {
|
|
/* auto setting */
|
|
fan_control.rpm_offset = 0;
|
|
if (fan <= 1)
|
|
fan_control.fanmode = ICX3_FANMODE_GPU_CONTROL;
|
|
else
|
|
fan_control.fanmode = ICX3_FANMODE_GPU_RPM_OFFSET;
|
|
} else if (setting[0] == '+' || setting[0] == '-') {
|
|
/* RPM offset */
|
|
fan_control.fanmode = ICX3_FANMODE_GPU_RPM_OFFSET;
|
|
fan_control.rpm_offset = atoi(setting);
|
|
} else {
|
|
fan_control.fanmode = ICX3_FANMODE_RPM;
|
|
fan_control.duty = atoi(setting);
|
|
}
|
|
|
|
enable_write(1, card);
|
|
|
|
i2c_smbus_write_i2c_block_data(card->i2c_fd, reg, ICX3_FANCONTROL_SIZE, (char *)&fan_control);
|
|
|
|
/* Read back data and verify we set the fan properly */
|
|
i2c_smbus_read_i2c_block_data(card->i2c_fd, reg, ICX3_FANCONTROL_SIZE, (char *)&fan_readback);
|
|
if (fan_readback.fanmode != fan_control.fanmode ||
|
|
fan_readback.rpm_offset != fan_control.rpm_offset ||
|
|
fan_readback.duty != fan_control.duty)
|
|
printf("Error setting fan %d on %s\n", fan, card->i2c_fd);
|
|
}
|
|
|