#include #include #include #include #include #include #include #include #define DRIVER_NAME "vfiec_hwmon" /* SuperIO配置端口 */ #define REG_2E 0x2e #define REG_4E 0x4e #define DEV 0x07 #define PME 0x04 /* EC逻辑设备 */ #define GPIO 0x07 #define DEVID 0x20 #define DEVREV 0x22 #define IT87_ACT_REG 0x30 #define IT87_BASE_REG 0x60 /* IT8786E设备ID */ #define IT8786E_DEVID 0x8786 /* EC寄存器偏移 (相对于EC基地址) */ #define IT87_EC_OFFSET 5 #define IT87_ADDR_REG_OFFSET 0 #define IT87_DATA_REG_OFFSET 1 /* EC内部寄存器 */ #define IT87_REG_CONFIG 0x00 #define IT87_REG_FAN_MAIN_CTRL 0x13 #define IT87_REG_FAN_CTL 0x14 #define IT87_REG_FAN_16BIT 0x0c /* 风扇寄存器 */ static const u8 IT87_REG_FAN[] = {0x0d, 0x0e, 0x0f, 0x80, 0x82, 0x4c}; static const u8 IT87_REG_FANX[] = {0x18, 0x19, 0x1a, 0x81, 0x83, 0x4d}; /* PWM寄存器 */ static const u8 IT87_REG_PWM[] = {0x15, 0x16, 0x17, 0x7f, 0xa7, 0xaf}; static const u8 IT87_REG_PWM_DUTY[] = {0x63, 0x6b, 0x73, 0x7b, 0xa3, 0xab}; /* 温度寄存器 */ #define IT87_REG_TEMP(nr) (0x29 + (nr)) /* 特性标志 */ #define FEAT_16BIT_FANS BIT(3) #define FEAT_NEWER_AUTOPWM BIT(1) #define FEAT_TEMP_OFFSET BIT(4) #define FEAT_TEMP_PECI BIT(5) #define FEAT_PWM_FREQ2 BIT(16) /* 设备数据结构 */ struct vfiec_data { unsigned short addr; /* EC基地址 */ int sioaddr; /* SuperIO地址 */ u32 features; u8 fan_main_ctrl; u8 has_fan; struct mutex lock; }; static struct vfiec_data *g_data = NULL; extern struct kobject *hwmon_kobj; /* ========== SuperIO操作 ========== */ static inline int superio_inw(int ioreg, int reg) { int val; outb(reg++, ioreg); val = inb(ioreg + 1) << 8; outb(reg, ioreg); val |= inb(ioreg + 1); return val; } static inline int superio_inb(int ioreg, int reg) { outb(reg, ioreg); return inb(ioreg + 1); } static inline void superio_outb(int ioreg, int reg, int val) { outb(reg, ioreg); outb(val, ioreg + 1); } static inline void superio_select(int ioreg, int ldn) { outb(DEV, ioreg); outb(ldn, ioreg + 1); } static inline int superio_enter(int ioreg) { if (!request_muxed_region(ioreg, 2, DRIVER_NAME)) return -EBUSY; outb(0x87, ioreg); outb(0x01, ioreg); outb(0x55, ioreg); outb(ioreg == REG_4E ? 0xaa : 0x55, ioreg); return 0; } static inline void superio_exit(int ioreg) { outb(0x02, ioreg); outb(0x02, ioreg + 1); release_region(ioreg, 2); } /* ========== EC寄存器读写 ========== */ static int ec_read_reg(struct vfiec_data *data, u8 reg) { outb_p(reg, data->addr + IT87_ADDR_REG_OFFSET); return inb_p(data->addr + IT87_DATA_REG_OFFSET); } static void ec_write_reg(struct vfiec_data *data, u8 reg, u8 value) { outb_p(reg, data->addr + IT87_ADDR_REG_OFFSET); outb_p(value, data->addr + IT87_DATA_REG_OFFSET); } /* ========== 数据转换 ========== */ #define FAN16_FROM_REG(val) ((val) == 0 ? -1 : (val) == 0xffff ? 0 \ : 1350000 / ((val) * 2)) #define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : (val) == 255 ? 0 \ : 1350000 / ((val) * (div))) #define TEMP_FROM_REG(val) ((val) * 1000) static int pwm_from_reg(u8 reg) { /* IT8786使用newer autopwm,直接返回8位值 */ return reg; } static u8 pwm_to_reg(long val) { return (u8)(val & 0xff); } /* ========== 风扇模式检测 ========== */ static int pwm_mode(struct vfiec_data *data, int nr) { u8 pwm_ctrl = ec_read_reg(data, IT87_REG_PWM[nr]); if (nr < 3 && !(data->fan_main_ctrl & BIT(nr))) return 0; /* Full speed */ if (pwm_ctrl & 0x80) return 2; /* Automatic mode */ return 1; /* Manual mode */ } /* ========== 设备探测 ========== */ static int vfiec_find(int sioaddr, unsigned short *address) { int err; u16 chip_type; err = superio_enter(sioaddr); if (err) return err; chip_type = superio_inw(sioaddr, DEVID); if (chip_type != IT8786E_DEVID) { superio_exit(sioaddr); return -ENODEV; } superio_select(sioaddr, PME); if (!(superio_inb(sioaddr, IT87_ACT_REG) & 0x01)) { pr_info("Device not activated\n"); superio_exit(sioaddr); return -ENODEV; } *address = superio_inw(sioaddr, IT87_BASE_REG) & ~0x07; if (*address == 0) { pr_info("Base address not set\n"); superio_exit(sioaddr); return -ENODEV; } pr_info("Found IT8786E chip at 0x%x\n", *address); superio_exit(sioaddr); return 0; } /* ========== sysfs属性回调 ========== */ /* fan1_input */ static ssize_t fan1_input_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct vfiec_data *data = g_data; int val; u16 reg; mutex_lock(&data->lock); reg = ec_read_reg(data, IT87_REG_FAN[0]); reg |= (ec_read_reg(data, IT87_REG_FANX[0]) << 8); val = FAN16_FROM_REG(reg); mutex_unlock(&data->lock); return sprintf(buf, "%d\n", val); } /* fan2_input */ static ssize_t fan2_input_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct vfiec_data *data = g_data; int val; u16 reg; mutex_lock(&data->lock); reg = ec_read_reg(data, IT87_REG_FAN[1]); reg |= (ec_read_reg(data, IT87_REG_FANX[1]) << 8); val = FAN16_FROM_REG(reg); mutex_unlock(&data->lock); return sprintf(buf, "%d\n", val); } /* pwm1 */ static ssize_t pwm1_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct vfiec_data *data = g_data; int val; mutex_lock(&data->lock); val = pwm_from_reg(ec_read_reg(data, IT87_REG_PWM_DUTY[0])); mutex_unlock(&data->lock); return sprintf(buf, "%d\n", val); } static ssize_t pwm1_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct vfiec_data *data = g_data; long val; int mode; int ret; ret = kstrtol(buf, 10, &val); if (ret < 0) return ret; if (val < 0 || val > 255) return -EINVAL; mutex_lock(&data->lock); if(val > 200) val = 200; mode = pwm_mode(data, 0); if (mode == 1) { ec_write_reg(data, IT87_REG_PWM_DUTY[0], pwm_to_reg(val)); ret = count; } else { ret = -EINVAL; } mutex_unlock(&data->lock); return ret; } /* pwm2 */ static ssize_t pwm2_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct vfiec_data *data = g_data; int val; mutex_lock(&data->lock); val = pwm_from_reg(ec_read_reg(data, IT87_REG_PWM_DUTY[1])); mutex_unlock(&data->lock); return sprintf(buf, "%d\n", val); } static ssize_t pwm2_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct vfiec_data *data = g_data; long val; int mode; int ret; ret = kstrtol(buf, 10, &val); if (ret < 0) return ret; if (val < 0 || val > 255) return -EINVAL; mutex_lock(&data->lock); if(val > 200) val = 200; mode = pwm_mode(data, 1); if (mode == 1) { ec_write_reg(data, IT87_REG_PWM_DUTY[1], pwm_to_reg(val)); ret = count; } else { ret = -EINVAL; } mutex_unlock(&data->lock); return ret; } /* pwm1_enable */ static ssize_t pwm1_enable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct vfiec_data *data = g_data; int val; mutex_lock(&data->lock); val = pwm_mode(data, 0); mutex_unlock(&data->lock); return sprintf(buf, "%d\n", val); } static ssize_t pwm1_enable_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct vfiec_data *data = g_data; long val; int ret; u8 reg; int nr = 0; ret = kstrtol(buf, 10, &val); if (ret < 0) return ret; mutex_lock(&data->lock); if (val == 1) { /* Manual mode */ reg = ec_read_reg(data, IT87_REG_PWM[nr]); reg = (reg & 0x7c) | (nr & 0x03); /* temp map */ ec_write_reg(data, IT87_REG_PWM[nr], reg); data->fan_main_ctrl |= BIT(nr); ec_write_reg(data, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); } else if (val == 2) { /* Auto mode */ reg = ec_read_reg(data, IT87_REG_PWM[nr]); reg = 0x80 | (reg & 0x7c) | (nr & 0x03); ec_write_reg(data, IT87_REG_PWM[nr], reg); data->fan_main_ctrl |= BIT(nr); ec_write_reg(data, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); } else { mutex_unlock(&data->lock); return -EINVAL; } mutex_unlock(&data->lock); return count; } /* pwm2_enable */ static ssize_t pwm2_enable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct vfiec_data *data = g_data; int val; mutex_lock(&data->lock); val = pwm_mode(data, 1); mutex_unlock(&data->lock); return sprintf(buf, "%d\n", val); } static ssize_t pwm2_enable_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct vfiec_data *data = g_data; long val; int ret; u8 reg; int nr = 1; ret = kstrtol(buf, 10, &val); if (ret < 0) return ret; mutex_lock(&data->lock); if (val == 1) { /* Manual mode */ reg = ec_read_reg(data, IT87_REG_PWM[nr]); reg = (reg & 0x7c) | (nr & 0x03); /* temp map */ ec_write_reg(data, IT87_REG_PWM[nr], reg); data->fan_main_ctrl |= BIT(nr); ec_write_reg(data, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); } else if (val == 2) { /* Auto mode */ reg = ec_read_reg(data, IT87_REG_PWM[nr]); reg = 0x80 | (reg & 0x7c) | (nr & 0x03); ec_write_reg(data, IT87_REG_PWM[nr], reg); data->fan_main_ctrl |= BIT(nr); ec_write_reg(data, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); } else { mutex_unlock(&data->lock); return -EINVAL; } mutex_unlock(&data->lock); return count; } /* ========== 定义kobj_attribute ========== */ static struct kobj_attribute fan1_input_attr = __ATTR(fan1_input, 0444, fan1_input_show, NULL); static struct kobj_attribute fan2_input_attr = __ATTR(fan2_input, 0444, fan2_input_show, NULL); static struct kobj_attribute pwm1_attr = __ATTR(pwm1, 0644, pwm1_show, pwm1_store); static struct kobj_attribute pwm2_attr = __ATTR(pwm2, 0644, pwm2_show, pwm2_store); static struct kobj_attribute pwm1_enable_attr = __ATTR(pwm1_enable, 0644, pwm1_enable_show, pwm1_enable_store); static struct kobj_attribute pwm2_enable_attr = __ATTR(pwm2_enable, 0644, pwm2_enable_show, pwm2_enable_store); static struct attribute *hwmon_attrs[] = { &fan1_input_attr.attr, &fan2_input_attr.attr, &pwm1_attr.attr, &pwm2_attr.attr, &pwm1_enable_attr.attr, &pwm2_enable_attr.attr, NULL, }; static struct attribute_group fan_attr_group = { .attrs = hwmon_attrs, }; /* ========== 模块初始化 ========== */ static void set_auto_mode(int nr) { u8 reg; struct vfiec_data *data = g_data; reg = ec_read_reg(data, IT87_REG_PWM[nr]); reg = 0x80 | (reg & 0x7c) | (nr & 0x03); ec_write_reg(data, IT87_REG_PWM[nr], reg); data->fan_main_ctrl |= BIT(nr); ec_write_reg(data, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); } int fan_init(void) { int err; unsigned short address = 0; struct vfiec_data *data; /* 探测SuperIO */ err = vfiec_find(REG_2E, &address); if (err) { err = vfiec_find(REG_4E, &address); if (err) { pr_err("IT8786E not found\n"); return -ENODEV; } } /* 分配数据结构 */ data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; data->addr = address + IT87_EC_OFFSET; data->sioaddr = (err == 0) ? REG_2E : REG_4E; /* 简化判断 */ data->features = FEAT_16BIT_FANS | FEAT_NEWER_AUTOPWM | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_PWM_FREQ2; mutex_init(&data->lock); /* 请求I/O区域 */ if (!request_region(data->addr, 2, DRIVER_NAME)) { pr_err("Failed to request I/O region 0x%x\n", data->addr); kfree(data); return -EBUSY; } /* 读取初始状态 */ data->fan_main_ctrl = ec_read_reg(data, IT87_REG_FAN_MAIN_CTRL); data->has_fan = (data->fan_main_ctrl >> 4) & 0x07; /* 启用16位风扇模式 */ ec_write_reg(data, IT87_REG_FAN_16BIT, ec_read_reg(data, IT87_REG_FAN_16BIT) | 0x07); /* 启动监控 */ ec_write_reg(data, IT87_REG_CONFIG, (ec_read_reg(data, IT87_REG_CONFIG) & 0x3e) | 0x01); g_data = data; /* 注册属性组 */ err = sysfs_create_group(hwmon_kobj, &fan_attr_group); if (err) { pr_err("Failed to create sysfs attributes\n"); goto err_release; } set_auto_mode(0); set_auto_mode(1); pr_info("VFIEC hwmon driver loaded, path: /sys/kernel/vfiec/hwmon/\n"); return 0; err_release: release_region(data->addr, 2); kfree(data); g_data = NULL; return err; } void fan_exit(void) { if (g_data) { sysfs_remove_group(hwmon_kobj, &fan_attr_group); release_region(g_data->addr, 2); kfree(g_data); g_data = NULL; } pr_info("VFIEC hwmon driver unloaded\n"); }