// SPDX-License-Identifier: GPL-2.0-or-only /* * Deepcool Digital Linux Kernel Driver * * Driver for Deepcool Digital USB HID devices (CPU coolers, AIOs, cases) * Provides sysfs interface for monitoring and controlling display modes * * Copyright (C) 2025 */ #include #include #include #include #include #include #include #include #include #include #include #include #define DRIVER_NAME "deepcool_digital" #define DRIVER_VERSION "0.1.0" // Vendor and Product IDs #define DEEPCOOL_VENDOR_ID 0x3633 #define CH510_VENDOR_ID 0x34D3 #define CH510_PRODUCT_ID 0x1100 // Update intervals #define DEFAULT_UPDATE_INTERVAL_MS 1000 #define MIN_UPDATE_INTERVAL_MS 100 #define MAX_UPDATE_INTERVAL_MS 2000 #define AUTO_MODE_INTERVAL_MS 5000 // Temperature limits #define TEMP_WARNING_C 80 #define TEMP_WARNING_F 176 #define TEMP_LIMIT_C 90 #define TEMP_LIMIT_F 194 // Device series identifiers enum deepcool_series { DEEPCOOL_AK_SERIES, DEEPCOOL_LS_SERIES, DEEPCOOL_AG_SERIES, DEEPCOOL_LQ_SERIES, DEEPCOOL_LD_SERIES, DEEPCOOL_LP_SERIES, DEEPCOOL_CH_SERIES, DEEPCOOL_CH_SERIES_GEN2, DEEPCOOL_CH510, DEEPCOOL_AK400_PRO, DEEPCOOL_AK620_PRO, DEEPCOOL_UNKNOWN, }; // Display modes enum deepcool_mode { MODE_DEFAULT = 0, MODE_AUTO, MODE_CPU_TEMP, MODE_CPU_USAGE, MODE_CPU_POWER, MODE_CPU_FREQ, MODE_CPU_FAN, MODE_GPU_TEMP, MODE_GPU_USAGE, MODE_GPU_POWER, MODE_CPU, MODE_GPU, MODE_PSU, MODE_MAX, }; // Represents a device struct deepcool_device { struct hid_device *hdev; struct device *hwmon_dev; struct delayed_work update_work; struct mutex lock; // Device identification enum deepcool_series series; u16 product_id; // Configuration enum deepcool_mode mode; enum deepcool_mode secondary_mode; unsigned int update_interval_ms; bool fahrenheit; bool alarm_enabled; u8 rotation; // CPU monitoring state u64 prev_idle_time; u64 prev_total_time; u64 prev_energy_uj; unsigned long prev_jiffies; // Cached sensor values s32 cpu_temp; u8 cpu_usage; u16 cpu_power; u16 cpu_freq; // Auto mode state unsigned long auto_mode_last_change; enum deepcool_mode auto_mode_current; // Device capabilities bool supports_secondary_mode; bool supports_rotation; bool supports_alarm; bool supports_fahrenheit; }; // Map mode names static const char *mode_names[MODE_MAX] = { [MODE_DEFAULT] = "default", [MODE_AUTO] = "auto", [MODE_CPU_TEMP] = "cpu_temp", [MODE_CPU_USAGE] = "cpu_usage", [MODE_CPU_POWER] = "cpu_power", [MODE_CPU_FREQ] = "cpu_freq", [MODE_CPU_FAN] = "cpu_fan", [MODE_GPU_TEMP] = "gpu_temp", [MODE_GPU_USAGE] = "gpu_usage", [MODE_GPU_POWER] = "gpu_power", [MODE_CPU] = "cpu", [MODE_GPU] = "gpu", [MODE_PSU] = "psu", }; static void deepcool_update_work(struct work_struct *work); static int deepcool_send_packet(struct deepcool_device *ddata); // CPU Monitoring static s32 deepcool_read_cpu_temp(void) { struct thermal_zone_device *tz; int temp = 0; int ret; // Try to find CPU thermal zone // Keyword: try // This sometimes fails unexpectedly and I do not know why tz = thermal_zone_get_zone_by_name("x86_pkg_temp"); if (IS_ERR(tz)) { tz = thermal_zone_get_zone_by_name("cpu_thermal"); if (IS_ERR(tz)) return 0; } ret = thermal_zone_get_temp(tz, &temp); if (ret < 0) return 0; return temp / 1000; } static void deepcool_read_cpu_usage(struct deepcool_device *ddata) { u64 idle_time = 0, total_time = 0; u64 delta_idle, delta_total; int cpu; // Sum up idle and total time across all CPUs for_each_possible_cpu(cpu) { struct kernel_cpustat *kcs = &kcpustat_cpu(cpu); idle_time += kcs->cpustat[CPUTIME_IDLE]; idle_time += kcs->cpustat[CPUTIME_IOWAIT]; total_time += kcs->cpustat[CPUTIME_USER]; total_time += kcs->cpustat[CPUTIME_NICE]; total_time += kcs->cpustat[CPUTIME_SYSTEM]; total_time += kcs->cpustat[CPUTIME_IDLE]; total_time += kcs->cpustat[CPUTIME_IOWAIT]; total_time += kcs->cpustat[CPUTIME_IRQ]; total_time += kcs->cpustat[CPUTIME_SOFTIRQ]; total_time += kcs->cpustat[CPUTIME_STEAL]; } if (ddata->prev_total_time == 0) { ddata->prev_idle_time = idle_time; ddata->prev_total_time = total_time; ddata->cpu_usage = 0; return; } delta_idle = idle_time - ddata->prev_idle_time; delta_total = total_time - ddata->prev_total_time; if (delta_total > 0) ddata->cpu_usage = 100 - (u8)((delta_idle * 100) / delta_total); else ddata->cpu_usage = 0; ddata->prev_idle_time = idle_time; ddata->prev_total_time = total_time; } static u16 deepcool_read_cpu_freq(void) { unsigned int freq_khz = 0; unsigned int max_freq = 0; int cpu; // Find the highest CPU frequency across all cores for_each_possible_cpu(cpu) { freq_khz = cpufreq_quick_get(cpu); if (freq_khz > max_freq) max_freq = freq_khz; } // Convert kHz to MHz return max_freq / 1000; } static u64 deepcool_read_cpu_energy(void) { struct file *fp; char buf[64]; u64 energy = 0; loff_t pos = 0; int ret; // TODO: confirm if this path can change fp = filp_open("/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj", O_RDONLY, 0); if (IS_ERR(fp)) return 0; ret = kernel_read(fp, buf, sizeof(buf) - 1, &pos); if (ret > 0) { buf[ret] = '\0'; ret = kstrtou64(buf, 10, &energy); if (ret < 0) energy = 0; } filp_close(fp, NULL); return energy; } static void deepcool_calc_cpu_power(struct deepcool_device *ddata) { u64 current_energy; u64 delta_energy; unsigned long current_jiffies; unsigned long delta_ms; u64 max_energy_uj = 262143328850ULL; // default max for Intel RAPL current_energy = deepcool_read_cpu_energy(); current_jiffies = jiffies; if (ddata->prev_energy_uj == 0 || current_energy == 0) { ddata->prev_energy_uj = current_energy; ddata->prev_jiffies = current_jiffies; ddata->cpu_power = 0; return; } delta_ms = jiffies_to_msecs(current_jiffies - ddata->prev_jiffies); if (delta_ms == 0) { ddata->cpu_power = 0; return; } // Handle counter rollover if (current_energy > ddata->prev_energy_uj) delta_energy = current_energy - ddata->prev_energy_uj; else delta_energy = (max_energy_uj + current_energy) - ddata->prev_energy_uj; // Calculate power: W = ΔμJ / (Δms * 1000) ddata->cpu_power = (u16)(delta_energy / (delta_ms * 1000)); ddata->prev_energy_uj = current_energy; ddata->prev_jiffies = current_jiffies; } static void deepcool_update_sensors(struct deepcool_device *ddata) { ddata->cpu_temp = deepcool_read_cpu_temp(); deepcool_read_cpu_usage(ddata); ddata->cpu_freq = deepcool_read_cpu_freq(); deepcool_calc_cpu_power(ddata); } // Auto Mode Management static enum deepcool_mode deepcool_next_auto_mode(struct deepcool_device *ddata) { enum deepcool_mode next; switch (ddata->series) { case DEEPCOOL_AK_SERIES: // Cycle: cpu_temp -> cpu_usage -> cpu_temp if (ddata->auto_mode_current == MODE_CPU_TEMP) next = MODE_CPU_USAGE; else next = MODE_CPU_TEMP; break; case DEEPCOOL_LS_SERIES: // cpu_temp -> cpu_power -> cpu_temp if (ddata->auto_mode_current == MODE_CPU_TEMP) next = MODE_CPU_POWER; else next = MODE_CPU_TEMP; break; case DEEPCOOL_AG_SERIES: // cpu_temp -> cpu_usage -> cpu_temp if (ddata->auto_mode_current == MODE_CPU_TEMP) next = MODE_CPU_USAGE; else next = MODE_CPU_TEMP; break; case DEEPCOOL_AK620_PRO: case DEEPCOOL_AK400_PRO: // Always show all metrics simultaneously next = MODE_AUTO; break; default: next = MODE_CPU_TEMP; break; } return next; } static void deepcool_handle_auto_mode(struct deepcool_device *ddata) { unsigned long now = jiffies; if (time_after(now, ddata->auto_mode_last_change + msecs_to_jiffies(AUTO_MODE_INTERVAL_MS))) { ddata->auto_mode_current = deepcool_next_auto_mode(ddata); ddata->auto_mode_last_change = now; } } // Device communication static int deepcool_send_ak620_pro_packet(struct deepcool_device *ddata) { u8 *data = kzalloc(64, GFP_KERNEL); u16 checksum = 0; u16 power, freq; u8 temp; int i, ret; if (!data) return -ENOMEM; // Packet header data[0] = 16; data[1] = 104; data[2] = 1; data[3] = 4; data[4] = 13; data[5] = 1; data[6] = 2; data[7] = 8; // Power consumption (big-endian) power = cpu_to_be16(ddata->cpu_power); data[8] = (power >> 8) & 0xFF; data[9] = power & 0xFF; // Temp temp = ddata->cpu_temp; if (ddata->fahrenheit) { temp = (temp * 9 / 5) + 32; data[10] = 1; } else { data[10] = 0; } // Convert to float representation // TODO: this can be made more robust data[11] = 0; data[12] = 0; data[13] = temp; data[14] = 0; // Utilization data[15] = ddata->cpu_usage; // Frequency (big-endian) freq = cpu_to_be16(ddata->cpu_freq); data[16] = (freq >> 8) & 0xFF; data[17] = freq & 0xFF; // Calculate checksum for (i = 1; i <= 17; i++) checksum += data[i]; data[18] = checksum % 256; data[19] = 22; // Send HID output report ret = hid_hw_output_report(ddata->hdev, data, 64); kfree(data); if (ret < 0) { hid_err(ddata->hdev, "Failed to send packet: %d\n", ret); return ret; } return 0; } static int deepcool_send_ak_series_packet(struct deepcool_device *ddata) { u8 *data = kzalloc(64, GFP_KERNEL); u16 checksum = 0; enum deepcool_mode current_mode; u8 mode_byte; int i, ret; if (!data) return -ENOMEM; // Determine current mode if (ddata->mode == MODE_AUTO) { deepcool_handle_auto_mode(ddata); current_mode = ddata->auto_mode_current; } else { current_mode = ddata->mode; } // Initialize packet header data[0] = 16; data[1] = 106; data[2] = 1; data[3] = 3; // Mode byte switch (current_mode) { case MODE_CPU_TEMP: mode_byte = 0; break; case MODE_CPU_USAGE: mode_byte = 1; break; default: mode_byte = 0; break; } data[4] = mode_byte; // Alarm flag data[5] = ddata->alarm_enabled ? 1 : 0; // Temperature/usage value if (current_mode == MODE_CPU_TEMP) { u8 temp = ddata->cpu_temp; if (ddata->fahrenheit) temp = (temp * 9 / 5) + 32; data[6] = temp; } else { data[6] = ddata->cpu_usage; } // Calculate checksum for (i = 1; i <= 6; i++) checksum += data[i]; data[7] = checksum % 256; data[8] = 22; // Send HID output report ret = hid_hw_output_report(ddata->hdev, data, 64); kfree(data); if (ret < 0) { hid_err(ddata->hdev, "Failed to send packet: %d\n", ret); return ret; } return 0; } static int deepcool_send_ls_series_packet(struct deepcool_device *ddata) { u8 *data = kzalloc(64, GFP_KERNEL); u16 checksum = 0; enum deepcool_mode current_mode; u8 mode_byte; u16 value; int i, ret; if (!data) return -ENOMEM; // Determine current mode if (ddata->mode == MODE_AUTO) { deepcool_handle_auto_mode(ddata); current_mode = ddata->auto_mode_current; } else { current_mode = ddata->mode; } // Initialize packet header data[0] = 16; data[1] = 175; data[2] = 3; // Mode byte switch (current_mode) { case MODE_CPU_TEMP: mode_byte = 0; break; case MODE_CPU_POWER: mode_byte = 2; break; default: mode_byte = 0; break; } data[3] = mode_byte; // Alarm flag data[4] = ddata->alarm_enabled ? 1 : 0; // Value (big-endian) if (current_mode == MODE_CPU_TEMP) { u8 temp = ddata->cpu_temp; if (ddata->fahrenheit) temp = (temp * 9 / 5) + 32; value = cpu_to_be16(temp); } else { value = cpu_to_be16(ddata->cpu_power); } data[5] = (value >> 8) & 0xFF; data[6] = value & 0xFF; // Calculate checksum for (i = 1; i <= 6; i++) checksum += data[i]; data[7] = checksum % 256; data[8] = 22; // Send HID output report ret = hid_hw_output_report(ddata->hdev, data, 64); kfree(data); if (ret < 0) { hid_err(ddata->hdev, "Failed to send packet: %d\n", ret); return ret; } return 0; } static int deepcool_send_ag_series_packet(struct deepcool_device *ddata) { u8 *data = kzalloc(64, GFP_KERNEL); u16 checksum = 0; enum deepcool_mode current_mode; u8 mode_byte; int i, ret; if (!data) return -ENOMEM; // Determine current mode if (ddata->mode == MODE_AUTO) { deepcool_handle_auto_mode(ddata); current_mode = ddata->auto_mode_current; } else { current_mode = ddata->mode; } // Initialize packet header data[0] = 16; data[1] = 170; data[2] = 1; data[3] = 3; // Mode byte switch (current_mode) { case MODE_CPU_TEMP: mode_byte = 0; break; case MODE_CPU_USAGE: mode_byte = 1; break; default: mode_byte = 0; break; } data[4] = mode_byte; // Alarm flag data[5] = ddata->alarm_enabled ? 1 : 0; // Temperature/usage value if (current_mode == MODE_CPU_TEMP) { data[6] = ddata->cpu_temp; } else { data[6] = ddata->cpu_usage; } // Calculate checksum for (i = 1; i <= 6; i++) checksum += data[i]; data[7] = checksum % 256; data[8] = 22; // termination byte // Send HID output report ret = hid_hw_output_report(ddata->hdev, data, 64); kfree(data); if (ret < 0) { hid_err(ddata->hdev, "Failed to send packet: %d\n", ret); return ret; } return 0; } static int deepcool_send_packet(struct deepcool_device *ddata) { int ret; switch (ddata->series) { case DEEPCOOL_AK620_PRO: case DEEPCOOL_AK400_PRO: ret = deepcool_send_ak620_pro_packet(ddata); break; case DEEPCOOL_AK_SERIES: ret = deepcool_send_ak_series_packet(ddata); break; case DEEPCOOL_LS_SERIES: ret = deepcool_send_ls_series_packet(ddata); break; case DEEPCOOL_AG_SERIES: ret = deepcool_send_ag_series_packet(ddata); break; default: hid_warn(ddata->hdev, "Unsupported device series\n"); ret = -EOPNOTSUPP; break; } return ret; } // Work queue handler static void deepcool_update_work(struct work_struct *work) { struct deepcool_device *ddata = container_of(work, struct deepcool_device, update_work.work); mutex_lock(&ddata->lock); // Update sensor readings deepcool_update_sensors(ddata); // Send updated packet to device deepcool_send_packet(ddata); mutex_unlock(&ddata->lock); // Schedule next update schedule_delayed_work(&ddata->update_work, msecs_to_jiffies(ddata->update_interval_ms)); } // Sysfs attrs static ssize_t mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct deepcool_device *ddata = dev_get_drvdata(dev); const char *mode_str; mutex_lock(&ddata->lock); mode_str = mode_names[ddata->mode]; mutex_unlock(&ddata->lock); return sprintf(buf, "%s\n", mode_str); } static ssize_t mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct deepcool_device *ddata = dev_get_drvdata(dev); char mode_str[32]; enum deepcool_mode new_mode = MODE_DEFAULT; int i; if (count >= sizeof(mode_str)) return -EINVAL; memcpy(mode_str, buf, count); mode_str[count] = '\0'; if (mode_str[count - 1] == '\n') mode_str[count - 1] = '\0'; // Find matching mode for (i = 0; i < MODE_MAX; i++) { if (mode_names[i] && strcmp(mode_str, mode_names[i]) == 0) { new_mode = i; break; } } if (i == MODE_MAX) return -EINVAL; mutex_lock(&ddata->lock); ddata->mode = new_mode; if (new_mode == MODE_AUTO) { ddata->auto_mode_current = MODE_CPU_TEMP; ddata->auto_mode_last_change = jiffies; } mutex_unlock(&ddata->lock); return count; } static ssize_t update_interval_show(struct device *dev, struct device_attribute *attr, char *buf) { struct deepcool_device *ddata = dev_get_drvdata(dev); unsigned int interval; mutex_lock(&ddata->lock); interval = ddata->update_interval_ms; mutex_unlock(&ddata->lock); return sprintf(buf, "%u\n", interval); } static ssize_t update_interval_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct deepcool_device *ddata = dev_get_drvdata(dev); unsigned int interval; int ret; ret = kstrtouint(buf, 10, &interval); if (ret) return ret; if (interval < MIN_UPDATE_INTERVAL_MS || interval > MAX_UPDATE_INTERVAL_MS) return -EINVAL; mutex_lock(&ddata->lock); ddata->update_interval_ms = interval; mutex_unlock(&ddata->lock); // Restart work with new interval cancel_delayed_work_sync(&ddata->update_work); schedule_delayed_work(&ddata->update_work, msecs_to_jiffies(interval)); return count; } static ssize_t fahrenheit_show(struct device *dev, struct device_attribute *attr, char *buf) { struct deepcool_device *ddata = dev_get_drvdata(dev); bool fahrenheit; mutex_lock(&ddata->lock); fahrenheit = ddata->fahrenheit; mutex_unlock(&ddata->lock); return sprintf(buf, "%d\n", fahrenheit ? 1 : 0); } static ssize_t fahrenheit_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct deepcool_device *ddata = dev_get_drvdata(dev); bool value; int ret; ret = kstrtobool(buf, &value); if (ret) return ret; if (value && !ddata->supports_fahrenheit) return -EOPNOTSUPP; mutex_lock(&ddata->lock); ddata->fahrenheit = value; mutex_unlock(&ddata->lock); return count; } static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf) { struct deepcool_device *ddata = dev_get_drvdata(dev); bool alarm; mutex_lock(&ddata->lock); alarm = ddata->alarm_enabled; mutex_unlock(&ddata->lock); return sprintf(buf, "%d\n", alarm ? 1 : 0); } static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct deepcool_device *ddata = dev_get_drvdata(dev); bool value; int ret; ret = kstrtobool(buf, &value); if (ret) return ret; if (value && !ddata->supports_alarm) return -EOPNOTSUPP; mutex_lock(&ddata->lock); ddata->alarm_enabled = value; mutex_unlock(&ddata->lock); return count; } static ssize_t cpu_temp_show(struct device *dev, struct device_attribute *attr, char *buf) { struct deepcool_device *ddata = dev_get_drvdata(dev); s32 temp; mutex_lock(&ddata->lock); temp = ddata->cpu_temp; if (ddata->fahrenheit) temp = (temp * 9 / 5) + 32; mutex_unlock(&ddata->lock); return sprintf(buf, "%d\n", temp); } static ssize_t cpu_usage_show(struct device *dev, struct device_attribute *attr, char *buf) { struct deepcool_device *ddata = dev_get_drvdata(dev); u8 usage; mutex_lock(&ddata->lock); usage = ddata->cpu_usage; mutex_unlock(&ddata->lock); return sprintf(buf, "%u\n", usage); } static ssize_t cpu_power_show(struct device *dev, struct device_attribute *attr, char *buf) { struct deepcool_device *ddata = dev_get_drvdata(dev); u16 power; mutex_lock(&ddata->lock); power = ddata->cpu_power; mutex_unlock(&ddata->lock); return sprintf(buf, "%u\n", power); } static ssize_t cpu_freq_show(struct device *dev, struct device_attribute *attr, char *buf) { struct deepcool_device *ddata = dev_get_drvdata(dev); u16 freq; mutex_lock(&ddata->lock); freq = ddata->cpu_freq; mutex_unlock(&ddata->lock); return sprintf(buf, "%u\n", freq); } static DEVICE_ATTR_RW(mode); static DEVICE_ATTR_RW(update_interval); static DEVICE_ATTR_RW(fahrenheit); static DEVICE_ATTR_RW(alarm); static DEVICE_ATTR_RO(cpu_temp); static DEVICE_ATTR_RO(cpu_usage); static DEVICE_ATTR_RO(cpu_power); static DEVICE_ATTR_RO(cpu_freq); static struct attribute *deepcool_attrs[] = {&dev_attr_mode.attr, &dev_attr_update_interval.attr, &dev_attr_fahrenheit.attr, &dev_attr_alarm.attr, &dev_attr_cpu_temp.attr, &dev_attr_cpu_usage.attr, &dev_attr_cpu_power.attr, &dev_attr_cpu_freq.attr, NULL}; ATTRIBUTE_GROUPS(deepcool); // Device identification static enum deepcool_series deepcool_identify_device(u16 product_id) { switch (product_id) { case 1 ... 4: return DEEPCOOL_AK_SERIES; case 5: return DEEPCOOL_AK400_PRO; case 6: return DEEPCOOL_LS_SERIES; case 7: return DEEPCOOL_AK620_PRO; case 8: return DEEPCOOL_AG_SERIES; case 9: return DEEPCOOL_LQ_SERIES; case 10 ... 11: return DEEPCOOL_LD_SERIES; case 12 ... 14: return DEEPCOOL_LP_SERIES; case 15 ... 17: return DEEPCOOL_CH_SERIES; case 18 ... 21: return DEEPCOOL_CH_SERIES_GEN2; case CH510_PRODUCT_ID: return DEEPCOOL_CH510; default: return DEEPCOOL_UNKNOWN; } } static void deepcool_set_capabilities(struct deepcool_device *ddata) { // Set default capabilities based on device series switch (ddata->series) { case DEEPCOOL_AK_SERIES: ddata->supports_alarm = true; ddata->supports_fahrenheit = true; ddata->supports_secondary_mode = false; ddata->supports_rotation = false; break; case DEEPCOOL_LS_SERIES: ddata->supports_alarm = true; ddata->supports_fahrenheit = true; ddata->supports_secondary_mode = false; ddata->supports_rotation = false; break; case DEEPCOOL_AG_SERIES: ddata->supports_alarm = true; ddata->supports_fahrenheit = false; ddata->supports_secondary_mode = false; ddata->supports_rotation = false; break; case DEEPCOOL_LP_SERIES: ddata->supports_alarm = false; ddata->supports_fahrenheit = true; ddata->supports_secondary_mode = true; ddata->supports_rotation = true; break; case DEEPCOOL_AK620_PRO: case DEEPCOOL_AK400_PRO: ddata->supports_alarm = false; // hard-coded alarm ddata->supports_fahrenheit = true; ddata->supports_secondary_mode = false; ddata->supports_rotation = false; break; default: ddata->supports_alarm = false; ddata->supports_fahrenheit = false; ddata->supports_secondary_mode = false; ddata->supports_rotation = false; break; } } // HID Driver Callbacks static int deepcool_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct deepcool_device *ddata; int ret; ddata = devm_kzalloc(&hdev->dev, sizeof(*ddata), GFP_KERNEL); if (!ddata) return -ENOMEM; ddata->hdev = hdev; ddata->product_id = hdev->product; ddata->series = deepcool_identify_device(hdev->product); if (ddata->series == DEEPCOOL_UNKNOWN) { hid_err(hdev, "Unknown Deepcool device: 0x%04x\n", hdev->product); return -ENODEV; } // Set device capabilities deepcool_set_capabilities(ddata); // Initialize default configuration ddata->mode = MODE_AUTO; ddata->auto_mode_current = MODE_CPU_TEMP; ddata->auto_mode_last_change = jiffies; ddata->secondary_mode = MODE_DEFAULT; ddata->update_interval_ms = DEFAULT_UPDATE_INTERVAL_MS; ddata->fahrenheit = false; ddata->alarm_enabled = false; ddata->rotation = 0; mutex_init(&ddata->lock); hid_set_drvdata(hdev, ddata); ret = hid_parse(hdev); if (ret) { hid_err(hdev, "HID parse failed: %d\n", ret); return ret; } ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); if (ret) { hid_err(hdev, "HID hw start failed: %d\n", ret); return ret; } ret = hid_hw_open(hdev); if (ret) { hid_err(hdev, "HID hw open failed: %d\n", ret); goto err_hw_stop; } // Create sysfs attributes ret = sysfs_create_group(&hdev->dev.kobj, &deepcool_group); if (ret) { hid_err(hdev, "Failed to create sysfs group: %d\n", ret); goto err_hw_close; } // Initialize work queue INIT_DELAYED_WORK(&ddata->update_work, deepcool_update_work); // Start update work schedule_delayed_work(&ddata->update_work, msecs_to_jiffies(ddata->update_interval_ms)); hid_info(hdev, "Deepcool Digital device initialized (series: %d)\n", ddata->series); return 0; err_hw_close: hid_hw_close(hdev); err_hw_stop: hid_hw_stop(hdev); return ret; } static void deepcool_remove(struct hid_device *hdev) { struct deepcool_device *ddata = hid_get_drvdata(hdev); // Stop work queue cancel_delayed_work_sync(&ddata->update_work); // Remove sysfs attributes sysfs_remove_group(&hdev->dev.kobj, &deepcool_group); hid_hw_close(hdev); hid_hw_stop(hdev); hid_info(hdev, "Deepcool Digital device removed\n"); } // HID Device ID Table static const struct hid_device_id deepcool_devices[] = { /* Deepcool Digital devices (vendor ID 0x3633) */ {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 1)}, // AK Series {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 2)}, {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 3)}, {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 4)}, {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 5)}, // AK400 PRO {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 6)}, // LS Series {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 7)}, // AK620 PRO {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 8)}, // AG Series {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 9)}, // LQ Series {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 10)}, // LD Series {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 11)}, {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 12)}, // LP Series {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 13)}, {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 14)}, {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 15)}, // CH Series {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 16)}, {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 17)}, {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 18)}, // CH Series Gen2 {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 19)}, {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 20)}, {HID_USB_DEVICE(DEEPCOOL_VENDOR_ID, 21)}, {HID_USB_DEVICE(CH510_VENDOR_ID, CH510_PRODUCT_ID)}, // CH510 (different vendor ID) {}}; MODULE_DEVICE_TABLE(hid, deepcool_devices); // Represents a HID device static struct hid_driver deepcool_driver = { .name = DRIVER_NAME, .id_table = deepcool_devices, .probe = deepcool_probe, .remove = deepcool_remove, .driver = { .groups = deepcool_groups, }, }; module_hid_driver(deepcool_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("NotAShelf "); MODULE_DESCRIPTION("Deepcool Digital USB HID driver"); MODULE_VERSION(DRIVER_VERSION);