# ---------------------------------------- # Module: ayaneo_platform # Version: c824eae52426 # ---------------------------------------- diff --git a/drivers/custom/ayaneo_platform/Kconfig b/drivers/custom/ayaneo_platform/Kconfig new file mode 100644 index 000000000000..ae06caf85090 --- /dev/null +++ b/drivers/custom/ayaneo_platform/Kconfig @@ -0,0 +1,8 @@ +menuconfig AYANEO_PLATFORM + tristate "Ayaneo x86 PWM Control Support" + select LEDS_CLASS + select LEDS_CLASS_MULTICOLOR + help + This driver provides support for Ayaneo x86 Handheld + Consoles by providing an RGB LED control via a + multicolor LED sysfs interface. diff --git a/drivers/custom/ayaneo_platform/Makefile b/drivers/custom/ayaneo_platform/Makefile new file mode 100644 index 000000000000..2e9842d766c5 --- /dev/null +++ b/drivers/custom/ayaneo_platform/Makefile @@ -0,0 +1,108 @@ +# For building for the current running version of Linux +ifndef TARGET +TARGET = $(shell uname -r) +endif +# Or specific version +#TARGET = 2.6.33.5 + +KERNEL_MODULES = /lib/modules/$(TARGET) + +ifneq ("","$(wildcard /usr/src/linux-headers-$(TARGET)/*)") +# Ubuntu +KERNEL_BUILD = /usr/src/linux-headers-$(TARGET) +else +ifneq ("","$(wildcard /usr/src/kernels/$(TARGET)/*)") +# Fedora +KERNEL_BUILD = /usr/src/kernels/$(TARGET) +else +KERNEL_BUILD = $(KERNEL_MODULES)/build +endif +endif + +# SYSTEM_MAP = $(KERNEL_BUILD)/System.map +ifneq ("","$(wildcard /boot/System.map-$(TARGET))") +SYSTEM_MAP = /boot/System.map-$(TARGET) +else +# Arch +SYSTEM_MAP = /proc/kallsyms +endif + +DRIVER := ayaneo-platform +ifneq ("","$(wildcard .git/*)") +DRIVER_VERSION := $(shell git describe --long --tags | sed s/\-/\./g ) +else +ifneq ("", "$(wildcard VERSION)") +DRIVER_VERSION := $(shell cat VERSION) +else +DRIVER_VERSION := unknown +endif +endif + +# DKMS +DKMS_ROOT_PATH=/usr/src/$(DRIVER)-$(DRIVER_VERSION) +MODPROBE_OUTPUT=$(shell lsmod | grep ayaneo-platform) + +# Directory below /lib/modules/$(TARGET)/kernel into which to install +# the module: +MOD_SUBDIR = drivers/platform/x86 +MODDESTDIR=$(KERNEL_MODULES)/kernel/$(MOD_SUBDIR) + +obj-m = $(patsubst %,%.o,$(DRIVER)) +obj-ko := $(patsubst %,%.ko,$(DRIVER)) + +MAKEFLAGS += --no-print-directory + +ifneq ("","$(wildcard $(MODDESTDIR)/*.ko.gz)") +COMPRESS_GZIP := y +endif +ifneq ("","$(wildcard $(MODDESTDIR)/*.ko.xz)") +COMPRESS_XZ := y +endif + + +.PHONY: all install modules modules_install clean dkms dkms_clean + +all: modules + + +# Targets for running make directly in the external module directory: + +OXP_PLATFORM_CFLAGS=-DOXP_PLATFORM_DRIVER_VERSION='\"$(DRIVER_VERSION)\"' + +modules: + @$(MAKE) EXTRA_CFLAGS="$(OXP_PLATFORM_CFLAGS)" -C $(KERNEL_BUILD) M=$(CURDIR) $@ + +clean: + @$(MAKE) -C $(KERNEL_BUILD) M=$(CURDIR) $@ + +install: modules_install + +modules_install: + mkdir -p $(MODDESTDIR) + cp $(DRIVER).ko $(MODDESTDIR)/ +ifeq ($(COMPRESS_GZIP), y) + @gzip -f $(MODDESTDIR)/$(DRIVER).ko +endif +ifeq ($(COMPRESS_XZ), y) + @xz -f $(MODDESTDIR)/$(DRIVER).ko +endif + depmod -a -F $(SYSTEM_MAP) $(TARGET) + +dkms: + @sed -i -e '/^PACKAGE_VERSION=/ s/=.*/=\"$(DRIVER_VERSION)\"/' dkms.conf + @echo "$(DRIVER_VERSION)" >VERSION + @mkdir -p $(DKMS_ROOT_PATH) + @cp `pwd`/dkms.conf $(DKMS_ROOT_PATH) + @cp `pwd`/VERSION $(DKMS_ROOT_PATH) + @cp `pwd`/Makefile $(DKMS_ROOT_PATH) + @cp `pwd`/ayaneo-platform.c $(DKMS_ROOT_PATH) + @dkms add -m $(DRIVER) -v $(DRIVER_VERSION) + @dkms build -m $(DRIVER) -v $(DRIVER_VERSION) --kernelsourcedir=$(KERNEL_BUILD) + @dkms install --force -m $(DRIVER) -v $(DRIVER_VERSION) + +dkms_clean: + @if [ ! -z "$(MODPROBE_OUTPUT)" ]; then \ + rmmod $(DRIVER);\ + fi + @dkms remove -m $(DRIVER) -v $(DRIVER_VERSION) --all + @rm -rf $(DKMS_ROOT_PATH) diff --git a/drivers/custom/ayaneo_platform/ayaneo-platform.c b/drivers/custom/ayaneo_platform/ayaneo-platform.c new file mode 100644 index 000000000000..17eb0c0a8fba --- /dev/null +++ b/drivers/custom/ayaneo_platform/ayaneo-platform.c @@ -0,0 +1,1425 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Platform driver for AYANEO x86 Handhelds that exposes RGB LED + * control via a sysfs led_class_multicolor interface. + * + * Copyright (C) 2023-2024 Derek J. Clark + * Copyright (C) 2023-2024 JELOS + * Copyright (C) 2024, 2025 Sebastian Kranz + * Copyright (C) 2024 Trevor Heslop + * Derived from ayaled originally developed by Maya Matuszczyk + * + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Handle ACPI lock mechanism */ +static u32 ayaneo_mutex; + +#define ACPI_LOCK_DELAY_MS 500 + +static bool lock_global_acpi_lock(void) +{ + return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &ayaneo_mutex)); +} + +static bool unlock_global_acpi_lock(void) +{ + return ACPI_SUCCESS(acpi_release_global_lock(ayaneo_mutex)); +} + +/* Common ec ram port data */ +#define AYANEO_ADDR_PORT 0x4e +#define AYANEO_DATA_PORT 0x4f +#define AYANEO_HIGH_BYTE 0xd1 + +/* RGB LED EC Ram Registers + * #define AYANEO_LED_MC_L_Q1_R 0xb3 + * #define AYANEO_LED_MC_L_Q1_G 0xb4 + * #define AYANEO_LED_MC_L_Q1_B 0xb5 + * #define AYANEO_LED_MC_L_Q2_R 0xb6 + * #define AYANEO_LED_MC_L_Q2_G 0xb7 + * #define AYANEO_LED_MC_L_Q2_B 0xb8 + * #define AYANEO_LED_MC_L_Q3_R 0xb9 + * #define AYANEO_LED_MC_L_Q3_G 0xba + * #define AYANEO_LED_MC_L_Q3_B 0xbb + * #define AYANEO_LED_MC_L_Q4_R 0xbc + * #define AYANEO_LED_MC_L_Q4_G 0xbd + * #define AYANEO_LED_MC_L_Q4_B 0xbe + * #define AYANEO_LED_MC_R_Q1_R 0x73 + * #define AYANEO_LED_MC_R_Q1_G 0x74 + * #define AYANEO_LED_MC_R_Q1_B 0x75 + * #define AYANEO_LED_MC_R_Q2_R 0x76 + * #define AYANEO_LED_MC_R_Q2_G 0x77 + * #define AYANEO_LED_MC_R_Q2_B 0x78 + * #define AYANEO_LED_MC_R_Q3_R 0x79 + * #define AYANEO_LED_MC_R_Q3_G 0x7a + * #define AYANEO_LED_MC_R_Q3_B 0x7b + * #define AYANEO_LED_MC_R_Q4_R 0x7c + * #define AYANEO_LED_MC_R_Q4_G 0x7d + * #define AYANEO_LED_MC_R_Q4_B 0x7e + */ + +#define AYANEO_LED_MC_ADDR_L 0xb0 +#define AYANEO_LED_MC_ADDR_R 0x70 + +#define AYANEO_LED_MC_ADDR_CLOSE_1 0x86 +#define AYANEO_LED_MC_ADDR_CLOSE_2 0xc6 + +#define AYANEO_LED_MC_MODE_ADDR 0x87 +#define AYANEO_LED_MC_MODE_HOLD 0xa5 +#define AYANEO_LED_MC_MODE_RELEASE 0x00 + +/* Bypass Charge EC Ram Registers */ +#define AYANEO_BYPASSCHARGE_CONTROL 0xd1 +#define AYANEO_BYPASSCHARGE_OPEN 0x01 +#define AYANEO_BYPASSCHARGE_CLOSE 0x65 + +/* Schema: + * + * 0x6d - LED PWM control (0x03) + * + * 0xb1 - Support for 4 zones and RGB color + * Colors: Red (1), Green (2), Blue (3) + * Zones: Right (2), Down (5), Left (8) , Up (11) + * Off: Set 0xb1 to 02 + * + * 0xb2 - Brightness [0-255]. Left/Right produce different brightness for + * the same value on different models, must be scaled. Requires b1 + * to be set at the same time. + * + * 0xbf - Set Mode + * Modes: Enable (0x10), Tint (0xe2), Close (0xff) + */ + +/* EC Controlled RGB registers */ +#define AYANEO_LED_PWM_CONTROL 0x6d +#define AYANEO_LED_POS 0xb1 +#define AYANEO_LED_BRIGHTNESS 0xb2 +#define AYANEO_LED_MODE_REG 0xbf + +#define AYANEO_LED_CMD_ENABLE_ADDR 0x02 +#define AYANEO_LED_CMD_ENABLE_ON 0xb1 +#define AYANEO_LED_CMD_ENABLE_OFF 0x31 +#define AYANEO_LED_CMD_ENABLE_RESET 0xc0 + +#define AYANEO_LED_CMD_PATTERN_ADDR 0x0f +#define AYANEO_LED_CMD_PATTERN_OFF 0x00 + +#define AYANEO_LED_CMD_FADE_ADDR 0x10 +#define AYANEO_LED_CMD_FADE_OFF 0x00 + +#define AYANEO_LED_CMD_WATCHDOG_ADDR 0x15 +#define AYANEO_LED_CMD_WATCHDOG_ON 0x07 + +#define AYANEO_LED_CMD_ANIM_1_ADDR 0x11 /* Animation step 1 */ +#define AYANEO_LED_CMD_ANIM_2_ADDR 0x12 /* Animation step 2 */ +#define AYANEO_LED_CMD_ANIM_3_ADDR 0x13 /* Animation step 3 */ +#define AYANEO_LED_CMD_ANIM_4_ADDR 0x14 /* Animation step 4 */ +#define AYANEO_LED_CMD_ANIM_STATIC 0x05 + +/* RGB Mode values */ +#define AYANEO_LED_MODE_RELEASE 0x00 /* close channel, release control */ +#define AYANEO_LED_MODE_WRITE 0x10 /* Default write mode */ +#define AYANEO_LED_MODE_HOLD 0xfe /* close channel, hold control */ + +#define AYANEO_LED_GROUP_LEFT 0x01 +#define AYANEO_LED_GROUP_RIGHT 0x02 +#define AYANEO_LED_GROUP_LEFT_RIGHT 0x03 /* omit for aya flip when implemented */ +#define AYANEO_LED_GROUP_BUTTON 0x04 + +#define AYANEO_LED_WRITE_DELAY_LEGACY_MS 2 +#define AYANEO_LED_WRITE_DELAY_MS 1 +#define AYANEO_LED_WRITER_DELAY_RANGE_US 10000, 20000 +#define AYANEO_LED_SUSPEND_RESUME_DELAY_MS 100 + +/* EC Controlled Bypass Charge Register */ +#define AYANEO_BYPASS_CHARGE_CONTROL 0x1e +#define AYANEO_BYPASS_CHARGE_OPEN 0x55 +#define AYANEO_BYPASS_CHARGE_CLOSE 0xaa +#define AYANEO_BYPASS_WRITER_DELAY_MS 30000 +#define AYANEO_EC_VERSION_REG 0x00 /* until 0x04 */ + +enum ayaneo_model { + air = 1, + air_1s, + air_1s_limited, + air_plus, + air_plus_mendo, + air_pro, + ayaneo_2, + ayaneo_2s, + geek, + geek_1s, + kun, + slide, +}; + +static enum ayaneo_model model; + +enum AYANEO_LED_SUSPEND_MODE { + AYANEO_LED_SUSPEND_MODE_OFF, + AYANEO_LED_SUSPEND_MODE_OEM, + AYANEO_LED_SUSPEND_MODE_KEEP +}; + +static const char * const AYANEO_LED_SUSPEND_MODE_TEXT[] = { + [AYANEO_LED_SUSPEND_MODE_OFF] = "off", + [AYANEO_LED_SUSPEND_MODE_OEM] = "oem", + [AYANEO_LED_SUSPEND_MODE_KEEP] ="keep" +}; + +static enum AYANEO_LED_SUSPEND_MODE suspend_mode; + +static const struct dmi_system_id dmi_table[] = { + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"), + }, + .driver_data = (void *)air, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"), + }, + .driver_data = (void *)air_1s, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S Limited"), + }, + .driver_data = (void *)air_1s_limited, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-AMD"), + }, + .driver_data = (void *)air_plus, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"), + }, + .driver_data = (void *)air_plus_mendo, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"), + }, + .driver_data = (void *)air_pro, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 2"), + }, + .driver_data = (void *)ayaneo_2, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 2S"), + }, + .driver_data = (void *)ayaneo_2s, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Mysten Labs, Inc."), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "SuiPlay0X1"), + }, + .driver_data = (void *)ayaneo_2s, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GEEK"), + }, + .driver_data = (void *)geek, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GEEK 1S"), + }, + .driver_data = (void *)geek_1s, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_MATCH(DMI_BOARD_NAME, "KUN"), + }, + .driver_data = (void *)kun, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AS01"), + }, + .driver_data = (void *)slide, + }, + {}, +}; + +static int ec_write_ram(u8 index, u8 val) +{ + int ret; + + if (!lock_global_acpi_lock()) + return -EBUSY; + + outb(0x2e, AYANEO_ADDR_PORT); + outb(0x11, AYANEO_DATA_PORT); + outb(0x2f, AYANEO_ADDR_PORT); + outb(AYANEO_HIGH_BYTE, AYANEO_DATA_PORT); + outb(0x2e, AYANEO_ADDR_PORT); + outb(0x10, AYANEO_DATA_PORT); + outb(0x2f, AYANEO_ADDR_PORT); + outb(index, AYANEO_DATA_PORT); + outb(0x2e, AYANEO_ADDR_PORT); + outb(0x12, AYANEO_DATA_PORT); + outb(0x2f, AYANEO_ADDR_PORT); + outb(val, AYANEO_DATA_PORT); + + if (!unlock_global_acpi_lock()) + return -EBUSY; + + return ret; +} + +static int ec_read_ram(u8 index, u8 *val) +{ + int ret; + + if (!lock_global_acpi_lock()) + return -EBUSY; + + outb(0x2e, AYANEO_ADDR_PORT); + outb(0x11, AYANEO_DATA_PORT); + outb(0x2f, AYANEO_ADDR_PORT); + outb(AYANEO_HIGH_BYTE, AYANEO_DATA_PORT); + outb(0x2e, AYANEO_ADDR_PORT); + outb(0x10, AYANEO_DATA_PORT); + outb(0x2f, AYANEO_ADDR_PORT); + outb(index, AYANEO_DATA_PORT); + outb(0x2e, AYANEO_ADDR_PORT); + outb(0x12, AYANEO_DATA_PORT); + outb(0x2f, AYANEO_ADDR_PORT); + *val = inb(AYANEO_DATA_PORT); + + if (!unlock_global_acpi_lock()) + return -EBUSY; + + return ret; +} + +/* Function Summary + * AYANEO devices can be largely divided into 2 groups; modern and legacy. + * - Legacy devices use a microcontroller either embedded into or controlled via + * the system's ACPI controller. + * - Modern devices use a dedicated microcontroller and communicate via shared + * memory. + * + * The control scheme is largely shared between both device types and many of + * the command values are shared. + * + * ayaneo_led_mc_set / ayaneo_led_mc_legacy_set + * Sets the value of a single address or subpixel + * + * ayaneo_led_mc_release / ayaneo_led_mc_legacy_release + * Releases control of the LEDs back to the microcontroller. + * This function is abstracted by ayaneo_led_mc_release_control. + * + * ayaneo_led_mc_hold / ayaneo_led_mc_legacy_hold + * Takes and holds control of the LEDs from the microcontroller. + * This function is abstracted by ayaneo_led_mc_take_control. + * + * ayaneo_led_mc_intensity / ayaneo_led_mc_legacy_intensity + * Sets the values of all of the LEDs in the zones of a given group. + * + * ayaneo_led_mc_off / ayaneo_led_mc_legacy_off + * Instructs the microcontroller to disable output for the given group. + * + * ayaneo_led_mc_on / ayaneo_led_mc_legacy_on + * Instructs the microcontroller to enable output for the given group. + * + * ayaneo_led_mc_reset / ayaneo_led_mc_legacy_reset + * Reverts all of the microcontroller internal registers to power on + * defaults. + */ + +/* Dedicated microcontroller methods */ +static void ayaneo_led_mc_set(u8 group, u8 pos, u8 brightness) +{ + u8 led_offset; + u8 close_cmd; + + if (group < 2) + { + led_offset = AYANEO_LED_MC_ADDR_L; + close_cmd = AYANEO_LED_MC_ADDR_CLOSE_2; + } + else + { + led_offset = AYANEO_LED_MC_ADDR_R; + close_cmd = AYANEO_LED_MC_ADDR_CLOSE_1; + } + + ec_write_ram(led_offset + pos, brightness); + ec_write_ram(close_cmd, 0x01); + mdelay(AYANEO_LED_WRITE_DELAY_MS); +} + +static void ayaneo_led_mc_release(void) +{ + ec_write_ram(AYANEO_LED_MC_MODE_ADDR, AYANEO_LED_MC_MODE_RELEASE); +} + +static void ayaneo_led_mc_hold(void) +{ + ec_write_ram(AYANEO_LED_MC_MODE_ADDR, AYANEO_LED_MC_MODE_HOLD); + + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT_RIGHT, 0x00, 0x00); +} + +static void ayaneo_led_mc_intensity(u8 group, u8 *color, u8 zones[]) +{ + int zone; + + for (zone = 0; zone < 4; zone++) { + ayaneo_led_mc_set(group, zones[zone], color[0]); + ayaneo_led_mc_set(group, zones[zone] + 1, color[1]); + ayaneo_led_mc_set(group, zones[zone] + 2, color[2]); + } + + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT_RIGHT, 0x00, 0x00); +} + +static void ayaneo_led_mc_off(void) +{ + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_ENABLE_ADDR, AYANEO_LED_CMD_ENABLE_OFF); + ayaneo_led_mc_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_ENABLE_ADDR, AYANEO_LED_CMD_ENABLE_OFF); + + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT_RIGHT, 0x00, 0x00); +} + +static void ayaneo_led_mc_on(void) +{ + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_ENABLE_ADDR, AYANEO_LED_CMD_ENABLE_ON); + ayaneo_led_mc_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_ENABLE_ADDR, AYANEO_LED_CMD_ENABLE_ON); + + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_PATTERN_ADDR, AYANEO_LED_CMD_PATTERN_OFF); + ayaneo_led_mc_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_PATTERN_ADDR, AYANEO_LED_CMD_PATTERN_OFF); + + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_FADE_ADDR, AYANEO_LED_CMD_FADE_OFF); + ayaneo_led_mc_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_FADE_ADDR, AYANEO_LED_CMD_FADE_OFF); + + /* Set static color animation */ + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_ANIM_1_ADDR, AYANEO_LED_CMD_ANIM_STATIC); + ayaneo_led_mc_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_ANIM_1_ADDR, AYANEO_LED_CMD_ANIM_STATIC); + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_ANIM_2_ADDR, AYANEO_LED_CMD_ANIM_STATIC); + ayaneo_led_mc_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_ANIM_2_ADDR, AYANEO_LED_CMD_ANIM_STATIC); + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_ANIM_3_ADDR, AYANEO_LED_CMD_ANIM_STATIC); + ayaneo_led_mc_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_ANIM_3_ADDR, AYANEO_LED_CMD_ANIM_STATIC); + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_ANIM_4_ADDR, AYANEO_LED_CMD_ANIM_STATIC); + ayaneo_led_mc_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_ANIM_4_ADDR, AYANEO_LED_CMD_ANIM_STATIC); + + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_WATCHDOG_ADDR, AYANEO_LED_CMD_WATCHDOG_ON); + ayaneo_led_mc_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_WATCHDOG_ADDR, AYANEO_LED_CMD_WATCHDOG_ON); + + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT_RIGHT, 0x00, 0x00); +} + +static void ayaneo_led_mc_reset(void) +{ + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_ENABLE_ADDR, AYANEO_LED_CMD_ENABLE_RESET); + ayaneo_led_mc_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_ENABLE_ADDR, AYANEO_LED_CMD_ENABLE_RESET); + + ayaneo_led_mc_set(AYANEO_LED_GROUP_LEFT_RIGHT, 0x00, 0x00); +} + +/* ACPI controller methods */ +static void ayaneo_led_mc_legacy_set(u8 group, u8 pos, u8 brightness) +{ + if (!lock_global_acpi_lock()) + return; + + ec_write(AYANEO_LED_PWM_CONTROL, group); + ec_write(AYANEO_LED_POS, pos); + ec_write(AYANEO_LED_BRIGHTNESS, brightness); + ec_write(AYANEO_LED_MODE_REG, AYANEO_LED_MODE_WRITE); + + if (!unlock_global_acpi_lock()) + return; + + mdelay(AYANEO_LED_WRITE_DELAY_LEGACY_MS); + + if (!lock_global_acpi_lock()) + return; + + ec_write(AYANEO_LED_MODE_REG, AYANEO_LED_MODE_HOLD); + + if (!unlock_global_acpi_lock()) + return; +} + +static void ayaneo_led_mc_legacy_release(void) +{ + if (!lock_global_acpi_lock()) + return; + + ec_write(AYANEO_LED_MODE_REG, AYANEO_LED_MODE_RELEASE); + + if (!unlock_global_acpi_lock()) + return; +} + +static void ayaneo_led_mc_legacy_hold(void) +{ + if (!lock_global_acpi_lock()) + return; + + ec_write(AYANEO_LED_MODE_REG, AYANEO_LED_MODE_HOLD); + + if (!unlock_global_acpi_lock()) + return; +} + +static void ayaneo_led_mc_legacy_intensity_single(u8 group, u8 *color, u8 zone) +{ + ayaneo_led_mc_legacy_set(group, zone, color[0]); + ayaneo_led_mc_legacy_set(group, zone + 1, color[1]); + ayaneo_led_mc_legacy_set(group, zone + 2, color[2]); +} + +static void ayaneo_led_mc_legacy_intensity(u8 group, u8 *color, u8 zones[]) +{ + int zone; + + for (zone = 0; zone < 4; zone++) { + ayaneo_led_mc_legacy_intensity_single(group, color, zones[zone]); + } + + ayaneo_led_mc_legacy_set(AYANEO_LED_GROUP_LEFT_RIGHT, 0x00, 0x00); +} + +/* KUN doesn't use consistant zone mapping for RGB, adjust */ +static void ayaneo_led_mc_legacy_intensity_kun(u8 group, u8 *color) +{ + u8 zone; + u8 remap_color[3]; + + if (group == AYANEO_LED_GROUP_BUTTON) + { + zone = 12; + remap_color[0] = color[2]; + remap_color[1] = color[0]; + remap_color[2] = color[1]; + ayaneo_led_mc_legacy_intensity_single(AYANEO_LED_GROUP_BUTTON, remap_color, zone); + ayaneo_led_mc_legacy_set(AYANEO_LED_GROUP_LEFT_RIGHT, 0x00, 0x00); + return; + } + + zone = 3; + remap_color[0] = color[1]; + remap_color[1] = color[0]; + remap_color[2] = color[2]; + ayaneo_led_mc_legacy_intensity_single(group, remap_color, zone); + + zone = 6; + remap_color[0] = color[1]; + remap_color[1] = color[2]; + remap_color[2] = color[0]; + ayaneo_led_mc_legacy_intensity_single(group, remap_color, zone); + + zone = 9; + remap_color[0] = color[2]; + remap_color[1] = color[0]; + remap_color[2] = color[1]; + ayaneo_led_mc_legacy_intensity_single(group, remap_color, zone); + + zone = 12; + remap_color[0] = color[2]; + remap_color[1] = color[1]; + remap_color[2] = color[0]; + ayaneo_led_mc_legacy_intensity_single(group, remap_color, zone); + + ayaneo_led_mc_legacy_set(AYANEO_LED_GROUP_LEFT_RIGHT, 0x00, 0x00); +} + +static void ayaneo_led_mc_legacy_off(void) +{ + ayaneo_led_mc_legacy_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_ENABLE_ADDR, AYANEO_LED_CMD_ENABLE_OFF); + ayaneo_led_mc_legacy_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_ENABLE_ADDR, AYANEO_LED_CMD_ENABLE_OFF); + + ayaneo_led_mc_legacy_set(AYANEO_LED_GROUP_LEFT_RIGHT, 0x00, 0x00); +} + +static void ayaneo_led_mc_legacy_on(void) +{ + ayaneo_led_mc_legacy_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_ENABLE_ADDR, AYANEO_LED_CMD_ENABLE_ON); + ayaneo_led_mc_legacy_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_ENABLE_ADDR, AYANEO_LED_CMD_ENABLE_ON); + + // note: omit for aya flip when implemented, causes unexpected behavior + ayaneo_led_mc_legacy_set(AYANEO_LED_GROUP_LEFT_RIGHT, 0x00, 0x00); +} + +static void ayaneo_led_mc_legacy_reset(void) +{ + ayaneo_led_mc_legacy_set(AYANEO_LED_GROUP_LEFT, + AYANEO_LED_CMD_ENABLE_ADDR, AYANEO_LED_CMD_ENABLE_RESET); + ayaneo_led_mc_legacy_set(AYANEO_LED_GROUP_RIGHT, + AYANEO_LED_CMD_ENABLE_ADDR, AYANEO_LED_CMD_ENABLE_RESET); + + // note: omit for aya flip when implemented, causes unexpected behavior + ayaneo_led_mc_legacy_set(AYANEO_LED_GROUP_LEFT_RIGHT, 0x00, 0x00); +} + +/* Device command abstractions */ +static void ayaneo_led_mc_take_control(void) +{ + switch (model) { + case air: + case air_1s: + case air_1s_limited: + case air_pro: + case air_plus_mendo: + case geek: + case geek_1s: + case ayaneo_2: + case ayaneo_2s: + case kun: + ayaneo_led_mc_legacy_hold(); + ayaneo_led_mc_legacy_reset(); + ayaneo_led_mc_legacy_off(); + break; + case air_plus: + case slide: + ayaneo_led_mc_hold(); + ayaneo_led_mc_reset(); + ayaneo_led_mc_off(); + break; + default: + break; + } +} + +static void ayaneo_led_mc_release_control(void) +{ + switch (model) { + case air: + case air_1s: + case air_1s_limited: + case air_pro: + case air_plus_mendo: + case geek: + case geek_1s: + case ayaneo_2: + case ayaneo_2s: + case kun: + ayaneo_led_mc_legacy_reset(); + ayaneo_led_mc_legacy_release(); + break; + case air_plus: + case slide: + ayaneo_led_mc_reset(); + ayaneo_led_mc_release(); + break; + default: + break; + } +} + +/* Threaded writes: + * The writer thread's job is to push updates to the physical LEDs as fast as + * possible while allowing updates to the LED multi_intensity/brightness sysfs + * attributes to return quickly. + * + * During multi_intensity/brightness set, the ayaneo_led_mc_update_color array + * is updated with the target color and ayaneo_led_mc_update_required is + * incremented by 1. + * + * When the writer thread begins its next loop, it copies the current values of + * ayaneo_led_mc_update_required, and ayaneo_led_mc_update_color, after which + * the new color is pushed to the microcontroller. After the color has been + * pushed the writer thread subtracts the starting value from + * ayaneo_led_mc_update_required. If any updates were pushed to + * ayaneo_led_mc_update_required during the writes then the following iteration + * will immediately begin writing the new colors to the microcontroller, + * otherwise it'll sleep for a short while. + * + * Updates to ayaneo_led_mc_update_required and ayaneo_led_mc_update_color are + * syncronised by ayaneo_led_mc_update_lock to prevent a race condition between + * the writer thread and the brightness set function. + * + * During suspend kthread_stop is called which causes the writer thread to + * terminate after its current iteration. The writer thread is restarted during + * resume to allow updates to continue. + */ +static struct task_struct *ayaneo_led_mc_writer_thread; +static int ayaneo_led_mc_update_required; +static u8 ayaneo_led_mc_update_color[3]; +DEFINE_RWLOCK(ayaneo_led_mc_update_lock); + +static void ayaneo_led_mc_scale_color(u8 *color, u8 max_value) +{ + for (int i = 0; i < 3; i++) + { + int c_color = (int)color[i] * (int)max_value / 255; + + // prevents left-right discrepancy when brightness/color are low + if (c_color == 0 && color[i] > 0) + c_color = 1; + + color[i] = (u8)c_color; + } +} + +static void ayaneo_led_mc_brightness_apply(u8 *color) +{ + u8 color_l[3]; /* Left joystick ring */ + u8 color_r[3]; /* Right joystick ring */ + u8 color_b[3]; /* AyaSpace Button (KUN Only) */ + + u8 zones[4] = {3, 6, 9, 12}; + + for (int i = 0; i < 3; i++) + { + color_l[i] = color[i]; + color_r[i] = color[i]; + color_b[i] = color[i]; + } + + ayaneo_led_mc_scale_color(color_l, 192); + ayaneo_led_mc_scale_color(color_r, 192); + ayaneo_led_mc_scale_color(color_b, 192); + + switch (model) { + case air: + case air_pro: + case air_1s: + ayaneo_led_mc_legacy_on(); + ayaneo_led_mc_legacy_intensity(AYANEO_LED_GROUP_LEFT, color_l, zones); + ayaneo_led_mc_legacy_intensity(AYANEO_LED_GROUP_RIGHT, color_r, zones); + break; + case air_1s_limited: + ayaneo_led_mc_scale_color(color_r, 204); + ayaneo_led_mc_legacy_on(); + ayaneo_led_mc_legacy_intensity(AYANEO_LED_GROUP_LEFT, color_l, zones); + ayaneo_led_mc_legacy_intensity(AYANEO_LED_GROUP_RIGHT, color_r, zones); + break; + case geek: + case geek_1s: + case ayaneo_2: + case ayaneo_2s: + ayaneo_led_mc_legacy_on(); + ayaneo_led_mc_legacy_intensity(AYANEO_LED_GROUP_LEFT, color_l, zones); + ayaneo_led_mc_legacy_intensity(AYANEO_LED_GROUP_RIGHT, color_r, zones); + break; + case air_plus_mendo: + ayaneo_led_mc_scale_color(color_l, 64); + ayaneo_led_mc_scale_color(color_r, 32); + ayaneo_led_mc_legacy_on(); + ayaneo_led_mc_legacy_intensity(AYANEO_LED_GROUP_LEFT, color_l, zones); + ayaneo_led_mc_legacy_intensity(AYANEO_LED_GROUP_RIGHT, color_r, zones); + break; + case air_plus: + ayaneo_led_mc_scale_color(color_l, 64); + ayaneo_led_mc_scale_color(color_r, 32); + ayaneo_led_mc_on(); + ayaneo_led_mc_intensity(AYANEO_LED_GROUP_LEFT, color_l, zones); + ayaneo_led_mc_intensity(AYANEO_LED_GROUP_RIGHT, color_r, zones); + break; + case slide: + ayaneo_led_mc_on(); + ayaneo_led_mc_intensity(AYANEO_LED_GROUP_LEFT, color_l, zones); + ayaneo_led_mc_intensity(AYANEO_LED_GROUP_RIGHT, color_r, zones); + break; + case kun: + ayaneo_led_mc_legacy_on(); + ayaneo_led_mc_legacy_intensity_kun(AYANEO_LED_GROUP_LEFT, color_l); + ayaneo_led_mc_legacy_intensity_kun(AYANEO_LED_GROUP_RIGHT, color_r); + ayaneo_led_mc_legacy_intensity_kun(AYANEO_LED_GROUP_BUTTON, color_b); + break; + default: + break; + } +} + +int ayaneo_led_mc_writer(void *pv); +int ayaneo_led_mc_writer(void *pv) +{ + pr_info("Writer thread started.\n"); + int count; + u8 color[3]; + + while (!kthread_should_stop()) + { + read_lock(&ayaneo_led_mc_update_lock); + count = ayaneo_led_mc_update_required; + + if (count) + { + color[0] = ayaneo_led_mc_update_color[0]; + color[1] = ayaneo_led_mc_update_color[1]; + color[2] = ayaneo_led_mc_update_color[2]; + } + read_unlock(&ayaneo_led_mc_update_lock); + + if (count) + { + ayaneo_led_mc_brightness_apply(color); + + write_lock(&ayaneo_led_mc_update_lock); + ayaneo_led_mc_update_required -= count; + write_unlock(&ayaneo_led_mc_update_lock); + } + else + usleep_range(AYANEO_LED_WRITER_DELAY_RANGE_US); + } + + pr_info("Writer thread stopped.\n"); + return 0; +} + +/* RGB LED Logic */ +static void ayaneo_led_mc_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev); + int val; + int i; + struct mc_subled s_led; + + if (brightness < 0 || brightness > 255) + return; + + led_cdev->brightness = brightness; + + write_lock(&ayaneo_led_mc_update_lock); + for (i = 0; i < mc_cdev->num_colors; i++) { + s_led = mc_cdev->subled_info[i]; + if (s_led.intensity < 0 || s_led.intensity > 255) + return; + val = brightness * s_led.intensity / led_cdev->max_brightness; + ayaneo_led_mc_update_color[s_led.channel] = val; + } + ayaneo_led_mc_update_required++; + write_unlock(&ayaneo_led_mc_update_lock); +}; + +static enum led_brightness ayaneo_led_mc_brightness_get(struct led_classdev *led_cdev) +{ + return led_cdev->brightness; +}; + +/* Suspend Mode +# Multiple modes of operation are supported during suspend: +# +# OEM: Retains the default behavior of the device by returning control to the +# microcontroller. On most devices the LEDs will flash periodically, and +# will turn red when charging. +# Off: The LEDs are turned off during suspend, and control of the LEDs is +# retained by the driver. On most devices, charging will not turn on the +# LEDs. During resume, the last color set is restored. +# Keep: The color currently set on the LEDs remains unchanged, and control of +# the LEDs is retained by the driver. On most devices, charging will not +# change the color of the LEDs. +*/ +static ssize_t suspend_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + bool active; + ssize_t count = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(AYANEO_LED_SUSPEND_MODE_TEXT); i++) { + active = i == suspend_mode; + + if (active) { + count += sysfs_emit_at(buf, count, "[%s] ", + AYANEO_LED_SUSPEND_MODE_TEXT[i]); + } + else { + count += sysfs_emit_at(buf, count, "%s ", + AYANEO_LED_SUSPEND_MODE_TEXT[i]); + } + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t suspend_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int res = sysfs_match_string(AYANEO_LED_SUSPEND_MODE_TEXT, buf); + + if (res < 0) + return -EINVAL; + + suspend_mode = res; + + return count; +} + +static DEVICE_ATTR_RW(suspend_mode); + +static struct attribute *ayaneo_led_mc_attrs[] = { + NULL, +}; + +static struct attribute_group ayaneo_led_mc_group = { + .attrs = ayaneo_led_mc_attrs, +}; + +static void suspend_mode_register_attr(void) +{ + switch (model) { + case air: + case air_1s: + case air_1s_limited: + case air_pro: + case air_plus_mendo: + case geek: + case geek_1s: + case ayaneo_2: + case ayaneo_2s: + case kun: + case air_plus: + case slide: + ayaneo_led_mc_attrs[0] = &dev_attr_suspend_mode.attr; + break; + default: + break; + } +} + +struct mc_subled ayaneo_led_mc_subled_info[] = { + { + .color_index = LED_COLOR_ID_RED, + .brightness = 0, + .intensity = 0, + .channel = 0, + }, + { + .color_index = LED_COLOR_ID_GREEN, + .brightness = 0, + .intensity = 0, + .channel = 1, + }, + { + .color_index = LED_COLOR_ID_BLUE, + .brightness = 0, + .intensity = 0, + .channel = 2, + }, +}; + +struct led_classdev_mc ayaneo_led_mc = { + .led_cdev = { + .name = "ayaneo:rgb:joystick_rings", + .brightness = 0, + .max_brightness = 255, + .brightness_set = ayaneo_led_mc_brightness_set, + .brightness_get = ayaneo_led_mc_brightness_get, + }, + .num_colors = ARRAY_SIZE(ayaneo_led_mc_subled_info), + .subled_info = ayaneo_led_mc_subled_info, +}; + +/* Handling bypass charge */ +struct ayaneo_ps_priv { + u8 charge_behaviour; + u8 bypass_available; +}; +static struct ayaneo_ps_priv ps_priv = { POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO, 0 }; + +static int ayaneo_psy_ext_get_prop(struct power_supply *psy, const struct power_supply_ext *ext, + void *data, enum power_supply_property psp, union power_supply_propval *val) +{ + if(psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR) { + val->intval = ps_priv.charge_behaviour; + return 0; + } + return -EINVAL; +} + +static int ayaneo_psy_ext_set_prop(struct power_supply *psy, const struct power_supply_ext *ext, + void *data, enum power_supply_property psp, const union power_supply_propval *val) +{ + if((psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR) && ((val->intval == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) || (val->intval == POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE))) { + ps_priv.charge_behaviour = val->intval; + return 0; + } + return -EINVAL; +} + +static int ayaneo_psy_ext_is_writeable(struct power_supply *psy, const struct power_supply_ext *ext, + void *data, enum power_supply_property psp) +{ + if(psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR) { + return 1; + } else { + return -ENOENT; + } +} + +/* Function Summary + * AYANEO devices can be largely divided into 2 groups; modern and legacy. + * - Legacy devices use a microcontroller either embedded into or controlled via + * the system's ACPI controller. + * - Modern devices use a dedicated microcontroller and communicate via shared + * memory. + * + * The control scheme is largely shared between both device types and many of + * the command values are shared. + * + * ayaneo_bypass_charge_open / ayaneo_bypass_charge_legacy_open + * Bypasses the charging of the battery and supplies power directly to + * the hardware. + * + * ayaneo_bypass_charge_close / ayaneo_bypass_charge_legacy_close + * Renable the charging of the battery. + */ +static void ayaneo_bypass_charge_open(void) +{ + u8 val; + ec_read_ram(AYANEO_BYPASSCHARGE_CONTROL, &val); + if(val != AYANEO_BYPASSCHARGE_OPEN) { + ec_write_ram(AYANEO_BYPASSCHARGE_CONTROL, AYANEO_BYPASSCHARGE_OPEN); + } +} + +static void ayaneo_bypass_charge_close(void) +{ + u8 val; + ec_read_ram(AYANEO_BYPASSCHARGE_CONTROL, &val); + if(val != AYANEO_BYPASSCHARGE_CLOSE) { + ec_write_ram(AYANEO_BYPASSCHARGE_CONTROL, AYANEO_BYPASSCHARGE_CLOSE); + } +} + +static void ayaneo_bypass_charge_legacy_open(void) +{ + u8 val; + if (!lock_global_acpi_lock()) + return; + + if(ec_read(AYANEO_BYPASS_CHARGE_CONTROL, &val) == 0) { + if(val != AYANEO_BYPASS_CHARGE_OPEN) { + ec_write(AYANEO_BYPASS_CHARGE_CONTROL, AYANEO_BYPASS_CHARGE_OPEN); + } + } + + if (!unlock_global_acpi_lock()) + return; +} + +static void ayaneo_bypass_charge_legacy_close(void) +{ + u8 val; + if (!lock_global_acpi_lock()) + return; + + if(ec_read(AYANEO_BYPASS_CHARGE_CONTROL, &val) == 0) { + if(val != AYANEO_BYPASS_CHARGE_CLOSE) { + ec_write(AYANEO_BYPASS_CHARGE_CONTROL, AYANEO_BYPASS_CHARGE_CLOSE); + } + } + + if (!unlock_global_acpi_lock()) + return; +} +/* Threaded writes: + * The writer thread's job is to enable or disable the bypass charge function + * depending on the POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR. + * + * When the writer thread begins its next loop, it checks if it should activate + * the bypass charge if POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE is set. It + * then saves the state of charge behaviour and sleeps for some seconds. + * + * During suspend kthread_stop is called which causes the writer thread to + * terminate after its current iteration. The writer thread is restarted during + * resume to allow updates to continue. + */ +static struct task_struct *ayaneo_bypass_charge_writer_thread; +int ayaneo_bypass_charge_writer(void *pv); +int ayaneo_bypass_charge_writer(void *pv) +{ + u8 last_charge_behaviour = 0xff; + pr_info("Bypass-Writer thread started.\n"); + + while (!kthread_should_stop()) + { + if(last_charge_behaviour != ps_priv.charge_behaviour) { + if (POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE == ps_priv.charge_behaviour){ + switch (model) { + case air: + case air_1s: + case air_1s_limited: + case air_pro: + case air_plus_mendo: + case geek_1s: + case ayaneo_2s: + case kun: + ayaneo_bypass_charge_legacy_open(); + break; + case air_plus: + case slide: + ayaneo_bypass_charge_open(); + break; + default: + break; + } + } else if(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO == ps_priv.charge_behaviour) { + switch (model) { + case air: + case air_1s: + case air_1s_limited: + case air_pro: + case air_plus_mendo: + case geek_1s: + case ayaneo_2s: + case kun: + ayaneo_bypass_charge_legacy_close(); + break; + case air_plus: + case slide: + ayaneo_bypass_charge_close(); + break; + default: + break; + } + } + last_charge_behaviour = ps_priv.charge_behaviour; + } + msleep(AYANEO_BYPASS_WRITER_DELAY_MS); + } + + pr_info("Bypass-Writer thread stopped.\n"); + return 0; +} + +static enum power_supply_property ayaneo_psy_ext_props[] = { + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, +}; + +static struct power_supply_ext ayaneo_psy_ext = { + .name = "ayaneo-bypass-charge", + .charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE), + .properties = ayaneo_psy_ext_props, + .num_properties = ARRAY_SIZE(ayaneo_psy_ext_props), + .get_property = ayaneo_psy_ext_get_prop, + .set_property = ayaneo_psy_ext_set_prop, + .property_is_writeable = ayaneo_psy_ext_is_writeable, +}; + +static int ayaneo_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + /* Ayaneo devices only have one battery. */ + if (strcmp(battery->desc->name, "BAT0") != 0 && + strcmp(battery->desc->name, "BAT1") != 0 && + strcmp(battery->desc->name, "BATC") != 0 && + strcmp(battery->desc->name, "BATT") != 0) + return -ENODEV; + + return power_supply_register_extension(battery, &ayaneo_psy_ext, &battery->dev, NULL); +} + +static int ayaneo_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + power_supply_unregister_extension(battery, &ayaneo_psy_ext); + return 0; +} + +static struct acpi_battery_hook battery_hook = { + .add_battery = ayaneo_battery_add, + .remove_battery = ayaneo_battery_remove, + .name = "Ayaneo Battery", +}; + +/* check the ec version of devices which can handle the bypass charge function */ +static int ayaneo_check_charge_control(void) +{ +#define VERSION_LENGTH 5 + int ret; + u8 version[VERSION_LENGTH]; + u8 version_needed[VERSION_LENGTH]; + u8 version_length = VERSION_LENGTH; + int index; + for(index = 0; index < VERSION_LENGTH; index++) { + ec_read(AYANEO_EC_VERSION_REG + index, &version[index]); + } + switch (model) { + case air: + case air_pro: + version_needed[0] = 3; + version_needed[1] = 1; + version_needed[2] = 0; + version_needed[3] = 4; + version_needed[4] = 78; + break; + case air_1s: + case air_1s_limited: + version_needed[0] = 8; + version_needed[1] = 4; + version_needed[2] = 0; + version_needed[3] = 0; + version_needed[4] = 27; + break; + case air_plus_mendo: + version_needed[0] = 7; + version_needed[1] = 0; + version_needed[2] = 0; + version_needed[3] = 0; + version_needed[4] = 13; + break; + case ayaneo_2s: + case geek_1s: + version_needed[0] = 8; + version_needed[1] = 0; + version_needed[2] = 0; + version_needed[3] = 1; + version_needed[4] = 10; + break; + case kun: + version_needed[0] = 8; + version_needed[1] = 3; + version_needed[2] = 0; + version_needed[3] = 0; + version_needed[4] = 63; + break; + case air_plus: + case slide: + version_needed[0] = 0; + version_needed[1] = 0x1b; + version_needed[2] = 0; + version_needed[3] = 0; + version_needed[4] = 0; + version_length = 2; + break; + default: + return -1; + break; + } + ret = memcmp(version, version_needed, version_length); + return ret; +} + +static int ayaneo_platform_resume(struct platform_device *pdev) +{ + ayaneo_led_mc_take_control(); + + /* Re-apply last color */ + write_lock(&ayaneo_led_mc_update_lock); + ayaneo_led_mc_update_required++; + write_unlock(&ayaneo_led_mc_update_lock); + + /* Allow the MCU to sync with the new state */ + msleep(AYANEO_LED_SUSPEND_RESUME_DELAY_MS); + + ayaneo_led_mc_writer_thread = kthread_run(ayaneo_led_mc_writer, + NULL, + "ayaneo-platform led writer"); + if(ps_priv.bypass_available) { + ayaneo_bypass_charge_writer_thread = kthread_run(ayaneo_bypass_charge_writer, + NULL, + "ayaneo-platform bypass charge writer"); + } + return 0; +} + +static int ayaneo_platform_suspend(struct platform_device *pdev, pm_message_t state) +{ + kthread_stop(ayaneo_led_mc_writer_thread); + + switch (suspend_mode) + { + case AYANEO_LED_SUSPEND_MODE_OEM: + ayaneo_led_mc_release_control(); + break; + + case AYANEO_LED_SUSPEND_MODE_KEEP: + // Nothing to do. + break; + + case AYANEO_LED_SUSPEND_MODE_OFF: + ayaneo_led_mc_take_control(); + break; + + default: + break; + } + + /* Allow the MCU to sync with the new state */ + msleep(AYANEO_LED_SUSPEND_RESUME_DELAY_MS); + + if(ps_priv.bypass_available) { + kthread_stop(ayaneo_bypass_charge_writer_thread); + } + + return 0; +} + +static int ayaneo_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct dmi_system_id *match; + int ret; + + match = dmi_first_match(dmi_table); + ret = PTR_ERR_OR_ZERO(match); + if (ret) + return ret; + + model = (enum ayaneo_model)match->driver_data; + suspend_mode_register_attr(); + ayaneo_led_mc_take_control(); + + ret = devm_led_classdev_multicolor_register(dev, &ayaneo_led_mc); + if (ret) + return ret; + + ret = devm_device_add_group(ayaneo_led_mc.led_cdev.dev, &ayaneo_led_mc_group); + if (ret) + return ret; + + if(ayaneo_check_charge_control() >= 0) { + ps_priv.bypass_available = 1; + } + return ret; +} + +static void ayaneo_platform_shutdown(struct platform_device *pdev) +{ + kthread_stop(ayaneo_led_mc_writer_thread); + ayaneo_led_mc_release_control(); + if(ps_priv.bypass_available) { + kthread_stop(ayaneo_bypass_charge_writer_thread); + } +} + +static void ayaneo_platform_remove(struct platform_device *pdev) +{ + kthread_stop(ayaneo_led_mc_writer_thread); + ayaneo_led_mc_release_control(); + if(ps_priv.bypass_available) { + kthread_stop(ayaneo_bypass_charge_writer_thread); + } +} + +static struct platform_driver ayaneo_platform_driver = { + .driver = { + .name = "ayaneo-platform", + }, + .probe = ayaneo_platform_probe, + .resume = ayaneo_platform_resume, + .suspend = ayaneo_platform_suspend, + .shutdown = ayaneo_platform_shutdown, + .remove = ayaneo_platform_remove, +}; + +static struct platform_device *ayaneo_platform_device; + +static int __init ayaneo_platform_init(void) +{ + int ret; + ayaneo_platform_device = platform_create_bundle(&ayaneo_platform_driver, + ayaneo_platform_probe, NULL, 0, NULL, 0); + ret = PTR_ERR_OR_ZERO(ayaneo_platform_device); + if (ret) + return ret; + + ayaneo_led_mc_writer_thread = kthread_run(ayaneo_led_mc_writer, + NULL, + "ayaneo-platform led writer"); + + if (!ayaneo_led_mc_writer_thread) + { + pr_err("Failed to start writer thread.\n"); + platform_device_unregister(ayaneo_platform_device); + return -1; + } + + if(ps_priv.bypass_available) { + battery_hook_register(&battery_hook); + ayaneo_bypass_charge_writer_thread = kthread_run(ayaneo_bypass_charge_writer, + NULL, + "ayaneo-platform bypass charge writer"); + + if (!ayaneo_bypass_charge_writer_thread) + { + pr_err("Failed to start ps writer thread.\n"); + platform_device_unregister(ayaneo_platform_device); + return -1; + } + } + + return 0; +} + +static void __exit ayaneo_platform_exit(void) +{ + kthread_stop(ayaneo_led_mc_writer_thread); + if(ps_priv.bypass_available) { + kthread_stop(ayaneo_bypass_charge_writer_thread); + battery_hook_unregister(&battery_hook); + } + platform_device_unregister(ayaneo_platform_device); + platform_driver_unregister(&ayaneo_platform_driver); +} + +MODULE_DEVICE_TABLE(dmi, dmi_table); + +module_init(ayaneo_platform_init); +module_exit(ayaneo_platform_exit); + +MODULE_AUTHOR("Derek John Clark "); +MODULE_DESCRIPTION("Platform driver that handles EC sensors of AYANEO x86 devices"); +MODULE_LICENSE("GPL"); # ---------------------------------------- # Module: ayn_platform # Version: fee1bab81d4e # ---------------------------------------- diff --git a/drivers/custom/ayn_platform/Kconfig b/drivers/custom/ayn_platform/Kconfig new file mode 100644 index 000000000000..b08b31733983 --- /dev/null +++ b/drivers/custom/ayn_platform/Kconfig @@ -0,0 +1,10 @@ +menuconfig AYN_PLATFORM + tristate "Ayn x86 PWM Control Support" + depends on HWMON + select LEDS_CLASS + select LEDS_CLASS_MULTICOLOR + help + This driver provides support for Ayn x86 Handheld Consoles by + providing a hwmon interface for pwm fan control and a BIOS + controlled custom fan curve, as well as an RGB LED control + via a multicolor LED sysfs interface. diff --git a/drivers/custom/ayn_platform/Makefile b/drivers/custom/ayn_platform/Makefile new file mode 100644 index 000000000000..62a2fe123bfb --- /dev/null +++ b/drivers/custom/ayn_platform/Makefile @@ -0,0 +1,108 @@ +# For building for the current running version of Linux +ifndef TARGET +TARGET = $(shell uname -r) +endif +# Or specific version +#TARGET = 2.6.33.5 + +KERNEL_MODULES = /lib/modules/$(TARGET) + +ifneq ("","$(wildcard /usr/src/linux-headers-$(TARGET)/*)") +# Ubuntu +KERNEL_BUILD = /usr/src/linux-headers-$(TARGET) +else +ifneq ("","$(wildcard /usr/src/kernels/$(TARGET)/*)") +# Fedora +KERNEL_BUILD = /usr/src/kernels/$(TARGET) +else +KERNEL_BUILD = $(KERNEL_MODULES)/build +endif +endif + +# SYSTEM_MAP = $(KERNEL_BUILD)/System.map +ifneq ("","$(wildcard /boot/System.map-$(TARGET))") +SYSTEM_MAP = /boot/System.map-$(TARGET) +else +# Arch +SYSTEM_MAP = /proc/kallsyms +endif + +DRIVER := ayn-platform +ifneq ("","$(wildcard .git/*)") +DRIVER_VERSION := $(shell git describe --long --tags | sed s/\-/\./g ) +else +ifneq ("", "$(wildcard VERSION)") +DRIVER_VERSION := $(shell cat VERSION) +else +DRIVER_VERSION := unknown +endif +endif + +# DKMS +DKMS_ROOT_PATH=/usr/src/$(DRIVER)-$(DRIVER_VERSION) +MODPROBE_OUTPUT=$(shell lsmod | grep ayn-platform) + +# Directory below /lib/modules/$(TARGET)/kernel into which to install +# the module: +MOD_SUBDIR = drivers/platform/x86 +MODDESTDIR=$(KERNEL_MODULES)/kernel/$(MOD_SUBDIR) + +obj-m = $(patsubst %,%.o,$(DRIVER)) +obj-ko := $(patsubst %,%.ko,$(DRIVER)) + +MAKEFLAGS += --no-print-directory + +ifneq ("","$(wildcard $(MODDESTDIR)/*.ko.gz)") +COMPRESS_GZIP := y +endif +ifneq ("","$(wildcard $(MODDESTDIR)/*.ko.xz)") +COMPRESS_XZ := y +endif + + +.PHONY: all install modules modules_install clean dkms dkms_clean + +all: modules + + +# Targets for running make directly in the external module directory: + +OXP_PLATFORM_CFLAGS=-DOXP_PLATFORM_DRIVER_VERSION='\"$(DRIVER_VERSION)\"' + +modules: + @$(MAKE) EXTRA_CFLAGS="$(OXP_PLATFORM_CFLAGS)" -C $(KERNEL_BUILD) M=$(CURDIR) $@ + +clean: + @$(MAKE) -C $(KERNEL_BUILD) M=$(CURDIR) $@ + +install: modules_install + +modules_install: + mkdir -p $(MODDESTDIR) + cp $(DRIVER).ko $(MODDESTDIR)/ +ifeq ($(COMPRESS_GZIP), y) + @gzip -f $(MODDESTDIR)/$(DRIVER).ko +endif +ifeq ($(COMPRESS_XZ), y) + @xz -f $(MODDESTDIR)/$(DRIVER).ko +endif + depmod -a -F $(SYSTEM_MAP) $(TARGET) + +dkms: + @sed -i -e '/^PACKAGE_VERSION=/ s/=.*/=\"$(DRIVER_VERSION)\"/' dkms.conf + @echo "$(DRIVER_VERSION)" >VERSION + @mkdir -p $(DKMS_ROOT_PATH) + @cp `pwd`/dkms.conf $(DKMS_ROOT_PATH) + @cp `pwd`/VERSION $(DKMS_ROOT_PATH) + @cp `pwd`/Makefile $(DKMS_ROOT_PATH) + @cp `pwd`/ayn-platform.c $(DKMS_ROOT_PATH) + @dkms add -m $(DRIVER) -v $(DRIVER_VERSION) + @dkms build -m $(DRIVER) -v $(DRIVER_VERSION) --kernelsourcedir=$(KERNEL_BUILD) + @dkms install --force -m $(DRIVER) -v $(DRIVER_VERSION) + +dkms_clean: + @if [ ! -z "$(MODPROBE_OUTPUT)" ]; then \ + rmmod $(DRIVER);\ + fi + @dkms remove -m $(DRIVER) -v $(DRIVER_VERSION) --all + @rm -rf $(DKMS_ROOT_PATH) diff --git a/drivers/custom/ayn_platform/ayn-platform.c b/drivers/custom/ayn_platform/ayn-platform.c new file mode 100644 index 000000000000..ab50e5dcbc0b --- /dev/null +++ b/drivers/custom/ayn_platform/ayn-platform.c @@ -0,0 +1,767 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Platform driver for Ayn x86 Handhelds that expose fan reading and + * control, as well as temperature sensor readings exposed by the EC + * via hwmon sysfs. + * + * Fan control is provided via pwm interface in the range [0-255]. + * Ayn use [0-128] as the range in the EC, the written value is + * scaled to accommodate. The EC also provides a configurable fan + * curve with five set points that associate a temperature [0-100] + * in Celcius with a fan speed [0-128]. The auto_point fan speeds + * are scaled from the range [0-255]. Temperature readings are + * scaled from the hwmon expected millidegrees to degrees when read. + * + * Copyright (C) 2023-2024 Derek J. Clark + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Handle ACPI lock mechanism */ +static u32 ayn_mutex; + +#define ACPI_LOCK_DELAY_MS 500 + +static bool lock_global_acpi_lock(void) { + return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &ayn_mutex)); +} + +static bool unlock_global_acpi_lock(void) { + return ACPI_SUCCESS(acpi_release_global_lock(ayn_mutex)); +} + +enum ayn_model { + ayn_loki_max = 1, + ayn_loki_minipro, + ayn_loki_zero, +}; + +static enum ayn_model model; + +/* EC Teperature Sensors */ +#define AYN_SENSOR_BAT_TEMP_REG 0x04 /* Battery */ +#define AYN_SENSOR_CHARGE_TEMP_REG 0x07 /* Charger IC */ +#define AYN_SENSOR_MB_TEMP_REG 0x05 /* Motherboard */ +#define AYN_SENSOR_PROC_TEMP_REG 0x09 /* CPU Core */ +#define AYN_SENSOR_VCORE_TEMP_REG 0x08 /* vCore */ + +/* Fan reading and PWM */ +#define AYN_SENSOR_PWM_FAN_ENABLE_REG 0x10 /* PWM operating mode */ +#define AYN_SENSOR_PWM_FAN_SET_REG 0x11 /* PWM duty cycle */ +#define AYN_SENSOR_PWM_FAN_SPEED_REG 0x20 /* Fan speed */ + +/* EC controlled fan curve registers */ +#define AYN_SENSOR_PWM_FAN_SPEED_1_REG 0x12 +#define AYN_SENSOR_PWM_FAN_SPEED_2_REG 0x14 +#define AYN_SENSOR_PWM_FAN_SPEED_3_REG 0x16 +#define AYN_SENSOR_PWM_FAN_SPEED_4_REG 0x18 +#define AYN_SENSOR_PWM_FAN_SPEED_5_REG 0x1A +#define AYN_SENSOR_PWM_FAN_TEMP_1_REG 0x13 +#define AYN_SENSOR_PWM_FAN_TEMP_2_REG 0x15 +#define AYN_SENSOR_PWM_FAN_TEMP_3_REG 0x17 +#define AYN_SENSOR_PWM_FAN_TEMP_4_REG 0x19 +#define AYN_SENSOR_PWM_FAN_TEMP_5_REG 0x1B + +/* EC Controlled RGB registers */ +#define AYN_LED_MC_B_REG 0xB2 /* Blue, range 0x00-0xFF */ +#define AYN_LED_MC_G_REG 0xB1 /* Green, range 0x00-0xFF */ +#define AYN_LED_MC_R_REG 0xB0 /* Red, range 0x00-0xFF */ +#define AYN_LED_MODE_REG 0xB3 /* RGB Mode */ + +/* RGB Mode values */ +#define AYN_LED_MODE_BREATH 0x00 /* Default breathing mode */ +#define AYN_LED_MODE_WRITE 0xAA /* User defined mode */ +#define AYN_LED_MODE_WRITE_ENABLED 0x55 /* Return value when probed */ + +enum led_mode { + breath = 0, + write, +}; + +static const struct dmi_system_id dmi_table[] = { + { + .matches = + { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki Max"), + }, + .driver_data = (void *)ayn_loki_max, + }, + { + .matches = + { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki MiniPro"), + }, + .driver_data = (void *)ayn_loki_minipro, + }, + { + .matches = + { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki Zero"), + }, + .driver_data = (void *)ayn_loki_zero, + }, + {}, +}; + +/* Helper functions to handle EC read/write */ +static int read_from_ec(u8 reg, int size, long *val) +{ + int i; + int ret; + u8 buffer; + + if (!lock_global_acpi_lock()) + return -EBUSY; + + *val = 0; + for (i = 0; i < size; i++) { + ret = ec_read(reg + i, &buffer); + if (ret) + return ret; + *val <<= i * 8; + *val += buffer; + } + + if (!unlock_global_acpi_lock()) + return -EBUSY; + + return 0; +} + +static int write_to_ec(u8 reg, u8 val) +{ + int ret; + + if (!lock_global_acpi_lock()) + return -EBUSY; + + ret = ec_write(reg, val); + + if (!unlock_global_acpi_lock()) + return -EBUSY; + + return ret; +} + +/* Thermal Sensor Functions*/ +struct thermal_sensor { + char *name; + int reg; +}; + +static struct thermal_sensor thermal_sensors[] = { + {"Battery", AYN_SENSOR_BAT_TEMP_REG}, + {"Motherboard", AYN_SENSOR_MB_TEMP_REG}, + {"Charger IC", AYN_SENSOR_CHARGE_TEMP_REG}, + {"vCore", AYN_SENSOR_VCORE_TEMP_REG}, + {"CPU Core", AYN_SENSOR_PROC_TEMP_REG}, + {0,} +}; + +static long thermal_sensor_temp(u8 reg, long *val) +{ + long retval; + retval = read_from_ec(reg, 1, val); + + if (retval) + return retval; + + *val = *val * (long)1000; // convert from hwmon expected millidegree to degree + return retval; +}; + +static ssize_t thermal_sensor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int index; + long retval; + long val; + index = to_sensor_dev_attr(attr)->index; + + retval = thermal_sensor_temp(thermal_sensors[index].reg, &val); + if (retval) + return retval; + + return sprintf(buf, "%ld\n", val); +} + +static ssize_t thermal_sensor_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int index; + index = to_sensor_dev_attr(attr)->index; + return sprintf(buf, "%s\n", thermal_sensors[index].name); +} + +/* PWM mode functions */ +/* Callbacks for pwm_auto_point attributes */ +static ssize_t pwm_curve_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int index; + int retval; + int val; + u8 reg; + + retval = kstrtoint(buf, 0, &val); + if (retval) + return retval; + + index = to_sensor_dev_attr(attr)->index; + switch (index) { + case 0: + reg = AYN_SENSOR_PWM_FAN_SPEED_1_REG; + break; + case 1: + reg = AYN_SENSOR_PWM_FAN_SPEED_2_REG; + break; + case 2: + reg = AYN_SENSOR_PWM_FAN_SPEED_3_REG; + break; + case 3: + reg = AYN_SENSOR_PWM_FAN_SPEED_4_REG; + break; + case 4: + reg = AYN_SENSOR_PWM_FAN_SPEED_5_REG; + break; + case 5: + reg = AYN_SENSOR_PWM_FAN_TEMP_1_REG; + break; + case 6: + reg = AYN_SENSOR_PWM_FAN_TEMP_2_REG; + break; + case 7: + reg = AYN_SENSOR_PWM_FAN_TEMP_3_REG; + break; + case 8: + reg = AYN_SENSOR_PWM_FAN_TEMP_4_REG; + break; + case 9: + reg = AYN_SENSOR_PWM_FAN_TEMP_5_REG; + break; + default: + return -EINVAL; + } + + switch (index) { + case 0: + case 1: + case 2: + case 3: + case 4: + if (val < 0 || val > 255) + return -EINVAL; + val = val >> 1; + break; + case 5: + case 6: + case 7: + case 8: + case 9: + if (val < 0 || val > 100) + return -EINVAL; + break; + default: + return -EINVAL; + } + + retval = write_to_ec(reg, val); + if (retval) + return retval; + return count; +} + +static ssize_t pwm_curve_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index; + int retval; + long val; + u8 reg; + + index = to_sensor_dev_attr(attr)->index; + switch (index) { + case 0: + reg = AYN_SENSOR_PWM_FAN_SPEED_1_REG; + break; + case 1: + reg = AYN_SENSOR_PWM_FAN_SPEED_2_REG; + break; + case 2: + reg = AYN_SENSOR_PWM_FAN_SPEED_3_REG; + break; + case 3: + reg = AYN_SENSOR_PWM_FAN_SPEED_4_REG; + break; + case 4: + reg = AYN_SENSOR_PWM_FAN_SPEED_5_REG; + break; + case 5: + reg = AYN_SENSOR_PWM_FAN_TEMP_1_REG; + break; + case 6: + reg = AYN_SENSOR_PWM_FAN_TEMP_2_REG; + break; + case 7: + reg = AYN_SENSOR_PWM_FAN_TEMP_3_REG; + break; + case 8: + reg = AYN_SENSOR_PWM_FAN_TEMP_4_REG; + break; + case 9: + reg = AYN_SENSOR_PWM_FAN_TEMP_5_REG; + break; + default: + return -EINVAL; + } + + retval = read_from_ec(reg, 1, &val); + if (retval) + return retval; + + switch (index) { + case 0: + case 1: + case 2: + case 3: + case 4: + val = val << 1; + break; + default: + break; + } + + return sysfs_emit(buf, "%ld\n", val); +} + +/* Manual provides direct control of the PWM */ +static int ayn_pwm_manual(void) +{ + return write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x00); +} + +/* Auto provides EC full control of the PWM */ +static int ayn_pwm_auto(void) +{ + return write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x01); +} + +/* User defined mode allows users to set a custom 5 point + * fan curve in the EC which uses the CPU temperature. */ +static int ayn_pwm_user(void) +{ + return write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x02); +} + +/* Temperature sensor and fan curve attributes */ +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, thermal_sensor_show, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, thermal_sensor_label, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, thermal_sensor_show, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, thermal_sensor_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, thermal_sensor_show, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, thermal_sensor_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, thermal_sensor_show, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, thermal_sensor_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, thermal_sensor_show, NULL, 4); +static SENSOR_DEVICE_ATTR(temp5_label, S_IRUGO, thermal_sensor_label, NULL, 4); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_pwm, pwm_curve, 0); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_pwm, pwm_curve, 1); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_pwm, pwm_curve, 2); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_pwm, pwm_curve, 3); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_pwm, pwm_curve, 4); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, pwm_curve, 5); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, pwm_curve, 6); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_curve, 7); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_curve, 8); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_curve, 9); + +static struct attribute *ayn_sensors_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_label.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_label.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(ayn_sensors); + +/* Callbacks for fan1/pwm attributes */ +static umode_t ayn_ec_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + switch (type) { + case hwmon_fan: + return 0444; + case hwmon_pwm: + return 0644; + default: + return 0; + } +} + +static int ayn_platform_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + return read_from_ec(AYN_SENSOR_PWM_FAN_SPEED_REG, 2, val); + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + ret = read_from_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 1, val); + switch (*val) { + /* EC uses 0 for manual and 1 for automatic, + reflect hwmon usage instead */ + case 0: + *val = 1; + break; + case 1: + *val = 0; + break; + default: + break; + } + return ret; + case hwmon_pwm_input: + ret = read_from_ec(AYN_SENSOR_PWM_FAN_SET_REG, 1, val); + if (ret) + return ret; + switch (model) { + case ayn_loki_max: + case ayn_loki_minipro: + case ayn_loki_zero: + *val = *val << 1; /* EC max value is 128 */ + break; + default: + break; + } + return 0; + default: + break; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int ayn_platform_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + if (val == 1) + return ayn_pwm_manual(); + else if (val == 2) + return ayn_pwm_user(); + else if (val == 0) + return ayn_pwm_auto(); + return -EINVAL; + case hwmon_pwm_input: + if (val < 0 || val > 255) + return -EINVAL; + switch (model) { + case ayn_loki_max: + case ayn_loki_minipro: + case ayn_loki_zero: + val = val >> 1; /* EC max value is 128 */ + break; + default: + break; + } + return write_to_ec(AYN_SENSOR_PWM_FAN_SET_REG, val); + default: + break; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +/* RGB LED Logic */ +static int led_mode_write(int mode) +{ + return write_to_ec(AYN_LED_MODE_REG, mode); +}; + +static ssize_t led_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int val; + int retval; + int mode; + + retval = kstrtoint(buf, 0, &val); + if (retval) + return retval; + + if (val) { + mode = AYN_LED_MODE_WRITE; + } else { + mode = AYN_LED_MODE_BREATH; + } + + retval = led_mode_write(mode); + if (retval) + return retval; + + return count; +}; + +static ssize_t led_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + long mode; + int val; + int retval; + + retval = read_from_ec(AYN_LED_MODE_REG, 1, &mode); + if (retval) + return retval; + switch (mode) { + case AYN_LED_MODE_BREATH: + val = breath; + break; + case AYN_LED_MODE_WRITE: + case AYN_LED_MODE_WRITE_ENABLED: + val = write; + break; + default: + break; + } + return sysfs_emit(buf, "%d\n", val); +}; + +static DEVICE_ATTR_RW(led_mode); + +static int ayn_led_mc_brightness_write(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev); + struct mc_subled s_led; + int i; + int retval; + int val; + + for (i = 0; i < mc_cdev->num_colors; i++) { + s_led = mc_cdev->subled_info[i]; + val = brightness * s_led.intensity / led_cdev->max_brightness; + retval = write_to_ec(s_led.channel, val); + if (retval) + return retval; + } + + return write_to_ec(AYN_LED_MODE_REG, AYN_LED_MODE_WRITE); + +}; + +static void ayn_led_mc_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + long mode; + int retval; + + retval = read_from_ec(AYN_LED_MODE_REG, 1, &mode); + if (retval) + return; + + switch (mode) { + case AYN_LED_MODE_WRITE: + case AYN_LED_MODE_WRITE_ENABLED: + break; + default: + return; + } + + led_cdev->brightness = brightness; + ayn_led_mc_brightness_write(led_cdev, brightness); +}; + +static enum led_brightness +ayn_led_mc_brightness_get(struct led_classdev *led_cdev) +{ + return led_cdev->brightness; +}; + +static struct attribute *ayn_led_mc_attrs[] = { + &dev_attr_led_mode.attr, + NULL, +}; + +static struct attribute_group ayn_led_mc_group = { + .attrs = ayn_led_mc_attrs, +}; + +/* Initialization logic */ +static const struct hwmon_channel_info *ayn_platform_sensors[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE), + NULL, +}; + +static const struct hwmon_ops ayn_ec_hwmon_ops = { + .is_visible = ayn_ec_hwmon_is_visible, + .read = ayn_platform_read, + .write = ayn_platform_write, +}; + +static const struct hwmon_chip_info ayn_ec_chip_info = { + .ops = &ayn_ec_hwmon_ops, + .info = ayn_platform_sensors, +}; + +struct mc_subled ayn_led_mc_subled_info[] = { + { + .color_index = LED_COLOR_ID_RED, + .brightness = 0, + .intensity = 0, + .channel = AYN_LED_MC_R_REG, + }, + { + .color_index = LED_COLOR_ID_GREEN, + .brightness = 0, + .intensity = 0, + .channel = AYN_LED_MC_G_REG, + }, + { + .color_index = LED_COLOR_ID_BLUE, + .brightness = 0, + .intensity = 0, + .channel = AYN_LED_MC_B_REG, + }, +}; + +struct led_classdev_mc ayn_led_mc = { + .led_cdev = { + .name = "ayn:rgb:joystick_rings", + .brightness = 0, + .max_brightness = 255, + .brightness_set = ayn_led_mc_brightness_set, + .brightness_get = ayn_led_mc_brightness_get, + }, + .num_colors = ARRAY_SIZE(ayn_led_mc_subled_info), + .subled_info = ayn_led_mc_subled_info, +}; + +static int ayn_platform_resume(struct platform_device *pdev) +{ + struct led_classdev *led_cdev = &ayn_led_mc.led_cdev; + int retval; + + retval = led_mode_write(AYN_LED_MODE_WRITE); + if (retval) + return retval; + + retval = ayn_led_mc_brightness_write(led_cdev, led_cdev->brightness); + if (retval) + return retval; + + return 0; +} + +static int ayn_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *hwdev; + int retval; + + retval = devm_led_classdev_multicolor_register(dev, &ayn_led_mc); + if (retval) + return retval; + + struct led_classdev *led_cdev = &ayn_led_mc.led_cdev; + + retval = devm_device_add_group(ayn_led_mc.led_cdev.dev, &ayn_led_mc_group); + if (retval) + return retval; + + retval = led_mode_write(AYN_LED_MODE_WRITE); + if (retval) + return retval; + + retval = ayn_led_mc_brightness_write(led_cdev, 0); + if (retval) + return retval; + + hwdev = devm_hwmon_device_register_with_info( + dev, "aynec", NULL, &ayn_ec_chip_info, ayn_sensors_groups); + return PTR_ERR_OR_ZERO(hwdev); +} + +static struct platform_driver ayn_platform_driver = { + .driver = { + .name = "ayn-platform", + }, + .probe = ayn_platform_probe, + .resume = ayn_platform_resume, +}; + +static struct platform_device *ayn_platform_device; + +static int __init ayn_platform_init(void) +{ + ayn_platform_device = platform_create_bundle( + &ayn_platform_driver, ayn_platform_probe, NULL, 0, NULL, 0); + + return PTR_ERR_OR_ZERO(ayn_platform_device); +} + +static void __exit ayn_platform_exit(void) +{ + platform_device_unregister(ayn_platform_device); + platform_driver_unregister(&ayn_platform_driver); +} + +MODULE_DEVICE_TABLE(dmi, dmi_table); + +module_init(ayn_platform_init); +module_exit(ayn_platform_exit); + +MODULE_AUTHOR("Derek John Clark "); +MODULE_DESCRIPTION( + "Platform driver that handles EC sensors of Ayn x86 devices"); +MODULE_LICENSE("GPL"); # ---------------------------------------- # Module: bmi260 # Version: v1.1.0 # ---------------------------------------- diff --git a/drivers/custom/bmi260/Makefile b/drivers/custom/bmi260/Makefile new file mode 100644 index 000000000000..9ffaba87b5fe --- /dev/null +++ b/drivers/custom/bmi260/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for Bosch BMI260 IMU +# +obj-m += bmi260_core.o +obj-m += bmi260_i2c.o +# TODO: bmi260_spi + +KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build + +all default: modules +install: modules_install + +modules modules_install help clean: + $(MAKE) -C $(KERNEL_SRC) M=$(shell pwd) $@ diff --git a/drivers/custom/bmi260/bmi260.h b/drivers/custom/bmi260/bmi260.h new file mode 100644 index 000000000000..54bf47185d3f --- /dev/null +++ b/drivers/custom/bmi260/bmi260.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef BMI260_H_ +#define BMI260_H_ + +#include +#include +#include + +enum bmi260_int_pin { + BMI260_PIN_INT1, + BMI260_PIN_INT2 +}; + +struct bmi260_config { + int accel_scale; + int accel_odr; + int accel_uodr; + int gyro_scale; + int gyro_odr; + int gyro_uodr; + bool irq; + bool suspended; +}; + +struct bmi260_data { + struct regmap *regmap; + struct iio_trigger *trig; + struct regulator_bulk_data supplies[2]; + struct iio_mount_matrix orientation; + enum bmi260_int_pin int_pin; + bool use_spi; + struct bmi260_config conf; + + /* + * Ensure natural alignment for timestamp if present. + * Max length needed: 2 * 3 channels + 4 bytes padding + 8 byte ts. + * If fewer channels are enabled, less space may be needed, as + * long as the timestamp is still aligned to 8 bytes. + */ + __le16 buf[12] __aligned(8); +}; + +extern const struct regmap_config bmi260_regmap_config; + +int bmi260_core_probe(struct device *dev, struct regmap *regmap, + int irq, const char *name, bool use_spi); + +int bmi260_enable_irq(struct regmap *regmap, enum bmi260_int_pin pin, bool enable); + +int bmi260_probe_trigger(struct iio_dev *indio_dev, int irq, u32 irq_type); + +extern const struct dev_pm_ops bmi260_pm_ops; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) +#define BMI260_EXPORT_SYMBOL(symbol) EXPORT_SYMBOL_NS_GPL(symbol, "IIO_BMI260"); +#define BMI260_IMPORT_NS MODULE_IMPORT_NS("IIO_BMI260"); +#else +#define BMI260_EXPORT_SYMBOL(symbol) EXPORT_SYMBOL_NS_GPL(symbol, IIO_BMI260); +#define BMI260_IMPORT_NS MODULE_IMPORT_NS(IIO_BMI260); +#endif + +#endif /* BMI260_H_ */ diff --git a/drivers/custom/bmi260/bmi260_config.h b/drivers/custom/bmi260/bmi260_config.h new file mode 100644 index 000000000000..c5ce53fedd56 --- /dev/null +++ b/drivers/custom/bmi260/bmi260_config.h @@ -0,0 +1,718 @@ +/* + * Copyright (c) 2020 Bosch Sensortec GmbH. All rights reserved. + * + * BSD-3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +const unsigned char bmi260_config_file[] = { + 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x63, 0xb3, 0xc8, 0x2e, 0x00, 0x2e, + 0x80, 0x2e, 0x15, 0x03, 0x80, 0x2e, 0xbb, 0xb4, 0x80, 0x2e, 0x91, 0x03, + 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0xe7, 0xb3, 0x50, 0x30, 0x21, 0x2e, + 0x59, 0xf5, 0x10, 0x30, 0x21, 0x2e, 0x4a, 0xf1, 0x21, 0x2e, 0x6a, 0xf5, + 0x80, 0x2e, 0xe0, 0x01, 0x0d, 0x0d, 0x01, 0x00, 0x22, 0x00, 0x76, 0x00, + 0x00, 0x10, 0x00, 0x10, 0xc8, 0x00, 0x01, 0x1c, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0xfd, 0x2d, 0xe4, 0x78, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x0d, 0x00, 0x00, + 0x88, 0x00, 0x05, 0xe0, 0xaa, 0x38, 0x05, 0xe0, 0x90, 0x30, 0x86, 0x00, + 0x30, 0x0a, 0x80, 0x40, 0x10, 0x27, 0xe8, 0x73, 0x04, 0x30, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x30, 0x10, 0x0b, 0x09, 0x08, 0xfa, 0x00, 0x96, 0x00, + 0x4b, 0x09, 0x11, 0x00, 0x11, 0x00, 0x02, 0x00, 0x00, 0x00, 0x22, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x01, + 0xe6, 0x78, 0x84, 0x00, 0x9c, 0x6c, 0x07, 0x00, 0x64, 0x75, 0xaa, 0x7e, + 0x5f, 0x05, 0xbe, 0x0a, 0x5f, 0x05, 0x96, 0xe8, 0xef, 0x41, 0x01, 0x00, + 0x0c, 0x00, 0x0c, 0x00, 0x4a, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x0c, 0x00, + 0xf0, 0x3c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0xa1, 0x01, 0x8f, 0x01, + 0x9d, 0x01, 0x8b, 0x01, 0x00, 0x0c, 0xff, 0x0f, 0x00, 0x04, 0xc0, 0x00, + 0x5b, 0xf5, 0x74, 0x01, 0x1e, 0xf2, 0xfd, 0xf5, 0xfc, 0xf5, 0x6f, 0x01, + 0x77, 0x01, 0x80, 0x00, 0xa0, 0x00, 0x5f, 0xff, 0x00, 0x08, 0x00, 0xf8, + 0x7a, 0x01, 0x85, 0x01, 0x7f, 0x01, 0x84, 0x01, 0x4c, 0x04, 0xe8, 0x03, + 0xff, 0x7f, 0xb8, 0x7e, 0xe1, 0x7a, 0x81, 0x01, 0x7c, 0x01, 0x7e, 0x01, + 0xc8, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xd2, 0x00, 0x88, 0x01, 0x69, 0xf5, + 0xe0, 0x00, 0x3f, 0xff, 0x19, 0xf4, 0x58, 0xf5, 0x66, 0xf5, 0x64, 0xf5, + 0xc0, 0xf1, 0xba, 0xf1, 0xa0, 0x00, 0xa6, 0x01, 0xf7, 0x00, 0xf9, 0x00, + 0xb7, 0x01, 0xff, 0x3f, 0xff, 0xfb, 0x00, 0x38, 0x00, 0x30, 0xb8, 0x01, + 0xbf, 0x01, 0xc1, 0x01, 0xc7, 0x01, 0xcf, 0x01, 0xff, 0x01, 0x95, 0x01, + 0x74, 0xf7, 0x00, 0x40, 0xff, 0x00, 0x00, 0x80, 0x7c, 0x0f, 0xeb, 0x00, + 0x7f, 0xff, 0xc2, 0xf5, 0x68, 0xf7, 0xb3, 0xf1, 0x76, 0x0f, 0x6a, 0x0f, + 0x70, 0x0f, 0x8f, 0x0f, 0x58, 0xf7, 0x5b, 0xf7, 0x92, 0x0f, 0x86, 0x00, + 0x81, 0x0f, 0x94, 0x0f, 0xc6, 0xf1, 0x8e, 0x0f, 0x6c, 0xf7, 0x00, 0xe0, + 0x00, 0xff, 0xd1, 0xf5, 0x96, 0x0f, 0x99, 0x0f, 0xff, 0x03, 0x00, 0xfc, + 0xf0, 0x3f, 0x8b, 0x00, 0x90, 0x00, 0x8f, 0x00, 0x95, 0x00, 0x92, 0x00, + 0x98, 0x00, 0x8d, 0x00, 0xa2, 0x00, 0xb9, 0x00, 0x2d, 0xf5, 0xca, 0xf5, + 0x75, 0x01, 0x20, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1a, 0x24, 0x22, 0x00, 0x80, 0x2e, 0x00, 0xb0, 0x17, 0x52, 0x00, 0x2e, + 0x60, 0x40, 0x41, 0x40, 0x0d, 0xbc, 0x98, 0xbc, 0xc0, 0x2e, 0x01, 0x0a, + 0x0f, 0xb8, 0x19, 0x52, 0x53, 0x3c, 0x52, 0x40, 0x40, 0x40, 0x4b, 0x00, + 0x82, 0x16, 0x26, 0xb9, 0x01, 0xb8, 0x41, 0x40, 0x10, 0x08, 0x97, 0xb8, + 0x01, 0x08, 0xc0, 0x2e, 0x11, 0x30, 0x01, 0x08, 0x43, 0x86, 0x25, 0x40, + 0x04, 0x40, 0xd8, 0xbe, 0x2c, 0x0b, 0x22, 0x11, 0x54, 0x42, 0x03, 0x80, + 0x4b, 0x0e, 0xf6, 0x2f, 0xb8, 0x2e, 0x1b, 0x50, 0x10, 0x50, 0x1d, 0x52, + 0x05, 0x2e, 0xd5, 0x00, 0xfb, 0x7f, 0x00, 0x2e, 0x13, 0x40, 0x93, 0x42, + 0x41, 0x0e, 0xfb, 0x2f, 0x98, 0x2e, 0x0b, 0x03, 0x98, 0x2e, 0x87, 0xcf, + 0x01, 0x2e, 0x6e, 0x01, 0x00, 0xb2, 0xfb, 0x6f, 0x0b, 0x2f, 0x01, 0x2e, + 0x69, 0xf7, 0xb1, 0x3f, 0x01, 0x08, 0x01, 0x30, 0xf0, 0x5f, 0x23, 0x2e, + 0x6e, 0x01, 0x21, 0x2e, 0x69, 0xf7, 0x80, 0x2e, 0x29, 0x02, 0xf0, 0x5f, + 0xb8, 0x2e, 0x01, 0x2e, 0xc0, 0xf8, 0x03, 0x2e, 0xfc, 0xf5, 0x1f, 0x54, + 0x21, 0x56, 0x82, 0x08, 0x0b, 0x2e, 0x69, 0xf7, 0xcb, 0x0a, 0x23, 0x58, + 0x80, 0x90, 0xdd, 0xbe, 0x4c, 0x08, 0x5f, 0xb9, 0x59, 0x22, 0x80, 0x90, + 0x07, 0x2f, 0x03, 0x34, 0xc3, 0x08, 0xf2, 0x3a, 0x0a, 0x08, 0x02, 0x35, + 0xc0, 0x90, 0x4a, 0x0a, 0x48, 0x22, 0xc0, 0x2e, 0x23, 0x2e, 0xfc, 0xf5, + 0x03, 0x2e, 0x77, 0x01, 0x43, 0x40, 0xbf, 0xbc, 0x37, 0xbc, 0x30, 0x50, + 0x40, 0xb2, 0x0c, 0xb8, 0xe0, 0x7f, 0xfb, 0x7f, 0x01, 0x30, 0x23, 0x2f, + 0x01, 0x2e, 0x7c, 0x00, 0x00, 0xb2, 0x04, 0x2f, 0x04, 0x30, 0x98, 0x2e, + 0x7f, 0x02, 0x29, 0x2e, 0x7c, 0x00, 0x3b, 0xbc, 0xbc, 0xbc, 0x0f, 0xb8, + 0x9d, 0xb8, 0x23, 0x2e, 0x78, 0x01, 0xd0, 0x7f, 0x98, 0x2e, 0x77, 0xb1, + 0x10, 0x25, 0xd0, 0x6f, 0x00, 0x90, 0x06, 0x2f, 0xfb, 0x6f, 0xe0, 0x6f, + 0x22, 0x30, 0xd0, 0x5f, 0x4a, 0x08, 0x80, 0x2e, 0x95, 0xcf, 0xfb, 0x6f, + 0xe0, 0x6f, 0x12, 0x30, 0xd0, 0x5f, 0x4a, 0x08, 0x80, 0x2e, 0x95, 0xcf, + 0xe0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0x11, 0x30, 0x23, 0x2e, 0x7c, 0x00, + 0xfb, 0x6f, 0xd0, 0x5f, 0xb8, 0x2e, 0x29, 0x50, 0x25, 0x52, 0x11, 0x42, + 0x00, 0x2e, 0x27, 0x52, 0x01, 0x42, 0x01, 0x30, 0x2b, 0x54, 0x11, 0x42, + 0x42, 0x0e, 0xfc, 0x2f, 0xb8, 0x2e, 0x2d, 0x54, 0x00, 0x2e, 0x83, 0x40, + 0xbd, 0x84, 0x18, 0x1a, 0x80, 0x40, 0x13, 0x2f, 0xc0, 0x90, 0x22, 0x2f, + 0x03, 0x35, 0x03, 0x0f, 0x0a, 0x2f, 0x09, 0x2e, 0x7e, 0x01, 0x00, 0xb3, + 0x01, 0x2f, 0x04, 0xa8, 0x04, 0x2f, 0x00, 0x30, 0x80, 0x42, 0x21, 0x2e, + 0x7f, 0x01, 0xb8, 0x2e, 0x83, 0x42, 0xc0, 0x2e, 0x23, 0x2e, 0x7f, 0x01, + 0x02, 0x35, 0x82, 0x0e, 0x0d, 0x2f, 0x03, 0x3b, 0x03, 0x00, 0x0c, 0xa8, + 0x09, 0x2f, 0x2f, 0x58, 0x3b, 0x81, 0x3d, 0x86, 0x04, 0x41, 0xc2, 0x42, + 0xc8, 0x84, 0x01, 0x87, 0x01, 0x42, 0x83, 0x42, 0xb8, 0x2e, 0xb8, 0x2e, + 0x01, 0x2e, 0x86, 0x01, 0x01, 0x86, 0x13, 0x25, 0xd2, 0x40, 0x50, 0x50, + 0xc3, 0x40, 0x23, 0xbd, 0x2f, 0xb9, 0xbc, 0xb9, 0xfb, 0x7f, 0x80, 0xb2, + 0xe3, 0x7f, 0x0b, 0x30, 0x39, 0x2f, 0x05, 0x2e, 0x7e, 0x00, 0x80, 0x90, + 0x04, 0x2f, 0x81, 0x84, 0x25, 0x2e, 0x7e, 0x00, 0x37, 0x2e, 0x7f, 0x00, + 0x41, 0x40, 0x02, 0x40, 0x02, 0x80, 0x94, 0xbc, 0x94, 0xb9, 0x00, 0x40, + 0x04, 0xbc, 0x21, 0xbd, 0x04, 0xb8, 0x21, 0xb9, 0x07, 0x52, 0xd3, 0x7f, + 0xc2, 0x7f, 0xb0, 0x7f, 0x98, 0x2e, 0xb3, 0xc0, 0xd1, 0x6f, 0xc2, 0x6f, + 0x51, 0x28, 0x41, 0x0f, 0x11, 0x30, 0x0d, 0x2f, 0xc2, 0x0e, 0x07, 0x2e, + 0x7f, 0x00, 0x19, 0x28, 0x04, 0x2f, 0xc0, 0xa6, 0x04, 0x2f, 0x21, 0x2e, + 0x7f, 0x00, 0x02, 0x2d, 0x21, 0x2e, 0x7f, 0x00, 0x04, 0x2c, 0x02, 0x30, + 0x02, 0x30, 0x25, 0x2e, 0x7f, 0x00, 0xb0, 0x6f, 0x07, 0x2e, 0x7f, 0x00, + 0x58, 0x0f, 0xfb, 0x6f, 0xe0, 0x6f, 0xb0, 0x5f, 0x4a, 0x22, 0x80, 0x2e, + 0x95, 0xcf, 0xe0, 0x6f, 0x01, 0x30, 0x98, 0x2e, 0x95, 0xcf, 0x00, 0x30, + 0x21, 0x2e, 0x7e, 0x00, 0xfb, 0x6f, 0xb0, 0x5f, 0xb8, 0x2e, 0x03, 0x2e, + 0xd5, 0x00, 0x16, 0xb8, 0x02, 0x34, 0x4a, 0x0c, 0x21, 0x2e, 0x2d, 0xf5, + 0xc0, 0x2e, 0x23, 0x2e, 0xd5, 0x00, 0x20, 0x50, 0xf6, 0x7f, 0xe7, 0x7f, + 0x00, 0x2e, 0x4b, 0x5c, 0x00, 0x2e, 0x87, 0x41, 0xff, 0xbf, 0xff, 0xbb, + 0xc0, 0x91, 0x02, 0x2f, 0x37, 0x30, 0x2f, 0x2e, 0x69, 0xf5, 0xb8, 0x8f, + 0x06, 0x32, 0xc7, 0x41, 0xfe, 0x09, 0xc0, 0xb3, 0x04, 0x2f, 0x17, 0x30, + 0x2f, 0x2e, 0x9c, 0x01, 0x2d, 0x2e, 0x61, 0xf5, 0xf6, 0x6f, 0xe7, 0x6f, + 0xe0, 0x5f, 0xc8, 0x2e, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0x56, 0xc7, + 0x98, 0x2e, 0x49, 0xc3, 0x00, 0x30, 0xfb, 0x6f, 0xf0, 0x5f, 0x21, 0x2e, + 0x7e, 0x00, 0x21, 0x2e, 0xce, 0x00, 0xb8, 0x2e, 0x21, 0x2e, 0x59, 0xf5, + 0x10, 0x30, 0xc0, 0x2e, 0x21, 0x2e, 0x4a, 0xf1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x9a, 0x01, 0x34, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x50, 0xe5, 0x7f, 0xf6, 0x7f, 0xd7, 0x7f, 0x00, 0x2e, + 0x4b, 0x5a, 0x00, 0x2e, 0x46, 0x41, 0x6f, 0xbf, 0x6f, 0xbb, 0x80, 0x91, + 0x02, 0x2f, 0x36, 0x30, 0x2d, 0x2e, 0x69, 0xf5, 0x46, 0x30, 0x0f, 0x2e, + 0xa4, 0xf1, 0xbe, 0x09, 0x77, 0x8b, 0x80, 0xb3, 0x06, 0x2f, 0x0d, 0x2e, + 0xff, 0x00, 0x84, 0xaf, 0x02, 0x2f, 0x16, 0x30, 0x2d, 0x2e, 0xdc, 0x00, + 0x86, 0x30, 0x46, 0x43, 0x00, 0x2e, 0xf6, 0x6f, 0xe5, 0x6f, 0xd7, 0x6f, + 0xd0, 0x5f, 0xc8, 0x2e, 0x03, 0x2e, 0x9f, 0x00, 0x1b, 0xbc, 0x60, 0x50, + 0x9f, 0xbc, 0x0c, 0xb8, 0xf0, 0x7f, 0x40, 0xb2, 0xeb, 0x7f, 0x2b, 0x2f, + 0x03, 0x2e, 0xfc, 0x00, 0x41, 0x40, 0x01, 0x2e, 0xfd, 0x00, 0x01, 0x1a, + 0x11, 0x2f, 0x71, 0x58, 0x23, 0x2e, 0xfd, 0x00, 0x10, 0x41, 0xa0, 0x7f, + 0x38, 0x81, 0x01, 0x41, 0xd0, 0x7f, 0xb1, 0x7f, 0x98, 0x2e, 0x64, 0xcf, + 0xd0, 0x6f, 0x07, 0x80, 0xa1, 0x6f, 0x11, 0x42, 0x00, 0x2e, 0xb1, 0x6f, + 0x01, 0x42, 0x11, 0x30, 0x01, 0x2e, 0xb7, 0x01, 0x00, 0xa8, 0x03, 0x30, + 0xcb, 0x22, 0x4a, 0x25, 0x01, 0x2e, 0xfc, 0x00, 0x3c, 0x89, 0x6f, 0x52, + 0x07, 0x54, 0x98, 0x2e, 0xc4, 0xce, 0xc1, 0x6f, 0xf0, 0x6f, 0x98, 0x2e, + 0x95, 0xcf, 0x04, 0x2d, 0x01, 0x30, 0xf0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, + 0xeb, 0x6f, 0xa0, 0x5f, 0xb8, 0x2e, 0x11, 0x30, 0x81, 0x08, 0x01, 0x2e, + 0x6a, 0xf7, 0x71, 0x3f, 0x23, 0xbd, 0x01, 0x08, 0x02, 0x0a, 0xc0, 0x2e, + 0x21, 0x2e, 0x6a, 0xf7, 0x80, 0x2e, 0x00, 0xc1, 0x30, 0x50, 0x98, 0x2e, + 0xd7, 0x0e, 0x50, 0x32, 0x98, 0x2e, 0x40, 0x03, 0x00, 0x30, 0xf0, 0x7f, + 0x21, 0x2e, 0x69, 0xf5, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0x00, 0x2e, + 0x01, 0x80, 0x08, 0xa2, 0xfb, 0x2f, 0x03, 0x2e, 0x8f, 0x00, 0x01, 0x2e, + 0x93, 0x00, 0x9f, 0xbc, 0x9f, 0xb8, 0x0f, 0xb8, 0x08, 0x0a, 0x21, 0x2e, + 0x79, 0x00, 0x98, 0x2e, 0xab, 0xb6, 0x03, 0x2e, 0xa4, 0x01, 0x21, 0x2e, + 0x7a, 0x00, 0x40, 0xb2, 0x10, 0x2f, 0x01, 0x2e, 0x79, 0x00, 0x00, 0xb2, + 0x0c, 0x2f, 0x05, 0x2e, 0x8a, 0x00, 0x01, 0x52, 0x98, 0x2e, 0xc7, 0xc1, + 0xf0, 0x7f, 0x98, 0x2e, 0x46, 0x02, 0x98, 0x2e, 0x34, 0xb2, 0x10, 0x30, + 0x21, 0x2e, 0x7b, 0x00, 0x01, 0x2e, 0xa0, 0x01, 0x00, 0xb2, 0x07, 0x2f, + 0x01, 0x2e, 0x7a, 0x00, 0x00, 0xb2, 0x03, 0x2f, 0x03, 0x50, 0x05, 0x52, + 0x98, 0x2e, 0x07, 0xcc, 0x01, 0x2e, 0x8e, 0x01, 0x00, 0xb2, 0x2c, 0x2f, + 0x05, 0x2e, 0x8a, 0x00, 0x07, 0x52, 0x98, 0x2e, 0xc7, 0xc1, 0x03, 0x2e, + 0x9a, 0x01, 0x40, 0xb2, 0xf0, 0x7f, 0x08, 0x2f, 0x01, 0x2e, 0x7a, 0x00, + 0x00, 0xb2, 0x04, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0x9a, 0x01, 0x98, 0x2e, + 0x48, 0xb6, 0x01, 0x2e, 0x78, 0x00, 0x00, 0xb2, 0x15, 0x2f, 0x98, 0x2e, + 0x81, 0xb5, 0x98, 0x2e, 0xb6, 0x03, 0x07, 0x50, 0x98, 0x2e, 0x4d, 0xc3, + 0x07, 0x50, 0x98, 0x2e, 0x5a, 0xc7, 0x98, 0x2e, 0xb8, 0x02, 0x07, 0x52, + 0x98, 0x2e, 0xff, 0xc5, 0x21, 0x2e, 0x72, 0x01, 0x98, 0x2e, 0xe6, 0xb2, + 0x10, 0x30, 0x21, 0x2e, 0x7b, 0x00, 0x01, 0x2e, 0x9c, 0x01, 0x00, 0xb2, + 0x04, 0x2f, 0x98, 0x2e, 0x29, 0x02, 0x00, 0x30, 0x21, 0x2e, 0x9c, 0x01, + 0x01, 0x2e, 0xff, 0x00, 0x04, 0xae, 0x0b, 0x2f, 0x01, 0x2e, 0x8e, 0x01, + 0x00, 0xb2, 0x07, 0x2f, 0x07, 0x52, 0x98, 0x2e, 0x8e, 0x0e, 0x00, 0xb2, + 0x02, 0x2f, 0x10, 0x30, 0x21, 0x2e, 0xd7, 0x00, 0x01, 0x2e, 0xd7, 0x00, + 0x00, 0x90, 0x90, 0x2e, 0x2b, 0xb1, 0x01, 0x2e, 0x6c, 0x01, 0x00, 0xb2, + 0x04, 0x2f, 0x98, 0x2e, 0x2f, 0x0e, 0x00, 0x30, 0x21, 0x2e, 0xdc, 0x00, + 0x01, 0x2e, 0xdc, 0x00, 0x00, 0xb2, 0x12, 0x2f, 0x01, 0x2e, 0xff, 0x00, + 0x00, 0x90, 0x02, 0x2f, 0x98, 0x2e, 0x1f, 0x0e, 0x09, 0x2d, 0x98, 0x2e, + 0x81, 0x0d, 0x01, 0x2e, 0xff, 0x00, 0x04, 0x90, 0x02, 0x2f, 0x50, 0x32, + 0x98, 0x2e, 0x40, 0x03, 0x00, 0x30, 0x21, 0x2e, 0xdc, 0x00, 0x01, 0x2e, + 0xd6, 0x00, 0x00, 0xb2, 0x90, 0x2e, 0x43, 0xb1, 0x01, 0x2e, 0xd6, 0x00, + 0x01, 0x31, 0x01, 0x08, 0x00, 0xb2, 0x04, 0x2f, 0x98, 0x2e, 0x47, 0xcb, + 0x10, 0x30, 0x21, 0x2e, 0x7b, 0x00, 0x81, 0x30, 0x01, 0x2e, 0xd6, 0x00, + 0x01, 0x08, 0x00, 0xb2, 0x61, 0x2f, 0x03, 0x2e, 0x89, 0x00, 0x01, 0x2e, + 0xff, 0x00, 0x98, 0xbc, 0x98, 0xb8, 0x05, 0xb2, 0x11, 0x58, 0x23, 0x2f, + 0x07, 0x90, 0x0b, 0x54, 0x00, 0x30, 0x37, 0x2f, 0x15, 0x41, 0x04, 0x41, + 0xdc, 0xbe, 0x44, 0xbe, 0xdc, 0xba, 0x2c, 0x01, 0x61, 0x00, 0x11, 0x56, + 0x4a, 0x0f, 0x0c, 0x2f, 0xd1, 0x42, 0x94, 0xb8, 0xc1, 0x42, 0x11, 0x30, + 0x05, 0x2e, 0x6a, 0xf7, 0x2c, 0xbd, 0x2f, 0xb9, 0x80, 0xb2, 0x08, 0x22, + 0x98, 0x2e, 0xf3, 0x03, 0x21, 0x2d, 0x61, 0x30, 0x23, 0x2e, 0xff, 0x00, + 0x98, 0x2e, 0xf3, 0x03, 0x00, 0x30, 0x21, 0x2e, 0x5a, 0xf5, 0x18, 0x2d, + 0xe1, 0x7f, 0x50, 0x30, 0x98, 0x2e, 0x40, 0x03, 0x11, 0x52, 0x09, 0x50, + 0x50, 0x42, 0x70, 0x30, 0x0f, 0x54, 0x42, 0x42, 0x7e, 0x82, 0xe2, 0x6f, + 0x80, 0xb2, 0x42, 0x42, 0x05, 0x2f, 0x21, 0x2e, 0xff, 0x00, 0x10, 0x30, + 0x98, 0x2e, 0xf3, 0x03, 0x03, 0x2d, 0x60, 0x30, 0x21, 0x2e, 0xff, 0x00, + 0x01, 0x2e, 0xff, 0x00, 0x06, 0x90, 0x18, 0x2f, 0x01, 0x2e, 0x77, 0x00, + 0x0d, 0x54, 0x09, 0x52, 0xe0, 0x7f, 0x98, 0x2e, 0x7a, 0xc1, 0xe1, 0x6f, + 0x08, 0x1a, 0x40, 0x30, 0x08, 0x2f, 0x21, 0x2e, 0xff, 0x00, 0x20, 0x30, + 0x98, 0x2e, 0xe4, 0xb6, 0x50, 0x32, 0x98, 0x2e, 0x40, 0x03, 0x05, 0x2d, + 0x98, 0x2e, 0x38, 0x0e, 0x00, 0x30, 0x21, 0x2e, 0xff, 0x00, 0x00, 0x30, + 0x21, 0x2e, 0xd6, 0x00, 0x18, 0x2d, 0x01, 0x2e, 0xff, 0x00, 0x03, 0xaa, + 0x01, 0x2f, 0x98, 0x2e, 0x45, 0x0e, 0x01, 0x2e, 0xff, 0x00, 0x3f, 0x80, + 0x03, 0xa2, 0x01, 0x2f, 0x00, 0x2e, 0x02, 0x2d, 0x98, 0x2e, 0x5b, 0x0e, + 0x30, 0x30, 0x98, 0x2e, 0xf8, 0xb6, 0x00, 0x30, 0x21, 0x2e, 0xd7, 0x00, + 0x50, 0x32, 0x98, 0x2e, 0x40, 0x03, 0x01, 0x2e, 0x7b, 0x00, 0x00, 0xb2, + 0x24, 0x2f, 0x98, 0x2e, 0xf5, 0xcb, 0x03, 0x2e, 0x6a, 0x01, 0x13, 0x54, + 0x01, 0x0a, 0xbb, 0x84, 0x83, 0x86, 0x21, 0x2e, 0x74, 0x01, 0xe0, 0x40, + 0x15, 0x52, 0xc4, 0x40, 0x82, 0x40, 0xa8, 0xb9, 0x52, 0x42, 0x43, 0xbe, + 0x53, 0x42, 0x04, 0x0a, 0x50, 0x42, 0xe1, 0x7f, 0xf0, 0x31, 0x41, 0x40, + 0xf2, 0x6f, 0x25, 0xbd, 0x08, 0x08, 0x02, 0x0a, 0xd0, 0x7f, 0x98, 0x2e, + 0xa8, 0xcf, 0x06, 0xbc, 0xd1, 0x6f, 0xe2, 0x6f, 0x08, 0x0a, 0x80, 0x42, + 0x98, 0x2e, 0x07, 0x02, 0x00, 0x30, 0x21, 0x2e, 0xa4, 0x01, 0x21, 0x2e, + 0xa0, 0x01, 0x21, 0x2e, 0x7b, 0x00, 0x21, 0x2e, 0x8e, 0x01, 0x80, 0x2e, + 0x08, 0xb0, 0x70, 0x50, 0x0b, 0x2e, 0xa3, 0x01, 0x21, 0x50, 0x03, 0x2e, + 0x78, 0x01, 0x08, 0x18, 0x3b, 0x54, 0x31, 0x50, 0x94, 0x40, 0x30, 0x00, + 0x33, 0x52, 0xf0, 0x7f, 0x01, 0x00, 0x4c, 0x16, 0x2c, 0x05, 0xe2, 0x7f, + 0xcd, 0x16, 0x59, 0x07, 0x97, 0x40, 0xd2, 0x7f, 0x67, 0x04, 0x82, 0x40, + 0x35, 0x56, 0xab, 0x7f, 0xc4, 0x7f, 0x90, 0x7f, 0xb5, 0x7f, 0xaa, 0x06, + 0x98, 0x2e, 0x0c, 0xc1, 0x50, 0x25, 0xd0, 0x6f, 0xbb, 0x6f, 0x0b, 0x42, + 0x3e, 0x80, 0xcb, 0x6f, 0x3b, 0x84, 0xe1, 0x6f, 0x83, 0x40, 0x4b, 0x42, + 0xc1, 0x86, 0x03, 0x2e, 0xa3, 0x01, 0x83, 0x42, 0x82, 0x84, 0x01, 0x42, + 0xbc, 0x8e, 0x80, 0x40, 0x00, 0xb2, 0x04, 0x2f, 0x03, 0x2e, 0x7d, 0x01, + 0x41, 0x82, 0x23, 0x2e, 0x7d, 0x01, 0x67, 0x25, 0xe2, 0x41, 0x2a, 0x0f, + 0x92, 0x6f, 0xc1, 0x41, 0xe7, 0x7f, 0x37, 0x2f, 0x07, 0x2e, 0x7b, 0x01, + 0x2b, 0x0e, 0x29, 0x2f, 0x3d, 0x52, 0x02, 0x35, 0x41, 0x40, 0x8a, 0x0e, + 0x03, 0x30, 0x03, 0x2f, 0x05, 0x2e, 0x7f, 0x01, 0x80, 0xb2, 0x1b, 0x2f, + 0xc2, 0x35, 0x8a, 0x0e, 0x2f, 0x2f, 0x2f, 0x54, 0x01, 0x30, 0x83, 0x40, + 0xff, 0x86, 0xc3, 0xa2, 0x02, 0x2f, 0x00, 0x2e, 0x0c, 0x2c, 0x03, 0x30, + 0x00, 0x90, 0x01, 0x2f, 0x23, 0x2e, 0x7d, 0x01, 0x3d, 0x56, 0xc2, 0x86, + 0x01, 0x80, 0xc0, 0x42, 0x23, 0x2e, 0x7c, 0x01, 0x13, 0x30, 0xbb, 0x80, + 0x23, 0x2e, 0x84, 0x01, 0x18, 0x2c, 0x01, 0x42, 0x00, 0x35, 0x21, 0x2e, + 0x7c, 0x01, 0x13, 0x2d, 0x0a, 0x04, 0x28, 0x1e, 0x21, 0x2e, 0x7b, 0x01, + 0x10, 0x30, 0x21, 0x30, 0x98, 0x2e, 0x8b, 0x02, 0x0a, 0x2c, 0x03, 0x30, + 0x0a, 0x00, 0x28, 0x1c, 0x21, 0x2e, 0x7a, 0x01, 0x20, 0x30, 0x11, 0x30, + 0x98, 0x2e, 0x8b, 0x02, 0x03, 0x30, 0xc3, 0x7f, 0xd6, 0x7f, 0x25, 0x25, + 0x37, 0x52, 0xe0, 0x6f, 0x98, 0x2e, 0x0c, 0xb7, 0xe1, 0x6f, 0xd0, 0x6f, + 0x42, 0x40, 0x39, 0x52, 0x98, 0x2e, 0x0c, 0xb7, 0xe1, 0x6f, 0xd0, 0x6f, + 0x42, 0x40, 0x01, 0x40, 0xf3, 0x6f, 0xd3, 0x00, 0xcb, 0x1e, 0x39, 0x52, + 0x13, 0x42, 0xe0, 0x7f, 0x98, 0x2e, 0x0c, 0xb7, 0xe0, 0x6f, 0x3e, 0x84, + 0xf1, 0x6f, 0x82, 0x40, 0x03, 0x40, 0x51, 0x04, 0x59, 0x1c, 0x01, 0x42, + 0x03, 0x82, 0x00, 0x30, 0x42, 0x40, 0x80, 0xb2, 0x12, 0x2f, 0xc1, 0x6f, + 0x40, 0x90, 0x10, 0x22, 0x82, 0xac, 0x01, 0x30, 0x04, 0x2f, 0x05, 0x2e, + 0x7d, 0x01, 0x03, 0x35, 0x13, 0x0e, 0x07, 0x2f, 0x3f, 0x54, 0x86, 0x88, + 0x39, 0x87, 0x01, 0x43, 0xc1, 0x42, 0xc2, 0x86, 0x81, 0x42, 0xc1, 0x42, + 0x00, 0x2e, 0xab, 0x6f, 0x90, 0x5f, 0xb8, 0x2e, 0x01, 0x2e, 0x85, 0x01, + 0x01, 0x80, 0xf0, 0x50, 0x02, 0x40, 0x04, 0x40, 0x1a, 0x25, 0xa3, 0xbe, + 0x03, 0x40, 0x71, 0x82, 0x12, 0x40, 0xdf, 0xba, 0x42, 0xbe, 0x45, 0x42, + 0x4f, 0xba, 0xb1, 0xbd, 0x00, 0x40, 0xf1, 0x7f, 0xbf, 0xb8, 0x2f, 0xb9, + 0x0c, 0xb8, 0x24, 0x7f, 0xd0, 0x7f, 0x31, 0x7f, 0x80, 0xb2, 0xeb, 0x7f, + 0x08, 0x2f, 0x10, 0x6f, 0x00, 0x90, 0x0b, 0x2f, 0x20, 0x6f, 0x00, 0x90, + 0x08, 0x2f, 0x30, 0x6f, 0x00, 0x90, 0x05, 0x2f, 0x01, 0x30, 0x23, 0x2e, + 0x7d, 0x00, 0xd0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0x05, 0x2e, 0x7d, 0x00, + 0x80, 0x90, 0x00, 0x30, 0x41, 0x52, 0x45, 0x56, 0x07, 0x2f, 0x41, 0x58, + 0x00, 0x2e, 0x10, 0x43, 0x63, 0x0e, 0xfc, 0x2f, 0x81, 0x84, 0x25, 0x2e, + 0x7d, 0x00, 0x09, 0x2e, 0x85, 0x01, 0x01, 0x85, 0xb0, 0x7f, 0xc0, 0x7f, + 0x00, 0x2e, 0x82, 0x40, 0x03, 0x41, 0x02, 0x89, 0x24, 0xbd, 0x05, 0x41, + 0xb1, 0xbd, 0xb1, 0xb9, 0x24, 0xba, 0x54, 0xbd, 0xa3, 0x7f, 0x5c, 0x05, + 0x24, 0xb9, 0x01, 0x56, 0x43, 0x58, 0x82, 0x7f, 0x95, 0x7f, 0x73, 0x7f, + 0x64, 0x7f, 0x00, 0x2e, 0xf2, 0x6f, 0x40, 0x7f, 0x51, 0x7f, 0x00, 0x2e, + 0x90, 0x40, 0xf2, 0x7f, 0x00, 0x90, 0x04, 0x2f, 0x51, 0x6f, 0x00, 0x30, + 0x40, 0x42, 0x44, 0x2c, 0x62, 0x6f, 0xc1, 0x40, 0x98, 0x2e, 0x74, 0xc0, + 0x51, 0x6f, 0x00, 0x2e, 0x44, 0x40, 0x00, 0xb3, 0x2c, 0x2f, 0x62, 0x6f, + 0x95, 0x6f, 0x83, 0x40, 0xc5, 0x0e, 0x07, 0x2f, 0x75, 0x6f, 0x10, 0x30, + 0x45, 0x41, 0x40, 0xa1, 0x05, 0x30, 0x05, 0x22, 0x18, 0x1a, 0x02, 0x2f, + 0x00, 0x30, 0x40, 0x42, 0x2b, 0x2d, 0x10, 0x30, 0x20, 0x28, 0x84, 0x6f, + 0x40, 0x42, 0xc4, 0x0e, 0x24, 0x2f, 0xc0, 0x6f, 0x00, 0x90, 0x21, 0x2f, + 0x45, 0x6f, 0x10, 0x30, 0x05, 0x15, 0xb3, 0xbd, 0xc4, 0x7f, 0xdc, 0x0a, + 0x41, 0x58, 0x65, 0x01, 0x45, 0x5c, 0x0b, 0x30, 0x25, 0x1a, 0x00, 0x2f, + 0x0b, 0x43, 0x01, 0x89, 0x27, 0x2e, 0x73, 0x01, 0x66, 0x0e, 0xf7, 0x2f, + 0xb0, 0x7f, 0x0e, 0x2d, 0xa2, 0x6f, 0xc2, 0x0e, 0x08, 0x2f, 0x10, 0x30, + 0x40, 0x42, 0x02, 0x30, 0x74, 0x6f, 0x63, 0x6f, 0x04, 0x41, 0x00, 0xa1, + 0x02, 0x22, 0xc0, 0x42, 0x00, 0x2e, 0x62, 0x6f, 0x73, 0x6f, 0x40, 0x6f, + 0x01, 0x80, 0xc1, 0x86, 0x81, 0x84, 0x41, 0x82, 0x03, 0xa2, 0x62, 0x7f, + 0x73, 0x7f, 0xa5, 0x2f, 0xeb, 0x6f, 0xd0, 0x6f, 0xb1, 0x6f, 0x10, 0x5f, + 0x80, 0x2e, 0x95, 0xcf, 0x01, 0x2e, 0x87, 0x01, 0x02, 0x40, 0x01, 0x40, + 0x90, 0x50, 0x2f, 0xbd, 0x93, 0xbc, 0x2f, 0xb9, 0x9c, 0xb8, 0xfb, 0x7f, + 0xe1, 0x7f, 0x80, 0xb2, 0x0b, 0x30, 0x65, 0x2f, 0x05, 0x2e, 0xce, 0x00, + 0x47, 0x52, 0x80, 0x90, 0x0b, 0x2f, 0x5b, 0x42, 0x5b, 0x42, 0x81, 0x84, + 0x25, 0x2e, 0xce, 0x00, 0x37, 0x2e, 0xcf, 0x00, 0x37, 0x2e, 0xd0, 0x00, + 0x37, 0x2e, 0xd1, 0x00, 0x4b, 0x42, 0x00, 0x2e, 0x03, 0x40, 0x12, 0x40, + 0x01, 0x40, 0x00, 0x40, 0x0a, 0xbe, 0x27, 0xbd, 0x2e, 0xb8, 0x92, 0xbc, + 0x18, 0xb9, 0xb9, 0xbd, 0xba, 0xb9, 0x4a, 0xba, 0x07, 0x5a, 0x1a, 0x25, + 0x17, 0x2e, 0xcf, 0x00, 0x77, 0x82, 0x83, 0x7f, 0xab, 0x7f, 0x75, 0x7f, + 0x94, 0x7f, 0xd2, 0x7f, 0xc0, 0x7f, 0x98, 0x2e, 0xd1, 0xc3, 0x03, 0x2e, + 0xcf, 0x00, 0x08, 0x1a, 0xb0, 0x7f, 0x01, 0x30, 0x01, 0x2f, 0x23, 0x2e, + 0xd1, 0x00, 0x01, 0x2e, 0xd1, 0x00, 0xd1, 0x6f, 0x41, 0x0e, 0x14, 0x2f, + 0xc1, 0x6f, 0x40, 0xb2, 0x0b, 0x2f, 0x43, 0xb2, 0x09, 0x2f, 0x07, 0x54, + 0x47, 0x56, 0x98, 0x2e, 0x0b, 0xc4, 0x00, 0x90, 0x06, 0x2f, 0xb1, 0x6f, + 0x23, 0x2e, 0xd0, 0x00, 0x03, 0x2d, 0xb1, 0x6f, 0x23, 0x2e, 0xd0, 0x00, + 0xd1, 0x6f, 0x23, 0x2e, 0xd1, 0x00, 0x03, 0x2e, 0xd1, 0x00, 0x41, 0x82, + 0x23, 0x2e, 0xd1, 0x00, 0x07, 0x50, 0x47, 0x52, 0x12, 0x40, 0x52, 0x42, + 0x00, 0x2e, 0x12, 0x40, 0x52, 0x42, 0x00, 0x2e, 0x00, 0x40, 0x40, 0x42, + 0x00, 0x2e, 0x03, 0x2e, 0xd0, 0x00, 0xe0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, + 0xb1, 0x6f, 0x23, 0x2e, 0xcf, 0x00, 0x06, 0x2d, 0x37, 0x2e, 0xce, 0x00, + 0xe0, 0x6f, 0x01, 0x30, 0x98, 0x2e, 0x95, 0xcf, 0xfb, 0x6f, 0x70, 0x5f, + 0xb8, 0x2e, 0xd0, 0x50, 0x80, 0x7f, 0x91, 0x7f, 0xd7, 0x7f, 0xc5, 0x7f, + 0xb3, 0x7f, 0xa2, 0x7f, 0xe4, 0x7f, 0xf6, 0x7f, 0x7b, 0x7f, 0x00, 0x2e, + 0x4b, 0x50, 0x00, 0x2e, 0x01, 0x40, 0x9f, 0xbc, 0x9f, 0xb8, 0x40, 0x90, + 0x02, 0x2f, 0x31, 0x30, 0x23, 0x2e, 0x69, 0xf5, 0x38, 0x82, 0x61, 0x7f, + 0x20, 0x30, 0x41, 0x40, 0x23, 0x2e, 0xd6, 0x00, 0x03, 0x2e, 0xd6, 0x00, + 0x08, 0x08, 0x00, 0xb2, 0x0b, 0x2f, 0x49, 0x50, 0x1a, 0x25, 0x12, 0x40, + 0x32, 0x7f, 0x73, 0x82, 0x12, 0x40, 0x42, 0x7f, 0x00, 0x2e, 0x00, 0x40, + 0x50, 0x7f, 0x98, 0x2e, 0x6a, 0xd6, 0x01, 0x2e, 0xd6, 0x00, 0x81, 0x30, + 0x01, 0x08, 0x00, 0xb2, 0x42, 0x2f, 0x03, 0x2e, 0x89, 0x00, 0x01, 0x2e, + 0x89, 0x00, 0x97, 0xbc, 0x06, 0xbc, 0x9f, 0xb8, 0x0f, 0xb8, 0x00, 0x90, + 0x23, 0x2e, 0x6d, 0x01, 0x10, 0x30, 0x01, 0x30, 0x2a, 0x2f, 0x03, 0x2e, + 0xff, 0x00, 0x44, 0xb2, 0x05, 0x2f, 0x47, 0xb2, 0x00, 0x30, 0x2d, 0x2f, + 0x21, 0x2e, 0xd6, 0x00, 0x2b, 0x2d, 0x03, 0x2e, 0xfd, 0xf5, 0x9e, 0xbc, + 0x9f, 0xb8, 0x40, 0x90, 0x14, 0x2f, 0x03, 0x2e, 0xfc, 0xf5, 0x99, 0xbc, + 0x9f, 0xb8, 0x40, 0x90, 0x0e, 0x2f, 0x03, 0x2e, 0x49, 0xf1, 0x4d, 0x54, + 0x4a, 0x08, 0x40, 0x90, 0x08, 0x2f, 0x98, 0x2e, 0xe4, 0x01, 0x00, 0xb2, + 0x10, 0x30, 0x03, 0x2f, 0x50, 0x30, 0x21, 0x2e, 0xff, 0x00, 0x10, 0x2d, + 0x98, 0x2e, 0xe4, 0xb6, 0x00, 0x30, 0x21, 0x2e, 0xd6, 0x00, 0x0a, 0x2d, + 0x05, 0x2e, 0x69, 0xf7, 0x2d, 0xbd, 0x2f, 0xb9, 0x80, 0xb2, 0x01, 0x2f, + 0x21, 0x2e, 0xd7, 0x00, 0x23, 0x2e, 0xd6, 0x00, 0x60, 0x6f, 0xe1, 0x31, + 0x01, 0x42, 0x00, 0x2e, 0xf6, 0x6f, 0xe4, 0x6f, 0x80, 0x6f, 0x91, 0x6f, + 0xa2, 0x6f, 0xb3, 0x6f, 0xc5, 0x6f, 0xd7, 0x6f, 0x7b, 0x6f, 0x30, 0x5f, + 0xc8, 0x2e, 0xa0, 0x50, 0x82, 0x7f, 0x90, 0x7f, 0xd7, 0x7f, 0xc5, 0x7f, + 0xb3, 0x7f, 0xa1, 0x7f, 0xe4, 0x7f, 0xf6, 0x7f, 0x7b, 0x7f, 0x00, 0x2e, + 0x4b, 0x54, 0x00, 0x2e, 0x80, 0x40, 0x0f, 0xbc, 0x0f, 0xb8, 0x00, 0x90, + 0x02, 0x2f, 0x30, 0x30, 0x21, 0x2e, 0x69, 0xf5, 0xb7, 0x84, 0x62, 0x7f, + 0x98, 0x2e, 0xe4, 0x01, 0x00, 0xb2, 0x90, 0x2e, 0x95, 0xb4, 0x03, 0x2e, + 0x8c, 0x00, 0x07, 0x2e, 0x8e, 0x00, 0x3f, 0xba, 0x05, 0x2e, 0xa0, 0x00, + 0xa3, 0xbd, 0x9f, 0xb8, 0x01, 0x2e, 0xa0, 0x00, 0x4c, 0x0a, 0xbf, 0xb9, + 0x04, 0xbe, 0x4b, 0x0a, 0x05, 0x2e, 0xa0, 0x00, 0xcf, 0xb9, 0x01, 0x2e, + 0x96, 0x00, 0x22, 0xbe, 0xcb, 0x0a, 0x4f, 0xba, 0x03, 0xbc, 0x05, 0x2e, + 0x98, 0x00, 0xdc, 0x0a, 0x0f, 0xb8, 0x03, 0x2e, 0x90, 0x00, 0x2f, 0xbe, + 0x18, 0x0a, 0xcf, 0xb9, 0x9f, 0xbc, 0x05, 0x2e, 0x9f, 0x00, 0x9f, 0xb8, + 0x03, 0x0a, 0x2f, 0xbd, 0x01, 0x0a, 0x2f, 0xb9, 0x82, 0x0a, 0x25, 0x2e, + 0x78, 0x00, 0x05, 0x2e, 0xc1, 0xf5, 0x2e, 0xbd, 0x2e, 0xb9, 0x01, 0x2e, + 0x7a, 0x00, 0x31, 0x30, 0x8a, 0x04, 0x00, 0x90, 0x07, 0x2f, 0x01, 0x2e, + 0xff, 0x00, 0x04, 0xa2, 0x03, 0x2f, 0x01, 0x2e, 0x78, 0x00, 0x00, 0xb2, + 0x0c, 0x2f, 0x51, 0x50, 0x07, 0x52, 0x98, 0x2e, 0xfc, 0x01, 0x05, 0x2e, + 0xd8, 0x00, 0x80, 0x90, 0x10, 0x30, 0x01, 0x2f, 0x21, 0x2e, 0xd8, 0x00, + 0x25, 0x2e, 0x8e, 0x01, 0x98, 0x2e, 0xed, 0x01, 0x00, 0xb2, 0x22, 0x30, + 0x21, 0x30, 0x03, 0x2f, 0x01, 0x2e, 0x7a, 0x00, 0x00, 0x90, 0x05, 0x2f, + 0x01, 0x2e, 0x79, 0x00, 0x01, 0xb2, 0x30, 0x30, 0x01, 0x30, 0x41, 0x22, + 0x01, 0x2e, 0x9b, 0x01, 0x08, 0x1a, 0x0e, 0x2f, 0x23, 0x2e, 0x9b, 0x01, + 0x33, 0x30, 0x53, 0x50, 0x0b, 0x09, 0x01, 0x40, 0x4f, 0x56, 0x46, 0xbe, + 0x4b, 0x08, 0x4c, 0x0a, 0x01, 0x42, 0x0a, 0x80, 0x1f, 0x52, 0x01, 0x42, + 0x00, 0x2e, 0x01, 0x2e, 0x78, 0x00, 0x00, 0xb2, 0x1f, 0x2f, 0x03, 0x2e, + 0xc0, 0xf5, 0xf0, 0x30, 0x48, 0x08, 0x47, 0xaa, 0x74, 0x30, 0x07, 0x2e, + 0xdb, 0x00, 0x61, 0x22, 0x4b, 0x1a, 0x05, 0x2f, 0x07, 0x2e, 0x66, 0xf5, + 0xbf, 0xbd, 0xbf, 0xb9, 0xc0, 0x90, 0x0b, 0x2f, 0x55, 0x56, 0x04, 0x30, + 0xd4, 0x42, 0xd2, 0x42, 0x81, 0x04, 0x24, 0xbd, 0xfe, 0x80, 0x81, 0x84, + 0xc4, 0x42, 0x23, 0x2e, 0xdb, 0x00, 0x02, 0x42, 0x02, 0x32, 0x25, 0x2e, + 0x62, 0xf5, 0x05, 0x2e, 0x6b, 0x01, 0x81, 0x80, 0x21, 0x2e, 0x6b, 0x01, + 0x62, 0x6f, 0x00, 0x31, 0x80, 0x42, 0x00, 0x2e, 0x05, 0x2e, 0x8a, 0x00, + 0x0d, 0x50, 0x90, 0x08, 0x80, 0xb2, 0x0b, 0x2f, 0x05, 0x2e, 0xca, 0xf5, + 0xf0, 0x3e, 0x90, 0x08, 0x25, 0x2e, 0xca, 0xf5, 0x05, 0x2e, 0x59, 0xf5, + 0xe0, 0x3f, 0x90, 0x08, 0x25, 0x2e, 0x59, 0xf5, 0xf6, 0x6f, 0xe4, 0x6f, + 0x90, 0x6f, 0xa1, 0x6f, 0xb3, 0x6f, 0xc5, 0x6f, 0xd7, 0x6f, 0x7b, 0x6f, + 0x82, 0x6f, 0x60, 0x5f, 0xc8, 0x2e, 0xc0, 0x50, 0x80, 0x7f, 0x92, 0x7f, + 0xd5, 0x7f, 0xc4, 0x7f, 0xb3, 0x7f, 0xa1, 0x7f, 0xe7, 0x7f, 0xf6, 0x7f, + 0x7b, 0x7f, 0x00, 0x2e, 0x4b, 0x50, 0x00, 0x2e, 0x02, 0x40, 0x2f, 0xbd, + 0x2f, 0xb9, 0x80, 0x90, 0x02, 0x2f, 0x32, 0x30, 0x25, 0x2e, 0x69, 0xf5, + 0x37, 0x80, 0x00, 0x2e, 0x00, 0x40, 0x60, 0x7f, 0x98, 0x2e, 0xe4, 0x01, + 0x63, 0x6f, 0x02, 0x30, 0x62, 0x7f, 0x50, 0x7f, 0x02, 0x32, 0x1f, 0x52, + 0x80, 0x2e, 0x6e, 0xb5, 0x1a, 0x09, 0x00, 0xb3, 0x14, 0x2f, 0x00, 0xb2, + 0x03, 0x2f, 0x09, 0x2e, 0x78, 0x00, 0x00, 0x91, 0x0c, 0x2f, 0x43, 0x7f, + 0x98, 0x2e, 0x32, 0x03, 0x57, 0x50, 0x02, 0x8a, 0x02, 0x32, 0x04, 0x30, + 0x25, 0x2e, 0x64, 0xf5, 0x1f, 0x52, 0x50, 0x6f, 0x43, 0x6f, 0x44, 0x43, + 0x25, 0x2e, 0x60, 0xf5, 0xd9, 0x08, 0xc0, 0xb2, 0x6d, 0x2f, 0x98, 0x2e, + 0xed, 0x01, 0x00, 0xb2, 0x06, 0x2f, 0x01, 0x2e, 0x7a, 0x00, 0x00, 0xb2, + 0x02, 0x2f, 0x50, 0x6f, 0x00, 0x90, 0x0a, 0x2f, 0x01, 0x2e, 0xda, 0x00, + 0x00, 0x90, 0x19, 0x2f, 0x10, 0x30, 0x21, 0x2e, 0xda, 0x00, 0x00, 0x30, + 0x98, 0x2e, 0xcd, 0xb6, 0x13, 0x2d, 0x01, 0x2e, 0xc3, 0xf5, 0x0c, 0xbc, + 0x0f, 0xb8, 0x12, 0x30, 0x10, 0x04, 0x03, 0xb0, 0x26, 0x25, 0x59, 0x50, + 0x05, 0x52, 0x98, 0x2e, 0xfc, 0x01, 0x10, 0x30, 0x21, 0x2e, 0xa0, 0x01, + 0x02, 0x30, 0x60, 0x7f, 0x25, 0x2e, 0xda, 0x00, 0x50, 0x6f, 0x00, 0xb2, + 0x03, 0x2f, 0x05, 0x2e, 0x79, 0x00, 0x80, 0x90, 0x0e, 0x2f, 0x05, 0x2e, + 0xd9, 0x00, 0x80, 0x90, 0x2c, 0x2f, 0x11, 0x30, 0x02, 0x30, 0x23, 0x2e, + 0xd9, 0x00, 0x23, 0x2e, 0x7c, 0x00, 0x25, 0x2e, 0x7d, 0x00, 0x25, 0x2e, + 0xa5, 0x01, 0x22, 0x2d, 0x05, 0x2e, 0xa5, 0x01, 0x81, 0x82, 0x23, 0x2e, + 0xa5, 0x01, 0x12, 0x30, 0x4a, 0x08, 0x40, 0xb2, 0x05, 0x2f, 0x03, 0x2e, + 0x58, 0xf5, 0x98, 0xbc, 0x9e, 0xb8, 0x43, 0x90, 0x10, 0x2f, 0x01, 0x2e, + 0xc1, 0xf5, 0x0e, 0xbc, 0x0e, 0xb8, 0x32, 0x30, 0x90, 0x04, 0x5b, 0x50, + 0x01, 0x52, 0x98, 0x2e, 0xfc, 0x01, 0x12, 0x30, 0x25, 0x2e, 0xa4, 0x01, + 0x00, 0x30, 0x21, 0x2e, 0xd9, 0x00, 0x50, 0x6f, 0x62, 0x7f, 0x00, 0x2e, + 0x62, 0x6f, 0x80, 0x90, 0x05, 0x2f, 0x02, 0x30, 0x25, 0x2e, 0x9b, 0x01, + 0x1f, 0x54, 0x25, 0x2e, 0x64, 0xf5, 0x1f, 0x52, 0x23, 0x2e, 0x60, 0xf5, + 0x02, 0x32, 0x00, 0x90, 0x02, 0x2f, 0x03, 0x30, 0x27, 0x2e, 0xd8, 0x00, + 0x07, 0x2e, 0x60, 0xf5, 0x1a, 0x09, 0x00, 0x91, 0x90, 0x2e, 0xde, 0xb4, + 0x19, 0x09, 0x00, 0x91, 0x90, 0x2e, 0xde, 0xb4, 0x80, 0x6f, 0x92, 0x6f, + 0xa1, 0x6f, 0xb3, 0x6f, 0xc4, 0x6f, 0xd5, 0x6f, 0x7b, 0x6f, 0xf6, 0x6f, + 0xe7, 0x6f, 0x40, 0x5f, 0xc8, 0x2e, 0x5d, 0x52, 0x00, 0x51, 0x52, 0x40, + 0x47, 0x40, 0xf8, 0xbc, 0x9c, 0xb9, 0x1a, 0x25, 0x01, 0x2e, 0x9f, 0x00, + 0xf3, 0x7f, 0x8f, 0xbe, 0x72, 0x88, 0xeb, 0x7f, 0x5f, 0xbb, 0x0b, 0x30, + 0x78, 0xb8, 0x6b, 0x56, 0xd3, 0x08, 0x70, 0x8a, 0xfc, 0xbf, 0x0b, 0x43, + 0xc4, 0x7f, 0xfc, 0xbb, 0x1e, 0x0b, 0x21, 0x2e, 0xe1, 0x00, 0x1b, 0x7f, + 0x4b, 0x43, 0x00, 0xb3, 0xd6, 0x7f, 0xa7, 0x7f, 0xb5, 0x7f, 0x93, 0x7f, + 0x90, 0x2e, 0x39, 0xb6, 0x01, 0x2e, 0xfb, 0x00, 0x00, 0xb2, 0x0b, 0x2f, + 0x5f, 0x52, 0x01, 0x2e, 0xf6, 0x00, 0x82, 0x7f, 0x98, 0x2e, 0xbb, 0xcc, + 0x0b, 0x30, 0x37, 0x2e, 0xfb, 0x00, 0x82, 0x6f, 0x93, 0x6f, 0x1a, 0x25, + 0xc0, 0xb2, 0x8b, 0x7f, 0x14, 0x2f, 0x26, 0xbc, 0x25, 0xbd, 0x06, 0xb8, + 0x2f, 0xb9, 0x80, 0xb2, 0x14, 0xb0, 0x0c, 0x2f, 0x61, 0x50, 0x63, 0x54, + 0x0b, 0x30, 0x0b, 0x2e, 0xa0, 0x00, 0x69, 0x58, 0x1b, 0x42, 0x9b, 0x42, + 0x6c, 0x09, 0x2b, 0x2e, 0xa0, 0x00, 0x0b, 0x42, 0x8b, 0x42, 0x86, 0x7f, + 0x73, 0x84, 0x6d, 0x50, 0xd8, 0x08, 0x67, 0x52, 0x07, 0x50, 0x72, 0x7f, + 0x63, 0x7f, 0x98, 0x2e, 0xc2, 0xc0, 0xd1, 0x6f, 0x62, 0x6f, 0xd1, 0x0a, + 0x01, 0x2e, 0xf6, 0x00, 0xc5, 0x6f, 0xb4, 0x6f, 0x72, 0x6f, 0x5f, 0x52, + 0x65, 0x5c, 0x98, 0x2e, 0x06, 0xcd, 0x23, 0x6f, 0x90, 0x6f, 0x61, 0x52, + 0xc0, 0xb2, 0x04, 0xbd, 0x54, 0x40, 0xaf, 0xb9, 0x45, 0x40, 0xd1, 0x7f, + 0x02, 0x30, 0x06, 0x2f, 0xc0, 0xb2, 0x02, 0x30, 0x03, 0x2f, 0x63, 0x5c, + 0x12, 0x30, 0x94, 0x43, 0x85, 0x43, 0x03, 0xbf, 0x6f, 0xbb, 0x80, 0xb3, + 0x20, 0x2f, 0x06, 0x6f, 0x26, 0x01, 0x16, 0x6f, 0x6e, 0x03, 0x45, 0x42, + 0xc0, 0x90, 0x29, 0x2e, 0xf7, 0x00, 0x63, 0x52, 0x14, 0x2f, 0x63, 0x5c, + 0x00, 0x2e, 0x93, 0x41, 0x86, 0x41, 0xe3, 0x04, 0xae, 0x07, 0x80, 0xab, + 0x04, 0x2f, 0x80, 0x91, 0x0a, 0x2f, 0x86, 0x6f, 0x73, 0x0f, 0x07, 0x2f, + 0x83, 0x6f, 0xc0, 0xb2, 0x04, 0x2f, 0x54, 0x42, 0x45, 0x42, 0x12, 0x30, + 0x04, 0x2c, 0x11, 0x30, 0x02, 0x2c, 0x11, 0x30, 0x11, 0x30, 0x02, 0xbc, + 0x0f, 0xb8, 0xc2, 0x7f, 0x00, 0xb2, 0x0a, 0x2f, 0x01, 0x2e, 0xb7, 0x01, + 0x05, 0x2e, 0x71, 0x01, 0x10, 0x1a, 0x02, 0x2f, 0x21, 0x2e, 0x71, 0x01, + 0x03, 0x2d, 0x02, 0x2c, 0x01, 0x30, 0x01, 0x30, 0xf0, 0x6f, 0x98, 0x2e, + 0x95, 0xcf, 0xc1, 0x6f, 0xa0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0xd2, 0x6f, + 0x1b, 0x52, 0x01, 0x2e, 0xf7, 0x00, 0x82, 0x40, 0x50, 0x42, 0x0c, 0x2c, + 0x42, 0x42, 0x11, 0x30, 0x23, 0x2e, 0xfb, 0x00, 0x01, 0x30, 0xf0, 0x6f, + 0x98, 0x2e, 0x95, 0xcf, 0xa0, 0x6f, 0x01, 0x30, 0x98, 0x2e, 0x95, 0xcf, + 0x00, 0x2e, 0xeb, 0x6f, 0x00, 0x5f, 0xb8, 0x2e, 0x60, 0x51, 0x0a, 0x25, + 0x36, 0x88, 0xf4, 0x7f, 0xeb, 0x7f, 0x00, 0x32, 0x7b, 0x52, 0x32, 0x30, + 0x13, 0x30, 0x98, 0x2e, 0x15, 0xcb, 0x0a, 0x25, 0x33, 0x84, 0xd2, 0x7f, + 0x43, 0x30, 0x07, 0x50, 0x35, 0x52, 0x98, 0x2e, 0x95, 0xc1, 0xd2, 0x6f, + 0x73, 0x52, 0x98, 0x2e, 0xd7, 0xc7, 0x2a, 0x25, 0xb0, 0x86, 0xc0, 0x7f, + 0xd3, 0x7f, 0xaf, 0x84, 0x75, 0x50, 0xf1, 0x6f, 0x98, 0x2e, 0x4d, 0xc8, + 0x2a, 0x25, 0xae, 0x8a, 0xaa, 0x88, 0xf2, 0x6e, 0x77, 0x50, 0xc1, 0x6f, + 0xd3, 0x6f, 0xf4, 0x7f, 0x98, 0x2e, 0xb6, 0xc8, 0xe0, 0x6e, 0x00, 0xb2, + 0x32, 0x2f, 0x7d, 0x54, 0x83, 0x86, 0xf1, 0x6f, 0xc3, 0x7f, 0x04, 0x30, + 0x30, 0x30, 0xf4, 0x7f, 0xd0, 0x7f, 0xb2, 0x7f, 0xe3, 0x30, 0xc5, 0x6f, + 0x56, 0x40, 0x45, 0x41, 0x28, 0x08, 0x03, 0x14, 0x0e, 0xb4, 0x08, 0xbc, + 0x82, 0x40, 0x10, 0x0a, 0x79, 0x54, 0x26, 0x05, 0x91, 0x7f, 0x44, 0x28, + 0xa3, 0x7f, 0x98, 0x2e, 0xd9, 0xc0, 0x08, 0xb9, 0x33, 0x30, 0x53, 0x09, + 0xc1, 0x6f, 0xd3, 0x6f, 0xf4, 0x6f, 0x83, 0x17, 0x47, 0x40, 0x6c, 0x15, + 0xb2, 0x6f, 0xbe, 0x09, 0x75, 0x0b, 0x90, 0x42, 0x45, 0x42, 0x51, 0x0e, + 0x32, 0xbc, 0x02, 0x89, 0xa1, 0x6f, 0x7e, 0x86, 0xf4, 0x7f, 0xd0, 0x7f, + 0xb2, 0x7f, 0x04, 0x30, 0x91, 0x6f, 0xd6, 0x2f, 0xeb, 0x6f, 0xa0, 0x5e, + 0xb8, 0x2e, 0x01, 0x2e, 0x77, 0xf7, 0x09, 0xbc, 0x0f, 0xb8, 0x00, 0xb2, + 0x10, 0x50, 0xfb, 0x7f, 0x10, 0x30, 0x0b, 0x2f, 0x03, 0x2e, 0x8a, 0x00, + 0x96, 0xbc, 0x9f, 0xb8, 0x40, 0xb2, 0x05, 0x2f, 0x03, 0x2e, 0x68, 0xf7, + 0x9e, 0xbc, 0x9f, 0xb8, 0x40, 0xb2, 0x07, 0x2f, 0x03, 0x2e, 0xfe, 0x00, + 0x41, 0x90, 0x01, 0x2f, 0x98, 0x2e, 0xcd, 0xb6, 0x03, 0x2c, 0x00, 0x30, + 0x21, 0x2e, 0xfe, 0x00, 0xfb, 0x6f, 0xf0, 0x5f, 0xb8, 0x2e, 0x20, 0x50, + 0xe0, 0x7f, 0xfb, 0x7f, 0x00, 0x2e, 0x73, 0x50, 0x98, 0x2e, 0x3b, 0xc8, + 0x75, 0x50, 0x98, 0x2e, 0xa7, 0xc8, 0x03, 0x50, 0x98, 0x2e, 0x55, 0xcc, + 0xe1, 0x6f, 0x77, 0x50, 0x98, 0x2e, 0xe0, 0xc9, 0xfb, 0x6f, 0x00, 0x30, + 0xe0, 0x5f, 0x21, 0x2e, 0xfe, 0x00, 0xb8, 0x2e, 0x03, 0xbc, 0x21, 0x2e, + 0x6a, 0x01, 0x03, 0x2e, 0x6a, 0x01, 0x40, 0xb2, 0x10, 0x30, 0x21, 0x2e, + 0x7b, 0x00, 0x01, 0x30, 0x05, 0x2f, 0x05, 0x2e, 0x6d, 0x01, 0x80, 0x90, + 0x01, 0x2f, 0x23, 0x2e, 0x6f, 0xf5, 0xc0, 0x2e, 0x21, 0x2e, 0x6e, 0x01, + 0x30, 0x25, 0x00, 0x30, 0x21, 0x2e, 0x5a, 0xf5, 0x10, 0x50, 0x21, 0x2e, + 0xdc, 0x00, 0x21, 0x2e, 0xd6, 0x00, 0xfb, 0x7f, 0x98, 0x2e, 0xf3, 0x03, + 0x40, 0x30, 0x21, 0x2e, 0xff, 0x00, 0xfb, 0x6f, 0xf0, 0x5f, 0x03, 0x25, + 0x80, 0x2e, 0xe4, 0xb6, 0x10, 0x50, 0x03, 0x40, 0x19, 0x18, 0x83, 0x56, + 0x19, 0x05, 0x36, 0x25, 0xf7, 0x7f, 0x4a, 0x17, 0x54, 0x18, 0xec, 0x18, + 0x09, 0x17, 0x01, 0x30, 0x0c, 0x07, 0xe2, 0x18, 0xde, 0x00, 0xf2, 0x6f, + 0x97, 0x02, 0x7f, 0x58, 0xdc, 0x00, 0x91, 0x02, 0xbf, 0xb8, 0x21, 0xbd, + 0x8a, 0x0a, 0xc0, 0x2e, 0x02, 0x42, 0xf0, 0x5f, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x01, 0x2e, 0x5d, 0xf7, 0x08, 0xbc, 0x80, 0xac, 0x0e, 0xbb, 0x02, 0x2f, + 0x00, 0x30, 0x41, 0x04, 0x82, 0x06, 0xc0, 0xa4, 0x00, 0x30, 0x11, 0x2f, + 0x40, 0xa9, 0x03, 0x2f, 0x40, 0x91, 0x0d, 0x2f, 0x00, 0xa7, 0x0b, 0x2f, + 0x80, 0xb3, 0x7f, 0x58, 0x02, 0x2f, 0x90, 0xa1, 0x26, 0x13, 0x20, 0x23, + 0x80, 0x90, 0x10, 0x30, 0x01, 0x2f, 0xcc, 0x0e, 0x00, 0x2f, 0x00, 0x30, + 0xb8, 0x2e, 0x81, 0x50, 0x18, 0x08, 0x08, 0xbc, 0x88, 0xb6, 0x0d, 0x17, + 0xc6, 0xbd, 0x56, 0xbc, 0x83, 0x58, 0xda, 0xba, 0x04, 0x01, 0x1d, 0x0a, + 0x10, 0x50, 0x05, 0x30, 0x32, 0x25, 0x45, 0x03, 0xfb, 0x7f, 0xf6, 0x30, + 0x21, 0x25, 0x98, 0x2e, 0x37, 0xca, 0x16, 0xb5, 0x9a, 0xbc, 0x06, 0xb8, + 0x80, 0xa8, 0x41, 0x0a, 0x0e, 0x2f, 0x80, 0x90, 0x02, 0x2f, 0x35, 0x50, + 0x48, 0x0f, 0x09, 0x2f, 0xbf, 0xa0, 0x04, 0x2f, 0xbf, 0x90, 0x06, 0x2f, + 0x83, 0x54, 0xca, 0x0f, 0x03, 0x2f, 0x00, 0x2e, 0x02, 0x2c, 0x83, 0x52, + 0x35, 0x52, 0xf2, 0x33, 0x98, 0x2e, 0xd9, 0xc0, 0xfb, 0x6f, 0xf1, 0x37, + 0xc0, 0x2e, 0x01, 0x08, 0xf0, 0x5f, 0x8b, 0x56, 0x85, 0x54, 0xd0, 0x40, + 0xc4, 0x40, 0x0b, 0x2e, 0xfd, 0xf3, 0x8b, 0x52, 0x90, 0x42, 0x94, 0x42, + 0x95, 0x42, 0x05, 0x30, 0x8d, 0x50, 0x0f, 0x88, 0x06, 0x40, 0x04, 0x41, + 0x96, 0x42, 0xc5, 0x42, 0x48, 0xbe, 0x73, 0x30, 0x0d, 0x2e, 0x6d, 0x01, + 0x4f, 0xba, 0x84, 0x42, 0x03, 0x42, 0x81, 0xb3, 0x02, 0x2f, 0x2b, 0x2e, + 0x6f, 0xf5, 0x06, 0x2d, 0x05, 0x2e, 0x77, 0xf7, 0x89, 0x56, 0x93, 0x08, + 0x25, 0x2e, 0x77, 0xf7, 0x87, 0x54, 0x25, 0x2e, 0xc2, 0xf5, 0x07, 0x2e, + 0xfd, 0xf3, 0x42, 0x30, 0xb4, 0x33, 0xda, 0x0a, 0x4c, 0x00, 0x27, 0x2e, + 0xfd, 0xf3, 0x43, 0x40, 0xd4, 0x3f, 0xdc, 0x08, 0x43, 0x42, 0x00, 0x2e, + 0x00, 0x2e, 0x43, 0x40, 0x24, 0x30, 0xdc, 0x0a, 0x43, 0x42, 0x04, 0x80, + 0x03, 0x2e, 0xfd, 0xf3, 0x4a, 0x0a, 0x23, 0x2e, 0xfd, 0xf3, 0x61, 0x34, + 0xc0, 0x2e, 0x01, 0x42, 0x00, 0x2e, 0x60, 0x50, 0x1a, 0x25, 0x7a, 0x86, + 0xe0, 0x7f, 0xf3, 0x7f, 0x03, 0x25, 0x8f, 0x52, 0x41, 0x84, 0xdb, 0x7f, + 0x33, 0x30, 0x98, 0x2e, 0x16, 0xc2, 0x1a, 0x25, 0x7d, 0x82, 0xf0, 0x6f, + 0xe2, 0x6f, 0x32, 0x25, 0x16, 0x40, 0x94, 0x40, 0x26, 0x01, 0x85, 0x40, + 0x8e, 0x17, 0xc4, 0x42, 0x6e, 0x03, 0x95, 0x42, 0x41, 0x0e, 0xf4, 0x2f, + 0xdb, 0x6f, 0xa0, 0x5f, 0xb8, 0x2e, 0xb0, 0x51, 0xfb, 0x7f, 0x98, 0x2e, + 0xe8, 0x0d, 0x5a, 0x25, 0x98, 0x2e, 0x0f, 0x0e, 0x97, 0x58, 0x32, 0x87, + 0xc4, 0x7f, 0x65, 0x89, 0x6b, 0x8d, 0x91, 0x5a, 0x65, 0x7f, 0xe1, 0x7f, + 0x83, 0x7f, 0xa6, 0x7f, 0x74, 0x7f, 0xd0, 0x7f, 0xb6, 0x7f, 0x94, 0x7f, + 0x17, 0x30, 0x93, 0x52, 0x95, 0x54, 0x51, 0x7f, 0x00, 0x2e, 0x85, 0x6f, + 0x42, 0x7f, 0x00, 0x2e, 0x51, 0x41, 0x45, 0x81, 0x42, 0x41, 0x13, 0x40, + 0x3b, 0x8a, 0x00, 0x40, 0x4b, 0x04, 0xd0, 0x06, 0xc0, 0xac, 0x85, 0x7f, + 0x02, 0x2f, 0x02, 0x30, 0x51, 0x04, 0xd3, 0x06, 0x41, 0x84, 0x05, 0x30, + 0x5d, 0x02, 0xc9, 0x16, 0xdf, 0x08, 0xd3, 0x00, 0x8d, 0x02, 0xaf, 0xbc, + 0xb1, 0xb9, 0x59, 0x0a, 0x65, 0x6f, 0x11, 0x43, 0xa1, 0xb4, 0x52, 0x41, + 0x53, 0x41, 0x01, 0x43, 0x34, 0x7f, 0x65, 0x7f, 0x26, 0x31, 0xe5, 0x6f, + 0xd4, 0x6f, 0x98, 0x2e, 0x37, 0xca, 0x32, 0x6f, 0x75, 0x6f, 0x83, 0x40, + 0x42, 0x41, 0x23, 0x7f, 0x12, 0x7f, 0xf6, 0x30, 0x40, 0x25, 0x51, 0x25, + 0x98, 0x2e, 0x37, 0xca, 0x14, 0x6f, 0x20, 0x05, 0x70, 0x6f, 0x25, 0x6f, + 0x69, 0x07, 0xa2, 0x6f, 0x31, 0x6f, 0x0b, 0x30, 0x04, 0x42, 0x9b, 0x42, + 0x8b, 0x42, 0x55, 0x42, 0x32, 0x7f, 0x40, 0xa9, 0xc3, 0x6f, 0x71, 0x7f, + 0x02, 0x30, 0xd0, 0x40, 0xc3, 0x7f, 0x03, 0x2f, 0x40, 0x91, 0x15, 0x2f, + 0x00, 0xa7, 0x13, 0x2f, 0x00, 0xa4, 0x11, 0x2f, 0x84, 0xbd, 0x98, 0x2e, + 0x79, 0xca, 0x55, 0x6f, 0x83, 0x54, 0x54, 0x41, 0x82, 0x00, 0xf3, 0x3f, + 0x45, 0x41, 0xcb, 0x02, 0xf6, 0x30, 0x98, 0x2e, 0x37, 0xca, 0x35, 0x6f, + 0xa4, 0x6f, 0x41, 0x43, 0x03, 0x2c, 0x00, 0x43, 0xa4, 0x6f, 0x35, 0x6f, + 0x17, 0x30, 0x42, 0x6f, 0x51, 0x6f, 0x93, 0x40, 0x42, 0x82, 0x00, 0x41, + 0xc3, 0x00, 0x03, 0x43, 0x51, 0x7f, 0x00, 0x2e, 0x94, 0x40, 0x41, 0x41, + 0x4c, 0x02, 0xc4, 0x6f, 0x9d, 0x56, 0x63, 0x0e, 0x74, 0x6f, 0x51, 0x43, + 0xa5, 0x7f, 0x8a, 0x2f, 0x09, 0x2e, 0x6d, 0x01, 0x01, 0xb3, 0x21, 0x2f, + 0x97, 0x58, 0x90, 0x6f, 0x13, 0x41, 0xb6, 0x6f, 0xe4, 0x7f, 0x00, 0x2e, + 0x91, 0x41, 0x14, 0x40, 0x92, 0x41, 0x15, 0x40, 0x17, 0x2e, 0x6f, 0xf5, + 0xb6, 0x7f, 0xd0, 0x7f, 0xcb, 0x7f, 0x98, 0x2e, 0x00, 0x0c, 0x07, 0x15, + 0xc2, 0x6f, 0x14, 0x0b, 0x29, 0x2e, 0x6f, 0xf5, 0xc3, 0xa3, 0xc1, 0x8f, + 0xe4, 0x6f, 0xd0, 0x6f, 0xe6, 0x2f, 0x14, 0x30, 0x05, 0x2e, 0x6f, 0xf5, + 0x14, 0x0b, 0x29, 0x2e, 0x6f, 0xf5, 0x18, 0x2d, 0x99, 0x56, 0x04, 0x32, + 0xb5, 0x6f, 0x1c, 0x01, 0x51, 0x41, 0x52, 0x41, 0xc3, 0x40, 0xb5, 0x7f, + 0xe4, 0x7f, 0x98, 0x2e, 0x1f, 0x0c, 0xe4, 0x6f, 0x21, 0x87, 0x00, 0x43, + 0x04, 0x32, 0x9b, 0x54, 0x5a, 0x0e, 0xef, 0x2f, 0x1f, 0x54, 0x09, 0x2e, + 0x77, 0xf7, 0x22, 0x0b, 0x29, 0x2e, 0x77, 0xf7, 0xfb, 0x6f, 0x50, 0x5e, + 0xb8, 0x2e, 0x10, 0x50, 0x01, 0x2e, 0xff, 0x00, 0x00, 0xb2, 0xfb, 0x7f, + 0x51, 0x2f, 0x01, 0xb2, 0x48, 0x2f, 0x02, 0xb2, 0x42, 0x2f, 0x03, 0x90, + 0x56, 0x2f, 0xa3, 0x52, 0x79, 0x80, 0x42, 0x40, 0x81, 0x84, 0x00, 0x40, + 0x42, 0x42, 0x98, 0x2e, 0x93, 0x0c, 0xa5, 0x54, 0xa3, 0x50, 0xa1, 0x40, + 0x98, 0xbd, 0x82, 0x40, 0x3e, 0x82, 0xda, 0x0a, 0x44, 0x40, 0x8b, 0x16, + 0xe3, 0x00, 0x53, 0x42, 0x00, 0x2e, 0x43, 0x40, 0x9a, 0x02, 0x52, 0x42, + 0x00, 0x2e, 0x41, 0x40, 0x1f, 0x54, 0x4a, 0x0e, 0x3a, 0x2f, 0x3a, 0x82, + 0x00, 0x30, 0x41, 0x40, 0x21, 0x2e, 0x94, 0x0f, 0x40, 0xb2, 0x0a, 0x2f, + 0x98, 0x2e, 0xb1, 0x0c, 0x98, 0x2e, 0x45, 0x0e, 0x98, 0x2e, 0x5b, 0x0e, + 0xfb, 0x6f, 0xf0, 0x5f, 0x00, 0x30, 0x80, 0x2e, 0xf8, 0xb6, 0xa9, 0x52, + 0x9f, 0x54, 0x42, 0x42, 0x4f, 0x84, 0x73, 0x30, 0xa7, 0x52, 0x83, 0x42, + 0x1b, 0x30, 0x6b, 0x42, 0x23, 0x30, 0x27, 0x2e, 0x6c, 0x01, 0x37, 0x2e, + 0xff, 0x00, 0x21, 0x2e, 0x6b, 0x01, 0x7a, 0x84, 0x17, 0x2c, 0x42, 0x42, + 0x30, 0x30, 0x21, 0x2e, 0xff, 0x00, 0x12, 0x2d, 0x21, 0x30, 0x00, 0x30, + 0x23, 0x2e, 0xff, 0x00, 0x21, 0x2e, 0x7b, 0xf7, 0x0b, 0x2d, 0x17, 0x30, + 0x98, 0x2e, 0x51, 0x0c, 0xa1, 0x50, 0x0c, 0x82, 0x72, 0x30, 0x2f, 0x2e, + 0xff, 0x00, 0x25, 0x2e, 0x7b, 0xf7, 0x40, 0x42, 0x00, 0x2e, 0xfb, 0x6f, + 0xf0, 0x5f, 0xb8, 0x2e, 0x70, 0x50, 0x0a, 0x25, 0x39, 0x86, 0xfb, 0x7f, + 0xe1, 0x32, 0x62, 0x30, 0x98, 0x2e, 0xc2, 0xc4, 0x81, 0x56, 0xa5, 0x6f, + 0xab, 0x08, 0x91, 0x6f, 0x4b, 0x08, 0xab, 0x56, 0xc4, 0x6f, 0x23, 0x09, + 0x4d, 0xba, 0x93, 0xbc, 0x8c, 0x0b, 0xd1, 0x6f, 0x0b, 0x09, 0x97, 0x52, + 0xad, 0x5e, 0x56, 0x42, 0xaf, 0x09, 0x4d, 0xba, 0x23, 0xbd, 0x94, 0x0a, + 0xe5, 0x6f, 0x68, 0xbb, 0xeb, 0x08, 0xbd, 0xb9, 0x63, 0xbe, 0xfb, 0x6f, + 0x52, 0x42, 0xe3, 0x0a, 0xc0, 0x2e, 0x43, 0x42, 0x90, 0x5f, 0x9d, 0x50, + 0x03, 0x2e, 0x25, 0xf3, 0x13, 0x40, 0x00, 0x40, 0x9b, 0xbc, 0x9b, 0xb4, + 0x08, 0xbd, 0xb8, 0xb9, 0x98, 0xbc, 0xda, 0x0a, 0x08, 0xb6, 0x89, 0x16, + 0xc0, 0x2e, 0x19, 0x00, 0x62, 0x02, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, + 0x81, 0x0d, 0x01, 0x2e, 0xff, 0x00, 0x31, 0x30, 0x08, 0x04, 0xfb, 0x6f, + 0x01, 0x30, 0xf0, 0x5f, 0x23, 0x2e, 0x6b, 0x01, 0x21, 0x2e, 0x6c, 0x01, + 0xb8, 0x2e, 0x01, 0x2e, 0x6c, 0x01, 0x03, 0x2e, 0x6b, 0x01, 0x48, 0x0e, + 0x01, 0x2f, 0x80, 0x2e, 0x1f, 0x0e, 0xb8, 0x2e, 0xaf, 0x50, 0x21, 0x34, + 0x01, 0x42, 0x82, 0x30, 0xc1, 0x32, 0x25, 0x2e, 0x62, 0xf5, 0x01, 0x00, + 0x22, 0x30, 0x01, 0x40, 0x4a, 0x0a, 0x01, 0x42, 0xb8, 0x2e, 0xaf, 0x54, + 0xf0, 0x3b, 0x83, 0x40, 0xd8, 0x08, 0xb1, 0x52, 0x83, 0x42, 0x00, 0x30, + 0x83, 0x30, 0x50, 0x42, 0xc4, 0x32, 0x27, 0x2e, 0x64, 0xf5, 0x94, 0x00, + 0x50, 0x42, 0x40, 0x42, 0xd3, 0x3f, 0x84, 0x40, 0x7d, 0x82, 0xe3, 0x08, + 0x40, 0x42, 0x83, 0x42, 0xb8, 0x2e, 0xa9, 0x52, 0x00, 0x30, 0x40, 0x42, + 0x7c, 0x86, 0x85, 0x52, 0x09, 0x2e, 0x7f, 0x0f, 0x8b, 0x54, 0xc4, 0x42, + 0xd3, 0x86, 0x54, 0x40, 0x55, 0x40, 0x94, 0x42, 0x85, 0x42, 0x21, 0x2e, + 0x6c, 0x01, 0x42, 0x40, 0x25, 0x2e, 0xfd, 0xf3, 0xc0, 0x42, 0x7e, 0x82, + 0x05, 0x2e, 0xd7, 0x00, 0x80, 0xb2, 0x14, 0x2f, 0x05, 0x2e, 0x89, 0x00, + 0x27, 0xbd, 0x2f, 0xb9, 0x80, 0x90, 0x02, 0x2f, 0x21, 0x2e, 0x6f, 0xf5, + 0x0c, 0x2d, 0x07, 0x2e, 0x80, 0x0f, 0x14, 0x30, 0x1c, 0x09, 0x05, 0x2e, + 0x77, 0xf7, 0x89, 0x56, 0x47, 0xbe, 0x93, 0x08, 0x94, 0x0a, 0x25, 0x2e, + 0x77, 0xf7, 0xb3, 0x54, 0x50, 0x42, 0x4a, 0x0e, 0xfc, 0x2f, 0xb8, 0x2e, + 0x50, 0x50, 0x02, 0x30, 0x43, 0x86, 0xb1, 0x50, 0xfb, 0x7f, 0xe3, 0x7f, + 0xd2, 0x7f, 0xc0, 0x7f, 0xb1, 0x7f, 0x00, 0x2e, 0x41, 0x40, 0x00, 0x40, + 0x48, 0x04, 0x98, 0x2e, 0x74, 0xc0, 0x1e, 0xaa, 0xd3, 0x6f, 0x14, 0x30, + 0xb1, 0x6f, 0xe3, 0x22, 0xc0, 0x6f, 0x52, 0x40, 0xe4, 0x6f, 0x4c, 0x0e, + 0x12, 0x42, 0xd3, 0x7f, 0xeb, 0x2f, 0x03, 0x2e, 0x95, 0x0f, 0x40, 0x90, + 0x11, 0x30, 0x03, 0x2f, 0x23, 0x2e, 0x95, 0x0f, 0x02, 0x2c, 0x00, 0x30, + 0xd0, 0x6f, 0xfb, 0x6f, 0xb0, 0x5f, 0xb8, 0x2e, 0x40, 0x50, 0xf1, 0x7f, + 0x0a, 0x25, 0x3c, 0x86, 0xeb, 0x7f, 0x41, 0x33, 0x22, 0x30, 0x98, 0x2e, + 0xc2, 0xc4, 0xd3, 0x6f, 0xf4, 0x30, 0xdc, 0x09, 0xb7, 0x58, 0xc2, 0x6f, + 0x94, 0x09, 0xb9, 0x58, 0x6a, 0xbb, 0xdc, 0x08, 0xb4, 0xb9, 0xb1, 0xbd, + 0xb5, 0x5a, 0x95, 0x08, 0x21, 0xbd, 0xf6, 0xbf, 0x77, 0x0b, 0x51, 0xbe, + 0xf1, 0x6f, 0xeb, 0x6f, 0x52, 0x42, 0x54, 0x42, 0xc0, 0x2e, 0x43, 0x42, + 0xc0, 0x5f, 0x50, 0x50, 0xcd, 0x50, 0x31, 0x30, 0x11, 0x42, 0xfb, 0x7f, + 0x7b, 0x30, 0x0b, 0x42, 0x11, 0x30, 0x02, 0x80, 0x23, 0x33, 0x01, 0x42, + 0x03, 0x00, 0x07, 0x2e, 0x80, 0x03, 0x05, 0x2e, 0xd5, 0x00, 0x49, 0x52, + 0xe2, 0x7f, 0xd3, 0x7f, 0xc0, 0x7f, 0x98, 0x2e, 0xb6, 0x0e, 0xd1, 0x6f, + 0x08, 0x0a, 0x1a, 0x25, 0x7b, 0x86, 0xd0, 0x7f, 0x01, 0x33, 0x12, 0x30, + 0x98, 0x2e, 0xc2, 0xc4, 0xd1, 0x6f, 0x08, 0x0a, 0x00, 0xb2, 0x0d, 0x2f, + 0xe3, 0x6f, 0x01, 0x2e, 0x80, 0x03, 0x51, 0x30, 0xc7, 0x86, 0x23, 0x2e, + 0x21, 0xf2, 0x08, 0xbc, 0xc0, 0x42, 0x98, 0x2e, 0x0b, 0x03, 0x00, 0x2e, + 0x00, 0x2e, 0xd0, 0x2e, 0xb0, 0x6f, 0x0b, 0xb8, 0x03, 0x2e, 0x1b, 0x00, + 0x08, 0x1a, 0xb0, 0x7f, 0x70, 0x30, 0x04, 0x2f, 0x21, 0x2e, 0x21, 0xf2, + 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0x98, 0x2e, 0x6d, 0xc0, 0x98, 0x2e, + 0x5d, 0xc0, 0xbb, 0x50, 0x98, 0x2e, 0x46, 0xc3, 0xbd, 0x50, 0x98, 0x2e, + 0xfc, 0xc5, 0xbf, 0x50, 0x21, 0x2e, 0x77, 0x01, 0x6f, 0x50, 0x98, 0x2e, + 0x64, 0xcf, 0xc3, 0x50, 0x21, 0x2e, 0x85, 0x01, 0xc1, 0x56, 0xc5, 0x52, + 0x27, 0x2e, 0x86, 0x01, 0x23, 0x2e, 0x87, 0x01, 0xc7, 0x50, 0x98, 0x2e, + 0x53, 0xc7, 0xc9, 0x50, 0x98, 0x2e, 0x44, 0xcb, 0x10, 0x30, 0x98, 0x2e, + 0xcd, 0xb6, 0x20, 0x26, 0xc0, 0x6f, 0x02, 0x31, 0x12, 0x42, 0xab, 0x33, + 0x0b, 0x42, 0x37, 0x80, 0x01, 0x30, 0x01, 0x42, 0xf3, 0x37, 0xcf, 0x52, + 0xd3, 0x50, 0x44, 0x40, 0xa2, 0x0a, 0x42, 0x42, 0x8b, 0x31, 0x09, 0x2e, + 0x5e, 0xf7, 0xd1, 0x54, 0xe3, 0x08, 0x83, 0x42, 0x1b, 0x42, 0x23, 0x33, + 0x4b, 0x00, 0xbc, 0x84, 0x0b, 0x40, 0x33, 0x30, 0x83, 0x42, 0x0b, 0x42, + 0xe0, 0x7f, 0xd1, 0x7f, 0x98, 0x2e, 0x07, 0x02, 0xd1, 0x6f, 0x80, 0x30, + 0x40, 0x42, 0x03, 0x30, 0xe0, 0x6f, 0xcb, 0x54, 0x04, 0x30, 0x00, 0x2e, + 0x00, 0x2e, 0x01, 0x89, 0x62, 0x0e, 0xfa, 0x2f, 0x43, 0x42, 0x11, 0x30, + 0xfb, 0x6f, 0xc0, 0x2e, 0x01, 0x42, 0xb0, 0x5f, 0xc1, 0x4a, 0x00, 0x00, + 0x6d, 0x57, 0x00, 0x00, 0x77, 0x8e, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, + 0xd3, 0xff, 0xff, 0xff, 0xe5, 0xff, 0xff, 0xff, 0xee, 0xe1, 0xff, 0xff, + 0x7c, 0x13, 0x00, 0x00, 0x46, 0xe6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0xfd, 0x2d +}; diff --git a/drivers/custom/bmi260/bmi260_core.c b/drivers/custom/bmi260/bmi260_core.c new file mode 100644 index 000000000000..d27414814710 --- /dev/null +++ b/drivers/custom/bmi260/bmi260_core.c @@ -0,0 +1,1022 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO core driver for Bosch BMI260 6-Axis IMU. + * + * Copyright (C) 2023, Justin Weiss + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "bmi260.h" +#include "bmi260_config.h" + +#define BMI260_REG_CHIP_ID 0x00 +#define BMI260_CHIP_ID_VAL 0x27 /* 0x24 for BMI270 */ + +#define BMI260_REG_PMU_STATUS 0x03 + +/* X axis data low byte address, the rest can be obtained using axis offset */ +#define BMI260_REG_DATA_AUX_XOUT_L 0x04 +#define BMI260_REG_DATA_ACCEL_XOUT_L 0x0C +#define BMI260_REG_DATA_GYRO_XOUT_L 0x12 + +#define BMI260_REG_INTERNAL_STATUS 0x21 +#define BMI260_STATUS_MESSAGE_MASK GENMASK(3, 0) + +#define BMI260_REG_ACCEL_CONFIG 0x40 +#define BMI260_ACCEL_CONFIG_ODR_MASK GENMASK(3, 0) +#define BMI260_ACCEL_CONFIG_BWP_MASK GENMASK(6, 4) + +#define BMI260_REG_ACCEL_RANGE 0x41 +#define BMI260_ACCEL_RANGE_MASK GENMASK(1, 0) +#define BMI260_ACCEL_RANGE_2G 0x00 +#define BMI260_ACCEL_RANGE_4G 0x01 +#define BMI260_ACCEL_RANGE_8G 0x02 +#define BMI260_ACCEL_RANGE_16G 0x03 + +#define BMI260_REG_GYRO_CONFIG 0x42 +#define BMI260_GYRO_CONFIG_ODR_MASK GENMASK(3, 0) +#define BMI260_GYRO_CONFIG_BWP_MASK GENMASK(5, 4) + +#define BMI260_REG_GYRO_RANGE 0x43 +#define BMI260_GYRO_RANGE_MASK GENMASK(2, 0) +#define BMI260_GYRO_RANGE_2000DPS 0x00 +#define BMI260_GYRO_RANGE_1000DPS 0x01 +#define BMI260_GYRO_RANGE_500DPS 0x02 +#define BMI260_GYRO_RANGE_250DPS 0x03 +#define BMI260_GYRO_RANGE_125DPS 0x04 + +#define BMI260_REG_INIT_CTRL 0x59 +#define BMI260_REG_INIT_DATA 0x5E + +#define BMI260_REG_PWR_CONF 0x7C +#define BMI260_PWR_CONF_ADV_PWR_SAVE BIT(0) +#define BMI260_PWR_CONF_FIFO_WAKE_UP BIT(1) +#define BMI260_PWR_CONF_FUP_EN BIT(2) + +#define BMI260_REG_PWR_CTRL 0x7D +#define BMI260_PWR_CTRL_AUX_EN BIT(0) +#define BMI260_PWR_CTRL_GYR_EN BIT(1) +#define BMI260_PWR_CTRL_ACC_EN BIT(2) +#define BMI260_PWR_CTRL_TEMP_EN BIT(3) + +#define BMI260_REG_CMD 0x7E +#define BMI260_CMD_SOFTRESET 0xB6 + +#define BMI260_REG_FIFO_CONFIG_1 0x49 +#define BMI260_FIFO_TAG_INT1_LEVEL BIT(0) +#define BMI260_FIFO_TAG_INT2_LEVEL BIT(2) + +#define BMI260_REG_INT1_IO_CTRL 0x53 +#define BMI260_REG_INT2_IO_CTRL 0x54 +#define BMI260_INT_IO_CTRL_MASK GENMASK(4, 1) +#define BMI260_ACTIVE_HIGH BIT(1) +#define BMI260_OPEN_DRAIN BIT(2) +#define BMI260_OUTPUT_EN BIT(3) +#define BMI260_INPUT_EN BIT(4) + +#define BMI260_REG_INT_MAP_DATA 0x58 +#define BMI260_INT1_MAP_DRDY_EN BIT(2) +#define BMI260_INT2_MAP_DRDY_EN BIT(6) + +#define BMI260_REG_DUMMY 0x7F + +#define BMI260_NORMAL_WRITE_USLEEP 2 +#define BMI260_SUSPENDED_WRITE_USLEEP 450 +#define BMI260_SOFTRESET_USLEEP 2000 +#define BMI260_INIT_USLEEP 22000 + +#define BMI260_CHANNEL(_type, _axis, _index) { \ + .type = _type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = bmi260_ext_info, \ +} + +/* scan indexes follow DATA register order */ +enum bmi260_scan_axis { + BMI260_SCAN_AUX_X = 0, + BMI260_SCAN_AUX_Y, + BMI260_SCAN_AUX_Z, + BMI260_SCAN_AUX_R, + BMI260_SCAN_ACCEL_X, + BMI260_SCAN_ACCEL_Y, + BMI260_SCAN_ACCEL_Z, + BMI260_SCAN_GYRO_X, + BMI260_SCAN_GYRO_Y, + BMI260_SCAN_GYRO_Z, + BMI260_SCAN_TIMESTAMP, +}; + +enum bmi260_sensor_type { + BMI260_ACCEL = 0, + BMI260_GYRO, + BMI260_AUX, + BMI260_NUM_SENSORS /* must be last */ +}; + +const struct regmap_config bmi260_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; +BMI260_EXPORT_SYMBOL(bmi260_regmap_config); + +struct bmi260_regs { + u8 data; /* LSB byte register for X-axis */ + u8 config; + u8 config_odr_mask; + u8 config_bwp_mask; + u8 range; +}; + +static struct bmi260_regs bmi260_regs[] = { + [BMI260_ACCEL] = { + .data = BMI260_REG_DATA_ACCEL_XOUT_L, + .config = BMI260_REG_ACCEL_CONFIG, + .config_odr_mask = BMI260_ACCEL_CONFIG_ODR_MASK, + .config_bwp_mask = BMI260_ACCEL_CONFIG_BWP_MASK, + .range = BMI260_REG_ACCEL_RANGE, + }, + [BMI260_GYRO] = { + .data = BMI260_REG_DATA_GYRO_XOUT_L, + .config = BMI260_REG_GYRO_CONFIG, + .config_odr_mask = BMI260_GYRO_CONFIG_ODR_MASK, + .config_bwp_mask = BMI260_GYRO_CONFIG_BWP_MASK, + .range = BMI260_REG_GYRO_RANGE, + }, +}; + +struct bmi260_scale { + u8 bits; + int uscale; +}; + +struct bmi260_odr { + u8 bits; + int odr; + int uodr; +}; + +static const struct bmi260_scale bmi260_accel_scale[] = { + { BMI260_ACCEL_RANGE_2G, 598}, + { BMI260_ACCEL_RANGE_4G, 1197}, + { BMI260_ACCEL_RANGE_8G, 2394}, + { BMI260_ACCEL_RANGE_16G, 4788}, +}; + +static const struct bmi260_scale bmi260_gyro_scale[] = { + { BMI260_GYRO_RANGE_2000DPS, 1065}, + { BMI260_GYRO_RANGE_1000DPS, 532}, + { BMI260_GYRO_RANGE_500DPS, 266}, + { BMI260_GYRO_RANGE_250DPS, 133}, + { BMI260_GYRO_RANGE_125DPS, 66}, +}; + +struct bmi260_scale_item { + const struct bmi260_scale *tbl; + int num; +}; + +static const struct bmi260_scale_item bmi260_scale_table[] = { + [BMI260_ACCEL] = { + .tbl = bmi260_accel_scale, + .num = ARRAY_SIZE(bmi260_accel_scale), + }, + [BMI260_GYRO] = { + .tbl = bmi260_gyro_scale, + .num = ARRAY_SIZE(bmi260_gyro_scale), + }, +}; + +static const struct bmi260_odr bmi260_accel_odr[] = { + {0x01, 0, 781250}, + {0x02, 1, 562500}, + {0x03, 3, 125000}, + {0x04, 6, 250000}, + {0x05, 12, 500000}, + {0x06, 25, 0}, + {0x07, 50, 0}, + {0x08, 100, 0}, + {0x09, 200, 0}, + {0x0A, 400, 0}, + {0x0B, 800, 0}, + {0x0C, 1600, 0}, +}; + +static const struct bmi260_odr bmi260_gyro_odr[] = { + {0x06, 25, 0}, + {0x07, 50, 0}, + {0x08, 100, 0}, + {0x09, 200, 0}, + {0x0A, 400, 0}, + {0x0B, 800, 0}, + {0x0C, 1600, 0}, + {0x0D, 3200, 0}, +}; + +struct bmi260_odr_item { + const struct bmi260_odr *tbl; + int num; +}; + +static const struct bmi260_odr_item bmi260_odr_table[] = { + [BMI260_ACCEL] = { + .tbl = bmi260_accel_odr, + .num = ARRAY_SIZE(bmi260_accel_odr), + }, + [BMI260_GYRO] = { + .tbl = bmi260_gyro_odr, + .num = ARRAY_SIZE(bmi260_gyro_odr), + }, +}; + +#ifdef CONFIG_ACPI +/* + * Support for getting accelerometer information from ACPI nodes. + * Based off of the bmc150 implementation. + */ +static bool bmi260_apply_acpi_orientation(struct device *dev, + struct iio_mount_matrix *orientation) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct acpi_device *adev = ACPI_COMPANION(dev); + char *name, *alt_name, *label, *str; + union acpi_object *obj, *elements; + acpi_status status; + int i, j, val[3]; + + if (!adev) + return false; + + alt_name = "ROMS"; + label = "accel-display"; + + if (acpi_has_method(adev->handle, alt_name)) { + name = alt_name; + indio_dev->label = label; + } else { + return false; + } + + status = acpi_evaluate_object(adev->handle, name, NULL, &buffer); + if (ACPI_FAILURE(status)) { + dev_warn(dev, "Failed to get ACPI mount matrix: %d\n", status); + return false; + } + + obj = buffer.pointer; + if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 3) + goto unknown_format; + + elements = obj->package.elements; + for (i = 0; i < 3; i++) { + if (elements[i].type != ACPI_TYPE_STRING) + goto unknown_format; + + str = elements[i].string.pointer; + if (sscanf(str, "%d %d %d", &val[0], &val[1], &val[2]) != 3) + goto unknown_format; + + for (j = 0; j < 3; j++) { + switch (val[j]) { + case -1: str = "-1"; break; + case 0: str = "0"; break; + case 1: str = "1"; break; + default: goto unknown_format; + } + orientation->rotation[i * 3 + j] = str; + } + } + + kfree(buffer.pointer); + return true; + +unknown_format: + dev_warn(dev, "Unknown ACPI mount matrix format, ignoring\n"); + kfree(buffer.pointer); + return false; +} + +#else +static bool bmi260_apply_acpi_orientation(struct device *dev, + struct iio_mount_matrix *orientation) +{ + return false; +} +#endif + +static const struct iio_mount_matrix * +bmi260_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct bmi260_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info bmi260_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bmi260_get_mount_matrix), + { } +}; + +static const struct iio_chan_spec bmi260_channels[] = { + BMI260_CHANNEL(IIO_ACCEL, X, BMI260_SCAN_ACCEL_X), + BMI260_CHANNEL(IIO_ACCEL, Y, BMI260_SCAN_ACCEL_Y), + BMI260_CHANNEL(IIO_ACCEL, Z, BMI260_SCAN_ACCEL_Z), + BMI260_CHANNEL(IIO_ANGL_VEL, X, BMI260_SCAN_GYRO_X), + BMI260_CHANNEL(IIO_ANGL_VEL, Y, BMI260_SCAN_GYRO_Y), + BMI260_CHANNEL(IIO_ANGL_VEL, Z, BMI260_SCAN_GYRO_Z), + IIO_CHAN_SOFT_TIMESTAMP(BMI260_SCAN_TIMESTAMP), +}; + +static enum bmi260_sensor_type bmi260_to_sensor(enum iio_chan_type iio_type) +{ + switch (iio_type) { + case IIO_ACCEL: + return BMI260_ACCEL; + case IIO_ANGL_VEL: + return BMI260_GYRO; + default: + return -EINVAL; + } +} + +static +int bmi260_set_scale(struct bmi260_data *data, enum bmi260_sensor_type t, + int uscale) +{ + int i; + + for (i = 0; i < bmi260_scale_table[t].num; i++) + if (bmi260_scale_table[t].tbl[i].uscale == uscale) + break; + + if (i == bmi260_scale_table[t].num) + return -EINVAL; + + return regmap_write(data->regmap, bmi260_regs[t].range, + bmi260_scale_table[t].tbl[i].bits); +} + +static +int bmi260_get_scale(struct bmi260_data *data, enum bmi260_sensor_type t, + int *uscale) +{ + int i, ret, val; + + ret = regmap_read(data->regmap, bmi260_regs[t].range, &val); + if (ret) + return ret; + + for (i = 0; i < bmi260_scale_table[t].num; i++) + if (bmi260_scale_table[t].tbl[i].bits == val) { + *uscale = bmi260_scale_table[t].tbl[i].uscale; + return 0; + } + + return -EINVAL; +} + +static int bmi260_get_data(struct bmi260_data *data, int chan_type, + int axis, int *val) +{ + u8 reg; + int ret; + __le16 sample; + enum bmi260_sensor_type t = bmi260_to_sensor(chan_type); + + reg = bmi260_regs[t].data + (axis - IIO_MOD_X) * sizeof(sample); + + ret = regmap_bulk_read(data->regmap, reg, &sample, sizeof(sample)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(sample), 15); + + return 0; +} + +static +int bmi260_set_odr(struct bmi260_data *data, enum bmi260_sensor_type t, + int odr, int uodr) +{ + int i; + + for (i = 0; i < bmi260_odr_table[t].num; i++) + if (bmi260_odr_table[t].tbl[i].odr == odr && + bmi260_odr_table[t].tbl[i].uodr == uodr) + break; + + if (i >= bmi260_odr_table[t].num) + return -EINVAL; + + return regmap_update_bits(data->regmap, + bmi260_regs[t].config, + bmi260_regs[t].config_odr_mask, + bmi260_odr_table[t].tbl[i].bits); +} + +static int bmi260_get_odr(struct bmi260_data *data, enum bmi260_sensor_type t, + int *odr, int *uodr) +{ + int i, val, ret; + + ret = regmap_read(data->regmap, bmi260_regs[t].config, &val); + if (ret) + return ret; + + val &= bmi260_regs[t].config_odr_mask; + + for (i = 0; i < bmi260_odr_table[t].num; i++) + if (val == bmi260_odr_table[t].tbl[i].bits) + break; + + if (i >= bmi260_odr_table[t].num) + return -EINVAL; + + *odr = bmi260_odr_table[t].tbl[i].odr; + *uodr = bmi260_odr_table[t].tbl[i].uodr; + + return 0; +} + +static irqreturn_t bmi260_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmi260_data *data = iio_priv(indio_dev); + int i, ret, j = 0, base = BMI260_REG_DATA_AUX_XOUT_L; + __le16 sample; + + if (data->conf.suspended) + return IRQ_NONE; + + for_each_set_bit(i, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = regmap_bulk_read(data->regmap, base + i * sizeof(sample), + &sample, sizeof(sample)); + if (ret) + goto done; + data->buf[j++] = sample; + } + + iio_push_to_buffers_with_timestamp(indio_dev, data->buf, pf->timestamp); +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int bmi260_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct bmi260_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = bmi260_get_data(data, chan->type, chan->channel2, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + ret = bmi260_get_scale(data, + bmi260_to_sensor(chan->type), val2); + return ret ? ret : IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = bmi260_get_odr(data, bmi260_to_sensor(chan->type), + val, val2); + return ret ? ret : IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + return 0; +} + +static int bmi260_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmi260_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return bmi260_set_scale(data, + bmi260_to_sensor(chan->type), val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return bmi260_set_odr(data, bmi260_to_sensor(chan->type), + val, val2); + default: + return -EINVAL; + } + + return 0; +} + +static +IIO_CONST_ATTR(in_accel_sampling_frequency_available, + "0.78125 1.5625 3.125 6.25 12.5 25 50 100 200 400 800 1600"); +static +IIO_CONST_ATTR(in_anglvel_sampling_frequency_available, + "25 50 100 200 400 800 1600 3200"); +static +IIO_CONST_ATTR(in_accel_scale_available, + "0.000598 0.001197 0.002394 0.004788"); +static +IIO_CONST_ATTR(in_anglvel_scale_available, + "0.001065 0.000532 0.000266 0.000133 0.000066"); + +static struct attribute *bmi260_attrs[] = { + &iio_const_attr_in_accel_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_anglvel_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_accel_scale_available.dev_attr.attr, + &iio_const_attr_in_anglvel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bmi260_attrs_group = { + .attrs = bmi260_attrs, +}; + +static const struct iio_info bmi260_info = { + .read_raw = bmi260_read_raw, + .write_raw = bmi260_write_raw, + .attrs = &bmi260_attrs_group, +}; + +static int bmi260_write_conf_reg(struct regmap *regmap, unsigned int reg, + unsigned int mask, unsigned int bits, + unsigned int write_usleep) +{ + int ret; + unsigned int val; + + ret = regmap_read(regmap, reg, &val); + if (ret) + return ret; + + val = (val & ~mask) | bits; + + ret = regmap_write(regmap, reg, val); + if (ret) + return ret; + + /* + * We need to wait after writing before we can write again. See the + * datasheet, page 93. + */ + usleep_range(write_usleep, write_usleep + 1000); + + return 0; +} + +static int bmi260_config_pin(struct regmap *regmap, enum bmi260_int_pin pin, + bool level_triggered, u8 irq_mask, + unsigned long write_usleep) +{ + int ret; + struct device *dev = regmap_get_device(regmap); + unsigned int ctrl_reg; + unsigned int drdy_val; + unsigned int level_val; + u8 int_out_ctrl_bits; + const char *pin_name; + + switch (pin) { + case BMI260_PIN_INT1: + ctrl_reg = BMI260_REG_INT1_IO_CTRL; + drdy_val = BMI260_INT1_MAP_DRDY_EN; + level_val = BMI260_FIFO_TAG_INT1_LEVEL; + break; + case BMI260_PIN_INT2: + ctrl_reg = BMI260_REG_INT2_IO_CTRL; + drdy_val = BMI260_INT2_MAP_DRDY_EN; + level_val = BMI260_FIFO_TAG_INT2_LEVEL; + break; + } + + /* + * Enable the requested pin with the right settings: + * - Push-pull/open-drain + * - Active low/high + */ + int_out_ctrl_bits = BMI260_OUTPUT_EN | BMI260_INPUT_EN; + int_out_ctrl_bits |= irq_mask; + + ret = bmi260_write_conf_reg(regmap, ctrl_reg, + BMI260_INT_IO_CTRL_MASK, int_out_ctrl_bits, + write_usleep); + if (ret) + return ret; + + /* Set level/edge triggered */ + if (level_triggered) { + ret = bmi260_write_conf_reg(regmap, BMI260_REG_FIFO_CONFIG_1, + level_val, level_val, + write_usleep); + if (ret) + return ret; + } + + /* Map interrupts to the requested pin. */ + ret = bmi260_write_conf_reg(regmap, BMI260_REG_INT_MAP_DATA, + drdy_val, drdy_val, + write_usleep); + if (ret) { + switch (pin) { + case BMI260_PIN_INT1: + pin_name = "INT1"; + break; + case BMI260_PIN_INT2: + pin_name = "INT2"; + break; + } + dev_err(dev, "Failed to configure %s IRQ pin", pin_name); + } + + return ret; +} + +int bmi260_enable_irq(struct regmap *regmap, enum bmi260_int_pin pin, bool enable) +{ + unsigned int enable_bit = 0; + unsigned int mask = 0; + + switch (pin) { + case BMI260_PIN_INT1: + mask = BMI260_INT1_MAP_DRDY_EN; + break; + case BMI260_PIN_INT2: + mask = BMI260_INT2_MAP_DRDY_EN; + break; + } + + if (enable) + enable_bit = mask; + + return bmi260_write_conf_reg(regmap, BMI260_REG_INT_MAP_DATA, + mask, enable_bit, + BMI260_NORMAL_WRITE_USLEEP); +} +BMI260_EXPORT_SYMBOL(bmi260_enable_irq); + +static int bmi260_get_irq(struct fwnode_handle *fwnode, enum bmi260_int_pin *pin) +{ + int irq; + + /* Use INT1 if possible, otherwise fall back to INT2. */ + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq > 0) { + *pin = BMI260_PIN_INT1; + return irq; + } + + irq = fwnode_irq_get_byname(fwnode, "INT2"); + if (irq > 0) + *pin = BMI260_PIN_INT2; + + return irq; +} + +static int bmi260_config_device_irq(struct iio_dev *indio_dev, int irq_type, + enum bmi260_int_pin pin) +{ + bool open_drain; + u8 irq_mask; + bool level_triggered = true; + struct bmi260_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + + /* Edge-triggered, active-low is the default if we set all zeroes. */ + if (irq_type == IRQF_TRIGGER_RISING) { + irq_mask = BMI260_ACTIVE_HIGH; + level_triggered = false; + } else if (irq_type == IRQF_TRIGGER_FALLING) { + irq_mask = 0; + level_triggered = false; + } else if (irq_type == IRQF_TRIGGER_HIGH) { + irq_mask = BMI260_ACTIVE_HIGH; + } else if (irq_type == IRQF_TRIGGER_LOW) { + irq_mask = 0; + } else { + dev_err(&indio_dev->dev, + "Invalid interrupt type 0x%x specified\n", irq_type); + return -EINVAL; + } + + open_drain = device_property_read_bool(dev, "drive-open-drain"); + + if (open_drain) + irq_mask |= BMI260_OPEN_DRAIN; + + return bmi260_config_pin(data->regmap, pin, level_triggered, irq_mask, + BMI260_NORMAL_WRITE_USLEEP); +} + +static int bmi260_setup_irq(struct iio_dev *indio_dev, int irq, + enum bmi260_int_pin pin) +{ + struct irq_data *desc; + u32 irq_type; + int ret; + + desc = irq_get_irq_data(irq); + if (!desc) { + dev_err(&indio_dev->dev, "Could not find IRQ %d\n", irq); + return -EINVAL; + } + + irq_type = irqd_get_trigger_type(desc); + + ret = bmi260_config_device_irq(indio_dev, irq_type, pin); + if (ret) + return ret; + + return bmi260_probe_trigger(indio_dev, irq, irq_type); +} + +static int bmi260_chip_init(void *void_data) +{ + struct bmi260_data *data = void_data; + int ret; + unsigned int val; + struct device *dev = regmap_get_device(data->regmap); + + ret = regulator_bulk_enable(ARRAY_SIZE(data->supplies), data->supplies); + if (ret) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + ret = regmap_write(data->regmap, BMI260_REG_CMD, BMI260_CMD_SOFTRESET); + if (ret) + goto disable_regulator; + + usleep_range(BMI260_SOFTRESET_USLEEP, BMI260_SOFTRESET_USLEEP + 1); + + /* + * CS rising edge is needed before starting SPI, so do a dummy read + * See Section 4.4, page 25 of the datasheet + */ + if (data->use_spi) { + ret = regmap_read(data->regmap, BMI260_REG_DUMMY, &val); + if (ret) + goto disable_regulator; + } + + ret = regmap_read(data->regmap, BMI260_REG_CHIP_ID, &val); + if (ret) { + dev_err(dev, "Error reading chip id\n"); + goto disable_regulator; + } + if (val != BMI260_CHIP_ID_VAL) { + dev_err(dev, "Wrong chip id, got %x expected %x\n", + val, BMI260_CHIP_ID_VAL); + ret = -ENODEV; + goto disable_regulator; + } + + ret = bmi260_write_conf_reg(data->regmap, BMI260_REG_PWR_CONF, + BMI260_PWR_CONF_ADV_PWR_SAVE, false, + BMI260_SUSPENDED_WRITE_USLEEP); + if (ret) { + dev_err(dev, "Error disabling advanced power saving\n"); + goto disable_regulator; + } + + /* Upload the config file */ + ret = regmap_write(data->regmap, BMI260_REG_INIT_CTRL, 0); + if (ret) { + dev_err(dev, "Error preparing for config upload\n"); + goto disable_regulator; + } + + ret = regmap_raw_write(data->regmap, BMI260_REG_INIT_DATA, bmi260_config_file, ARRAY_SIZE(bmi260_config_file)); + if (ret) { + dev_err(dev, "Error uploading config\n"); + goto disable_regulator; + } + + ret = regmap_write(data->regmap, BMI260_REG_INIT_CTRL, 1); + if (ret) { + dev_err(dev, "Error finalizing config upload\n"); + goto disable_regulator; + } + + usleep_range(BMI260_INIT_USLEEP, BMI260_INIT_USLEEP + 1); + + ret = regmap_read(data->regmap, BMI260_REG_INTERNAL_STATUS, &val); + if (ret) { + dev_err(dev, "Error reading chip status\n"); + goto disable_regulator; + } + if ((val & BMI260_STATUS_MESSAGE_MASK) != 0x01) { + dev_err(dev, "Chip failed to init\n"); + ret = -ENODEV; + goto disable_regulator; + } + + /* Enable accel and gyro */ + ret = regmap_update_bits(data->regmap, BMI260_REG_PWR_CTRL, + BMI260_PWR_CTRL_ACC_EN | BMI260_PWR_CTRL_GYR_EN, + BMI260_PWR_CTRL_ACC_EN | BMI260_PWR_CTRL_GYR_EN); + if (ret) + goto disable_regulator; + + return 0; + +disable_regulator: + regulator_bulk_disable(ARRAY_SIZE(data->supplies), data->supplies); + return ret; +} + +static int bmi260_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool enable) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bmi260_data *data = iio_priv(indio_dev); + // FIXME: the irq enable func is exported, it could be called from elsewhere + data->conf.irq = enable; + + return bmi260_enable_irq(data->regmap, data->int_pin, enable); +} +static const struct iio_trigger_ops bmi260_trigger_ops = { + .set_trigger_state = &bmi260_data_rdy_trigger_set_state, +}; + +int bmi260_probe_trigger(struct iio_dev *indio_dev, int irq, u32 irq_type) +{ + struct bmi260_data *data = iio_priv(indio_dev); + int ret; + + data->trig = devm_iio_trigger_alloc(&indio_dev->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + + if (data->trig == NULL) + return -ENOMEM; + + ret = devm_request_irq(&indio_dev->dev, irq, + &iio_trigger_generic_data_rdy_poll, + irq_type, "bmi260", data->trig); + if (ret) + return ret; + + data->trig->dev.parent = regmap_get_device(data->regmap); + data->trig->ops = &bmi260_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = devm_iio_trigger_register(&indio_dev->dev, data->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(data->trig); + + return 0; +} + +static void bmi260_chip_uninit(void *data) +{ + struct bmi260_data *bmi_data = data; + struct device *dev = regmap_get_device(bmi_data->regmap); + int ret; + + /* Disable accel and gyro */ + regmap_update_bits(bmi_data->regmap, BMI260_REG_PWR_CTRL, + BMI260_PWR_CTRL_ACC_EN | BMI260_PWR_CTRL_GYR_EN, + 0); + + ret = regulator_bulk_disable(ARRAY_SIZE(bmi_data->supplies), + bmi_data->supplies); + if (ret) + dev_err(dev, "Failed to disable regulators: %d\n", ret); +} + +int bmi260_core_probe(struct device *dev, struct regmap *regmap, + int irq, const char *name, bool use_spi) +{ + struct iio_dev *indio_dev; + struct bmi260_data *data; + enum bmi260_int_pin int_pin = BMI260_PIN_INT1; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + data->regmap = regmap; + data->use_spi = use_spi; + + data->supplies[0].supply = "vdd"; + data->supplies[1].supply = "vddio"; + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(data->supplies), + data->supplies); + if (ret) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + if (!bmi260_apply_acpi_orientation(dev, &data->orientation)) { + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return ret; + } + + ret = bmi260_chip_init(data); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, bmi260_chip_uninit, data); + if (ret) + return ret; + + indio_dev->channels = bmi260_channels; + indio_dev->num_channels = ARRAY_SIZE(bmi260_channels); + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bmi260_info; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + bmi260_trigger_handler, NULL); + if (ret) + return ret; + + if (!irq) { + irq = bmi260_get_irq(dev_fwnode(dev), &int_pin); + } + + if (irq > 0) { + data->int_pin = int_pin; + ret = bmi260_setup_irq(indio_dev, irq, int_pin); + if (ret) + dev_err(&indio_dev->dev, "Failed to setup IRQ %d\n", + irq); + } else { + dev_info(&indio_dev->dev, "Not setting up IRQ trigger\n"); + } + + return devm_iio_device_register(dev, indio_dev); +} +BMI260_EXPORT_SYMBOL(bmi260_core_probe); + +static int bmi260_chip_resume(struct device *dev) { + struct bmi260_data *data = iio_priv(dev_get_drvdata(dev)); + int ret = bmi260_chip_init(data); + if (ret) + return ret; + bmi260_set_scale(data, BMI260_ACCEL, data->conf.accel_scale); + bmi260_set_scale(data, BMI260_GYRO, data->conf.gyro_scale); + bmi260_set_odr(data, BMI260_ACCEL, data->conf.accel_odr, data->conf.accel_uodr); + bmi260_set_odr(data, BMI260_GYRO, data->conf.gyro_odr, data->conf.gyro_uodr); + bmi260_enable_irq(data->regmap, data->int_pin, data->conf.irq); + data->conf.suspended = false; + return 0; +} + +static int bmi260_chip_suspend(struct device *dev) { + struct bmi260_data *data = iio_priv(dev_get_drvdata(dev)); + data->conf.suspended = true; + bmi260_get_scale(data, BMI260_ACCEL, &data->conf.accel_scale); + bmi260_get_scale(data, BMI260_GYRO, &data->conf.gyro_scale); + bmi260_get_odr(data, BMI260_ACCEL, &data->conf.accel_odr, &data->conf.accel_uodr); + bmi260_get_odr(data, BMI260_GYRO, &data->conf.gyro_odr, &data->conf.gyro_uodr); + bmi260_chip_uninit(data); + return 0; +} + +EXPORT_GPL_DEV_SLEEP_PM_OPS(bmi260_pm_ops) = { + LATE_SYSTEM_SLEEP_PM_OPS(bmi260_chip_suspend, bmi260_chip_resume) +}; + +MODULE_AUTHOR("Justin Weiss "); +MODULE_DESCRIPTION("Bosch BMI260 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/custom/bmi260/bmi260_i2c.c b/drivers/custom/bmi260/bmi260_i2c.c new file mode 100644 index 000000000000..23c6233239a0 --- /dev/null +++ b/drivers/custom/bmi260/bmi260_i2c.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * I2C driver for Bosch BMI260 IMU. + * + * Copyright (C) 2023, Justin Weiss + */ +#include +#include +#include +#include +#include + +#include "bmi260.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +static int bmi260_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); +#else +static int bmi260_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ +#endif + struct regmap *regmap; + const char *name; + + regmap = devm_regmap_init_i2c(client, &bmi260_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap: %pe\n", + regmap); + return PTR_ERR(regmap); + } + + if (id) + name = id->name; + else + name = dev_name(&client->dev); + + return bmi260_core_probe(&client->dev, regmap, client->irq, name, false); +} + +static const struct i2c_device_id bmi260_i2c_id[] = { + {"bmi260", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, bmi260_i2c_id); + +static const struct acpi_device_id bmi260_acpi_match[] = { + {"BOSC0260", 0}, + {"BMI0260", 0}, + {"BOSC0160", 0}, + {"BMI0160", 0}, + {"10EC5280", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, bmi260_acpi_match); + +static const struct of_device_id bmi260_of_match[] = { + { .compatible = "bosch,bmi260" }, + { }, +}; +MODULE_DEVICE_TABLE(of, bmi260_of_match); + +static struct i2c_driver bmi260_i2c_driver = { + .driver = { + .name = "bmi260_i2c", + .acpi_match_table = bmi260_acpi_match, + .of_match_table = bmi260_of_match, + .pm = &bmi260_pm_ops, + }, + .probe = bmi260_i2c_probe, + .id_table = bmi260_i2c_id, +}; +module_i2c_driver(bmi260_i2c_driver); + +MODULE_AUTHOR("Justin Weiss "); +MODULE_DESCRIPTION("BMI260 I2C driver"); +MODULE_LICENSE("GPL v2"); +BMI260_IMPORT_NS; # ---------------------------------------- # Module: broadcom-wl # Version: c747d6c228f5 # ---------------------------------------- diff --git a/drivers/custom/broadcom-wl/Makefile b/drivers/custom/broadcom-wl/Makefile new file mode 100644 index 000000000000..781ec6bad37c --- /dev/null +++ b/drivers/custom/broadcom-wl/Makefile @@ -0,0 +1,73 @@ +# +# Makefile fragment for Linux 2.6 +# Broadcom 802.11abg Networking Device Driver +# +# Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# $Id: Makefile_kbuild_portsrc 580354 2015-08-18 23:42:37Z $ + +subdir-ccflags-y += -DUSE_CFG80211 + +obj-m += wl.o + +wl-objs := +wl-objs += src/shared/linux_osl.o +wl-objs += src/wl/sys/wl_linux.o +wl-objs += src/wl/sys/wl_iw.o +wl-objs += src/wl/sys/wl_cfg80211_hybrid.o + +subdir-ccflags-y += -I$(src)/src/include -I$(src)/src/common/include +subdir-ccflags-y += -I$(src)/src/wl/sys -I$(src)/src/wl/phy -I$(src)/src/wl/ppr/include +subdir-ccflags-y += -I$(src)/src/shared/bcmwifi/include +#ccflags-y += -DBCMDBG_ASSERT -DBCMDBG_ERR + +ldflags-y := $(src)/lib/wlc_hybrid.o_shipped + +KBASE ?= /lib/modules/`uname -r` +KBUILD_DIR ?= $(KBASE)/build +MDEST_DIR ?= $(KBASE)/kernel/drivers/net/wireless + +# Cross compile setup. Tool chain and kernel tree, replace with your own. +CROSS_TOOLS = /path/to/tools +CROSS_KBUILD_DIR = /path/to/kernel/tree + +# Rel. commit "objtool: Always fail on fatal errors" (Josh Poimboeuf, 31 Mar 2025) +# This is a *ugly* hack to disable objtool during the final processing of wl.o. +# Since is embeds the proprietary blob (wlc_hybrid.o_shipped), objtool can't +# process it, as it does not follow the requirements of current kernels, +# including support for critical security features. As of Linux v6.15+, it causes +# a build error. Disable it, at your own risk. Note the MIT license applies: +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +wl.o: override objtool-enabled = +# Custom option, cannot be disabled during build otherwise +objtool-args-y += --skip-check + +all: + KBUILD_NOPEDANTIC=1 make -C $(KBUILD_DIR) M=`pwd` + +cross: + KBUILD_NOPEDANTIC=1 make CROSS_COMPILE=${CROSS_TOOLS} -C $(CROSS_KBUILD_DIR) M=`pwd` + +clean: + KBUILD_NOPEDANTIC=1 make -C $(KBUILD_DIR) M=`pwd` clean + +install: + install -D -m 755 wl.ko $(MDEST_DIR) diff --git a/drivers/custom/broadcom-wl/src/common/include/proto/802.11.h b/drivers/custom/broadcom-wl/src/common/include/proto/802.11.h new file mode 100644 index 000000000000..5b0ccb89a9d2 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/common/include/proto/802.11.h @@ -0,0 +1,3081 @@ +/* + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Fundamental types and constants relating to 802.11 + * + * $Id: 802.11.h 394280 2013-04-01 23:04:24Z $ + */ + +#ifndef _802_11_H_ +#define _802_11_H_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + +#ifndef _NET_ETHERNET_H_ +#include +#endif + +#include + +#include + +#define DOT11_TU_TO_US 1024 + +#define DOT11_A3_HDR_LEN 24 +#define DOT11_A4_HDR_LEN 30 +#define DOT11_MAC_HDR_LEN DOT11_A3_HDR_LEN +#define DOT11_FCS_LEN 4 +#define DOT11_ICV_LEN 4 +#define DOT11_ICV_AES_LEN 8 +#define DOT11_QOS_LEN 2 +#define DOT11_HTC_LEN 4 + +#define DOT11_KEY_INDEX_SHIFT 6 +#define DOT11_IV_LEN 4 +#define DOT11_IV_TKIP_LEN 8 +#define DOT11_IV_AES_OCB_LEN 4 +#define DOT11_IV_AES_CCM_LEN 8 +#define DOT11_IV_MAX_LEN 8 + +#define DOT11_MAX_MPDU_BODY_LEN 2304 + +#define DOT11_MAX_MPDU_LEN (DOT11_A4_HDR_LEN + \ + DOT11_QOS_LEN + \ + DOT11_IV_AES_CCM_LEN + \ + DOT11_MAX_MPDU_BODY_LEN + \ + DOT11_ICV_LEN + \ + DOT11_FCS_LEN) + +#define DOT11_MAX_SSID_LEN 32 + +#define DOT11_DEFAULT_RTS_LEN 2347 +#define DOT11_MAX_RTS_LEN 2347 + +#define DOT11_MIN_FRAG_LEN 256 +#define DOT11_MAX_FRAG_LEN 2346 +#define DOT11_DEFAULT_FRAG_LEN 2346 + +#define DOT11_MIN_BEACON_PERIOD 1 +#define DOT11_MAX_BEACON_PERIOD 0xFFFF + +#define DOT11_MIN_DTIM_PERIOD 1 +#define DOT11_MAX_DTIM_PERIOD 0xFF + +#define DOT11_LLC_SNAP_HDR_LEN 8 +#define DOT11_OUI_LEN 3 +BWL_PRE_PACKED_STRUCT struct dot11_llc_snap_header { + uint8 dsap; + uint8 ssap; + uint8 ctl; + uint8 oui[DOT11_OUI_LEN]; + uint16 type; +} BWL_POST_PACKED_STRUCT; + +#define RFC1042_HDR_LEN (ETHER_HDR_LEN + DOT11_LLC_SNAP_HDR_LEN) + +BWL_PRE_PACKED_STRUCT struct dot11_header { + uint16 fc; + uint16 durid; + struct ether_addr a1; + struct ether_addr a2; + struct ether_addr a3; + uint16 seq; + struct ether_addr a4; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct dot11_rts_frame { + uint16 fc; + uint16 durid; + struct ether_addr ra; + struct ether_addr ta; +} BWL_POST_PACKED_STRUCT; +#define DOT11_RTS_LEN 16 + +BWL_PRE_PACKED_STRUCT struct dot11_cts_frame { + uint16 fc; + uint16 durid; + struct ether_addr ra; +} BWL_POST_PACKED_STRUCT; +#define DOT11_CTS_LEN 10 + +BWL_PRE_PACKED_STRUCT struct dot11_ack_frame { + uint16 fc; + uint16 durid; + struct ether_addr ra; +} BWL_POST_PACKED_STRUCT; +#define DOT11_ACK_LEN 10 + +BWL_PRE_PACKED_STRUCT struct dot11_ps_poll_frame { + uint16 fc; + uint16 durid; + struct ether_addr bssid; + struct ether_addr ta; +} BWL_POST_PACKED_STRUCT; +#define DOT11_PS_POLL_LEN 16 + +BWL_PRE_PACKED_STRUCT struct dot11_cf_end_frame { + uint16 fc; + uint16 durid; + struct ether_addr ra; + struct ether_addr bssid; +} BWL_POST_PACKED_STRUCT; +#define DOT11_CS_END_LEN 16 + +BWL_PRE_PACKED_STRUCT struct dot11_action_wifi_vendor_specific { + uint8 category; + uint8 OUI[3]; + uint8 type; + uint8 subtype; + uint8 data[1040]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_action_wifi_vendor_specific dot11_action_wifi_vendor_specific_t; + +BWL_PRE_PACKED_STRUCT struct dot11_action_vs_frmhdr { + uint8 category; + uint8 OUI[3]; + uint8 type; + uint8 subtype; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_action_vs_frmhdr dot11_action_vs_frmhdr_t; +#define DOT11_ACTION_VS_HDR_LEN 6 + +#define BCM_ACTION_OUI_BYTE0 0x00 +#define BCM_ACTION_OUI_BYTE1 0x90 +#define BCM_ACTION_OUI_BYTE2 0x4c + +#define DOT11_BA_CTL_POLICY_NORMAL 0x0000 +#define DOT11_BA_CTL_POLICY_NOACK 0x0001 +#define DOT11_BA_CTL_POLICY_MASK 0x0001 + +#define DOT11_BA_CTL_MTID 0x0002 +#define DOT11_BA_CTL_COMPRESSED 0x0004 + +#define DOT11_BA_CTL_NUMMSDU_MASK 0x0FC0 +#define DOT11_BA_CTL_NUMMSDU_SHIFT 6 + +#define DOT11_BA_CTL_TID_MASK 0xF000 +#define DOT11_BA_CTL_TID_SHIFT 12 + +BWL_PRE_PACKED_STRUCT struct dot11_ctl_header { + uint16 fc; + uint16 durid; + struct ether_addr ra; + struct ether_addr ta; +} BWL_POST_PACKED_STRUCT; +#define DOT11_CTL_HDR_LEN 16 + +BWL_PRE_PACKED_STRUCT struct dot11_bar { + uint16 bar_control; + uint16 seqnum; +} BWL_POST_PACKED_STRUCT; +#define DOT11_BAR_LEN 4 + +#define DOT11_BA_BITMAP_LEN 128 +#define DOT11_BA_CMP_BITMAP_LEN 8 + +BWL_PRE_PACKED_STRUCT struct dot11_ba { + uint16 ba_control; + uint16 seqnum; + uint8 bitmap[DOT11_BA_BITMAP_LEN]; +} BWL_POST_PACKED_STRUCT; +#define DOT11_BA_LEN 4 + +BWL_PRE_PACKED_STRUCT struct dot11_management_header { + uint16 fc; + uint16 durid; + struct ether_addr da; + struct ether_addr sa; + struct ether_addr bssid; + uint16 seq; +} BWL_POST_PACKED_STRUCT; +#define DOT11_MGMT_HDR_LEN 24 + +BWL_PRE_PACKED_STRUCT struct dot11_bcn_prb { + uint32 timestamp[2]; + uint16 beacon_interval; + uint16 capability; +} BWL_POST_PACKED_STRUCT; +#define DOT11_BCN_PRB_LEN 12 +#define DOT11_BCN_PRB_FIXED_LEN 12 + +BWL_PRE_PACKED_STRUCT struct dot11_auth { + uint16 alg; + uint16 seq; + uint16 status; +} BWL_POST_PACKED_STRUCT; +#define DOT11_AUTH_FIXED_LEN 6 + +BWL_PRE_PACKED_STRUCT struct dot11_assoc_req { + uint16 capability; + uint16 listen; +} BWL_POST_PACKED_STRUCT; +#define DOT11_ASSOC_REQ_FIXED_LEN 4 + +BWL_PRE_PACKED_STRUCT struct dot11_reassoc_req { + uint16 capability; + uint16 listen; + struct ether_addr ap; +} BWL_POST_PACKED_STRUCT; +#define DOT11_REASSOC_REQ_FIXED_LEN 10 + +BWL_PRE_PACKED_STRUCT struct dot11_assoc_resp { + uint16 capability; + uint16 status; + uint16 aid; +} BWL_POST_PACKED_STRUCT; +#define DOT11_ASSOC_RESP_FIXED_LEN 6 + +BWL_PRE_PACKED_STRUCT struct dot11_action_measure { + uint8 category; + uint8 action; + uint8 token; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +#define DOT11_ACTION_MEASURE_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_action_ht_ch_width { + uint8 category; + uint8 action; + uint8 ch_width; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct dot11_action_ht_mimops { + uint8 category; + uint8 action; + uint8 control; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct dot11_action_sa_query { + uint8 category; + uint8 action; + uint16 id; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct dot11_action_vht_oper_mode { + uint8 category; + uint8 action; + uint8 mode; +} BWL_POST_PACKED_STRUCT; + +#define SM_PWRSAVE_ENABLE 1 +#define SM_PWRSAVE_MODE 2 + +BWL_PRE_PACKED_STRUCT struct dot11_power_cnst { + uint8 id; + uint8 len; + uint8 power; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_power_cnst dot11_power_cnst_t; + +BWL_PRE_PACKED_STRUCT struct dot11_power_cap { + uint8 min; + uint8 max; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_power_cap dot11_power_cap_t; + +BWL_PRE_PACKED_STRUCT struct dot11_tpc_rep { + uint8 id; + uint8 len; + uint8 tx_pwr; + uint8 margin; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tpc_rep dot11_tpc_rep_t; +#define DOT11_MNG_IE_TPC_REPORT_LEN 2 + +BWL_PRE_PACKED_STRUCT struct dot11_supp_channels { + uint8 id; + uint8 len; + uint8 first_channel; + uint8 num_channels; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_supp_channels dot11_supp_channels_t; + +BWL_PRE_PACKED_STRUCT struct dot11_extch { + uint8 id; + uint8 len; + uint8 extch; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_extch dot11_extch_ie_t; + +BWL_PRE_PACKED_STRUCT struct dot11_brcm_extch { + uint8 id; + uint8 len; + uint8 oui[3]; + uint8 type; + uint8 extch; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_brcm_extch dot11_brcm_extch_ie_t; + +#define BRCM_EXTCH_IE_LEN 5 +#define BRCM_EXTCH_IE_TYPE 53 +#define DOT11_EXTCH_IE_LEN 1 +#define DOT11_EXT_CH_MASK 0x03 +#define DOT11_EXT_CH_UPPER 0x01 +#define DOT11_EXT_CH_LOWER 0x03 +#define DOT11_EXT_CH_NONE 0x00 + +BWL_PRE_PACKED_STRUCT struct dot11_action_frmhdr { + uint8 category; + uint8 action; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +#define DOT11_ACTION_FRMHDR_LEN 2 + +BWL_PRE_PACKED_STRUCT struct dot11_channel_switch { + uint8 id; + uint8 len; + uint8 mode; + uint8 channel; + uint8 count; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_channel_switch dot11_chan_switch_ie_t; + +#define DOT11_SWITCH_IE_LEN 3 + +#define DOT11_CSA_MODE_ADVISORY 0 +#define DOT11_CSA_MODE_NO_TX 1 + +BWL_PRE_PACKED_STRUCT struct dot11_action_switch_channel { + uint8 category; + uint8 action; + dot11_chan_switch_ie_t chan_switch_ie; + dot11_brcm_extch_ie_t extch_ie; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct dot11_csa_body { + uint8 mode; + uint8 reg; + uint8 channel; + uint8 count; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct dot11_ext_csa { + uint8 id; + uint8 len; + struct dot11_csa_body b; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_ext_csa dot11_ext_csa_ie_t; +#define DOT11_EXT_CSA_IE_LEN 4 + +BWL_PRE_PACKED_STRUCT struct dot11_action_ext_csa { + uint8 category; + uint8 action; + dot11_ext_csa_ie_t chan_switch_ie; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct dot11y_action_ext_csa { + uint8 category; + uint8 action; + struct dot11_csa_body b; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct dot11_wide_bw_channel_switch { + uint8 id; + uint8 len; + uint8 channel_width; + uint8 center_frequency_segment_0; + uint8 center_frequency_segment_1; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_wide_bw_channel_switch dot11_wide_bw_chan_switch_ie_t; + +#define DOT11_WIDE_BW_SWITCH_IE_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_channel_switch_wrapper { + uint8 id; + uint8 len; + dot11_wide_bw_chan_switch_ie_t wb_chan_switch_ie; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_channel_switch_wrapper dot11_chan_switch_wrapper_ie_t; + +BWL_PRE_PACKED_STRUCT struct dot11_vht_transmit_power_envelope { + uint8 id; + uint8 len; + uint8 transmit_power_info; + uint8 local_max_transmit_power_20; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_vht_transmit_power_envelope dot11_vht_transmit_power_envelope_ie_t; + +BWL_PRE_PACKED_STRUCT struct dot11_obss_coex { + uint8 id; + uint8 len; + uint8 info; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_obss_coex dot11_obss_coex_t; +#define DOT11_OBSS_COEXINFO_LEN 1 + +#define DOT11_OBSS_COEX_INFO_REQ 0x01 +#define DOT11_OBSS_COEX_40MHZ_INTOLERANT 0x02 +#define DOT11_OBSS_COEX_20MHZ_WIDTH_REQ 0x04 + +BWL_PRE_PACKED_STRUCT struct dot11_obss_chanlist { + uint8 id; + uint8 len; + uint8 regclass; + uint8 chanlist[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_obss_chanlist dot11_obss_chanlist_t; +#define DOT11_OBSS_CHANLIST_FIXED_LEN 1 + +BWL_PRE_PACKED_STRUCT struct dot11_extcap_ie { + uint8 id; + uint8 len; + uint8 cap[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_extcap_ie dot11_extcap_ie_t; + +#define DOT11_EXTCAP_LEN_MAX 8 +#define DOT11_EXTCAP_LEN_COEX 1 +#define DOT11_EXTCAP_LEN_BT 3 +#define DOT11_EXTCAP_LEN_IW 4 +#define DOT11_EXTCAP_LEN_SI 6 + +#define DOT11_EXTCAP_LEN_TDLS 5 +#define DOT11_11AC_EXTCAP_LEN_TDLS 8 + +#define DOT11_EXTCAP_LEN_FMS 2 +#define DOT11_EXTCAP_LEN_PROXY_ARP 2 +#define DOT11_EXTCAP_LEN_TFS 3 +#define DOT11_EXTCAP_LEN_WNM_SLEEP 3 +#define DOT11_EXTCAP_LEN_TIMBC 3 +#define DOT11_EXTCAP_LEN_BSS_TRANSITION 3 +#define DOT11_EXTCAP_LEN_DMS 4 +#define DOT11_EXTCAP_LEN_WNM_NOTIFICATION 6 +#define DOT11_EXTCAP_LEN_TDLS_WBW 8 +#define DOT11_EXTCAP_LEN_OPMODE_NOTIFICATION 8 + +BWL_PRE_PACKED_STRUCT struct dot11_extcap { + uint8 extcap[DOT11_EXTCAP_LEN_MAX]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_extcap dot11_extcap_t; + +#define DOT11_TDLS_CAP_TDLS 37 +#define DOT11_TDLS_CAP_PU_BUFFER_STA 28 +#define DOT11_TDLS_CAP_PEER_PSM 20 +#define DOT11_TDLS_CAP_CH_SW 30 +#define DOT11_TDLS_CAP_PROH 38 +#define DOT11_TDLS_CAP_CH_SW_PROH 39 +#define DOT11_TDLS_CAP_TDLS_WIDER_BW 61 + +#define DOT11_MEASURE_TYPE_BASIC 0 +#define DOT11_MEASURE_TYPE_CCA 1 +#define DOT11_MEASURE_TYPE_RPI 2 +#define DOT11_MEASURE_TYPE_CHLOAD 3 +#define DOT11_MEASURE_TYPE_NOISE 4 +#define DOT11_MEASURE_TYPE_BEACON 5 +#define DOT11_MEASURE_TYPE_FRAME 6 +#define DOT11_MEASURE_TYPE_STAT 7 +#define DOT11_MEASURE_TYPE_LCI 8 +#define DOT11_MEASURE_TYPE_TXSTREAM 9 +#define DOT11_MEASURE_TYPE_PAUSE 255 + +#define DOT11_MEASURE_MODE_PARALLEL (1<<0) +#define DOT11_MEASURE_MODE_ENABLE (1<<1) +#define DOT11_MEASURE_MODE_REQUEST (1<<2) +#define DOT11_MEASURE_MODE_REPORT (1<<3) +#define DOT11_MEASURE_MODE_DUR (1<<4) + +#define DOT11_MEASURE_MODE_LATE (1<<0) +#define DOT11_MEASURE_MODE_INCAPABLE (1<<1) +#define DOT11_MEASURE_MODE_REFUSED (1<<2) + +#define DOT11_MEASURE_BASIC_MAP_BSS ((uint8)(1<<0)) +#define DOT11_MEASURE_BASIC_MAP_OFDM ((uint8)(1<<1)) +#define DOT11_MEASURE_BASIC_MAP_UKNOWN ((uint8)(1<<2)) +#define DOT11_MEASURE_BASIC_MAP_RADAR ((uint8)(1<<3)) +#define DOT11_MEASURE_BASIC_MAP_UNMEAS ((uint8)(1<<4)) + +BWL_PRE_PACKED_STRUCT struct dot11_meas_req { + uint8 id; + uint8 len; + uint8 token; + uint8 mode; + uint8 type; + uint8 channel; + uint8 start_time[8]; + uint16 duration; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_meas_req dot11_meas_req_t; +#define DOT11_MNG_IE_MREQ_LEN 14 + +#define DOT11_MNG_IE_MREQ_FIXED_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_meas_rep { + uint8 id; + uint8 len; + uint8 token; + uint8 mode; + uint8 type; + BWL_PRE_PACKED_STRUCT union + { + BWL_PRE_PACKED_STRUCT struct { + uint8 channel; + uint8 start_time[8]; + uint16 duration; + uint8 map; + } BWL_POST_PACKED_STRUCT basic; + uint8 data[1]; + } BWL_POST_PACKED_STRUCT rep; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_meas_rep dot11_meas_rep_t; + +#define DOT11_MNG_IE_MREP_FIXED_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_meas_rep_basic { + uint8 channel; + uint8 start_time[8]; + uint16 duration; + uint8 map; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_meas_rep_basic dot11_meas_rep_basic_t; +#define DOT11_MEASURE_BASIC_REP_LEN 12 + +BWL_PRE_PACKED_STRUCT struct dot11_quiet { + uint8 id; + uint8 len; + uint8 count; + uint8 period; + uint16 duration; + uint16 offset; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_quiet dot11_quiet_t; + +BWL_PRE_PACKED_STRUCT struct chan_map_tuple { + uint8 channel; + uint8 map; +} BWL_POST_PACKED_STRUCT; +typedef struct chan_map_tuple chan_map_tuple_t; + +BWL_PRE_PACKED_STRUCT struct dot11_ibss_dfs { + uint8 id; + uint8 len; + uint8 eaddr[ETHER_ADDR_LEN]; + uint8 interval; + chan_map_tuple_t map[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_ibss_dfs dot11_ibss_dfs_t; + +#define WME_OUI "\x00\x50\xf2" +#define WME_OUI_LEN 3 +#define WME_OUI_TYPE 2 +#define WME_TYPE 2 +#define WME_SUBTYPE_IE 0 +#define WME_SUBTYPE_PARAM_IE 1 +#define WME_SUBTYPE_TSPEC 2 +#define WME_VER 1 + +#define AC_BE 0 +#define AC_BK 1 +#define AC_VI 2 +#define AC_VO 3 +#define AC_COUNT 4 + +typedef uint8 ac_bitmap_t; + +#define AC_BITMAP_NONE 0x0 +#define AC_BITMAP_ALL 0xf +#define AC_BITMAP_TST(ab, ac) (((ab) & (1 << (ac))) != 0) +#define AC_BITMAP_SET(ab, ac) (((ab) |= (1 << (ac)))) +#define AC_BITMAP_RESET(ab, ac) (((ab) &= ~(1 << (ac)))) + +BWL_PRE_PACKED_STRUCT struct wme_ie { + uint8 oui[3]; + uint8 type; + uint8 subtype; + uint8 version; + uint8 qosinfo; +} BWL_POST_PACKED_STRUCT; +typedef struct wme_ie wme_ie_t; +#define WME_IE_LEN 7 + +BWL_PRE_PACKED_STRUCT struct edcf_acparam { + uint8 ACI; + uint8 ECW; + uint16 TXOP; +} BWL_POST_PACKED_STRUCT; +typedef struct edcf_acparam edcf_acparam_t; + +BWL_PRE_PACKED_STRUCT struct wme_param_ie { + uint8 oui[3]; + uint8 type; + uint8 subtype; + uint8 version; + uint8 qosinfo; + uint8 rsvd; + edcf_acparam_t acparam[AC_COUNT]; +} BWL_POST_PACKED_STRUCT; +typedef struct wme_param_ie wme_param_ie_t; +#define WME_PARAM_IE_LEN 24 + +#define WME_QI_AP_APSD_MASK 0x80 +#define WME_QI_AP_APSD_SHIFT 7 +#define WME_QI_AP_COUNT_MASK 0x0f +#define WME_QI_AP_COUNT_SHIFT 0 + +#define WME_QI_STA_MAXSPLEN_MASK 0x60 +#define WME_QI_STA_MAXSPLEN_SHIFT 5 +#define WME_QI_STA_APSD_ALL_MASK 0xf +#define WME_QI_STA_APSD_ALL_SHIFT 0 +#define WME_QI_STA_APSD_BE_MASK 0x8 +#define WME_QI_STA_APSD_BE_SHIFT 3 +#define WME_QI_STA_APSD_BK_MASK 0x4 +#define WME_QI_STA_APSD_BK_SHIFT 2 +#define WME_QI_STA_APSD_VI_MASK 0x2 +#define WME_QI_STA_APSD_VI_SHIFT 1 +#define WME_QI_STA_APSD_VO_MASK 0x1 +#define WME_QI_STA_APSD_VO_SHIFT 0 + +#define EDCF_AIFSN_MIN 1 +#define EDCF_AIFSN_MAX 15 +#define EDCF_AIFSN_MASK 0x0f +#define EDCF_ACM_MASK 0x10 +#define EDCF_ACI_MASK 0x60 +#define EDCF_ACI_SHIFT 5 +#define EDCF_AIFSN_SHIFT 12 + +#define EDCF_ECW_MIN 0 +#define EDCF_ECW_MAX 15 +#define EDCF_ECW2CW(exp) ((1 << (exp)) - 1) +#define EDCF_ECWMIN_MASK 0x0f +#define EDCF_ECWMAX_MASK 0xf0 +#define EDCF_ECWMAX_SHIFT 4 + +#define EDCF_TXOP_MIN 0 +#define EDCF_TXOP_MAX 65535 +#define EDCF_TXOP2USEC(txop) ((txop) << 5) + +#define NON_EDCF_AC_BE_ACI_STA 0x02 + +#define EDCF_AC_BE_ACI_STA 0x03 +#define EDCF_AC_BE_ECW_STA 0xA4 +#define EDCF_AC_BE_TXOP_STA 0x0000 +#define EDCF_AC_BK_ACI_STA 0x27 +#define EDCF_AC_BK_ECW_STA 0xA4 +#define EDCF_AC_BK_TXOP_STA 0x0000 +#define EDCF_AC_VI_ACI_STA 0x42 +#define EDCF_AC_VI_ECW_STA 0x43 +#define EDCF_AC_VI_TXOP_STA 0x005e +#define EDCF_AC_VO_ACI_STA 0x62 +#define EDCF_AC_VO_ECW_STA 0x32 +#define EDCF_AC_VO_TXOP_STA 0x002f + +#define EDCF_AC_BE_ACI_AP 0x03 +#define EDCF_AC_BE_ECW_AP 0x64 +#define EDCF_AC_BE_TXOP_AP 0x0000 +#define EDCF_AC_BK_ACI_AP 0x27 +#define EDCF_AC_BK_ECW_AP 0xA4 +#define EDCF_AC_BK_TXOP_AP 0x0000 +#define EDCF_AC_VI_ACI_AP 0x41 +#define EDCF_AC_VI_ECW_AP 0x43 +#define EDCF_AC_VI_TXOP_AP 0x005e +#define EDCF_AC_VO_ACI_AP 0x61 +#define EDCF_AC_VO_ECW_AP 0x32 +#define EDCF_AC_VO_TXOP_AP 0x002f + +BWL_PRE_PACKED_STRUCT struct edca_param_ie { + uint8 qosinfo; + uint8 rsvd; + edcf_acparam_t acparam[AC_COUNT]; +} BWL_POST_PACKED_STRUCT; +typedef struct edca_param_ie edca_param_ie_t; +#define EDCA_PARAM_IE_LEN 18 + +BWL_PRE_PACKED_STRUCT struct qos_cap_ie { + uint8 qosinfo; +} BWL_POST_PACKED_STRUCT; +typedef struct qos_cap_ie qos_cap_ie_t; + +BWL_PRE_PACKED_STRUCT struct dot11_qbss_load_ie { + uint8 id; + uint8 length; + uint16 station_count; + uint8 channel_utilization; + uint16 aac; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_qbss_load_ie dot11_qbss_load_ie_t; +#define BSS_LOAD_IE_SIZE 7 + +#define FIXED_MSDU_SIZE 0x8000 +#define MSDU_SIZE_MASK 0x7fff + +#define INTEGER_SHIFT 13 +#define FRACTION_MASK 0x1FFF + +BWL_PRE_PACKED_STRUCT struct dot11_management_notification { + uint8 category; + uint8 action; + uint8 token; + uint8 status; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +#define DOT11_MGMT_NOTIFICATION_LEN 4 + +BWL_PRE_PACKED_STRUCT struct ti_ie { + uint8 ti_type; + uint32 ti_val; +} BWL_POST_PACKED_STRUCT; +typedef struct ti_ie ti_ie_t; +#define TI_TYPE_REASSOC_DEADLINE 1 +#define TI_TYPE_KEY_LIFETIME 2 + +#define WME_ADDTS_REQUEST 0 +#define WME_ADDTS_RESPONSE 1 +#define WME_DELTS_REQUEST 2 + +#define WME_ADMISSION_ACCEPTED 0 +#define WME_INVALID_PARAMETERS 1 +#define WME_ADMISSION_REFUSED 3 + +#define BCN_PRB_SSID(body) ((char*)(body) + DOT11_BCN_PRB_LEN) + +#define DOT11_OPEN_SYSTEM 0 +#define DOT11_SHARED_KEY 1 +#define DOT11_FAST_BSS 2 +#define DOT11_CHALLENGE_LEN 128 + +#define FC_PVER_MASK 0x3 +#define FC_PVER_SHIFT 0 +#define FC_TYPE_MASK 0xC +#define FC_TYPE_SHIFT 2 +#define FC_SUBTYPE_MASK 0xF0 +#define FC_SUBTYPE_SHIFT 4 +#define FC_TODS 0x100 +#define FC_TODS_SHIFT 8 +#define FC_FROMDS 0x200 +#define FC_FROMDS_SHIFT 9 +#define FC_MOREFRAG 0x400 +#define FC_MOREFRAG_SHIFT 10 +#define FC_RETRY 0x800 +#define FC_RETRY_SHIFT 11 +#define FC_PM 0x1000 +#define FC_PM_SHIFT 12 +#define FC_MOREDATA 0x2000 +#define FC_MOREDATA_SHIFT 13 +#define FC_WEP 0x4000 +#define FC_WEP_SHIFT 14 +#define FC_ORDER 0x8000 +#define FC_ORDER_SHIFT 15 + +#define SEQNUM_SHIFT 4 +#define SEQNUM_MAX 0x1000 +#define FRAGNUM_MASK 0xF + +#define FC_TYPE_MNG 0 +#define FC_TYPE_CTL 1 +#define FC_TYPE_DATA 2 + +#define FC_SUBTYPE_ASSOC_REQ 0 +#define FC_SUBTYPE_ASSOC_RESP 1 +#define FC_SUBTYPE_REASSOC_REQ 2 +#define FC_SUBTYPE_REASSOC_RESP 3 +#define FC_SUBTYPE_PROBE_REQ 4 +#define FC_SUBTYPE_PROBE_RESP 5 +#define FC_SUBTYPE_BEACON 8 +#define FC_SUBTYPE_ATIM 9 +#define FC_SUBTYPE_DISASSOC 10 +#define FC_SUBTYPE_AUTH 11 +#define FC_SUBTYPE_DEAUTH 12 +#define FC_SUBTYPE_ACTION 13 +#define FC_SUBTYPE_ACTION_NOACK 14 + +#define FC_SUBTYPE_CTL_WRAPPER 7 +#define FC_SUBTYPE_BLOCKACK_REQ 8 +#define FC_SUBTYPE_BLOCKACK 9 +#define FC_SUBTYPE_PS_POLL 10 +#define FC_SUBTYPE_RTS 11 +#define FC_SUBTYPE_CTS 12 +#define FC_SUBTYPE_ACK 13 +#define FC_SUBTYPE_CF_END 14 +#define FC_SUBTYPE_CF_END_ACK 15 + +#define FC_SUBTYPE_DATA 0 +#define FC_SUBTYPE_DATA_CF_ACK 1 +#define FC_SUBTYPE_DATA_CF_POLL 2 +#define FC_SUBTYPE_DATA_CF_ACK_POLL 3 +#define FC_SUBTYPE_NULL 4 +#define FC_SUBTYPE_CF_ACK 5 +#define FC_SUBTYPE_CF_POLL 6 +#define FC_SUBTYPE_CF_ACK_POLL 7 +#define FC_SUBTYPE_QOS_DATA 8 +#define FC_SUBTYPE_QOS_DATA_CF_ACK 9 +#define FC_SUBTYPE_QOS_DATA_CF_POLL 10 +#define FC_SUBTYPE_QOS_DATA_CF_ACK_POLL 11 +#define FC_SUBTYPE_QOS_NULL 12 +#define FC_SUBTYPE_QOS_CF_POLL 14 +#define FC_SUBTYPE_QOS_CF_ACK_POLL 15 + +#define FC_SUBTYPE_ANY_QOS(s) (((s) & 8) != 0) +#define FC_SUBTYPE_ANY_NULL(s) (((s) & 4) != 0) +#define FC_SUBTYPE_ANY_CF_POLL(s) (((s) & 2) != 0) +#define FC_SUBTYPE_ANY_CF_ACK(s) (((s) & 1) != 0) + +#define FC_KIND_MASK (FC_TYPE_MASK | FC_SUBTYPE_MASK) + +#define FC_KIND(t, s) (((t) << FC_TYPE_SHIFT) | ((s) << FC_SUBTYPE_SHIFT)) + +#define FC_SUBTYPE(fc) (((fc) & FC_SUBTYPE_MASK) >> FC_SUBTYPE_SHIFT) +#define FC_TYPE(fc) (((fc) & FC_TYPE_MASK) >> FC_TYPE_SHIFT) + +#define FC_ASSOC_REQ FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_ASSOC_REQ) +#define FC_ASSOC_RESP FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_ASSOC_RESP) +#define FC_REASSOC_REQ FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_REASSOC_REQ) +#define FC_REASSOC_RESP FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_REASSOC_RESP) +#define FC_PROBE_REQ FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_PROBE_REQ) +#define FC_PROBE_RESP FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_PROBE_RESP) +#define FC_BEACON FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_BEACON) +#define FC_DISASSOC FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_DISASSOC) +#define FC_AUTH FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_AUTH) +#define FC_DEAUTH FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_DEAUTH) +#define FC_ACTION FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_ACTION) +#define FC_ACTION_NOACK FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_ACTION_NOACK) + +#define FC_CTL_WRAPPER FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_CTL_WRAPPER) +#define FC_BLOCKACK_REQ FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_BLOCKACK_REQ) +#define FC_BLOCKACK FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_BLOCKACK) +#define FC_PS_POLL FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_PS_POLL) +#define FC_RTS FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_RTS) +#define FC_CTS FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_CTS) +#define FC_ACK FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_ACK) +#define FC_CF_END FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_CF_END) +#define FC_CF_END_ACK FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_CF_END_ACK) + +#define FC_DATA FC_KIND(FC_TYPE_DATA, FC_SUBTYPE_DATA) +#define FC_NULL_DATA FC_KIND(FC_TYPE_DATA, FC_SUBTYPE_NULL) +#define FC_DATA_CF_ACK FC_KIND(FC_TYPE_DATA, FC_SUBTYPE_DATA_CF_ACK) +#define FC_QOS_DATA FC_KIND(FC_TYPE_DATA, FC_SUBTYPE_QOS_DATA) +#define FC_QOS_NULL FC_KIND(FC_TYPE_DATA, FC_SUBTYPE_QOS_NULL) + +#define QOS_PRIO_SHIFT 0 +#define QOS_PRIO_MASK 0x0007 +#define QOS_PRIO(qos) (((qos) & QOS_PRIO_MASK) >> QOS_PRIO_SHIFT) + +#define QOS_TID_SHIFT 0 +#define QOS_TID_MASK 0x000f +#define QOS_TID(qos) (((qos) & QOS_TID_MASK) >> QOS_TID_SHIFT) + +#define QOS_EOSP_SHIFT 4 +#define QOS_EOSP_MASK 0x0010 +#define QOS_EOSP(qos) (((qos) & QOS_EOSP_MASK) >> QOS_EOSP_SHIFT) + +#define QOS_ACK_NORMAL_ACK 0 +#define QOS_ACK_NO_ACK 1 +#define QOS_ACK_NO_EXP_ACK 2 +#define QOS_ACK_BLOCK_ACK 3 +#define QOS_ACK_SHIFT 5 +#define QOS_ACK_MASK 0x0060 +#define QOS_ACK(qos) (((qos) & QOS_ACK_MASK) >> QOS_ACK_SHIFT) + +#define QOS_AMSDU_SHIFT 7 +#define QOS_AMSDU_MASK 0x0080 + +#define DOT11_MNG_AUTH_ALGO_LEN 2 +#define DOT11_MNG_AUTH_SEQ_LEN 2 +#define DOT11_MNG_BEACON_INT_LEN 2 +#define DOT11_MNG_CAP_LEN 2 +#define DOT11_MNG_AP_ADDR_LEN 6 +#define DOT11_MNG_LISTEN_INT_LEN 2 +#define DOT11_MNG_REASON_LEN 2 +#define DOT11_MNG_AID_LEN 2 +#define DOT11_MNG_STATUS_LEN 2 +#define DOT11_MNG_TIMESTAMP_LEN 8 + +#define DOT11_AID_MASK 0x3fff + +#define DOT11_RC_RESERVED 0 +#define DOT11_RC_UNSPECIFIED 1 +#define DOT11_RC_AUTH_INVAL 2 +#define DOT11_RC_DEAUTH_LEAVING 3 +#define DOT11_RC_INACTIVITY 4 +#define DOT11_RC_BUSY 5 +#define DOT11_RC_INVAL_CLASS_2 6 +#define DOT11_RC_INVAL_CLASS_3 7 +#define DOT11_RC_DISASSOC_LEAVING 8 +#define DOT11_RC_NOT_AUTH 9 +#define DOT11_RC_BAD_PC 10 +#define DOT11_RC_BAD_CHANNELS 11 + +#define DOT11_RC_UNSPECIFIED_QOS 32 +#define DOT11_RC_INSUFFCIENT_BW 33 +#define DOT11_RC_EXCESSIVE_FRAMES 34 +#define DOT11_RC_TX_OUTSIDE_TXOP 35 +#define DOT11_RC_LEAVING_QBSS 36 +#define DOT11_RC_BAD_MECHANISM 37 +#define DOT11_RC_SETUP_NEEDED 38 +#define DOT11_RC_TIMEOUT 39 + +#define DOT11_RC_MAX 23 + +#define DOT11_RC_TDLS_PEER_UNREACH 25 +#define DOT11_RC_TDLS_DOWN_UNSPECIFIED 26 + +#define DOT11_SC_SUCCESS 0 +#define DOT11_SC_FAILURE 1 +#define DOT11_SC_TDLS_WAKEUP_SCH_ALT 2 + +#define DOT11_SC_TDLS_WAKEUP_SCH_REJ 3 +#define DOT11_SC_TDLS_SEC_DISABLED 5 +#define DOT11_SC_LIFETIME_REJ 6 +#define DOT11_SC_NOT_SAME_BSS 7 +#define DOT11_SC_CAP_MISMATCH 10 +#define DOT11_SC_REASSOC_FAIL 11 +#define DOT11_SC_ASSOC_FAIL 12 +#define DOT11_SC_AUTH_MISMATCH 13 +#define DOT11_SC_AUTH_SEQ 14 +#define DOT11_SC_AUTH_CHALLENGE_FAIL 15 +#define DOT11_SC_AUTH_TIMEOUT 16 +#define DOT11_SC_ASSOC_BUSY_FAIL 17 +#define DOT11_SC_ASSOC_RATE_MISMATCH 18 +#define DOT11_SC_ASSOC_SHORT_REQUIRED 19 +#define DOT11_SC_ASSOC_PBCC_REQUIRED 20 +#define DOT11_SC_ASSOC_AGILITY_REQUIRED 21 +#define DOT11_SC_ASSOC_SPECTRUM_REQUIRED 22 +#define DOT11_SC_ASSOC_BAD_POWER_CAP 23 +#define DOT11_SC_ASSOC_BAD_SUP_CHANNELS 24 +#define DOT11_SC_ASSOC_SHORTSLOT_REQUIRED 25 +#define DOT11_SC_ASSOC_ERPBCC_REQUIRED 26 +#define DOT11_SC_ASSOC_DSSOFDM_REQUIRED 27 +#define DOT11_SC_ASSOC_R0KH_UNREACHABLE 28 +#define DOT11_SC_ASSOC_TRY_LATER 30 +#define DOT11_SC_ASSOC_MFP_VIOLATION 31 + +#define DOT11_SC_DECLINED 37 +#define DOT11_SC_INVALID_PARAMS 38 +#define DOT11_SC_INVALID_PAIRWISE_CIPHER 42 +#define DOT11_SC_INVALID_AKMP 43 +#define DOT11_SC_INVALID_RSNIE_CAP 45 +#define DOT11_SC_DLS_NOT_ALLOWED 48 +#define DOT11_SC_INVALID_PMKID 53 +#define DOT11_SC_INVALID_MDID 54 +#define DOT11_SC_INVALID_FTIE 55 + +#define DOT11_SC_ADV_PROTO_NOT_SUPPORTED 59 +#define DOT11_SC_NO_OUTSTAND_REQ 60 +#define DOT11_SC_RSP_NOT_RX_FROM_SERVER 61 +#define DOT11_SC_TIMEOUT 62 +#define DOT11_SC_QUERY_RSP_TOO_LARGE 63 +#define DOT11_SC_SERVER_UNREACHABLE 65 + +#define DOT11_SC_UNEXP_MSG 70 +#define DOT11_SC_INVALID_SNONCE 71 +#define DOT11_SC_INVALID_RSNIE 72 + +#define DOT11_SC_TRANSMIT_FAILURE 79 + +#define DOT11_MNG_DS_PARAM_LEN 1 +#define DOT11_MNG_IBSS_PARAM_LEN 2 + +#define DOT11_MNG_TIM_FIXED_LEN 3 +#define DOT11_MNG_TIM_DTIM_COUNT 0 +#define DOT11_MNG_TIM_DTIM_PERIOD 1 +#define DOT11_MNG_TIM_BITMAP_CTL 2 +#define DOT11_MNG_TIM_PVB 3 + +#define TLV_TAG_OFF 0 +#define TLV_LEN_OFF 1 +#define TLV_HDR_LEN 2 +#define TLV_BODY_OFF 2 + +#define DOT11_MNG_SSID_ID 0 +#define DOT11_MNG_RATES_ID 1 +#define DOT11_MNG_FH_PARMS_ID 2 +#define DOT11_MNG_DS_PARMS_ID 3 +#define DOT11_MNG_CF_PARMS_ID 4 +#define DOT11_MNG_TIM_ID 5 +#define DOT11_MNG_IBSS_PARMS_ID 6 +#define DOT11_MNG_COUNTRY_ID 7 +#define DOT11_MNG_HOPPING_PARMS_ID 8 +#define DOT11_MNG_HOPPING_TABLE_ID 9 +#define DOT11_MNG_REQUEST_ID 10 +#define DOT11_MNG_QBSS_LOAD_ID 11 +#define DOT11_MNG_EDCA_PARAM_ID 12 +#define DOT11_MNG_TSPEC_ID 13 +#define DOT11_MNG_TCLAS_ID 14 +#define DOT11_MNG_CHALLENGE_ID 16 +#define DOT11_MNG_PWR_CONSTRAINT_ID 32 +#define DOT11_MNG_PWR_CAP_ID 33 +#define DOT11_MNG_TPC_REQUEST_ID 34 +#define DOT11_MNG_TPC_REPORT_ID 35 +#define DOT11_MNG_SUPP_CHANNELS_ID 36 +#define DOT11_MNG_CHANNEL_SWITCH_ID 37 +#define DOT11_MNG_MEASURE_REQUEST_ID 38 +#define DOT11_MNG_MEASURE_REPORT_ID 39 +#define DOT11_MNG_QUIET_ID 40 +#define DOT11_MNG_IBSS_DFS_ID 41 +#define DOT11_MNG_ERP_ID 42 +#define DOT11_MNG_TS_DELAY_ID 43 +#define DOT11_MNG_TCLAS_PROC_ID 44 +#define DOT11_MNG_HT_CAP 45 +#define DOT11_MNG_QOS_CAP_ID 46 +#define DOT11_MNG_NONERP_ID 47 +#define DOT11_MNG_RSN_ID 48 +#define DOT11_MNG_EXT_RATES_ID 50 +#define DOT11_MNG_AP_CHREP_ID 51 +#define DOT11_MNG_NBR_REP_ID 52 +#define DOT11_MNG_RCPI_ID 53 +#define DOT11_MNG_MDIE_ID 54 +#define DOT11_MNG_FTIE_ID 55 +#define DOT11_MNG_FT_TI_ID 56 +#define DOT11_MNG_RDE_ID 57 +#define DOT11_MNG_REGCLASS_ID 59 +#define DOT11_MNG_EXT_CSA_ID 60 +#define DOT11_MNG_HT_ADD 61 +#define DOT11_MNG_EXT_CHANNEL_OFFSET 62 +#define DOT11_MNG_BSS_AVR_ACCESS_DELAY_ID 63 +#define DOT11_MNG_ANTENNA_ID 64 +#define DOT11_MNG_RSNI_ID 65 +#define DOT11_MNG_MEASUREMENT_PILOT_TX_ID 66 +#define DOT11_MNG_BSS_AVAL_ADMISSION_CAP_ID 67 +#define DOT11_MNG_BSS_AC_ACCESS_DELAY_ID 68 +#define DOT11_MNG_WAPI_ID 68 +#define DOT11_MNG_TIME_ADVERTISE_ID 69 +#define DOT11_MNG_RRM_CAP_ID 70 +#define DOT11_MNG_MULTIPLE_BSSID_ID 71 +#define DOT11_MNG_HT_BSS_COEXINFO_ID 72 +#define DOT11_MNG_HT_BSS_CHANNEL_REPORT_ID 73 +#define DOT11_MNG_HT_OBSS_ID 74 +#define DOT11_MNG_MMIE_ID 76 +#define DOT11_MNG_BSS_MAX_IDLE_PERIOD_ID 90 +#define DOT11_MNG_TFS_REQUEST_ID 91 +#define DOT11_MNG_TFS_RESPONSE_ID 92 +#define DOT11_MNG_WNM_SLEEP_MODE_ID 93 +#define DOT11_MNG_TIMBC_REQ_ID 94 +#define DOT11_MNG_TIMBC_RESP_ID 95 +#define DOT11_MNG_CHANNEL_USAGE 97 +#define DOT11_MNG_TIME_ZONE_ID 98 +#define DOT11_MNG_DMS_REQUEST_ID 99 +#define DOT11_MNG_DMS_RESPONSE_ID 100 +#define DOT11_MNG_LINK_IDENTIFIER_ID 101 +#define DOT11_MNG_WAKEUP_SCHEDULE_ID 102 +#define DOT11_MNG_CHANNEL_SWITCH_TIMING_ID 104 +#define DOT11_MNG_PTI_CONTROL_ID 105 +#define DOT11_MNG_PU_BUFFER_STATUS_ID 106 +#define DOT11_MNG_INTERWORKING_ID 107 +#define DOT11_MNG_ADVERTISEMENT_ID 108 +#define DOT11_MNG_EXP_BW_REQ_ID 109 +#define DOT11_MNG_QOS_MAP_ID 110 +#define DOT11_MNG_ROAM_CONSORT_ID 111 +#define DOT11_MNG_EMERGCY_ALERT_ID 112 +#define DOT11_MNG_EXT_CAP_ID 127 +#define DOT11_MNG_VHT_CAP_ID 191 +#define DOT11_MNG_VHT_OPERATION_ID 192 +#define DOT11_MNG_WIDE_BW_CHANNEL_SWITCH_ID 194 +#define DOT11_MNG_VHT_TRANSMIT_POWER_ENVELOPE_ID 195 +#define DOT11_MNG_CHANNEL_SWITCH_WRAPPER_ID 196 +#define DOT11_MNG_AID_ID 197 +#define DOT11_MNG_OPER_MODE_NOTIF_ID 199 + +#define DOT11_MNG_WPA_ID 221 +#define DOT11_MNG_PROPR_ID 221 + +#define DOT11_MNG_VS_ID 221 + +#define DOT11_RATE_1M 2 +#define DOT11_RATE_2M 4 +#define DOT11_RATE_5M5 11 +#define DOT11_RATE_11M 22 +#define DOT11_RATE_6M 12 +#define DOT11_RATE_9M 18 +#define DOT11_RATE_12M 24 +#define DOT11_RATE_18M 36 +#define DOT11_RATE_24M 48 +#define DOT11_RATE_36M 72 +#define DOT11_RATE_48M 96 +#define DOT11_RATE_54M 108 +#define DOT11_RATE_MAX 108 + +#define DOT11_RATE_BASIC 0x80 +#define DOT11_RATE_MASK 0x7F + +#define DOT11_MNG_ERP_LEN 1 +#define DOT11_MNG_NONERP_PRESENT 0x01 +#define DOT11_MNG_USE_PROTECTION 0x02 +#define DOT11_MNG_BARKER_PREAMBLE 0x04 + +#define DOT11_MGN_TS_DELAY_LEN 4 +#define TS_DELAY_FIELD_SIZE 4 + +#define DOT11_CAP_ESS 0x0001 +#define DOT11_CAP_IBSS 0x0002 +#define DOT11_CAP_POLLABLE 0x0004 +#define DOT11_CAP_POLL_RQ 0x0008 +#define DOT11_CAP_PRIVACY 0x0010 +#define DOT11_CAP_SHORT 0x0020 +#define DOT11_CAP_PBCC 0x0040 +#define DOT11_CAP_AGILITY 0x0080 +#define DOT11_CAP_SPECTRUM 0x0100 +#define DOT11_CAP_QOS 0x0200 +#define DOT11_CAP_SHORTSLOT 0x0400 +#define DOT11_CAP_APSD 0x0800 +#define DOT11_CAP_RRM 0x1000 +#define DOT11_CAP_CCK_OFDM 0x2000 +#define DOT11_CAP_DELAY_BA 0x4000 +#define DOT11_CAP_IMMEDIATE_BA 0x8000 + +#define DOT11_EXT_CAP_OBSS_COEX_MGMT 0 + +#define DOT11_EXT_CAP_SPSMP 6 + +#define DOT11_EXT_CAP_FMS 11 + +#define DOT11_EXT_CAP_PROXY_ARP 12 + +#define DOT11_EXT_CAP_TFS 16 + +#define DOT11_EXT_CAP_WNM_SLEEP 17 + +#define DOT11_EXT_CAP_TIMBC 18 + +#define DOT11_EXT_CAP_BSS_TRANSITION_MGMT 19 + +#define DOT11_EXT_CAP_DMS 26 + +#define DOT11_EXT_CAP_IW 31 + +#define DOT11_EXT_CAP_SI 41 +#define DOT11_EXT_CAP_SI_MASK 0x0E + +#define DOT11_EXT_CAP_WNM_NOTIF 46 + +#define DOT11_EXT_CAP_OPER_MODE_NOTIF 62 + +#define DOT11_OPER_MODE_CHANNEL_WIDTH_SHIFT 0 +#define DOT11_OPER_MODE_CHANNEL_WIDTH_MASK 0x3 +#define DOT11_OPER_MODE_RXNSS_SHIFT 4 +#define DOT11_OPER_MODE_RXNSS_MASK 0x70 +#define DOT11_OPER_MODE_RXNSS_TYPE_SHIFT 7 +#define DOT11_OPER_MODE_RXNSS_TYPE_MASK 0x80 + +#define DOT11_OPER_MODE(type, nss, chanw) (\ + ((type) << DOT11_OPER_MODE_RXNSS_TYPE_SHIFT &\ + DOT11_OPER_MODE_RXNSS_TYPE_MASK) |\ + (((nss) - 1) << DOT11_OPER_MODE_RXNSS_SHIFT & DOT11_OPER_MODE_RXNSS_MASK) |\ + ((chanw) << DOT11_OPER_MODE_CHANNEL_WIDTH_SHIFT &\ + DOT11_OPER_MODE_CHANNEL_WIDTH_MASK)) + +#define DOT11_OPER_MODE_CHANNEL_WIDTH(mode) \ + (((mode) & DOT11_OPER_MODE_CHANNEL_WIDTH_MASK)\ + >> DOT11_OPER_MODE_CHANNEL_WIDTH_SHIFT) +#define DOT11_OPER_MODE_RXNSS(mode) \ + ((((mode) & DOT11_OPER_MODE_RXNSS_MASK) \ + >> DOT11_OPER_MODE_RXNSS_SHIFT) + 1) +#define DOT11_OPER_MODE_RXNSS_TYPE(mode) \ + (((mode) & DOT11_OPER_MODE_RXNSS_TYPE_MASK)\ + >> DOT11_OPER_MODE_RXNSS_TYPE_SHIFT) + +#define DOT11_OPER_MODE_20MHZ 0 +#define DOT11_OPER_MODE_40MHZ 1 +#define DOT11_OPER_MODE_80MHZ 2 +#define DOT11_OPER_MODE_160MHZ 3 +#define DOT11_OPER_MODE_8080MHZ 3 + +#define DOT11_OPER_MODE_CHANNEL_WIDTH_20MHZ(mode) (\ + ((mode) & DOT11_OPER_MODE_CHANNEL_WIDTH_MASK) == DOT11_OPER_MODE_20MHZ) +#define DOT11_OPER_MODE_CHANNEL_WIDTH_40MHZ(mode) (\ + ((mode) & DOT11_OPER_MODE_CHANNEL_WIDTH_MASK) == DOT11_OPER_MODE_40MHZ) +#define DOT11_OPER_MODE_CHANNEL_WIDTH_80MHZ(mode) (\ + ((mode) & DOT11_OPER_MODE_CHANNEL_WIDTH_MASK) == DOT11_OPER_MODE_80MHZ) +#define DOT11_OPER_MODE_CHANNEL_WIDTH_160MHZ(mode) (\ + ((mode) & DOT11_OPER_MODE_CHANNEL_WIDTH_MASK) == DOT11_OPER_MODE_160MHZ) +#define DOT11_OPER_MODE_CHANNEL_WIDTH_8080MHZ(mode) (\ + ((mode) & DOT11_OPER_MODE_CHANNEL_WIDTH_MASK) == DOT11_OPER_MODE_8080MHZ) + +BWL_PRE_PACKED_STRUCT struct dot11_oper_mode_notif_ie { + uint8 mode; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_oper_mode_notif_ie dot11_oper_mode_notif_ie_t; + +#define DOT11_OPER_MODE_NOTIF_IE_LEN 1 + +#define DOT11_ACTION_HDR_LEN 2 +#define DOT11_ACTION_CAT_OFF 0 +#define DOT11_ACTION_ACT_OFF 1 + +#define DOT11_ACTION_CAT_ERR_MASK 0x80 +#define DOT11_ACTION_CAT_MASK 0x7F +#define DOT11_ACTION_CAT_SPECT_MNG 0 +#define DOT11_ACTION_CAT_QOS 1 +#define DOT11_ACTION_CAT_DLS 2 +#define DOT11_ACTION_CAT_BLOCKACK 3 +#define DOT11_ACTION_CAT_PUBLIC 4 +#define DOT11_ACTION_CAT_RRM 5 +#define DOT11_ACTION_CAT_FBT 6 +#define DOT11_ACTION_CAT_HT 7 +#define DOT11_ACTION_CAT_SA_QUERY 8 +#define DOT11_ACTION_CAT_PDPA 9 +#define DOT11_ACTION_CAT_WNM 10 +#define DOT11_ACTION_CAT_UWNM 11 +#define DOT11_ACTION_NOTIFICATION 17 +#define DOT11_ACTION_CAT_VHT 21 +#define DOT11_ACTION_CAT_VSP 126 +#define DOT11_ACTION_CAT_VS 127 + +#define DOT11_SM_ACTION_M_REQ 0 +#define DOT11_SM_ACTION_M_REP 1 +#define DOT11_SM_ACTION_TPC_REQ 2 +#define DOT11_SM_ACTION_TPC_REP 3 +#define DOT11_SM_ACTION_CHANNEL_SWITCH 4 +#define DOT11_SM_ACTION_EXT_CSA 5 + +#define DOT11_ACTION_ID_HT_CH_WIDTH 0 +#define DOT11_ACTION_ID_HT_MIMO_PS 1 + +#define DOT11_PUB_ACTION_BSS_COEX_MNG 0 +#define DOT11_PUB_ACTION_CHANNEL_SWITCH 4 +#define DOT11_PUB_ACTION_GAS_CB_REQ 12 + +#define DOT11_BA_ACTION_ADDBA_REQ 0 +#define DOT11_BA_ACTION_ADDBA_RESP 1 +#define DOT11_BA_ACTION_DELBA 2 + +#define DOT11_ADDBA_PARAM_AMSDU_SUP 0x0001 +#define DOT11_ADDBA_PARAM_POLICY_MASK 0x0002 +#define DOT11_ADDBA_PARAM_POLICY_SHIFT 1 +#define DOT11_ADDBA_PARAM_TID_MASK 0x003c +#define DOT11_ADDBA_PARAM_TID_SHIFT 2 +#define DOT11_ADDBA_PARAM_BSIZE_MASK 0xffc0 +#define DOT11_ADDBA_PARAM_BSIZE_SHIFT 6 + +#define DOT11_ADDBA_POLICY_DELAYED 0 +#define DOT11_ADDBA_POLICY_IMMEDIATE 1 + +#define DOT11_FT_ACTION_FT_RESERVED 0 +#define DOT11_FT_ACTION_FT_REQ 1 +#define DOT11_FT_ACTION_FT_RES 2 +#define DOT11_FT_ACTION_FT_CON 3 +#define DOT11_FT_ACTION_FT_ACK 4 + +#define DOT11_DLS_ACTION_REQ 0 +#define DOT11_DLS_ACTION_RESP 1 +#define DOT11_DLS_ACTION_TD 2 + +#define DOT11_WNM_ACTION_EVENT_REQ 0 +#define DOT11_WNM_ACTION_EVENT_REP 1 +#define DOT11_WNM_ACTION_DIAG_REQ 2 +#define DOT11_WNM_ACTION_DIAG_REP 3 +#define DOT11_WNM_ACTION_LOC_CFG_REQ 4 +#define DOT11_WNM_ACTION_LOC_RFG_RESP 5 +#define DOT11_WNM_ACTION_BSS_TRANS_QURY 6 +#define DOT11_WNM_ACTION_BSS_TRANS_REQ 7 +#define DOT11_WNM_ACTION_BSS_TRANS_RESP 8 +#define DOT11_WNM_ACTION_FMS_REQ 9 +#define DOT11_WNM_ACTION_FMS_RESP 10 +#define DOT11_WNM_ACTION_COL_INTRFRNCE_REQ 11 +#define DOT11_WNM_ACTION_COL_INTRFRNCE_REP 12 +#define DOT11_WNM_ACTION_TFS_REQ 13 +#define DOT11_WNM_ACTION_TFS_RESP 14 +#define DOT11_WNM_ACTION_TFS_NOTIFY 15 +#define DOT11_WNM_ACTION_WNM_SLEEP_REQ 16 +#define DOT11_WNM_ACTION_WNM_SLEEP_RESP 17 +#define DOT11_WNM_ACTION_TIMBC_REQ 18 +#define DOT11_WNM_ACTION_TIMBC_RESP 19 +#define DOT11_WNM_ACTION_QOS_TRFC_CAP_UPD 20 +#define DOT11_WNM_ACTION_CHAN_USAGE_REQ 21 +#define DOT11_WNM_ACTION_CHAN_USAGE_RESP 22 +#define DOT11_WNM_ACTION_DMS_REQ 23 +#define DOT11_WNM_ACTION_DMS_RESP 24 +#define DOT11_WNM_ACTION_TMNG_MEASUR_REQ 25 +#define DOT11_WNM_ACTION_NOTFCTN_REQ 26 +#define DOT11_WNM_ACTION_NOTFCTN_RES 27 + +#define DOT11_UWNM_ACTION_TIM 0 +#define DOT11_UWNM_ACTION_TIMING_MEASUREMENT 1 + +#define DOT11_MNG_COUNTRY_ID_LEN 3 + +#define DOT11_VHT_ACTION_CBF 0 +#define DOT11_VHT_ACTION_GID_MGMT 1 +#define DOT11_VHT_ACTION_OPER_MODE_NOTIF 2 + +BWL_PRE_PACKED_STRUCT struct dot11_dls_req { + uint8 category; + uint8 action; + struct ether_addr da; + struct ether_addr sa; + uint16 cap; + uint16 timeout; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_dls_req dot11_dls_req_t; +#define DOT11_DLS_REQ_LEN 18 + +BWL_PRE_PACKED_STRUCT struct dot11_dls_resp { + uint8 category; + uint8 action; + uint16 status; + struct ether_addr da; + struct ether_addr sa; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_dls_resp dot11_dls_resp_t; +#define DOT11_DLS_RESP_LEN 16 + +BWL_PRE_PACKED_STRUCT struct dot11_bss_trans_query { + uint8 category; + uint8 action; + uint8 token; + uint8 reason; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_bss_trans_query dot11_bss_trans_query_t; +#define DOT11_BSS_TRANS_QUERY_LEN 4 + +BWL_PRE_PACKED_STRUCT struct dot11_bss_trans_req { + uint8 category; + uint8 action; + uint8 token; + uint8 reqmode; + uint16 disassoc_tmr; + uint8 validity_intrvl; + uint8 data[1]; + +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_bss_trans_req dot11_bss_trans_req_t; +#define DOT11_BSS_TRANS_REQ_LEN 7 + +#define DOT11_BSS_TERM_DUR_LEN 12 + +#define DOT11_BSS_TRNS_REQMODE_PREF_LIST_INCL 0x01 +#define DOT11_BSS_TRNS_REQMODE_ABRIDGED 0x02 +#define DOT11_BSS_TRNS_REQMODE_DISASSOC_IMMINENT 0x04 +#define DOT11_BSS_TRNS_REQMODE_BSS_TERM_INCL 0x08 +#define DOT11_BSS_TRNS_REQMODE_ESS_DISASSOC_IMNT 0x10 + +BWL_PRE_PACKED_STRUCT struct dot11_bss_trans_res { + uint8 category; + uint8 action; + uint8 token; + uint8 status; + uint8 term_delay; + uint8 data[1]; + +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_bss_trans_res dot11_bss_trans_res_t; +#define DOT11_BSS_TRANS_RES_LEN 5 + +#define DOT11_BSS_TRNS_RES_STATUS_ACCEPT 0 +#define DOT11_BSS_TRNS_RES_STATUS_REJECT 1 +#define DOT11_BSS_TRNS_RES_STATUS_REJ_INSUFF_BCN 2 +#define DOT11_BSS_TRNS_RES_STATUS_REJ_INSUFF_CAP 3 +#define DOT11_BSS_TRNS_RES_STATUS_REJ_TERM_UNDESIRED 4 +#define DOT11_BSS_TRNS_RES_STATUS_REJ_TERM_DELAY_REQ 5 +#define DOT11_BSS_TRNS_RES_STATUS_REJ_BSS_LIST_PROVIDED 6 +#define DOT11_BSS_TRNS_RES_STATUS_REJ_NO_SUITABLE_BSS 7 +#define DOT11_BSS_TRNS_RES_STATUS_REJ_LEAVING_ESS 8 + +#define DOT11_NBR_RPRT_BSSID_INFO_REACHABILTY_UNKNOWN 0x0002 +#define DOT11_NBR_RPRT_BSSID_INFO_REACHABILTY 0x0003 +#define DOT11_NBR_RPRT_BSSID_INFO_SEC 0x0004 +#define DOT11_NBR_RPRT_BSSID_INFO_KEY_SCOPE 0x0008 +#define DOT11_NBR_RPRT_BSSID_INFO_CAP 0x03f0 + +#define DOT11_NBR_RPRT_BSSID_INFO_CAP_SPEC_MGMT 0x0010 +#define DOT11_NBR_RPRT_BSSID_INFO_CAP_QOS 0x0020 +#define DOT11_NBR_RPRT_BSSID_INFO_CAP_APSD 0x0040 +#define DOT11_NBR_RPRT_BSSID_INFO_CAP_RDIO_MSMT 0x0080 +#define DOT11_NBR_RPRT_BSSID_INFO_CAP_DEL_BA 0x0100 +#define DOT11_NBR_RPRT_BSSID_INFO_CAP_IMM_BA 0x0200 + +#define DOT11_NBR_RPRT_SUBELEM_BSS_CANDDT_PREF_ID 3 +#define DOT11_NBR_RPRT_SUBELEM_BSS_TERM_DUR_ID 4 + +BWL_PRE_PACKED_STRUCT struct dot11_bss_term_dur_subelem { + uint8 id; + uint8 len; + uint8 bss_term_tsf[8]; + uint16 duration; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_bss_term_dur_subelem dot11_bss_term_dur_subelem_t; +#define DOT11_BSS_TERM_DUR_SUBELEM_LEN 12 + +BWL_PRE_PACKED_STRUCT struct dot11_bss_max_idle_period_ie { + uint8 id; + uint8 length; + uint16 max_idle_period; + uint8 idle_opt; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_bss_max_idle_period_ie dot11_bss_max_idle_period_ie_t; +#define DOT11_BSS_MAX_IDLE_PERIOD_IE_LEN 3 +#define DOT11_BSS_MAX_IDLE_PERIOD_OPT_PROTECTED 1 + +BWL_PRE_PACKED_STRUCT struct dot11_timbc_req_ie { + uint8 id; + uint8 length; + uint8 interval; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_timbc_req_ie dot11_timbc_req_ie_t; +#define DOT11_TIMBC_REQ_IE_LEN 1 + +BWL_PRE_PACKED_STRUCT struct dot11_timbc_req { + uint8 category; + uint8 action; + uint8 token; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_timbc_req dot11_timbc_req_t; +#define DOT11_TIMBC_REQ_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_timbc_resp_ie { + uint8 id; + uint8 length; + uint8 status; + uint8 interval; + int32 offset; + uint16 high_rate; + uint16 low_rate; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_timbc_resp_ie dot11_timbc_resp_ie_t; +#define DOT11_TIMBC_DENY_RESP_IE_LEN 1 +#define DOT11_TIMBC_ACCEPT_RESP_IE_LEN 10 + +#define DOT11_TIMBC_STATUS_ACCEPT 0 +#define DOT11_TIMBC_STATUS_ACCEPT_TSTAMP 1 +#define DOT11_TIMBC_STATUS_DENY 2 +#define DOT11_TIMBC_STATUS_OVERRIDDEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_timbc_resp { + uint8 category; + uint8 action; + uint8 token; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_timbc_resp dot11_timbc_resp_t; +#define DOT11_TIMBC_RESP_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_tim_ie { + uint8 id; + uint8 len; + uint8 dtim_count; + uint8 dtim_period; + uint8 bitmap_control; + uint8 pvb[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tim_ie dot11_tim_ie_t; +#define DOT11_TIM_IE_FIXED_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_timbc { + uint8 category; + uint8 action; + uint8 check_beacon; + uint8 tsf[8]; + dot11_tim_ie_t tim_ie; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_timbc dot11_timbc_t; +#define DOT11_TIMBC_HDR_LEN (sizeof(dot11_timbc_t) - sizeof(dot11_tim_ie_t)) +#define DOT11_TIMBC_FIXED_LEN (sizeof(dot11_timbc_t) - 1) + +BWL_PRE_PACKED_STRUCT struct dot11_tclas_fc_hdr { + uint8 type; + uint8 mask; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tclas_fc_hdr dot11_tclas_fc_hdr_t; +#define DOT11_TCLAS_FC_HDR_LEN 2 + +#define DOT11_TCLAS_MASK_0 0x1 +#define DOT11_TCLAS_MASK_1 0x2 +#define DOT11_TCLAS_MASK_2 0x4 +#define DOT11_TCLAS_MASK_3 0x8 +#define DOT11_TCLAS_MASK_4 0x10 +#define DOT11_TCLAS_MASK_5 0x20 +#define DOT11_TCLAS_MASK_6 0x40 +#define DOT11_TCLAS_MASK_7 0x80 + +#define DOT11_TCLAS_FC_0_ETH 0 +#define DOT11_TCLAS_FC_1_IP 1 +#define DOT11_TCLAS_FC_2_8021Q 2 +#define DOT11_TCLAS_FC_3_OFFSET 3 +#define DOT11_TCLAS_FC_4_IP_HIGHER 4 +#define DOT11_TCLAS_FC_5_8021D 5 + +BWL_PRE_PACKED_STRUCT struct dot11_tclas_fc_0_eth { + uint8 type; + uint8 mask; + uint8 sa[ETHER_ADDR_LEN]; + uint8 da[ETHER_ADDR_LEN]; + uint16 eth_type; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tclas_fc_0_eth dot11_tclas_fc_0_eth_t; +#define DOT11_TCLAS_FC_0_ETH_LEN 16 + +BWL_PRE_PACKED_STRUCT struct dot11_tclas_fc_1_ipv4 { + uint8 type; + uint8 mask; + uint8 version; + uint32 src_ip; + uint32 dst_ip; + uint16 src_port; + uint16 dst_port; + uint8 dscp; + uint8 protocol; + uint8 reserved; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tclas_fc_1_ipv4 dot11_tclas_fc_1_ipv4_t; +#define DOT11_TCLAS_FC_1_IPV4_LEN 18 + +BWL_PRE_PACKED_STRUCT struct dot11_tclas_fc_2_8021q { + uint8 type; + uint8 mask; + uint16 tci; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tclas_fc_2_8021q dot11_tclas_fc_2_8021q_t; +#define DOT11_TCLAS_FC_2_8021Q_LEN 4 + +BWL_PRE_PACKED_STRUCT struct dot11_tclas_fc_3_filter { + uint8 type; + uint8 mask; + uint16 offset; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tclas_fc_3_filter dot11_tclas_fc_3_filter_t; +#define DOT11_TCLAS_FC_3_FILTER_LEN 4 + +typedef struct dot11_tclas_fc_1_ipv4 dot11_tclas_fc_4_ipv4_t; +#define DOT11_TCLAS_FC_4_IPV4_LEN DOT11_TCLAS_FC_1_IPV4_LEN + +BWL_PRE_PACKED_STRUCT struct dot11_tclas_fc_4_ipv6 { + uint8 type; + uint8 mask; + uint8 version; + uint8 saddr[16]; + uint8 daddr[16]; + uint16 src_port; + uint16 dst_port; + uint8 dscp; + uint8 nexthdr; + uint8 flow_lbl[3]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tclas_fc_4_ipv6 dot11_tclas_fc_4_ipv6_t; +#define DOT11_TCLAS_FC_4_IPV6_LEN 44 + +BWL_PRE_PACKED_STRUCT struct dot11_tclas_fc_5_8021d { + uint8 type; + uint8 mask; + uint8 pcp; + uint8 cfi; + uint16 vid; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tclas_fc_5_8021d dot11_tclas_fc_5_8021d_t; +#define DOT11_TCLAS_FC_5_8021D_LEN 6 + +BWL_PRE_PACKED_STRUCT union dot11_tclas_fc { + uint8 data[1]; + dot11_tclas_fc_hdr_t hdr; + dot11_tclas_fc_0_eth_t t0_eth; + dot11_tclas_fc_1_ipv4_t t1_ipv4; + dot11_tclas_fc_2_8021q_t t2_8021q; + dot11_tclas_fc_3_filter_t t3_filter; + dot11_tclas_fc_4_ipv4_t t4_ipv4; + dot11_tclas_fc_4_ipv6_t t4_ipv6; + dot11_tclas_fc_5_8021d_t t5_8021d; +} BWL_POST_PACKED_STRUCT; +typedef union dot11_tclas_fc dot11_tclas_fc_t; +#define DOT11_TCLAS_FC_MIN_LEN 4 +#define DOT11_TCLAS_FC_MAX_LEN 254 + +BWL_PRE_PACKED_STRUCT struct dot11_tclas_ie { + uint8 id; + uint8 length; + uint8 user_priority; + dot11_tclas_fc_t fc; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tclas_ie dot11_tclas_ie_t; +#define DOT11_TCLAS_IE_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_tclas_proc_ie { + uint8 id; + uint8 length; + uint8 process; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tclas_proc_ie dot11_tclas_proc_ie_t; +#define DOT11_TCLAS_PROC_IE_LEN 3 + +#define DOT11_TCLAS_PROC_MATCHALL 0 +#define DOT11_TCLAS_PROC_MATCHONE 1 +#define DOT11_TCLAS_PROC_NONMATCH 2 + +#define DOT11_TSPEC_IE_LEN 57 + +BWL_PRE_PACKED_STRUCT struct dot11_tfs_req_ie { + uint8 id; + uint8 length; + uint8 tfs_id; + uint8 tfs_actcode; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tfs_req_ie dot11_tfs_req_ie_t; +#define DOT11_TFS_REQ_IE_LEN 4 + +#define DOT11_TFS_ACTCODE_DELETE 1 +#define DOT11_TFS_ACTCODE_MODIFY 2 + +BWL_PRE_PACKED_STRUCT struct dot11_tfs_se { + uint8 sub_id; + uint8 length; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tfs_se dot11_tfs_se_t; + +#define DOT11_TFS_REQ_SUBELEM_LEN 2 + +#define DOT11_TFS_SUBELEM_ID_TFS 1 +#define DOT11_TFS_SUBELEM_ID_VENDOR 221 + +BWL_PRE_PACKED_STRUCT struct dot11_tfs_resp_ie { + uint8 id; + uint8 length; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tfs_resp_ie dot11_tfs_resp_ie_t; +#define DOT11_TFS_RESP_IE_LEN 2 + +BWL_PRE_PACKED_STRUCT struct dot11_tfs_status_se { + uint8 id; + uint8 length; + uint8 resp_st; + uint8 tfs_id; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tfs_status_se dot11_tfs_status_se_t; + +#define DOT11_TFS_STATUS_SE_LEN 4 +#define DOT11_TFS_STATUS_SE_DATA_LEN 2 + +#define DOT11_TFS_STATUS_SE_ID_TFS_ST 1 +#define DOT11_TFS_STATUS_SE_ID_TFS 2 +#define DOT11_TFS_STATUS_SE_ID_VENDOR 221 + +#define DOT11_TFS_RESP_ST_ACCEPT 0 +#define DOT11_TFS_RESP_ST_DENY_FORMAT 1 +#define DOT11_TFS_RESP_ST_DENY_RESOURCE 2 +#define DOT11_TFS_RESP_ST_DENY_POLICY 4 +#define DOT11_TFS_RESP_ST_PREFERRED_AP_INCAP 14 + +BWL_PRE_PACKED_STRUCT struct dot11_tfs_req { + uint8 category; + uint8 action; + uint8 token; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tfs_req dot11_tfs_req_t; +#define DOT11_TFS_REQ_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_tfs_resp { + uint8 category; + uint8 action; + uint8 token; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tfs_resp dot11_tfs_resp_t; +#define DOT11_TFS_RESP_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_tfs_notify { + uint8 category; + uint8 action; + uint8 num_tfs_id; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tfs_notify dot11_tfs_notify_t; +#define DOT11_TFS_NOTIFY_LEN 3 + +#define DOT11_TFS_NOTIFY_ACT_DEL 1 +#define DOT11_TFS_NOTIFY_ACT_NOTIFY 2 + +BWL_PRE_PACKED_STRUCT struct dot11_wnm_sleep_req { + uint8 category; + uint8 action; + uint8 token; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_wnm_sleep_req dot11_wnm_sleep_req_t; +#define DOT11_WNM_SLEEP_REQ_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_wnm_sleep_resp { + uint8 category; + uint8 action; + uint8 token; + uint16 key_len; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_wnm_sleep_resp dot11_wnm_sleep_resp_t; +#define DOT11_WNM_SLEEP_RESP_LEN 5 + +#define DOT11_WNM_SLEEP_SUBELEM_ID_GTK 0 +#define DOT11_WNM_SLEEP_SUBELEM_ID_IGTK 1 + +BWL_PRE_PACKED_STRUCT struct dot11_wnm_sleep_subelem_gtk { + uint8 sub_id; + uint8 length; + uint16 key_info; + uint8 key_length; + uint8 rsc[8]; + uint8 key[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_wnm_sleep_subelem_gtk dot11_wnm_sleep_subelem_gtk_t; +#define DOT11_WNM_SLEEP_SUBELEM_GTK_FIXED_LEN 11 +#define DOT11_WNM_SLEEP_SUBELEM_GTK_MAX_LEN 43 + +BWL_PRE_PACKED_STRUCT struct dot11_wnm_sleep_subelem_igtk { + uint8 sub_id; + uint8 length; + uint16 key_id; + uint8 pn[6]; + uint8 key[16]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_wnm_sleep_subelem_igtk dot11_wnm_sleep_subelem_igtk_t; +#define DOT11_WNM_SLEEP_SUBELEM_IGTK_LEN 24 + +BWL_PRE_PACKED_STRUCT struct dot11_wnm_sleep_ie { + uint8 id; + uint8 length; + uint8 act_type; + uint8 resp_status; + uint16 interval; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_wnm_sleep_ie dot11_wnm_sleep_ie_t; +#define DOT11_WNM_SLEEP_IE_LEN 4 + +#define DOT11_WNM_SLEEP_ACT_TYPE_ENTER 0 +#define DOT11_WNM_SLEEP_ACT_TYPE_EXIT 1 + +#define DOT11_WNM_SLEEP_RESP_ACCEPT 0 +#define DOT11_WNM_SLEEP_RESP_UPDATE 1 +#define DOT11_WNM_SLEEP_RESP_DENY 2 +#define DOT11_WNM_SLEEP_RESP_DENY_TEMP 3 +#define DOT11_WNM_SLEEP_RESP_DENY_KEY 4 +#define DOT11_WNM_SLEEP_RESP_DENY_INUSE 5 +#define DOT11_WNM_SLEEP_RESP_LAST 6 + +BWL_PRE_PACKED_STRUCT struct dot11_dms_req { + uint8 category; + uint8 action; + uint8 token; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_dms_req dot11_dms_req_t; +#define DOT11_DMS_REQ_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_dms_resp { + uint8 category; + uint8 action; + uint8 token; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_dms_resp dot11_dms_resp_t; +#define DOT11_DMS_RESP_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_dms_req_ie { + uint8 id; + uint8 length; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_dms_req_ie dot11_dms_req_ie_t; +#define DOT11_DMS_REQ_IE_LEN 2 + +BWL_PRE_PACKED_STRUCT struct dot11_dms_resp_ie { + uint8 id; + uint8 length; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_dms_resp_ie dot11_dms_resp_ie_t; +#define DOT11_DMS_RESP_IE_LEN 2 + +BWL_PRE_PACKED_STRUCT struct dot11_dms_req_desc { + uint8 dms_id; + uint8 length; + uint8 type; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_dms_req_desc dot11_dms_req_desc_t; +#define DOT11_DMS_REQ_DESC_LEN 3 + +#define DOT11_DMS_REQ_TYPE_ADD 0 +#define DOT11_DMS_REQ_TYPE_REMOVE 1 +#define DOT11_DMS_REQ_TYPE_CHANGE 2 + +BWL_PRE_PACKED_STRUCT struct dot11_dms_resp_st { + uint8 dms_id; + uint8 length; + uint8 type; + uint16 lsc; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_dms_resp_st dot11_dms_resp_st_t; +#define DOT11_DMS_RESP_STATUS_LEN 5 + +#define DOT11_DMS_RESP_TYPE_ACCEPT 0 +#define DOT11_DMS_RESP_TYPE_DENY 1 +#define DOT11_DMS_RESP_TYPE_TERM 2 + +#define DOT11_DMS_RESP_LSC_UNSUPPORTED 0xFFFF + +BWL_PRE_PACKED_STRUCT struct dot11_addba_req { + uint8 category; + uint8 action; + uint8 token; + uint16 addba_param_set; + uint16 timeout; + uint16 start_seqnum; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_addba_req dot11_addba_req_t; +#define DOT11_ADDBA_REQ_LEN 9 + +BWL_PRE_PACKED_STRUCT struct dot11_addba_resp { + uint8 category; + uint8 action; + uint8 token; + uint16 status; + uint16 addba_param_set; + uint16 timeout; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_addba_resp dot11_addba_resp_t; +#define DOT11_ADDBA_RESP_LEN 9 + +#define DOT11_DELBA_PARAM_INIT_MASK 0x0800 +#define DOT11_DELBA_PARAM_INIT_SHIFT 11 +#define DOT11_DELBA_PARAM_TID_MASK 0xf000 +#define DOT11_DELBA_PARAM_TID_SHIFT 12 + +BWL_PRE_PACKED_STRUCT struct dot11_delba { + uint8 category; + uint8 action; + uint16 delba_param_set; + uint16 reason; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_delba dot11_delba_t; +#define DOT11_DELBA_LEN 6 + +#define SA_QUERY_REQUEST 0 +#define SA_QUERY_RESPONSE 1 + +BWL_PRE_PACKED_STRUCT struct dot11_ft_req { + uint8 category; + uint8 action; + uint8 sta_addr[ETHER_ADDR_LEN]; + uint8 tgt_ap_addr[ETHER_ADDR_LEN]; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_ft_req dot11_ft_req_t; +#define DOT11_FT_REQ_FIXED_LEN 14 + +BWL_PRE_PACKED_STRUCT struct dot11_ft_res { + uint8 category; + uint8 action; + uint8 sta_addr[ETHER_ADDR_LEN]; + uint8 tgt_ap_addr[ETHER_ADDR_LEN]; + uint16 status; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_ft_res dot11_ft_res_t; +#define DOT11_FT_RES_FIXED_LEN 16 + +BWL_PRE_PACKED_STRUCT struct dot11_rde_ie { + uint8 id; + uint8 length; + uint8 rde_id; + uint8 rd_count; + uint16 status; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rde_ie dot11_rde_ie_t; + +#define DOT11_MNG_RDE_IE_LEN sizeof(dot11_rde_ie_t) + +#define DOT11_RRM_CAP_LEN 5 +#define RCPI_IE_LEN 1 +#define RSNI_IE_LEN 1 +BWL_PRE_PACKED_STRUCT struct dot11_rrm_cap_ie { + uint8 cap[DOT11_RRM_CAP_LEN]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rrm_cap_ie dot11_rrm_cap_ie_t; + +#define DOT11_RRM_CAP_LINK 0 +#define DOT11_RRM_CAP_NEIGHBOR_REPORT 1 +#define DOT11_RRM_CAP_PARALLEL 2 +#define DOT11_RRM_CAP_REPEATED 3 +#define DOT11_RRM_CAP_BCN_PASSIVE 4 +#define DOT11_RRM_CAP_BCN_ACTIVE 5 +#define DOT11_RRM_CAP_BCN_TABLE 6 +#define DOT11_RRM_CAP_BCN_REP_COND 7 +#define DOT11_RRM_CAP_AP_CHANREP 16 + +#define DOT11_OP_CLASS_NONE 255 + +BWL_PRE_PACKED_STRUCT struct do11_ap_chrep { + uint8 id; + uint8 len; + uint8 reg; + uint8 chanlist[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct do11_ap_chrep dot11_ap_chrep_t; + +#define DOT11_RM_ACTION_RM_REQ 0 +#define DOT11_RM_ACTION_RM_REP 1 +#define DOT11_RM_ACTION_LM_REQ 2 +#define DOT11_RM_ACTION_LM_REP 3 +#define DOT11_RM_ACTION_NR_REQ 4 +#define DOT11_RM_ACTION_NR_REP 5 + +BWL_PRE_PACKED_STRUCT struct dot11_rm_action { + uint8 category; + uint8 action; + uint8 token; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rm_action dot11_rm_action_t; +#define DOT11_RM_ACTION_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_rmreq { + uint8 category; + uint8 action; + uint8 token; + uint16 reps; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmreq dot11_rmreq_t; +#define DOT11_RMREQ_LEN 5 + +BWL_PRE_PACKED_STRUCT struct dot11_rm_ie { + uint8 id; + uint8 len; + uint8 token; + uint8 mode; + uint8 type; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rm_ie dot11_rm_ie_t; +#define DOT11_RM_IE_LEN 5 + +#define DOT11_RMREQ_MODE_PARALLEL 1 +#define DOT11_RMREQ_MODE_ENABLE 2 +#define DOT11_RMREQ_MODE_REQUEST 4 +#define DOT11_RMREQ_MODE_REPORT 8 +#define DOT11_RMREQ_MODE_DURMAND 0x10 + +#define DOT11_RMREP_MODE_LATE 1 +#define DOT11_RMREP_MODE_INCAPABLE 2 +#define DOT11_RMREP_MODE_REFUSED 4 + +BWL_PRE_PACKED_STRUCT struct dot11_rmreq_bcn { + uint8 id; + uint8 len; + uint8 token; + uint8 mode; + uint8 type; + uint8 reg; + uint8 channel; + uint16 interval; + uint16 duration; + uint8 bcn_mode; + struct ether_addr bssid; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmreq_bcn dot11_rmreq_bcn_t; +#define DOT11_RMREQ_BCN_LEN 18 + +BWL_PRE_PACKED_STRUCT struct dot11_rmrep_bcn { + uint8 reg; + uint8 channel; + uint32 starttime[2]; + uint16 duration; + uint8 frame_info; + uint8 rcpi; + uint8 rsni; + struct ether_addr bssid; + uint8 antenna_id; + uint32 parent_tsf; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmrep_bcn dot11_rmrep_bcn_t; +#define DOT11_RMREP_BCN_LEN 26 + +#define DOT11_RMREQ_BCN_PASSIVE 0 +#define DOT11_RMREQ_BCN_ACTIVE 1 +#define DOT11_RMREQ_BCN_TABLE 2 + +#define DOT11_RMREQ_BCN_SSID_ID 0 +#define DOT11_RMREQ_BCN_REPINFO_ID 1 +#define DOT11_RMREQ_BCN_REPDET_ID 2 +#define DOT11_RMREQ_BCN_REQUEST_ID 10 +#define DOT11_RMREQ_BCN_APCHREP_ID DOT11_MNG_AP_CHREP_ID + +#define DOT11_RMREQ_BCN_REPDET_FIXED 0 +#define DOT11_RMREQ_BCN_REPDET_REQUEST 1 +#define DOT11_RMREQ_BCN_REPDET_ALL 2 + +#define DOT11_RMREP_BCN_FRM_BODY 1 + +#define DOT11_RMREP_FRAME_COUNT_REPORT 1 + +BWL_PRE_PACKED_STRUCT struct dot11_rmreq_chanload { + uint8 id; + uint8 len; + uint8 token; + uint8 mode; + uint8 type; + uint8 reg; + uint8 channel; + uint16 interval; + uint16 duration; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmreq_chanload dot11_rmreq_chanload_t; +#define DOT11_RMREQ_CHANLOAD_LEN 11 + +BWL_PRE_PACKED_STRUCT struct dot11_rmrep_chanload { + uint8 reg; + uint8 channel; + uint32 starttime[2]; + uint16 duration; + uint8 channel_load; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmrep_chanload dot11_rmrep_chanload_t; +#define DOT11_RMREP_CHANLOAD_LEN 13 + +BWL_PRE_PACKED_STRUCT struct dot11_rmreq_noise { + uint8 id; + uint8 len; + uint8 token; + uint8 mode; + uint8 type; + uint8 reg; + uint8 channel; + uint16 interval; + uint16 duration; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmreq_noise dot11_rmreq_noise_t; +#define DOT11_RMREQ_NOISE_LEN 11 + +BWL_PRE_PACKED_STRUCT struct dot11_rmrep_noise { + uint8 reg; + uint8 channel; + uint32 starttime[2]; + uint16 duration; + uint8 antid; + uint8 anpi; + uint8 ipi0_dens; + uint8 ipi1_dens; + uint8 ipi2_dens; + uint8 ipi3_dens; + uint8 ipi4_dens; + uint8 ipi5_dens; + uint8 ipi6_dens; + uint8 ipi7_dens; + uint8 ipi8_dens; + uint8 ipi9_dens; + uint8 ipi10_dens; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmrep_noise dot11_rmrep_noise_t; +#define DOT11_RMREP_NOISE_LEN 25 + +BWL_PRE_PACKED_STRUCT struct dot11_rmreq_frame { + uint8 id; + uint8 len; + uint8 token; + uint8 mode; + uint8 type; + uint8 reg; + uint8 channel; + uint16 interval; + uint16 duration; + uint8 req_type; + struct ether_addr ta; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmreq_frame dot11_rmreq_frame_t; +#define DOT11_RMREQ_FRAME_LEN 18 + +BWL_PRE_PACKED_STRUCT struct dot11_rmrep_frame { + uint8 reg; + uint8 channel; + uint32 starttime[2]; + uint16 duration; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmrep_frame dot11_rmrep_frame_t; +#define DOT11_RMREP_FRAME_LEN 12 + +BWL_PRE_PACKED_STRUCT struct dot11_rmrep_frmentry { + struct ether_addr ta; + struct ether_addr bssid; + uint8 phy_type; + uint8 avg_rcpi; + uint8 last_rsni; + uint8 last_rcpi; + uint8 ant_id; + uint16 frame_cnt; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmrep_frmentry dot11_rmrep_frmentry_t; +#define DOT11_RMREP_FRMENTRY_LEN 19 + +BWL_PRE_PACKED_STRUCT struct dot11_rmreq_stat { + uint8 id; + uint8 len; + uint8 token; + uint8 mode; + uint8 type; + struct ether_addr peer; + uint16 interval; + uint16 duration; + uint8 group_id; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmreq_stat dot11_rmreq_stat_t; +#define DOT11_RMREQ_STAT_LEN 16 + +BWL_PRE_PACKED_STRUCT struct dot11_rmrep_stat { + uint16 duration; + uint8 group_id; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmrep_stat dot11_rmrep_stat_t; + +BWL_PRE_PACKED_STRUCT struct dot11_rmreq_tx_stream { + uint8 id; + uint8 len; + uint8 token; + uint8 mode; + uint8 type; + uint16 interval; + uint16 duration; + struct ether_addr peer; + uint8 traffic_id; + uint8 bin0_range; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmreq_tx_stream dot11_rmreq_tx_stream_t; + +BWL_PRE_PACKED_STRUCT struct dot11_rmrep_tx_stream { + uint32 starttime[2]; + uint16 duration; + struct ether_addr peer; + uint8 traffic_id; + uint8 reason; + uint32 txmsdu_cnt; + uint32 msdu_discarded_cnt; + uint32 msdufailed_cnt; + uint32 msduretry_cnt; + uint32 cfpolls_lost_cnt; + uint32 avrqueue_delay; + uint32 avrtx_delay; + uint8 bin0_range; + uint32 bin0; + uint32 bin1; + uint32 bin2; + uint32 bin3; + uint32 bin4; + uint32 bin5; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmrep_tx_stream dot11_rmrep_tx_stream_t; + +BWL_PRE_PACKED_STRUCT struct dot11_rmreq_pause_time { + uint8 id; + uint8 len; + uint8 token; + uint8 mode; + uint8 type; + uint16 pause_time; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmreq_pause_time dot11_rmreq_pause_time_t; + +BWL_PRE_PACKED_STRUCT struct dot11_rmrep_nbr { + struct ether_addr bssid; + uint32 bssid_info; + uint8 reg; + uint8 channel; + uint8 phytype; + uchar sub_elements[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rmrep_nbr dot11_rmrep_nbr_t; +#define DOT11_RMREP_NBR_LEN 13 + +BWL_PRE_PACKED_STRUCT struct dot11_rrmrep_nbr { + uint8 id; + uint8 len; + dot11_rmrep_nbr_t nbr_rrmrep; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_rrmrep_nbr dot11_rrmrep_nbr_t; +#define DOT11_RRMREP_NBR_LEN 15 + +#define DOT11_BSSTYPE_INFRASTRUCTURE 0 +#define DOT11_BSSTYPE_INDEPENDENT 1 +#define DOT11_BSSTYPE_ANY 2 +#define DOT11_SCANTYPE_ACTIVE 0 +#define DOT11_SCANTYPE_PASSIVE 1 + +BWL_PRE_PACKED_STRUCT struct dot11_lmreq { + uint8 category; + uint8 action; + uint8 token; + uint8 txpwr; + uint8 maxtxpwr; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_lmreq dot11_lmreq_t; +#define DOT11_LMREQ_LEN 5 + +BWL_PRE_PACKED_STRUCT struct dot11_lmrep { + uint8 category; + uint8 action; + uint8 token; + dot11_tpc_rep_t tpc; + uint8 rxant; + uint8 txant; + uint8 rcpi; + uint8 rsni; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_lmrep dot11_lmrep_t; +#define DOT11_LMREP_LEN 11 + +#define PREN_PREAMBLE 24 +#define PREN_MM_EXT 12 +#define PREN_PREAMBLE_EXT 4 + +#define RIFS_11N_TIME 2 + +#define HT_SIG1_MCS_MASK 0x00007F +#define HT_SIG1_CBW 0x000080 +#define HT_SIG1_HT_LENGTH 0xFFFF00 + +#define HT_SIG2_SMOOTHING 0x000001 +#define HT_SIG2_NOT_SOUNDING 0x000002 +#define HT_SIG2_RESERVED 0x000004 +#define HT_SIG2_AGGREGATION 0x000008 +#define HT_SIG2_STBC_MASK 0x000030 +#define HT_SIG2_STBC_SHIFT 4 +#define HT_SIG2_FEC_CODING 0x000040 +#define HT_SIG2_SHORT_GI 0x000080 +#define HT_SIG2_ESS_MASK 0x000300 +#define HT_SIG2_ESS_SHIFT 8 +#define HT_SIG2_CRC 0x03FC00 +#define HT_SIG2_TAIL 0x1C0000 + +#define HT_T_LEG_PREAMBLE 16 +#define HT_T_L_SIG 4 +#define HT_T_SIG 8 +#define HT_T_LTF1 4 +#define HT_T_GF_LTF1 8 +#define HT_T_LTFs 4 +#define HT_T_STF 4 +#define HT_T_GF_STF 8 +#define HT_T_SYML 4 + +#define HT_N_SERVICE 16 +#define HT_N_TAIL 6 + +#define APHY_SLOT_TIME 9 +#define APHY_SIFS_TIME 16 +#define APHY_DIFS_TIME (APHY_SIFS_TIME + (2 * APHY_SLOT_TIME)) +#define APHY_PREAMBLE_TIME 16 +#define APHY_SIGNAL_TIME 4 +#define APHY_SYMBOL_TIME 4 +#define APHY_SERVICE_NBITS 16 +#define APHY_TAIL_NBITS 6 +#define APHY_CWMIN 15 + +#define BPHY_SLOT_TIME 20 +#define BPHY_SIFS_TIME 10 +#define BPHY_DIFS_TIME 50 +#define BPHY_PLCP_TIME 192 +#define BPHY_PLCP_SHORT_TIME 96 +#define BPHY_CWMIN 31 + +#define DOT11_OFDM_SIGNAL_EXTENSION 6 + +#define PHY_CWMAX 1023 + +#define DOT11_MAXNUMFRAGS 16 + +typedef int vht_group_id_t; + +#define VHT_SIGA1_CONST_MASK 0x800004 + +#define VHT_SIGA1_BW_MASK 0x000003 +#define VHT_SIGA1_20MHZ_VAL 0x000000 +#define VHT_SIGA1_40MHZ_VAL 0x000001 +#define VHT_SIGA1_80MHZ_VAL 0x000002 +#define VHT_SIGA1_160MHZ_VAL 0x000003 + +#define VHT_SIGA1_STBC 0x000008 + +#define VHT_SIGA1_GID_MASK 0x0003f0 +#define VHT_SIGA1_GID_SHIFT 4 +#define VHT_SIGA1_GID_TO_AP 0x00 +#define VHT_SIGA1_GID_NOT_TO_AP 0x3f +#define VHT_SIGA1_GID_MAX_GID 0x3f + +#define VHT_SIGA1_NSTS_SHIFT_MASK_USER0 0x001C00 +#define VHT_SIGA1_NSTS_SHIFT 10 + +#define VHT_SIGA1_PARTIAL_AID_MASK 0x3fe000 +#define VHT_SIGA1_PARTIAL_AID_SHIFT 13 + +#define VHT_SIGA1_TXOP_PS_NOT_ALLOWED 0x400000 + +#define VHT_SIGA2_GI_NONE 0x000000 +#define VHT_SIGA2_GI_SHORT 0x000001 +#define VHT_SIGA2_GI_W_MOD10 0x000002 +#define VHT_SIGA2_CODING_LDPC 0x000004 +#define VHT_SIGA2_LDPC_EXTRA_OFDM_SYM 0x000008 +#define VHT_SIGA2_BEAMFORM_ENABLE 0x000100 +#define VHT_SIGA2_MCS_SHIFT 4 + +#define VHT_SIGA2_B9_RESERVED 0x000200 +#define VHT_SIGA2_TAIL_MASK 0xfc0000 +#define VHT_SIGA2_TAIL_VALUE 0x000000 + +#define VHT_T_LEG_PREAMBLE 16 +#define VHT_T_L_SIG 4 +#define VHT_T_SIG_A 8 +#define VHT_T_LTF 4 +#define VHT_T_STF 4 +#define VHT_T_SIG_B 4 +#define VHT_T_SYML 4 + +#define VHT_N_SERVICE 16 +#define VHT_N_TAIL 6 + +typedef struct d11cnt { + uint32 txfrag; + uint32 txmulti; + uint32 txfail; + uint32 txretry; + uint32 txretrie; + uint32 rxdup; + uint32 txrts; + uint32 txnocts; + uint32 txnoack; + uint32 rxfrag; + uint32 rxmulti; + uint32 rxcrc; + uint32 txfrmsnt; + uint32 rxundec; +} d11cnt_t; + +#define BRCM_PROP_OUI "\x00\x90\x4C" + +#define BRCM_SYSCAP_WET_TUNNEL 0x0100 + +#define BRCM_OUI "\x00\x10\x18" + +BWL_PRE_PACKED_STRUCT struct brcm_ie { + uint8 id; + uint8 len; + uint8 oui[3]; + uint8 ver; + uint8 assoc; + uint8 flags; + uint8 flags1; + uint16 amsdu_mtu_pref; +} BWL_POST_PACKED_STRUCT; +typedef struct brcm_ie brcm_ie_t; +#define BRCM_IE_LEN 11 +#define BRCM_IE_VER 2 +#define BRCM_IE_LEGACY_AES_VER 1 + +#define BRF_LZWDS 0x4 +#define BRF_BLOCKACK 0x8 + +#define BRF1_AMSDU 0x1 +#define BRF1_WMEPS 0x4 +#define BRF1_PSOFIX 0x8 +#define BRF1_RX_LARGE_AGG 0x10 +#define BRF1_RFAWARE_DCS 0x20 +#define BRF1_SOFTAP 0x40 +#define BRF1_DWDS 0x80 + +BWL_PRE_PACKED_STRUCT struct vndr_ie { + uchar id; + uchar len; + uchar oui [3]; + uchar data [1]; +} BWL_POST_PACKED_STRUCT; +typedef struct vndr_ie vndr_ie_t; + +#define VNDR_IE_HDR_LEN 2 +#define VNDR_IE_MIN_LEN 3 +#define VNDR_IE_MAX_LEN 255 + +BWL_PRE_PACKED_STRUCT struct member_of_brcm_prop_ie { + uchar id; + uchar len; + uchar oui[3]; + uint8 type; + struct ether_addr ea; +} BWL_POST_PACKED_STRUCT; +typedef struct member_of_brcm_prop_ie member_of_brcm_prop_ie_t; + +#define MEMBER_OF_BRCM_PROP_IE_LEN 10 +#define MEMBER_OF_BRCM_PROP_IE_TYPE 54 + +BWL_PRE_PACKED_STRUCT struct relmcast_brcm_prop_ie { + uchar id; + uchar len; + uchar oui[3]; + uint8 type; + struct ether_addr ea; + struct ether_addr mcast_ea; + uint8 updtmo; +} BWL_POST_PACKED_STRUCT; +typedef struct relmcast_brcm_prop_ie relmcast_brcm_prop_ie_t; + +#define RELMCAST_BRCM_PROP_IE_LEN (sizeof(relmcast_brcm_prop_ie_t)-2) +#define RELMCAST_BRCM_PROP_IE_TYPE 55 + +#define MCSSET_LEN 16 +#define MAX_MCS_NUM (128) + +BWL_PRE_PACKED_STRUCT struct ht_cap_ie { + uint16 cap; + uint8 params; + uint8 supp_mcs[MCSSET_LEN]; + uint16 ext_htcap; + uint32 txbf_cap; + uint8 as_cap; +} BWL_POST_PACKED_STRUCT; +typedef struct ht_cap_ie ht_cap_ie_t; +BWL_PRE_PACKED_STRUCT struct dot11_ht_cap_ie { + uint8 id; + uint8 len; + ht_cap_ie_t ht_cap; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_ht_cap_ie dot11_ht_cap_ie_t; + +BWL_PRE_PACKED_STRUCT struct ht_prop_cap_ie { + uint8 id; + uint8 len; + uint8 oui[3]; + uint8 type; + ht_cap_ie_t cap_ie; +} BWL_POST_PACKED_STRUCT; +typedef struct ht_prop_cap_ie ht_prop_cap_ie_t; + +#define HT_PROP_IE_OVERHEAD 4 +#define HT_CAP_IE_LEN 26 +#define HT_CAP_IE_TYPE 51 + +#define HT_CAP_LDPC_CODING 0x0001 +#define HT_CAP_40MHZ 0x0002 +#define HT_CAP_MIMO_PS_MASK 0x000C +#define HT_CAP_MIMO_PS_SHIFT 0x0002 +#define HT_CAP_MIMO_PS_OFF 0x0003 +#define HT_CAP_MIMO_PS_RTS 0x0001 +#define HT_CAP_MIMO_PS_ON 0x0000 +#define HT_CAP_GF 0x0010 +#define HT_CAP_SHORT_GI_20 0x0020 +#define HT_CAP_SHORT_GI_40 0x0040 +#define HT_CAP_TX_STBC 0x0080 +#define HT_CAP_RX_STBC_MASK 0x0300 +#define HT_CAP_RX_STBC_SHIFT 8 +#define HT_CAP_DELAYED_BA 0x0400 +#define HT_CAP_MAX_AMSDU 0x0800 + +#define HT_CAP_DSSS_CCK 0x1000 +#define HT_CAP_PSMP 0x2000 +#define HT_CAP_40MHZ_INTOLERANT 0x4000 +#define HT_CAP_LSIG_TXOP 0x8000 + +#define HT_CAP_RX_STBC_NO 0x0 +#define HT_CAP_RX_STBC_ONE_STREAM 0x1 +#define HT_CAP_RX_STBC_TWO_STREAM 0x2 +#define HT_CAP_RX_STBC_THREE_STREAM 0x3 + +#define HT_CAP_TXBF_CAP_IMPLICIT_TXBF_RX 0x1 +#define HT_CAP_TXBF_CAP_NDP_TX 0x8 +#define HT_CAP_TXBF_CAP_NDP_RX 0x10 +#define HT_CAP_TXBF_CAP_EXPLICIT_CSI 0x100 +#define HT_CAP_TXBF_CAP_EXPLICIT_NC_STEERING 0x200 +#define HT_CAP_TXBF_CAP_EXPLICIT_C_STEERING 0x400 +#define HT_CAP_TXBF_CAP_EXPLICIT_CSI_FB_MASK 0x1800 +#define HT_CAP_TXBF_CAP_EXPLICIT_CSI_FB_SHIFT 11 +#define HT_CAP_TXBF_CAP_EXPLICIT_NC_FB_MASK 0x6000 +#define HT_CAP_TXBF_CAP_EXPLICIT_NC_FB_SHIFT 13 +#define HT_CAP_TXBF_CAP_EXPLICIT_C_FB_MASK 0x18000 +#define HT_CAP_TXBF_CAP_EXPLICIT_C_FB_SHIFT 15 +#define HT_CAP_TXBF_CAP_CSI_BFR_ANT_SHIFT 19 +#define HT_CAP_TXBF_CAP_NC_BFR_ANT_SHIFT 21 +#define HT_CAP_TXBF_CAP_C_BFR_ANT_SHIFT 23 +#define HT_CAP_TXBF_CAP_C_BFR_ANT_MASK 0x1800000 + +#define HT_CAP_TXBF_CAP_CHAN_ESTIM_SHIFT 27 +#define HT_CAP_TXBF_CAP_CHAN_ESTIM_MASK 0x18000000 + +#define HT_CAP_TXBF_FB_TYPE_NONE 0 +#define HT_CAP_TXBF_FB_TYPE_DELAYED 1 +#define HT_CAP_TXBF_FB_TYPE_IMMEDIATE 2 +#define HT_CAP_TXBF_FB_TYPE_BOTH 3 + +#define VHT_MAX_MPDU 11454 +#define VHT_MPDU_MSDU_DELTA 56 + +#define VHT_MAX_AMSDU (VHT_MAX_MPDU - VHT_MPDU_MSDU_DELTA) + +#define HT_MAX_AMSDU 7935 +#define HT_MIN_AMSDU 3835 + +#define HT_PARAMS_RX_FACTOR_MASK 0x03 +#define HT_PARAMS_DENSITY_MASK 0x1C +#define HT_PARAMS_DENSITY_SHIFT 2 + +#define AMPDU_MAX_MPDU_DENSITY 7 +#define AMPDU_DENSITY_NONE 0 +#define AMPDU_DENSITY_1over4_US 1 +#define AMPDU_DENSITY_1over2_US 2 +#define AMPDU_DENSITY_1_US 3 +#define AMPDU_DENSITY_2_US 4 +#define AMPDU_DENSITY_4_US 5 +#define AMPDU_DENSITY_8_US 6 +#define AMPDU_DENSITY_16_US 7 +#define AMPDU_RX_FACTOR_8K 0 +#define AMPDU_RX_FACTOR_16K 1 +#define AMPDU_RX_FACTOR_32K 2 +#define AMPDU_RX_FACTOR_64K 3 +#define AMPDU_RX_FACTOR_BASE 8*1024 + +#define AMPDU_DELIMITER_LEN 4 +#define AMPDU_DELIMITER_LEN_MAX 63 + +#define HT_CAP_EXT_PCO 0x0001 +#define HT_CAP_EXT_PCO_TTIME_MASK 0x0006 +#define HT_CAP_EXT_PCO_TTIME_SHIFT 1 +#define HT_CAP_EXT_MCS_FEEDBACK_MASK 0x0300 +#define HT_CAP_EXT_MCS_FEEDBACK_SHIFT 8 +#define HT_CAP_EXT_HTC 0x0400 +#define HT_CAP_EXT_RD_RESP 0x0800 + +BWL_PRE_PACKED_STRUCT struct ht_add_ie { + uint8 ctl_ch; + uint8 byte1; + uint16 opmode; + uint16 misc_bits; + uint8 basic_mcs[MCSSET_LEN]; +} BWL_POST_PACKED_STRUCT; +typedef struct ht_add_ie ht_add_ie_t; + +BWL_PRE_PACKED_STRUCT struct ht_prop_add_ie { + uint8 id; + uint8 len; + uint8 oui[3]; + uint8 type; + ht_add_ie_t add_ie; +} BWL_POST_PACKED_STRUCT; +typedef struct ht_prop_add_ie ht_prop_add_ie_t; + +#define HT_ADD_IE_LEN 22 +#define HT_ADD_IE_TYPE 52 + +#define HT_BW_ANY 0x04 +#define HT_RIFS_PERMITTED 0x08 + +#define HT_OPMODE_MASK 0x0003 +#define HT_OPMODE_SHIFT 0 +#define HT_OPMODE_PURE 0x0000 +#define HT_OPMODE_OPTIONAL 0x0001 +#define HT_OPMODE_HT20IN40 0x0002 +#define HT_OPMODE_MIXED 0x0003 +#define HT_OPMODE_NONGF 0x0004 +#define DOT11N_TXBURST 0x0008 +#define DOT11N_OBSS_NONHT 0x0010 + +#define HT_BASIC_STBC_MCS 0x007f +#define HT_DUAL_STBC_PROT 0x0080 +#define HT_SECOND_BCN 0x0100 +#define HT_LSIG_TXOP 0x0200 +#define HT_PCO_ACTIVE 0x0400 +#define HT_PCO_PHASE 0x0800 +#define HT_DUALCTS_PROTECTION 0x0080 + +#define DOT11N_2G_TXBURST_LIMIT 6160 +#define DOT11N_5G_TXBURST_LIMIT 3080 + +#define GET_HT_OPMODE(add_ie) ((ltoh16_ua(&add_ie->opmode) & HT_OPMODE_MASK) \ + >> HT_OPMODE_SHIFT) +#define HT_MIXEDMODE_PRESENT(add_ie) ((ltoh16_ua(&add_ie->opmode) & HT_OPMODE_MASK) \ + == HT_OPMODE_MIXED) +#define HT_HT20_PRESENT(add_ie) ((ltoh16_ua(&add_ie->opmode) & HT_OPMODE_MASK) \ + == HT_OPMODE_HT20IN40) +#define HT_OPTIONAL_PRESENT(add_ie) ((ltoh16_ua(&add_ie->opmode) & HT_OPMODE_MASK) \ + == HT_OPMODE_OPTIONAL) +#define HT_USE_PROTECTION(add_ie) (HT_HT20_PRESENT((add_ie)) || \ + HT_MIXEDMODE_PRESENT((add_ie))) +#define HT_NONGF_PRESENT(add_ie) ((ltoh16_ua(&add_ie->opmode) & HT_OPMODE_NONGF) \ + == HT_OPMODE_NONGF) +#define DOT11N_TXBURST_PRESENT(add_ie) ((ltoh16_ua(&add_ie->opmode) & DOT11N_TXBURST) \ + == DOT11N_TXBURST) +#define DOT11N_OBSS_NONHT_PRESENT(add_ie) ((ltoh16_ua(&add_ie->opmode) & DOT11N_OBSS_NONHT) \ + == DOT11N_OBSS_NONHT) + +BWL_PRE_PACKED_STRUCT struct obss_params { + uint16 passive_dwell; + uint16 active_dwell; + uint16 bss_widthscan_interval; + uint16 passive_total; + uint16 active_total; + uint16 chanwidth_transition_dly; + uint16 activity_threshold; +} BWL_POST_PACKED_STRUCT; +typedef struct obss_params obss_params_t; + +BWL_PRE_PACKED_STRUCT struct dot11_obss_ie { + uint8 id; + uint8 len; + obss_params_t obss_params; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_obss_ie dot11_obss_ie_t; +#define DOT11_OBSS_SCAN_IE_LEN sizeof(obss_params_t) + +#define HT_CTRL_LA_TRQ 0x00000002 +#define HT_CTRL_LA_MAI 0x0000003C +#define HT_CTRL_LA_MAI_SHIFT 2 +#define HT_CTRL_LA_MAI_MRQ 0x00000004 +#define HT_CTRL_LA_MAI_MSI 0x00000038 +#define HT_CTRL_LA_MFSI 0x000001C0 +#define HT_CTRL_LA_MFSI_SHIFT 6 +#define HT_CTRL_LA_MFB_ASELC 0x0000FE00 +#define HT_CTRL_LA_MFB_ASELC_SH 9 +#define HT_CTRL_LA_ASELC_CMD 0x00000C00 +#define HT_CTRL_LA_ASELC_DATA 0x0000F000 +#define HT_CTRL_CAL_POS 0x00030000 +#define HT_CTRL_CAL_SEQ 0x000C0000 +#define HT_CTRL_CSI_STEERING 0x00C00000 +#define HT_CTRL_CSI_STEER_SHIFT 22 +#define HT_CTRL_CSI_STEER_NFB 0 +#define HT_CTRL_CSI_STEER_CSI 1 +#define HT_CTRL_CSI_STEER_NCOM 2 +#define HT_CTRL_CSI_STEER_COM 3 +#define HT_CTRL_NDP_ANNOUNCE 0x01000000 +#define HT_CTRL_AC_CONSTRAINT 0x40000000 +#define HT_CTRL_RDG_MOREPPDU 0x80000000 + +#define HT_OPMODE_OPTIONAL 0x0001 +#define HT_OPMODE_HT20IN40 0x0002 +#define HT_OPMODE_MIXED 0x0003 +#define HT_OPMODE_NONGF 0x0004 +#define DOT11N_TXBURST 0x0008 +#define DOT11N_OBSS_NONHT 0x0010 + +BWL_PRE_PACKED_STRUCT struct vht_cap_ie { + uint32 vht_cap_info; + + uint16 rx_mcs_map; + uint16 rx_max_rate; + uint16 tx_mcs_map; + uint16 tx_max_rate; +} BWL_POST_PACKED_STRUCT; +typedef struct vht_cap_ie vht_cap_ie_t; + +#define VHT_CAP_IE_LEN 12 + +#define VHT_CAP_INFO_MAX_MPDU_LEN_MASK 0x00000003 +#define VHT_CAP_INFO_SUPP_CHAN_WIDTH_MASK 0x0000000c +#define VHT_CAP_INFO_LDPC 0x00000010 +#define VHT_CAP_INFO_SGI_80MHZ 0x00000020 +#define VHT_CAP_INFO_SGI_160MHZ 0x00000040 +#define VHT_CAP_INFO_TX_STBC 0x00000080 +#define VHT_CAP_INFO_RX_STBC_MASK 0x00000700 +#define VHT_CAP_INFO_RX_STBC_SHIFT 8 +#define VHT_CAP_INFO_SU_BEAMFMR 0x00000800 +#define VHT_CAP_INFO_SU_BEAMFMEE 0x00001000 +#define VHT_CAP_INFO_NUM_BMFMR_ANT_MASK 0x0000e000 +#define VHT_CAP_INFO_NUM_BMFMR_ANT_SHIFT 13 +#define VHT_CAP_INFO_NUM_SOUNDING_DIM_MASK 0x00070000 +#define VHT_CAP_INFO_NUM_SOUNDING_DIM_SHIFT 16 +#define VHT_CAP_INFO_MU_BEAMFMR 0x00080000 +#define VHT_CAP_INFO_MU_BEAMFMEE 0x00100000 +#define VHT_CAP_INFO_TXOPPS 0x00200000 +#define VHT_CAP_INFO_HTCVHT 0x00400000 +#define VHT_CAP_INFO_AMPDU_MAXLEN_EXP_MASK 0x03800000 +#define VHT_CAP_INFO_AMPDU_MAXLEN_EXP_SHIFT 23 +#define VHT_CAP_INFO_LINK_ADAPT_CAP_MASK 0x0c000000 +#define VHT_CAP_INFO_LINK_ADAPT_CAP_SHIFT 26 + +#define VHT_CAP_SUPP_MCS_RX_HIGHEST_RATE_MASK 0x1fff +#define VHT_CAP_SUPP_MCS_RX_HIGHEST_RATE_SHIFT 0 + +#define VHT_CAP_SUPP_MCS_TX_HIGHEST_RATE_MASK 0x1fff +#define VHT_CAP_SUPP_MCS_TX_HIGHEST_RATE_SHIFT 0 + +#define VHT_CAP_MCS_MAP_0_7 0 +#define VHT_CAP_MCS_MAP_0_8 1 +#define VHT_CAP_MCS_MAP_0_9 2 +#define VHT_CAP_MCS_MAP_NONE 3 +#define VHT_CAP_MCS_MAP_S 2 +#define VHT_CAP_MCS_MAP_M 0x3 + +#define VHT_CAP_MCS_MAP_NONE_ALL 0xffff + +#define VHT_CAP_MCS_MAP_0_9_NSS3 \ + ((VHT_CAP_MCS_MAP_0_9 << VHT_MCS_MAP_GET_SS_IDX(1)) | \ + (VHT_CAP_MCS_MAP_0_9 << VHT_MCS_MAP_GET_SS_IDX(2)) | \ + (VHT_CAP_MCS_MAP_0_9 << VHT_MCS_MAP_GET_SS_IDX(3))) + +#define VHT_CAP_MCS_MAP_NSS_MAX 8 + +#define VHT_CAP_MCS_MAP_CREATE(mcsmap, nss, mcs) \ + do { \ + int i; \ + for (i = 1; i <= nss; i++) { \ + VHT_MCS_MAP_SET_MCS_PER_SS(i, mcs, mcsmap); \ + } \ + } while (0) + +#define VHT_MCS_CODE_TO_MCS_MAP(mcs_code) \ + ((mcs_code == VHT_CAP_MCS_MAP_0_7) ? 0xff : \ + (mcs_code == VHT_CAP_MCS_MAP_0_8) ? 0x1ff : \ + (mcs_code == VHT_CAP_MCS_MAP_0_9) ? 0x3ff : 0) + +#define VHT_MCS_MAP_TO_MCS_CODE(mcs_map) \ + ((mcs_map == 0xff) ? VHT_CAP_MCS_MAP_0_7 : \ + (mcs_map == 0x1ff) ? VHT_CAP_MCS_MAP_0_8 : \ + (mcs_map == 0x3ff) ? VHT_CAP_MCS_MAP_0_9 : VHT_CAP_MCS_MAP_NONE) + +typedef enum vht_cap_chan_width { + VHT_CAP_CHAN_WIDTH_SUPPORT_MANDATORY = 0x00, + VHT_CAP_CHAN_WIDTH_SUPPORT_160 = 0x04, + VHT_CAP_CHAN_WIDTH_SUPPORT_160_8080 = 0x08 +} vht_cap_chan_width_t; + +typedef enum vht_cap_max_mpdu_len { + VHT_CAP_MPDU_MAX_4K = 0x00, + VHT_CAP_MPDU_MAX_8K = 0x01, + VHT_CAP_MPDU_MAX_11K = 0x02 +} vht_cap_max_mpdu_len_t; + +#define VHT_MPDU_LIMIT_4K 3895 +#define VHT_MPDU_LIMIT_8K 7991 +#define VHT_MPDU_LIMIT_11K 11454 + +BWL_PRE_PACKED_STRUCT struct vht_op_ie { + uint8 chan_width; + uint8 chan1; + uint8 chan2; + uint16 supp_mcs; +} BWL_POST_PACKED_STRUCT; +typedef struct vht_op_ie vht_op_ie_t; + +#define VHT_OP_IE_LEN 5 + +typedef enum vht_op_chan_width { + VHT_OP_CHAN_WIDTH_20_40 = 0, + VHT_OP_CHAN_WIDTH_80 = 1, + VHT_OP_CHAN_WIDTH_160 = 2, + VHT_OP_CHAN_WIDTH_80_80 = 3 +} vht_op_chan_width_t; + +#define AID_IE_LEN 2 + +#define VHT_FEATURES_IE_TYPE 0x4 +BWL_PRE_PACKED_STRUCT struct vht_features_ie_hdr { + uint8 oui[3]; + uint8 type; + uint8 rate_mask; +} BWL_POST_PACKED_STRUCT; +typedef struct vht_features_ie_hdr vht_features_ie_hdr_t; + +#define VHT_MCS_MAP_GET_SS_IDX(nss) (((nss)-1) * VHT_CAP_MCS_MAP_S) +#define VHT_MCS_MAP_GET_MCS_PER_SS(nss, mcsMap) \ + (((mcsMap) >> VHT_MCS_MAP_GET_SS_IDX(nss)) & VHT_CAP_MCS_MAP_M) +#define VHT_MCS_MAP_SET_MCS_PER_SS(nss, numMcs, mcsMap) \ + do { \ + (mcsMap) &= (~(VHT_CAP_MCS_MAP_M << VHT_MCS_MAP_GET_SS_IDX(nss))); \ + (mcsMap) |= (((numMcs) & VHT_CAP_MCS_MAP_M) << VHT_MCS_MAP_GET_SS_IDX(nss)); \ + } while (0) +#define VHT_MCS_SS_SUPPORTED(nss, mcsMap) \ + (VHT_MCS_MAP_GET_MCS_PER_SS((nss), (mcsMap)) != VHT_CAP_MCS_MAP_NONE) + +#define WPA_OUI "\x00\x50\xF2" +#define WPA_OUI_LEN 3 +#define WPA_OUI_TYPE 1 +#define WPA_VERSION 1 +#define WPA2_OUI "\x00\x0F\xAC" +#define WPA2_OUI_LEN 3 +#define WPA2_VERSION 1 +#define WPA2_VERSION_LEN 2 + +#define WPS_OUI "\x00\x50\xF2" +#define WPS_OUI_LEN 3 +#define WPS_OUI_TYPE 4 + +#define WFA_OUI "\x50\x6F\x9A" +#define WFA_OUI_LEN 3 +#define WFA_OUI_TYPE_P2P 9 + +#define WFA_OUI_TYPE_TPC 8 + +#define RSN_AKM_NONE 0 +#define RSN_AKM_UNSPECIFIED 1 +#define RSN_AKM_PSK 2 +#define RSN_AKM_FBT_1X 3 +#define RSN_AKM_FBT_PSK 4 +#define RSN_AKM_MFP_1X 5 +#define RSN_AKM_MFP_PSK 6 +#define RSN_AKM_TPK 7 + +#define DOT11_MAX_DEFAULT_KEYS 4 +#define DOT11_MAX_KEY_SIZE 32 +#define DOT11_MAX_IV_SIZE 16 +#define DOT11_EXT_IV_FLAG (1<<5) +#define DOT11_WPA_KEY_RSC_LEN 8 + +#define WEP1_KEY_SIZE 5 +#define WEP1_KEY_HEX_SIZE 10 +#define WEP128_KEY_SIZE 13 +#define WEP128_KEY_HEX_SIZE 26 +#define TKIP_MIC_SIZE 8 +#define TKIP_EOM_SIZE 7 +#define TKIP_EOM_FLAG 0x5a +#define TKIP_KEY_SIZE 32 +#define TKIP_MIC_AUTH_TX 16 +#define TKIP_MIC_AUTH_RX 24 +#define TKIP_MIC_SUP_RX TKIP_MIC_AUTH_TX +#define TKIP_MIC_SUP_TX TKIP_MIC_AUTH_RX +#define AES_KEY_SIZE 16 +#define AES_MIC_SIZE 8 +#define BIP_KEY_SIZE 16 +#define BIP_MIC_SIZE 8 + +#define WCN_OUI "\x00\x50\xf2" +#define WCN_TYPE 4 + +BWL_PRE_PACKED_STRUCT struct dot11_mdid_ie { + uint8 id; + uint8 len; + uint16 mdid; + uint8 cap; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_mdid_ie dot11_mdid_ie_t; + +#define FBT_MDID_CAP_OVERDS 0x01 +#define FBT_MDID_CAP_RRP 0x02 + +BWL_PRE_PACKED_STRUCT struct dot11_ft_ie { + uint8 id; + uint8 len; + uint16 mic_control; + uint8 mic[16]; + uint8 anonce[32]; + uint8 snonce[32]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_ft_ie dot11_ft_ie_t; + +#define TIE_TYPE_RESERVED 0 +#define TIE_TYPE_REASSOC_DEADLINE 1 +#define TIE_TYPE_KEY_LIEFTIME 2 +#define TIE_TYPE_ASSOC_COMEBACK 3 +BWL_PRE_PACKED_STRUCT struct dot11_timeout_ie { + uint8 id; + uint8 len; + uint8 type; + uint32 value; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_timeout_ie dot11_timeout_ie_t; + +BWL_PRE_PACKED_STRUCT struct dot11_gtk_ie { + uint8 id; + uint8 len; + uint16 key_info; + uint8 key_len; + uint8 rsc[8]; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_gtk_ie dot11_gtk_ie_t; + +BWL_PRE_PACKED_STRUCT struct mmic_ie { + uint8 id; + uint8 len; + uint16 key_id; + uint8 ipn[6]; + uint8 mic[BIP_MIC_SIZE]; +} BWL_POST_PACKED_STRUCT; +typedef struct mmic_ie mmic_ie_t; + +#define BSSID_INVALID "\x00\x00\x00\x00\x00\x00" +#define BSSID_BROADCAST "\xFF\xFF\xFF\xFF\xFF\xFF" + +#define WMM_OUI "\x00\x50\xF2" +#define WMM_OUI_LEN 3 +#define WMM_OUI_TYPE 2 +#define WMM_VERSION 1 +#define WMM_VERSION_LEN 1 + +#define WMM_OUI_SUBTYPE_PARAMETER 1 +#define WMM_PARAMETER_IE_LEN 24 + +BWL_PRE_PACKED_STRUCT struct link_id_ie { + uint8 id; + uint8 len; + struct ether_addr bssid; + struct ether_addr tdls_init_mac; + struct ether_addr tdls_resp_mac; +} BWL_POST_PACKED_STRUCT; +typedef struct link_id_ie link_id_ie_t; +#define TDLS_LINK_ID_IE_LEN 18 + +BWL_PRE_PACKED_STRUCT struct wakeup_sch_ie { + uint8 id; + uint8 len; + uint32 offset; + uint32 interval; + uint32 awake_win_slots; + uint32 max_wake_win; + uint16 idle_cnt; +} BWL_POST_PACKED_STRUCT; +typedef struct wakeup_sch_ie wakeup_sch_ie_t; +#define TDLS_WAKEUP_SCH_IE_LEN 18 + +BWL_PRE_PACKED_STRUCT struct channel_switch_timing_ie { + uint8 id; + uint8 len; + uint16 switch_time; + uint16 switch_timeout; +} BWL_POST_PACKED_STRUCT; +typedef struct channel_switch_timing_ie channel_switch_timing_ie_t; +#define TDLS_CHANNEL_SWITCH_TIMING_IE_LEN 4 + +BWL_PRE_PACKED_STRUCT struct pti_control_ie { + uint8 id; + uint8 len; + uint8 tid; + uint16 seq_control; +} BWL_POST_PACKED_STRUCT; +typedef struct pti_control_ie pti_control_ie_t; +#define TDLS_PTI_CONTROL_IE_LEN 3 + +BWL_PRE_PACKED_STRUCT struct pu_buffer_status_ie { + uint8 id; + uint8 len; + uint8 status; +} BWL_POST_PACKED_STRUCT; +typedef struct pu_buffer_status_ie pu_buffer_status_ie_t; +#define TDLS_PU_BUFFER_STATUS_IE_LEN 1 +#define TDLS_PU_BUFFER_STATUS_AC_BK 1 +#define TDLS_PU_BUFFER_STATUS_AC_BE 2 +#define TDLS_PU_BUFFER_STATUS_AC_VI 4 +#define TDLS_PU_BUFFER_STATUS_AC_VO 8 + +#define GAS_REQUEST_ACTION_FRAME 10 +#define GAS_RESPONSE_ACTION_FRAME 11 +#define GAS_COMEBACK_REQUEST_ACTION_FRAME 12 +#define GAS_COMEBACK_RESPONSE_ACTION_FRAME 13 + +#define IW_ANT_MASK 0x0f +#define IW_INTERNET_MASK 0x10 +#define IW_ASRA_MASK 0x20 +#define IW_ESR_MASK 0x40 +#define IW_UESA_MASK 0x80 + +#define IW_ANT_PRIVATE_NETWORK 0 +#define IW_ANT_PRIVATE_NETWORK_WITH_GUEST 1 +#define IW_ANT_CHARGEABLE_PUBLIC_NETWORK 2 +#define IW_ANT_FREE_PUBLIC_NETWORK 3 +#define IW_ANT_PERSONAL_DEVICE_NETWORK 4 +#define IW_ANT_EMERGENCY_SERVICES_NETWORK 5 +#define IW_ANT_TEST_NETWORK 14 +#define IW_ANT_WILDCARD_NETWORK 15 + +#define ADVP_ANQP_PROTOCOL_ID 0 + +#define ADVP_QRL_MASK 0x7f +#define ADVP_PAME_BI_MASK 0x80 + +#define ADVP_QRL_REQUEST 0x00 +#define ADVP_QRL_RESPONSE 0x7f +#define ADVP_PAME_BI_DEPENDENT 0x00 +#define ADVP_PAME_BI_INDEPENDENT ADVP_PAME_BI_MASK + +#define ANQP_ID_QUERY_LIST 256 +#define ANQP_ID_CAPABILITY_LIST 257 +#define ANQP_ID_VENUE_NAME_INFO 258 +#define ANQP_ID_EMERGENCY_CALL_NUMBER_INFO 259 +#define ANQP_ID_NETWORK_AUTHENTICATION_TYPE_INFO 260 +#define ANQP_ID_ROAMING_CONSORTIUM_LIST 261 +#define ANQP_ID_IP_ADDRESS_TYPE_AVAILABILITY_INFO 262 +#define ANQP_ID_NAI_REALM_LIST 263 +#define ANQP_ID_G3PP_CELLULAR_NETWORK_INFO 264 +#define ANQP_ID_AP_GEOSPATIAL_LOCATION 265 +#define ANQP_ID_AP_CIVIC_LOCATION 266 +#define ANQP_ID_AP_LOCATION_PUBLIC_ID_URI 267 +#define ANQP_ID_DOMAIN_NAME_LIST 268 +#define ANQP_ID_EMERGENCY_ALERT_ID_URI 269 +#define ANQP_ID_EMERGENCY_NAI 271 +#define ANQP_ID_VENDOR_SPECIFIC_LIST 56797 + +#define ANQP_OUI_SUBTYPE 9 + +#define VENUE_LANGUAGE_CODE_SIZE 3 +#define VENUE_NAME_SIZE 255 + +#define VENUE_UNSPECIFIED 0 +#define VENUE_ASSEMBLY 1 +#define VENUE_BUSINESS 2 +#define VENUE_EDUCATIONAL 3 +#define VENUE_FACTORY 4 +#define VENUE_INSTITUTIONAL 5 +#define VENUE_MERCANTILE 6 +#define VENUE_RESIDENTIAL 7 +#define VENUE_STORAGE 8 +#define VENUE_UTILITY 9 +#define VENUE_VEHICULAR 10 +#define VENUE_OUTDOOR 11 + +#define NATI_ACCEPTANCE_OF_TERMS_CONDITIONS 0 +#define NATI_ONLINE_ENROLLMENT_SUPPORTED 1 +#define NATI_HTTP_HTTPS_REDIRECTION 2 +#define NATI_DNS_REDIRECTION 3 + +#define IPA_IPV6_SHIFT 0 +#define IPA_IPV6_MASK (0x03 << IPA_IPV6_SHIFT) +#define IPA_IPV6_NOT_AVAILABLE 0x00 +#define IPA_IPV6_AVAILABLE 0x01 +#define IPA_IPV6_UNKNOWN_AVAILABILITY 0x02 + +#define IPA_IPV4_SHIFT 2 +#define IPA_IPV4_MASK (0x3f << IPA_IPV4_SHIFT) +#define IPA_IPV4_NOT_AVAILABLE 0x00 +#define IPA_IPV4_PUBLIC 0x01 +#define IPA_IPV4_PORT_RESTRICT 0x02 +#define IPA_IPV4_SINGLE_NAT 0x03 +#define IPA_IPV4_DOUBLE_NAT 0x04 +#define IPA_IPV4_PORT_RESTRICT_SINGLE_NAT 0x05 +#define IPA_IPV4_PORT_RESTRICT_DOUBLE_NAT 0x06 +#define IPA_IPV4_UNKNOWN_AVAILABILITY 0x07 + +#define REALM_ENCODING_RFC4282 0 +#define REALM_ENCODING_UTF8 1 + +#define REALM_EAP_TLS 13 +#define REALM_EAP_SIM 18 +#define REALM_EAP_TTLS 21 +#define REALM_EAP_AKA 23 +#define REALM_EAP_PSK 47 +#define REALM_EAP_AKAP 50 + +#define REALM_EXPANDED_EAP 1 +#define REALM_NON_EAP_INNER_AUTHENTICATION 2 +#define REALM_INNER_AUTHENTICATION_EAP 3 +#define REALM_EXPANDED_INNER_EAP 4 +#define REALM_CREDENTIAL 5 +#define REALM_TUNNELED_EAP_CREDENTIAL 6 +#define REALM_VENDOR_SPECIFIC_EAP 221 + +#define REALM_PAP 1 +#define REALM_CHAP 2 +#define REALM_MSCHAP 3 +#define REALM_MSCHAPV2 4 + +#define REALM_SIM 1 +#define REALM_USIM 2 +#define REALM_NFC 3 +#define REALM_HARDWARE_TOKEN 4 +#define REALM_SOFTOKEN 5 +#define REALM_CERTIFICATE 6 +#define REALM_USERNAME_PASSWORD 7 +#define REALM_SERVER_SIDE 8 + +#define G3PP_GUD_VERSION 0 +#define G3PP_PLMN_LIST_IE 0 + +#include + +#endif diff --git a/drivers/custom/broadcom-wl/src/common/include/proto/802.1d.h b/drivers/custom/broadcom-wl/src/common/include/proto/802.1d.h new file mode 100644 index 000000000000..b30912d3bd45 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/common/include/proto/802.1d.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Fundamental types and constants relating to 802.1D + * + * $Id: 802.1d.h 241182 2011-02-17 21:50:03Z $ + */ + +#ifndef _802_1_D_ +#define _802_1_D_ + +#define PRIO_8021D_NONE 2 +#define PRIO_8021D_BK 1 +#define PRIO_8021D_BE 0 +#define PRIO_8021D_EE 3 +#define PRIO_8021D_CL 4 +#define PRIO_8021D_VI 5 +#define PRIO_8021D_VO 6 +#define PRIO_8021D_NC 7 +#define MAXPRIO 7 +#define NUMPRIO (MAXPRIO + 1) + +#define ALLPRIO -1 + +#define PRIO2PREC(prio) \ + (((prio) == PRIO_8021D_NONE || (prio) == PRIO_8021D_BE) ? ((prio^2)) : (prio)) + +#endif diff --git a/drivers/custom/broadcom-wl/src/common/include/proto/bcmeth.h b/drivers/custom/broadcom-wl/src/common/include/proto/bcmeth.h new file mode 100644 index 000000000000..55fef46960f7 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/common/include/proto/bcmeth.h @@ -0,0 +1,62 @@ +/* + * Broadcom Ethernettype protocol definitions + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: bcmeth.h 294352 2011-11-06 19:23:00Z $ + */ + +#ifndef _BCMETH_H_ +#define _BCMETH_H_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + +#include + +#define BCMILCP_SUBTYPE_RATE 1 +#define BCMILCP_SUBTYPE_LINK 2 +#define BCMILCP_SUBTYPE_CSA 3 +#define BCMILCP_SUBTYPE_LARQ 4 +#define BCMILCP_SUBTYPE_VENDOR 5 +#define BCMILCP_SUBTYPE_FLH 17 + +#define BCMILCP_SUBTYPE_VENDOR_LONG 32769 +#define BCMILCP_SUBTYPE_CERT 32770 +#define BCMILCP_SUBTYPE_SES 32771 + +#define BCMILCP_BCM_SUBTYPE_RESERVED 0 +#define BCMILCP_BCM_SUBTYPE_EVENT 1 +#define BCMILCP_BCM_SUBTYPE_SES 2 + +#define BCMILCP_BCM_SUBTYPE_DPT 4 + +#define BCMILCP_BCM_SUBTYPEHDR_MINLENGTH 8 +#define BCMILCP_BCM_SUBTYPEHDR_VERSION 0 + +typedef BWL_PRE_PACKED_STRUCT struct bcmeth_hdr +{ + uint16 subtype; + uint16 length; + uint8 version; + uint8 oui[3]; + + uint16 usr_subtype; +} BWL_POST_PACKED_STRUCT bcmeth_hdr_t; + +#include + +#endif diff --git a/drivers/custom/broadcom-wl/src/common/include/proto/bcmevent.h b/drivers/custom/broadcom-wl/src/common/include/proto/bcmevent.h new file mode 100644 index 000000000000..f7253c0bcf4a --- /dev/null +++ b/drivers/custom/broadcom-wl/src/common/include/proto/bcmevent.h @@ -0,0 +1,343 @@ +/* + * Broadcom Event protocol definitions + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Dependencies: proto/bcmeth.h + * + * $Id: bcmevent.h 382794 2013-02-04 17:34:08Z $ + * + */ + +#ifndef _BCMEVENT_H_ +#define _BCMEVENT_H_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + +#include + +#include + +#define BCM_EVENT_MSG_VERSION 2 +#define BCM_MSG_IFNAME_MAX 16 + +#define WLC_EVENT_MSG_LINK 0x01 +#define WLC_EVENT_MSG_FLUSHTXQ 0x02 +#define WLC_EVENT_MSG_GROUP 0x04 +#define WLC_EVENT_MSG_UNKBSS 0x08 +#define WLC_EVENT_MSG_UNKIF 0x10 + +typedef BWL_PRE_PACKED_STRUCT struct +{ + uint16 version; + uint16 flags; + uint32 event_type; + uint32 status; + uint32 reason; + uint32 auth_type; + uint32 datalen; + struct ether_addr addr; + char ifname[BCM_MSG_IFNAME_MAX]; +} BWL_POST_PACKED_STRUCT wl_event_msg_v1_t; + +typedef BWL_PRE_PACKED_STRUCT struct +{ + uint16 version; + uint16 flags; + uint32 event_type; + uint32 status; + uint32 reason; + uint32 auth_type; + uint32 datalen; + struct ether_addr addr; + char ifname[BCM_MSG_IFNAME_MAX]; + uint8 ifidx; + uint8 bsscfgidx; +} BWL_POST_PACKED_STRUCT wl_event_msg_t; + +typedef BWL_PRE_PACKED_STRUCT struct bcm_event { + struct ether_header eth; + bcmeth_hdr_t bcm_hdr; + wl_event_msg_t event; + +} BWL_POST_PACKED_STRUCT bcm_event_t; + +#define BCM_MSG_LEN (sizeof(bcm_event_t) - sizeof(bcmeth_hdr_t) - sizeof(struct ether_header)) + +#define WLC_E_SET_SSID 0 +#define WLC_E_JOIN 1 +#define WLC_E_START 2 +#define WLC_E_AUTH 3 +#define WLC_E_AUTH_IND 4 +#define WLC_E_DEAUTH 5 +#define WLC_E_DEAUTH_IND 6 +#define WLC_E_ASSOC 7 +#define WLC_E_ASSOC_IND 8 +#define WLC_E_REASSOC 9 +#define WLC_E_REASSOC_IND 10 +#define WLC_E_DISASSOC 11 +#define WLC_E_DISASSOC_IND 12 +#define WLC_E_QUIET_START 13 +#define WLC_E_QUIET_END 14 +#define WLC_E_BEACON_RX 15 +#define WLC_E_LINK 16 +#define WLC_E_MIC_ERROR 17 +#define WLC_E_NDIS_LINK 18 +#define WLC_E_ROAM 19 +#define WLC_E_TXFAIL 20 +#define WLC_E_PMKID_CACHE 21 +#define WLC_E_RETROGRADE_TSF 22 +#define WLC_E_PRUNE 23 +#define WLC_E_AUTOAUTH 24 +#define WLC_E_EAPOL_MSG 25 +#define WLC_E_SCAN_COMPLETE 26 +#define WLC_E_ADDTS_IND 27 +#define WLC_E_DELTS_IND 28 +#define WLC_E_BCNSENT_IND 29 +#define WLC_E_BCNRX_MSG 30 +#define WLC_E_BCNLOST_MSG 31 +#define WLC_E_ROAM_PREP 32 +#define WLC_E_PFN_NET_FOUND 33 +#define WLC_E_PFN_NET_LOST 34 +#define WLC_E_RESET_COMPLETE 35 +#define WLC_E_JOIN_START 36 +#define WLC_E_ROAM_START 37 +#define WLC_E_ASSOC_START 38 +#define WLC_E_IBSS_ASSOC 39 +#define WLC_E_RADIO 40 +#define WLC_E_PSM_WATCHDOG 41 +#define WLC_E_PROBREQ_MSG 44 +#define WLC_E_SCAN_CONFIRM_IND 45 +#define WLC_E_PSK_SUP 46 +#define WLC_E_COUNTRY_CODE_CHANGED 47 +#define WLC_E_EXCEEDED_MEDIUM_TIME 48 +#define WLC_E_ICV_ERROR 49 +#define WLC_E_UNICAST_DECODE_ERROR 50 +#define WLC_E_MULTICAST_DECODE_ERROR 51 +#define WLC_E_TRACE 52 +#define WLC_E_IF 54 +#define WLC_E_P2P_DISC_LISTEN_COMPLETE 55 +#define WLC_E_RSSI 56 +#define WLC_E_PFN_SCAN_COMPLETE 57 +#define WLC_E_EXTLOG_MSG 58 +#define WLC_E_ACTION_FRAME 59 +#define WLC_E_ACTION_FRAME_COMPLETE 60 +#define WLC_E_PRE_ASSOC_IND 61 +#define WLC_E_PRE_REASSOC_IND 62 +#define WLC_E_CHANNEL_ADOPTED 63 +#define WLC_E_AP_STARTED 64 +#define WLC_E_DFS_AP_STOP 65 +#define WLC_E_DFS_AP_RESUME 66 +#define WLC_E_WAI_STA_EVENT 67 +#define WLC_E_WAI_MSG 68 +#define WLC_E_ESCAN_RESULT 69 +#define WLC_E_ACTION_FRAME_OFF_CHAN_COMPLETE 70 +#define WLC_E_PROBRESP_MSG 71 +#define WLC_E_P2P_PROBREQ_MSG 72 +#define WLC_E_DCS_REQUEST 73 + +#define WLC_E_FIFO_CREDIT_MAP 74 + +#define WLC_E_ACTION_FRAME_RX 75 +#define WLC_E_WAKE_EVENT 76 +#define WLC_E_RM_COMPLETE 77 +#define WLC_E_HTSFSYNC 78 +#define WLC_E_OVERLAY_REQ 79 +#define WLC_E_CSA_COMPLETE_IND 80 +#define WLC_E_EXCESS_PM_WAKE_EVENT 81 +#define WLC_E_PFN_SCAN_NONE 82 +#define WLC_E_PFN_SCAN_ALLGONE 83 +#define WLC_E_GTK_PLUMBED 84 +#define WLC_E_ASSOC_IND_NDIS 85 +#define WLC_E_REASSOC_IND_NDIS 86 +#define WLC_E_ASSOC_REQ_IE 87 +#define WLC_E_ASSOC_RESP_IE 88 +#define WLC_E_ASSOC_RECREATED 89 +#define WLC_E_ACTION_FRAME_RX_NDIS 90 +#define WLC_E_AUTH_REQ 91 +#define WLC_E_TDLS_PEER_EVENT 92 +#define WLC_E_SPEEDY_RECREATE_FAIL 93 +#define WLC_E_NATIVE 94 +#define WLC_E_PKTDELAY_IND 95 +#define WLC_E_IBSS_COALESCE 96 +#define WLC_E_SERVICE_FOUND 102 +#define WLC_E_GAS_FRAGMENT_RX 103 +#define WLC_E_GAS_COMPLETE 104 +#define WLC_E_P2PO_ADD_DEVICE 105 +#define WLC_E_P2PO_DEL_DEVICE 106 +#define WLC_E_WNM_STA_SLEEP 107 +#define WLC_E_NONE 108 +#define WLC_E_PROXD 109 +#define WLC_E_AWDL_AW_EXT_END 111 +#define WLC_E_AWDL_AW_EXT_START 112 +#define WLC_E_AWDL_AW_START 113 +#define WLC_E_AWDL_RADIO_OFF 114 +#define WLC_E_AWDL_PEER_STATE 115 +#define WLC_E_AWDL_SYNC_STATE_CHANGED 116 +#define WLC_E_AWDL_CHIP_RESET 117 +#define WLC_E_AWDL_INTERLEAVED_SCAN_START 118 +#define WLC_E_AWDL_INTERLEAVED_SCAN_STOP 119 +#define WLC_E_AWDL_PEER_CACHE_CONTROL 120 +#define WLC_E_CSA_START_IND 121 +#define WLC_E_CSA_DONE_IND 122 +#define WLC_E_CSA_FAILURE_IND 123 +#define WLC_E_LAST 124 + +typedef struct { + uint event; + const char *name; +} bcmevent_name_t; + +extern const bcmevent_name_t bcmevent_names[]; +extern const int bcmevent_names_size; + +#define WLC_E_STATUS_SUCCESS 0 +#define WLC_E_STATUS_FAIL 1 +#define WLC_E_STATUS_TIMEOUT 2 +#define WLC_E_STATUS_NO_NETWORKS 3 +#define WLC_E_STATUS_ABORT 4 +#define WLC_E_STATUS_NO_ACK 5 +#define WLC_E_STATUS_UNSOLICITED 6 +#define WLC_E_STATUS_ATTEMPT 7 +#define WLC_E_STATUS_PARTIAL 8 +#define WLC_E_STATUS_NEWSCAN 9 +#define WLC_E_STATUS_NEWASSOC 10 +#define WLC_E_STATUS_11HQUIET 11 +#define WLC_E_STATUS_SUPPRESS 12 +#define WLC_E_STATUS_NOCHANS 13 +#define WLC_E_STATUS_CS_ABORT 15 +#define WLC_E_STATUS_ERROR 16 + +#define WLC_E_REASON_INITIAL_ASSOC 0 +#define WLC_E_REASON_LOW_RSSI 1 +#define WLC_E_REASON_DEAUTH 2 +#define WLC_E_REASON_DISASSOC 3 +#define WLC_E_REASON_BCNS_LOST 4 +#define WLC_E_REASON_MINTXRATE 9 +#define WLC_E_REASON_TXFAIL 10 + +#define WLC_E_REASON_FAST_ROAM_FAILED 5 +#define WLC_E_REASON_DIRECTED_ROAM 6 +#define WLC_E_REASON_TSPEC_REJECTED 7 +#define WLC_E_REASON_BETTER_AP 8 + +#define WLC_E_REASON_REQUESTED_ROAM 11 + +#define WLC_E_PRUNE_ENCR_MISMATCH 1 +#define WLC_E_PRUNE_BCAST_BSSID 2 +#define WLC_E_PRUNE_MAC_DENY 3 +#define WLC_E_PRUNE_MAC_NA 4 +#define WLC_E_PRUNE_REG_PASSV 5 +#define WLC_E_PRUNE_SPCT_MGMT 6 +#define WLC_E_PRUNE_RADAR 7 +#define WLC_E_RSN_MISMATCH 8 +#define WLC_E_PRUNE_NO_COMMON_RATES 9 +#define WLC_E_PRUNE_BASIC_RATES 10 +#define WLC_E_PRUNE_CIPHER_NA 12 +#define WLC_E_PRUNE_KNOWN_STA 13 +#define WLC_E_PRUNE_WDS_PEER 15 +#define WLC_E_PRUNE_QBSS_LOAD 16 +#define WLC_E_PRUNE_HOME_AP 17 + +#define WLC_E_SUP_OTHER 0 +#define WLC_E_SUP_DECRYPT_KEY_DATA 1 +#define WLC_E_SUP_BAD_UCAST_WEP128 2 +#define WLC_E_SUP_BAD_UCAST_WEP40 3 +#define WLC_E_SUP_UNSUP_KEY_LEN 4 +#define WLC_E_SUP_PW_KEY_CIPHER 5 +#define WLC_E_SUP_MSG3_TOO_MANY_IE 6 +#define WLC_E_SUP_MSG3_IE_MISMATCH 7 +#define WLC_E_SUP_NO_INSTALL_FLAG 8 +#define WLC_E_SUP_MSG3_NO_GTK 9 +#define WLC_E_SUP_GRP_KEY_CIPHER 10 +#define WLC_E_SUP_GRP_MSG1_NO_GTK 11 +#define WLC_E_SUP_GTK_DECRYPT_FAIL 12 +#define WLC_E_SUP_SEND_FAIL 13 +#define WLC_E_SUP_DEAUTH 14 +#define WLC_E_SUP_WPA_PSK_TMO 15 + +typedef BWL_PRE_PACKED_STRUCT struct wl_event_rx_frame_data { + uint16 version; + uint16 channel; + int32 rssi; + uint32 mactime; + uint32 rate; +} BWL_POST_PACKED_STRUCT wl_event_rx_frame_data_t; + +#define BCM_RX_FRAME_DATA_VERSION 1 + +typedef struct wl_event_data_if { + uint8 ifidx; + uint8 opcode; + uint8 reserved; + uint8 bssidx; + uint8 role; +} wl_event_data_if_t; + +#define WLC_E_IF_ADD 1 +#define WLC_E_IF_DEL 2 +#define WLC_E_IF_CHANGE 3 + +#define WLC_E_IF_FLAGS_BSSCFG_NOIF 0x1 + +#define WLC_E_IF_ROLE_STA 0 +#define WLC_E_IF_ROLE_AP 1 +#define WLC_E_IF_ROLE_WDS 2 +#define WLC_E_IF_ROLE_P2P_GO 3 +#define WLC_E_IF_ROLE_P2P_CLIENT 4 + +#define WLC_E_LINK_BCN_LOSS 1 +#define WLC_E_LINK_DISASSOC 2 +#define WLC_E_LINK_ASSOC_REC 3 +#define WLC_E_LINK_BSSCFG_DIS 4 + +#define WLC_E_OVL_DOWNLOAD 0 +#define WLC_E_OVL_UPDATE_IND 1 + +#define WLC_E_TDLS_PEER_DISCOVERED 0 +#define WLC_E_TDLS_PEER_CONNECTED 1 +#define WLC_E_TDLS_PEER_DISCONNECTED 2 + +typedef BWL_PRE_PACKED_STRUCT struct wl_event_gas { + uint16 channel; + uint8 dialog_token; + uint8 fragment_id; + uint16 status_code; + uint16 data_len; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT wl_event_gas_t; + +typedef BWL_PRE_PACKED_STRUCT struct wl_sd_tlv { + uint16 length; + uint8 protocol; + uint8 transaction_id; + uint8 status_code; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT wl_sd_tlv_t; + +typedef BWL_PRE_PACKED_STRUCT struct wl_event_sd { + uint16 channel; + uint8 count; + wl_sd_tlv_t tlv[1]; +} BWL_POST_PACKED_STRUCT wl_event_sd_t; + +#define WLC_E_PROXD_FOUND 1 +#define WLC_E_PROXD_GONE 2 + +#include + +#endif diff --git a/drivers/custom/broadcom-wl/src/common/include/proto/bcmip.h b/drivers/custom/broadcom-wl/src/common/include/proto/bcmip.h new file mode 100644 index 000000000000..50ce97dc04f2 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/common/include/proto/bcmip.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Fundamental constants relating to IP Protocol + * + * $Id: bcmip.h 329791 2012-04-26 22:36:58Z $ + */ + +#ifndef _bcmip_h_ +#define _bcmip_h_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + +#include + +#define IP_VER_OFFSET 0x0 +#define IP_VER_MASK 0xf0 +#define IP_VER_SHIFT 4 +#define IP_VER_4 4 +#define IP_VER_6 6 + +#define IP_VER(ip_body) \ + ((((uint8 *)(ip_body))[IP_VER_OFFSET] & IP_VER_MASK) >> IP_VER_SHIFT) + +#define IP_PROT_ICMP 0x1 +#define IP_PROT_IGMP 0x2 +#define IP_PROT_TCP 0x6 +#define IP_PROT_UDP 0x11 +#define IP_PROT_ICMP6 0x3a + +#define IPV4_VER_HL_OFFSET 0 +#define IPV4_TOS_OFFSET 1 +#define IPV4_PKTLEN_OFFSET 2 +#define IPV4_PKTFLAG_OFFSET 6 +#define IPV4_PROT_OFFSET 9 +#define IPV4_CHKSUM_OFFSET 10 +#define IPV4_SRC_IP_OFFSET 12 +#define IPV4_DEST_IP_OFFSET 16 +#define IPV4_OPTIONS_OFFSET 20 +#define IPV4_MIN_HEADER_LEN 20 + +#define IPV4_VER_MASK 0xf0 +#define IPV4_VER_SHIFT 4 + +#define IPV4_HLEN_MASK 0x0f +#define IPV4_HLEN(ipv4_body) (4 * (((uint8 *)(ipv4_body))[IPV4_VER_HL_OFFSET] & IPV4_HLEN_MASK)) + +#define IPV4_ADDR_LEN 4 + +#define IPV4_ADDR_NULL(a) ((((uint8 *)(a))[0] | ((uint8 *)(a))[1] | \ + ((uint8 *)(a))[2] | ((uint8 *)(a))[3]) == 0) + +#define IPV4_ADDR_BCAST(a) ((((uint8 *)(a))[0] & ((uint8 *)(a))[1] & \ + ((uint8 *)(a))[2] & ((uint8 *)(a))[3]) == 0xff) + +#define IPV4_TOS_DSCP_MASK 0xfc +#define IPV4_TOS_DSCP_SHIFT 2 + +#define IPV4_TOS(ipv4_body) (((uint8 *)(ipv4_body))[IPV4_TOS_OFFSET]) + +#define IPV4_TOS_PREC_MASK 0xe0 +#define IPV4_TOS_PREC_SHIFT 5 + +#define IPV4_TOS_LOWDELAY 0x10 +#define IPV4_TOS_THROUGHPUT 0x8 +#define IPV4_TOS_RELIABILITY 0x4 + +#define IPV4_PROT(ipv4_body) (((uint8 *)(ipv4_body))[IPV4_PROT_OFFSET]) + +#define IPV4_FRAG_RESV 0x8000 +#define IPV4_FRAG_DONT 0x4000 +#define IPV4_FRAG_MORE 0x2000 +#define IPV4_FRAG_OFFSET_MASK 0x1fff + +#define IPV4_ADDR_STR_LEN 16 + +BWL_PRE_PACKED_STRUCT struct ipv4_addr { + uint8 addr[IPV4_ADDR_LEN]; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct ipv4_hdr { + uint8 version_ihl; + uint8 tos; + uint16 tot_len; + uint16 id; + uint16 frag; + uint8 ttl; + uint8 prot; + uint16 hdr_chksum; + uint8 src_ip[IPV4_ADDR_LEN]; + uint8 dst_ip[IPV4_ADDR_LEN]; +} BWL_POST_PACKED_STRUCT; + +#define IPV6_PAYLOAD_LEN_OFFSET 4 +#define IPV6_NEXT_HDR_OFFSET 6 +#define IPV6_HOP_LIMIT_OFFSET 7 +#define IPV6_SRC_IP_OFFSET 8 +#define IPV6_DEST_IP_OFFSET 24 + +#define IPV6_TRAFFIC_CLASS(ipv6_body) \ + (((((uint8 *)(ipv6_body))[0] & 0x0f) << 4) | \ + ((((uint8 *)(ipv6_body))[1] & 0xf0) >> 4)) + +#define IPV6_FLOW_LABEL(ipv6_body) \ + (((((uint8 *)(ipv6_body))[1] & 0x0f) << 16) | \ + (((uint8 *)(ipv6_body))[2] << 8) | \ + (((uint8 *)(ipv6_body))[3])) + +#define IPV6_PAYLOAD_LEN(ipv6_body) \ + ((((uint8 *)(ipv6_body))[IPV6_PAYLOAD_LEN_OFFSET + 0] << 8) | \ + ((uint8 *)(ipv6_body))[IPV6_PAYLOAD_LEN_OFFSET + 1]) + +#define IPV6_NEXT_HDR(ipv6_body) \ + (((uint8 *)(ipv6_body))[IPV6_NEXT_HDR_OFFSET]) + +#define IPV6_PROT(ipv6_body) IPV6_NEXT_HDR(ipv6_body) + +#define IPV6_ADDR_LEN 16 + +#define IP_TOS46(ip_body) \ + (IP_VER(ip_body) == IP_VER_4 ? IPV4_TOS(ip_body) : \ + IP_VER(ip_body) == IP_VER_6 ? IPV6_TRAFFIC_CLASS(ip_body) : 0) + +#define IPV6_EXTHDR_HOP 0 +#define IPV6_EXTHDR_ROUTING 43 +#define IPV6_EXTHDR_FRAGMENT 44 +#define IPV6_EXTHDR_AUTH 51 +#define IPV6_EXTHDR_NONE 59 +#define IPV6_EXTHDR_DEST 60 + +#define IPV6_EXTHDR(prot) (((prot) == IPV6_EXTHDR_HOP) || \ + ((prot) == IPV6_EXTHDR_ROUTING) || \ + ((prot) == IPV6_EXTHDR_FRAGMENT) || \ + ((prot) == IPV6_EXTHDR_AUTH) || \ + ((prot) == IPV6_EXTHDR_NONE) || \ + ((prot) == IPV6_EXTHDR_DEST)) + +#define IPV6_MIN_HLEN 40 + +#define IPV6_EXTHDR_LEN(eh) ((((struct ipv6_exthdr *)(eh))->hdrlen + 1) << 3) + +BWL_PRE_PACKED_STRUCT struct ipv6_exthdr { + uint8 nexthdr; + uint8 hdrlen; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct ipv6_exthdr_frag { + uint8 nexthdr; + uint8 rsvd; + uint16 frag_off; + uint32 ident; +} BWL_POST_PACKED_STRUCT; + +static INLINE int32 +ipv6_exthdr_len(uint8 *h, uint8 *proto) +{ + uint16 len = 0, hlen; + struct ipv6_exthdr *eh = (struct ipv6_exthdr *)h; + + while (IPV6_EXTHDR(eh->nexthdr)) { + if (eh->nexthdr == IPV6_EXTHDR_NONE) + return -1; + else if (eh->nexthdr == IPV6_EXTHDR_FRAGMENT) + hlen = 8; + else if (eh->nexthdr == IPV6_EXTHDR_AUTH) + hlen = (eh->hdrlen + 2) << 2; + else + hlen = IPV6_EXTHDR_LEN(eh); + + len += hlen; + eh = (struct ipv6_exthdr *)(h + len); + } + + *proto = eh->nexthdr; + return len; +} + +#define IPV4_ISMULTI(a) (((a) & 0xf0000000) == 0xe0000000) + +#include + +#endif diff --git a/drivers/custom/broadcom-wl/src/common/include/proto/ethernet.h b/drivers/custom/broadcom-wl/src/common/include/proto/ethernet.h new file mode 100644 index 000000000000..09315c9af8d9 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/common/include/proto/ethernet.h @@ -0,0 +1,156 @@ +/* + * From FreeBSD 2.2.7: Fundamental constants relating to ethernet. + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: ethernet.h 370577 2012-11-22 07:55:21Z $ + */ + +#ifndef _NET_ETHERNET_H_ +#define _NET_ETHERNET_H_ + +#ifndef _TYPEDEFS_H_ +#include "typedefs.h" +#endif + +#include + +#define ETHER_ADDR_LEN 6 + +#define ETHER_TYPE_LEN 2 + +#define ETHER_CRC_LEN 4 + +#define ETHER_HDR_LEN (ETHER_ADDR_LEN * 2 + ETHER_TYPE_LEN) + +#define ETHER_MIN_LEN 64 + +#define ETHER_MIN_DATA 46 + +#define ETHER_MAX_LEN 1518 + +#define ETHER_MAX_DATA 1500 + +#define ETHER_TYPE_MIN 0x0600 +#define ETHER_TYPE_IP 0x0800 +#define ETHER_TYPE_ARP 0x0806 +#define ETHER_TYPE_8021Q 0x8100 +#define ETHER_TYPE_IPV6 0x86dd +#define ETHER_TYPE_BRCM 0x886c +#define ETHER_TYPE_802_1X 0x888e +#define ETHER_TYPE_802_1X_PREAUTH 0x88c7 +#define ETHER_TYPE_WAI 0x88b4 +#define ETHER_TYPE_89_0D 0x890d + +#define ETHER_TYPE_PPP_SES 0x8864 + +#define ETHER_TYPE_IPV6 0x86dd + +#define ETHER_BRCM_SUBTYPE_LEN 4 + +#define ETHER_DEST_OFFSET (0 * ETHER_ADDR_LEN) +#define ETHER_SRC_OFFSET (1 * ETHER_ADDR_LEN) +#define ETHER_TYPE_OFFSET (2 * ETHER_ADDR_LEN) + +#define ETHER_IS_VALID_LEN(foo) \ + ((foo) >= ETHER_MIN_LEN && (foo) <= ETHER_MAX_LEN) + +#define ETHER_FILL_MCAST_ADDR_FROM_IP(ea, mgrp_ip) { \ + ((uint8 *)ea)[0] = 0x01; \ + ((uint8 *)ea)[1] = 0x00; \ + ((uint8 *)ea)[2] = 0x5e; \ + ((uint8 *)ea)[3] = ((mgrp_ip) >> 16) & 0x7f; \ + ((uint8 *)ea)[4] = ((mgrp_ip) >> 8) & 0xff; \ + ((uint8 *)ea)[5] = ((mgrp_ip) >> 0) & 0xff; \ +} + +#ifndef __INCif_etherh + +BWL_PRE_PACKED_STRUCT struct ether_header { + uint8 ether_dhost[ETHER_ADDR_LEN]; + uint8 ether_shost[ETHER_ADDR_LEN]; + uint16 ether_type; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct ether_addr { + uint8 octet[ETHER_ADDR_LEN]; +} BWL_POST_PACKED_STRUCT; +#endif + +#define ETHER_SET_LOCALADDR(ea) (((uint8 *)(ea))[0] = (((uint8 *)(ea))[0] | 2)) +#define ETHER_IS_LOCALADDR(ea) (((uint8 *)(ea))[0] & 2) +#define ETHER_CLR_LOCALADDR(ea) (((uint8 *)(ea))[0] = (((uint8 *)(ea))[0] & 0xfd)) +#define ETHER_TOGGLE_LOCALADDR(ea) (((uint8 *)(ea))[0] = (((uint8 *)(ea))[0] ^ 2)) + +#define ETHER_SET_UNICAST(ea) (((uint8 *)(ea))[0] = (((uint8 *)(ea))[0] & ~1)) + +#define ETHER_ISMULTI(ea) (((const uint8 *)(ea))[0] & 1) + +#define eacmp(a, b) ((((const uint16 *)(a))[0] ^ ((const uint16 *)(b))[0]) | \ + (((const uint16 *)(a))[1] ^ ((const uint16 *)(b))[1]) | \ + (((const uint16 *)(a))[2] ^ ((const uint16 *)(b))[2])) + +#define ether_cmp(a, b) eacmp(a, b) + +#define eacopy(s, d) \ +do { \ + ((uint16 *)(d))[0] = ((const uint16 *)(s))[0]; \ + ((uint16 *)(d))[1] = ((const uint16 *)(s))[1]; \ + ((uint16 *)(d))[2] = ((const uint16 *)(s))[2]; \ +} while (0) + +#define ether_copy(s, d) eacopy(s, d) + +#define ether_rcopy(s, d) \ +do { \ + ((uint16 *)(d))[2] = ((uint16 *)(s))[2]; \ + ((uint16 *)(d))[1] = ((uint16 *)(s))[1]; \ + ((uint16 *)(d))[0] = ((uint16 *)(s))[0]; \ +} while (0) + +static const struct ether_addr ether_bcast = {{255, 255, 255, 255, 255, 255}}; +static const struct ether_addr ether_null = {{0, 0, 0, 0, 0, 0}}; +static const struct ether_addr ether_ipv6_mcast = {{0x33, 0x33, 0x00, 0x00, 0x00, 0x01}}; + +#define ETHER_ISBCAST(ea) ((((const uint8 *)(ea))[0] & \ + ((const uint8 *)(ea))[1] & \ + ((const uint8 *)(ea))[2] & \ + ((const uint8 *)(ea))[3] & \ + ((const uint8 *)(ea))[4] & \ + ((const uint8 *)(ea))[5]) == 0xff) +#define ETHER_ISNULLADDR(ea) ((((const uint8 *)(ea))[0] | \ + ((const uint8 *)(ea))[1] | \ + ((const uint8 *)(ea))[2] | \ + ((const uint8 *)(ea))[3] | \ + ((const uint8 *)(ea))[4] | \ + ((const uint8 *)(ea))[5]) == 0) + +#define ETHER_ISNULLDEST(da) ((((const uint16 *)(da))[0] | \ + ((const uint16 *)(da))[1] | \ + ((const uint16 *)(da))[2]) == 0) +#define ETHER_ISNULLSRC(sa) ETHER_ISNULLDEST(sa) + +#define ETHER_MOVE_HDR(d, s) \ +do { \ + struct ether_header t; \ + t = *(struct ether_header *)(s); \ + *(struct ether_header *)(d) = t; \ +} while (0) + +#define ETHER_ISUCAST(ea) ((((uint8 *)(ea))[0] & 0x01) == 0) + +#include + +#endif diff --git a/drivers/custom/broadcom-wl/src/common/include/proto/ieee80211_radiotap.h b/drivers/custom/broadcom-wl/src/common/include/proto/ieee80211_radiotap.h new file mode 100644 index 000000000000..d3945b8403a0 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/common/include/proto/ieee80211_radiotap.h @@ -0,0 +1,161 @@ +/* $FreeBSD: src/sys/net80211/ieee80211_radiotap.h,v 1.11 2007/12/13 01:23:40 sam Exp $ */ + +#ifndef _NET80211_IEEE80211_RADIOTAP_H_ +#define _NET80211_IEEE80211_RADIOTAP_H_ + +#if defined(__KERNEL__) || defined(_KERNEL) +#ifndef DLT_IEEE802_11_RADIO +#define DLT_IEEE802_11_RADIO 127 +#endif +#endif + +#define IEEE80211_RADIOTAP_HDRLEN 64 + +struct ieee80211_radiotap_header { + uint8_t it_version; + uint8_t it_pad; + uint16_t it_len; + uint32_t it_present; +} __attribute__((packed)); + +enum ieee80211_radiotap_type { + IEEE80211_RADIOTAP_TSFT = 0, + IEEE80211_RADIOTAP_FLAGS = 1, + IEEE80211_RADIOTAP_RATE = 2, + IEEE80211_RADIOTAP_CHANNEL = 3, + IEEE80211_RADIOTAP_FHSS = 4, + IEEE80211_RADIOTAP_DBM_ANTSIGNAL = 5, + IEEE80211_RADIOTAP_DBM_ANTNOISE = 6, + IEEE80211_RADIOTAP_LOCK_QUALITY = 7, + IEEE80211_RADIOTAP_TX_ATTENUATION = 8, + IEEE80211_RADIOTAP_DB_TX_ATTENUATION = 9, + IEEE80211_RADIOTAP_DBM_TX_POWER = 10, + IEEE80211_RADIOTAP_ANTENNA = 11, + IEEE80211_RADIOTAP_DB_ANTSIGNAL = 12, + IEEE80211_RADIOTAP_DB_ANTNOISE = 13, + + IEEE80211_RADIOTAP_TXFLAGS = 15, + IEEE80211_RADIOTAP_RETRIES = 17, + IEEE80211_RADIOTAP_XCHANNEL = 18, + IEEE80211_RADIOTAP_MCS = 19, + IEEE80211_RADIOTAP_AMPDU = 20, + IEEE80211_RADIOTAP_VHT = 21, + IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE = 29, + IEEE80211_RADIOTAP_VENDOR_NAMESPACE = 30, + IEEE80211_RADIOTAP_EXT = 31, + }; + +#ifndef _KERNEL + +#define IEEE80211_CHAN_TURBO 0x00000010 +#define IEEE80211_CHAN_CCK 0x00000020 +#define IEEE80211_CHAN_OFDM 0x00000040 +#define IEEE80211_CHAN_2GHZ 0x00000080 +#define IEEE80211_CHAN_5GHZ 0x00000100 +#define IEEE80211_CHAN_PASSIVE 0x00000200 +#define IEEE80211_CHAN_DYN 0x00000400 +#define IEEE80211_CHAN_GFSK 0x00000800 +#define IEEE80211_CHAN_GSM 0x00001000 +#define IEEE80211_CHAN_STURBO 0x00002000 +#define IEEE80211_CHAN_HALF 0x00004000 +#define IEEE80211_CHAN_QUARTER 0x00008000 +#define IEEE80211_CHAN_HT20 0x00010000 +#define IEEE80211_CHAN_HT40U 0x00020000 +#define IEEE80211_CHAN_HT40D 0x00040000 +#endif + +#define IEEE80211_RADIOTAP_F_CFP 0x01 +#define IEEE80211_RADIOTAP_F_SHORTPRE 0x02 +#define IEEE80211_RADIOTAP_F_WEP 0x04 +#define IEEE80211_RADIOTAP_F_FRAG 0x08 +#define IEEE80211_RADIOTAP_F_FCS 0x10 +#define IEEE80211_RADIOTAP_F_DATAPAD 0x20 +#define IEEE80211_RADIOTAP_F_BADFCS 0x40 + +#define IEEE80211_RADIOTAP_MCS_HAVE_BW 0x01 +#define IEEE80211_RADIOTAP_MCS_HAVE_MCS 0x02 +#define IEEE80211_RADIOTAP_MCS_HAVE_GI 0x04 +#define IEEE80211_RADIOTAP_MCS_HAVE_FMT 0x08 +#define IEEE80211_RADIOTAP_MCS_HAVE_FEC 0x10 + +#define IEEE80211_RADIOTAP_MCS_BW_MASK 0x03 +#define IEEE80211_RADIOTAP_MCS_BW_20 0 +#define IEEE80211_RADIOTAP_MCS_BW_40 1 +#define IEEE80211_RADIOTAP_MCS_BW_20L 2 +#define IEEE80211_RADIOTAP_MCS_BW_20U 3 +#define IEEE80211_RADIOTAP_MCS_SGI 0x04 +#define IEEE80211_RADIOTAP_MCS_FMT_GF 0x08 +#define IEEE80211_RADIOTAP_MCS_FEC_LDPC 0x10 + +#define IEEE80211_RADIOTAP_MCS_BW_80 0x20 +#define IEEE80211_RADIOTAP_MCS_BW_20LL 0x40 +#define IEEE80211_RADIOTAP_MCS_BW_20LU 0x60 +#define IEEE80211_RADIOTAP_MCS_BW_20UL 0x80 +#define IEEE80211_RADIOTAP_MCS_BW_20UU 0xa0 +#define IEEE80211_RADIOTAP_MCS_BW_40L 0xc0 +#define IEEE80211_RADIOTAP_MCS_BW_40U 0xe0 + +#define IEEE80211_RADIOTAP_VHT_HAVE_STBC 0x0001 +#define IEEE80211_RADIOTAP_VHT_HAVE_TXOP_PS 0x0002 +#define IEEE80211_RADIOTAP_VHT_HAVE_GI 0x0004 +#define IEEE80211_RADIOTAP_VHT_HAVE_SGI_NSYM_DA 0x0008 +#define IEEE80211_RADIOTAP_VHT_HAVE_LDPC_EXTRA 0x0010 +#define IEEE80211_RADIOTAP_VHT_HAVE_BF 0x0020 +#define IEEE80211_RADIOTAP_VHT_HAVE_BW 0x0040 +#define IEEE80211_RADIOTAP_VHT_HAVE_GID 0x0080 +#define IEEE80211_RADIOTAP_VHT_HAVE_PAID 0x0100 + +#define IEEE80211_RADIOTAP_VHT_STBC 0x01 +#define IEEE80211_RADIOTAP_VHT_TXOP_PS 0x02 +#define IEEE80211_RADIOTAP_VHT_SGI 0x04 +#define IEEE80211_RADIOTAP_VHT_SGI_NSYM_DA 0x08 +#define IEEE80211_RADIOTAP_VHT_LDPC_EXTRA 0x10 +#define IEEE80211_RADIOTAP_VHT_BF 0x20 + +#define IEEE80211_RADIOTAP_VHT_NSS 0x0f +#define IEEE80211_RADIOTAP_VHT_MCS 0xf0 + +#define IEEE80211_RADIOTAP_VHT_CODING_LDPC 0x01 + +#define IEEE80211_RADIOTAP_VHT_BW_20 IEEE80211_RADIOTAP_MCS_BW_20 +#define IEEE80211_RADIOTAP_VHT_BW_40 IEEE80211_RADIOTAP_MCS_BW_40 +#define IEEE80211_RADIOTAP_VHT_BW_20L IEEE80211_RADIOTAP_MCS_BW_20L +#define IEEE80211_RADIOTAP_VHT_BW_20U IEEE80211_RADIOTAP_MCS_BW_20U +#define IEEE80211_RADIOTAP_VHT_BW_80 4 +#define IEEE80211_RADIOTAP_VHT_BW_40L 5 +#define IEEE80211_RADIOTAP_VHT_BW_40U 6 +#define IEEE80211_RADIOTAP_VHT_BW_20LL 7 +#define IEEE80211_RADIOTAP_VHT_BW_20LU 8 +#define IEEE80211_RADIOTAP_VHT_BW_20UL 9 +#define IEEE80211_RADIOTAP_VHT_BW_20UU 10 +#define IEEE80211_RADIOTAP_VHT_BW_160 11 +#define IEEE80211_RADIOTAP_VHT_BW_80L 12 +#define IEEE80211_RADIOTAP_VHT_BW_80U 13 +#define IEEE80211_RADIOTAP_VHT_BW_40LL 14 +#define IEEE80211_RADIOTAP_VHT_BW_40LU 15 +#define IEEE80211_RADIOTAP_VHT_BW_40UL 16 +#define IEEE80211_RADIOTAP_VHT_BW_40UU 17 +#define IEEE80211_RADIOTAP_VHT_BW_20LLL 18 +#define IEEE80211_RADIOTAP_VHT_BW_20LLU 19 +#define IEEE80211_RADIOTAP_VHT_BW_20LUL 20 +#define IEEE80211_RADIOTAP_VHT_BW_20LUU 21 +#define IEEE80211_RADIOTAP_VHT_BW_20ULL 22 +#define IEEE80211_RADIOTAP_VHT_BW_20ULU 23 +#define IEEE80211_RADIOTAP_VHT_BW_20UUL 24 +#define IEEE80211_RADIOTAP_VHT_BW_20UUU 25 + +#define IEEE80211_RADIOTAP_TXF_FAIL 0x0001 +#define IEEE80211_RADIOTAP_TXF_CTS 0x0002 +#define IEEE80211_RADIOTAP_TXF_RTSCTS 0x0004 +#define IEEE80211_RADIOTAP_TXF_NOACK 0x0008 +#define IEEE80211_RADIOTAP_TXF_SEQOVR 0x0010 + +#define IEEE80211_RADIOTAP_AMPDU_REPORT_ZEROLEN 0x0001 +#define IEEE80211_RADIOTAP_AMPDU_IS_ZEROLEN 0x0002 +#define IEEE80211_RADIOTAP_AMPDU_LAST_KNOWN 0x0004 +#define IEEE80211_RADIOTAP_AMPDU_IS_LAST 0x0008 +#define IEEE80211_RADIOTAP_AMPDU_DELIM_CRC_ERR 0x0010 +#define IEEE80211_RADIOTAP_AMPDU_DELIM_CRC_KNOWN 0x0020 +#define IEEE80211_RADIOTAP_AMPDU_MPDU_ONLY 0x8000 + +#endif diff --git a/drivers/custom/broadcom-wl/src/common/include/proto/wpa.h b/drivers/custom/broadcom-wl/src/common/include/proto/wpa.h new file mode 100644 index 000000000000..19cfdf0cb831 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/common/include/proto/wpa.h @@ -0,0 +1,148 @@ +/* + * Fundamental types and constants relating to WPA + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wpa.h 367468 2012-11-08 05:49:44Z $ + */ + +#ifndef _proto_wpa_h_ +#define _proto_wpa_h_ + +#include +#include + +#include + +#define DOT11_RC_INVALID_WPA_IE 13 +#define DOT11_RC_MIC_FAILURE 14 +#define DOT11_RC_4WH_TIMEOUT 15 +#define DOT11_RC_GTK_UPDATE_TIMEOUT 16 +#define DOT11_RC_WPA_IE_MISMATCH 17 +#define DOT11_RC_INVALID_MC_CIPHER 18 +#define DOT11_RC_INVALID_UC_CIPHER 19 +#define DOT11_RC_INVALID_AKMP 20 +#define DOT11_RC_BAD_WPA_VERSION 21 +#define DOT11_RC_INVALID_WPA_CAP 22 +#define DOT11_RC_8021X_AUTH_FAIL 23 + +#define WPA2_PMKID_LEN 16 + +typedef BWL_PRE_PACKED_STRUCT struct +{ + uint8 tag; + uint8 length; + uint8 oui[3]; + uint8 oui_type; + BWL_PRE_PACKED_STRUCT struct { + uint8 low; + uint8 high; + } BWL_POST_PACKED_STRUCT version; +} BWL_POST_PACKED_STRUCT wpa_ie_fixed_t; +#define WPA_IE_OUITYPE_LEN 4 +#define WPA_IE_FIXED_LEN 8 +#define WPA_IE_TAG_FIXED_LEN 6 + +typedef BWL_PRE_PACKED_STRUCT struct { + uint8 tag; + uint8 length; + BWL_PRE_PACKED_STRUCT struct { + uint8 low; + uint8 high; + } BWL_POST_PACKED_STRUCT version; +} BWL_POST_PACKED_STRUCT wpa_rsn_ie_fixed_t; +#define WPA_RSN_IE_FIXED_LEN 4 +#define WPA_RSN_IE_TAG_FIXED_LEN 2 +typedef uint8 wpa_pmkid_t[WPA2_PMKID_LEN]; + +typedef BWL_PRE_PACKED_STRUCT struct +{ + uint8 oui[3]; + uint8 type; +} BWL_POST_PACKED_STRUCT wpa_suite_t, wpa_suite_mcast_t; +#define WPA_SUITE_LEN 4 + +typedef BWL_PRE_PACKED_STRUCT struct +{ + BWL_PRE_PACKED_STRUCT struct { + uint8 low; + uint8 high; + } BWL_POST_PACKED_STRUCT count; + wpa_suite_t list[1]; +} BWL_POST_PACKED_STRUCT wpa_suite_ucast_t, wpa_suite_auth_key_mgmt_t; +#define WPA_IE_SUITE_COUNT_LEN 2 +typedef BWL_PRE_PACKED_STRUCT struct +{ + BWL_PRE_PACKED_STRUCT struct { + uint8 low; + uint8 high; + } BWL_POST_PACKED_STRUCT count; + wpa_pmkid_t list[1]; +} BWL_POST_PACKED_STRUCT wpa_pmkid_list_t; + +#define WPA_CIPHER_NONE 0 +#define WPA_CIPHER_WEP_40 1 +#define WPA_CIPHER_TKIP 2 +#define WPA_CIPHER_AES_OCB 3 +#define WPA_CIPHER_AES_CCM 4 +#define WPA_CIPHER_WEP_104 5 +#define WPA_CIPHER_BIP 6 +#define WPA_CIPHER_TPK 7 + +#define IS_WPA_CIPHER(cipher) ((cipher) == WPA_CIPHER_NONE || \ + (cipher) == WPA_CIPHER_WEP_40 || \ + (cipher) == WPA_CIPHER_WEP_104 || \ + (cipher) == WPA_CIPHER_TKIP || \ + (cipher) == WPA_CIPHER_AES_OCB || \ + (cipher) == WPA_CIPHER_AES_CCM || \ + (cipher) == WPA_CIPHER_TPK) + +#define WPA_TKIP_CM_DETECT 60 +#define WPA_TKIP_CM_BLOCK 60 + +#define RSN_CAP_LEN 2 + +#define RSN_CAP_PREAUTH 0x0001 +#define RSN_CAP_NOPAIRWISE 0x0002 +#define RSN_CAP_PTK_REPLAY_CNTR_MASK 0x000C +#define RSN_CAP_PTK_REPLAY_CNTR_SHIFT 2 +#define RSN_CAP_GTK_REPLAY_CNTR_MASK 0x0030 +#define RSN_CAP_GTK_REPLAY_CNTR_SHIFT 4 +#define RSN_CAP_1_REPLAY_CNTR 0 +#define RSN_CAP_2_REPLAY_CNTRS 1 +#define RSN_CAP_4_REPLAY_CNTRS 2 +#define RSN_CAP_16_REPLAY_CNTRS 3 +#define RSN_CAP_MFPR 0x0040 +#define RSN_CAP_MFPC 0x0080 +#define RSN_CAP_SPPC 0x0400 +#define RSN_CAP_SPPR 0x0800 + +#define WPA_CAP_4_REPLAY_CNTRS RSN_CAP_4_REPLAY_CNTRS +#define WPA_CAP_16_REPLAY_CNTRS RSN_CAP_16_REPLAY_CNTRS +#define WPA_CAP_REPLAY_CNTR_SHIFT RSN_CAP_PTK_REPLAY_CNTR_SHIFT +#define WPA_CAP_REPLAY_CNTR_MASK RSN_CAP_PTK_REPLAY_CNTR_MASK + +#define WPA_CAP_PEER_KEY_ENABLE (0x1 << 1) + +#define WPA_CAP_LEN RSN_CAP_LEN +#define WPA_PMKID_CNT_LEN 2 + +#define WPA_CAP_WPA2_PREAUTH RSN_CAP_PREAUTH + +#define WPA2_PMKID_COUNT_LEN 2 + +#include + +#endif diff --git a/drivers/custom/broadcom-wl/src/include/bcmcrypto/tkhash.h b/drivers/custom/broadcom-wl/src/include/bcmcrypto/tkhash.h new file mode 100644 index 000000000000..6afc33418a57 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/bcmcrypto/tkhash.h @@ -0,0 +1,33 @@ +/* + * tkhash.h + * Prototypes for TKIP hash functions. + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: tkhash.h 458337 2014-02-26 18:39:26Z $ + */ + +#ifndef _TKHASH_H_ +#define _TKHASH_H_ + +#include + +#define TKHASH_P1_KEY_SIZE 10 +#define TKHASH_P2_KEY_SIZE 16 + +extern void BCMROMFN(tkhash_phase1)(uint16 *P1K, const uint8 *TK, const uint8 *TA, uint32 IV32); +extern void BCMROMFN(tkhash_phase2)(uint8 *RC4KEY, const uint8 *TK, const uint16 *P1K, uint16 IV16); + +#endif diff --git a/drivers/custom/broadcom-wl/src/include/bcmdefs.h b/drivers/custom/broadcom-wl/src/include/bcmdefs.h new file mode 100644 index 000000000000..413ce849ba1d --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/bcmdefs.h @@ -0,0 +1,164 @@ +/* + * Misc system wide definitions + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: bcmdefs.h 432150 2013-10-25 20:57:18Z $ + */ + +#ifndef _bcmdefs_h_ +#define _bcmdefs_h_ + +#define BCM_REFERENCE(data) ((void)(data)) + +#ifdef __GNUC__ +#define UNUSED_VAR __attribute__ ((unused)) +#else +#define UNUSED_VAR +#endif + +#define STATIC_ASSERT(expr) { \ + \ + typedef enum { _STATIC_ASSERT_NOT_CONSTANT = (expr) } _static_assert_e UNUSED_VAR; \ + \ + typedef char STATIC_ASSERT_FAIL[(expr) ? 1 : -1] UNUSED_VAR; \ +} + +#define bcmreclaimed 0 +#define BCMATTACHDATA(_data) _data +#define BCMATTACHFN(_fn) _fn +#define BCMPREATTACHDATA(_data) _data +#define BCMPREATTACHFN(_fn) _fn +#define BCMINITDATA(_data) _data +#define BCMINITFN(_fn) _fn +#define BCMUNINITFN(_fn) _fn +#define BCMNMIATTACHFN(_fn) _fn +#define BCMNMIATTACHDATA(_data) _data +#define CONST const +#if defined(BCM47XX) && defined(__ARM_ARCH_7A__) +#define BCM47XX_CA9 +#else +#undef BCM47XX_CA9 +#endif +#if defined(BCM47XX_CA9) +#define BCMFASTPATH __attribute__ ((__section__ (".text.fastpath"))) +#define BCMFASTPATH_HOST __attribute__ ((__section__ (".text.fastpath_host"))) +#else +#define BCMFASTPATH +#define BCMFASTPATH_HOST +#endif + +#define BCMROMDATA(_data) _data +#define BCMROMDAT_NAME(_data) _data +#define BCMROMFN(_fn) _fn +#define BCMROMFN_NAME(_fn) _fn +#define STATIC static +#define BCMROMDAT_ARYSIZ(data) ARRAYSIZE(data) +#define BCMROMDAT_SIZEOF(data) sizeof(data) +#define BCMROMDAT_APATCH(data) +#define BCMROMDAT_SPATCH(data) + +#define SI_BUS 0 +#define PCI_BUS 1 +#define PCMCIA_BUS 2 +#define SDIO_BUS 3 +#define JTAG_BUS 4 +#define USB_BUS 5 +#define SPI_BUS 6 +#define RPC_BUS 7 + +#define BUSTYPE(bus) (bus) + +#define CHIPTYPE(bus) (bus) + +#define SPROMBUS (PCI_BUS) + +#define CHIPID(chip) (chip) + +#define CHIPREV(rev) (rev) + +#define DMADDR_MASK_32 0x0 +#define DMADDR_MASK_30 0xc0000000 +#define DMADDR_MASK_0 0xffffffff + +#define DMADDRWIDTH_30 30 +#define DMADDRWIDTH_32 32 +#define DMADDRWIDTH_63 63 +#define DMADDRWIDTH_64 64 + +typedef unsigned long dmaaddr_t; +#define PHYSADDRHI(_pa) (0) +#define PHYSADDRHISET(_pa, _val) +#define PHYSADDRLO(_pa) ((_pa)) +#define PHYSADDRLOSET(_pa, _val) \ + do { \ + (_pa) = (_val); \ + } while (0) + +typedef struct { + dmaaddr_t addr; + uint32 length; +} hnddma_seg_t; + +#define MAX_DMA_SEGS 8 + +typedef struct { + void *oshdmah; + uint origsize; + uint nsegs; + hnddma_seg_t segs[MAX_DMA_SEGS]; +} hnddma_seg_map_t; + +#define BCMEXTRAHDROOM 204 + +#define SDALIGN 32 + +#define BCMDONGLEHDRSZ 12 +#define BCMDONGLEPADSZ 16 + +#define BCMDONGLEOVERHEAD (BCMDONGLEHDRSZ + BCMDONGLEPADSZ) + +#ifdef BCMDBG + +#ifndef BCMDBG_ERR +#define BCMDBG_ERR +#endif + +#ifndef BCMDBG_ASSERT +#define BCMDBG_ASSERT +#endif + +#endif + +#if defined(BCMDBG_ASSERT) +#define BCMASSERT_SUPPORT +#endif + +#define BITFIELD_MASK(width) \ + (((unsigned)1 << (width)) - 1) +#define GFIELD(val, field) \ + (((val) >> field ## _S) & field ## _M) +#define SFIELD(val, field, bits) \ + (((val) & (~(field ## _M << field ## _S))) | \ + ((unsigned)(bits) << field ## _S)) + +#undef BCMSPACE +#define bcmspace FALSE + +#ifndef MAXSZ_NVRAM_VARS +#define MAXSZ_NVRAM_VARS 4096 +#endif + +#endif diff --git a/drivers/custom/broadcom-wl/src/include/bcmendian.h b/drivers/custom/broadcom-wl/src/include/bcmendian.h new file mode 100644 index 000000000000..4b04c6e0a742 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/bcmendian.h @@ -0,0 +1,280 @@ +/* + * Byte order utilities + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: bcmendian.h 370210 2012-11-21 05:35:27Z $ + * + * This file by default provides proper behavior on little-endian architectures. + * On big-endian architectures, IL_BIGENDIAN should be defined. + */ + +#ifndef _BCMENDIAN_H_ +#define _BCMENDIAN_H_ + +#include + +#define BCMSWAP16(val) \ + ((uint16)((((uint16)(val) & (uint16)0x00ffU) << 8) | \ + (((uint16)(val) & (uint16)0xff00U) >> 8))) + +#define BCMSWAP32(val) \ + ((uint32)((((uint32)(val) & (uint32)0x000000ffU) << 24) | \ + (((uint32)(val) & (uint32)0x0000ff00U) << 8) | \ + (((uint32)(val) & (uint32)0x00ff0000U) >> 8) | \ + (((uint32)(val) & (uint32)0xff000000U) >> 24))) + +#define BCMSWAP32BY16(val) \ + ((uint32)((((uint32)(val) & (uint32)0x0000ffffU) << 16) | \ + (((uint32)(val) & (uint32)0xffff0000U) >> 16))) + +#define BCMSWAP64(val) \ + ((uint64)((((uint64)(val) & 0x00000000000000ffULL) << 56) | \ + (((uint64)(val) & 0x000000000000ff00ULL) << 40) | \ + (((uint64)(val) & 0x0000000000ff0000ULL) << 24) | \ + (((uint64)(val) & 0x00000000ff000000ULL) << 8) | \ + (((uint64)(val) & 0x000000ff00000000ULL) >> 8) | \ + (((uint64)(val) & 0x0000ff0000000000ULL) >> 24) | \ + (((uint64)(val) & 0x00ff000000000000ULL) >> 40) | \ + (((uint64)(val) & 0xff00000000000000ULL) >> 56))) + +#define BCMSWAP64BY32(val) \ + ((uint64)((((uint64)(val) & 0x00000000ffffffffULL) << 32) | \ + (((uint64)(val) & 0xffffffff00000000ULL) >> 32))) + +#ifndef hton16 +#define HTON16(i) BCMSWAP16(i) +#define hton16(i) bcmswap16(i) +#define HTON32(i) BCMSWAP32(i) +#define hton32(i) bcmswap32(i) +#define NTOH16(i) BCMSWAP16(i) +#define ntoh16(i) bcmswap16(i) +#define NTOH32(i) BCMSWAP32(i) +#define ntoh32(i) bcmswap32(i) +#define LTOH16(i) (i) +#define ltoh16(i) (i) +#define LTOH32(i) (i) +#define ltoh32(i) (i) +#define HTOL16(i) (i) +#define htol16(i) (i) +#define HTOL32(i) (i) +#define htol32(i) (i) +#define HTOL64(i) (i) +#define htol64(i) (i) +#endif + +#define ltoh16_buf(buf, i) +#define htol16_buf(buf, i) + +#define load32_ua(a) ltoh32_ua(a) +#define store32_ua(a, v) htol32_ua_store(v, a) +#define load16_ua(a) ltoh16_ua(a) +#define store16_ua(a, v) htol16_ua_store(v, a) + +#define _LTOH16_UA(cp) ((cp)[0] | ((cp)[1] << 8)) +#define _LTOH32_UA(cp) ((cp)[0] | ((cp)[1] << 8) | ((cp)[2] << 16) | ((cp)[3] << 24)) +#define _NTOH16_UA(cp) (((cp)[0] << 8) | (cp)[1]) +#define _NTOH32_UA(cp) (((cp)[0] << 24) | ((cp)[1] << 16) | ((cp)[2] << 8) | (cp)[3]) + +#define ltoh_ua(ptr) \ + (sizeof(*(ptr)) == sizeof(uint8) ? *(const uint8 *)(ptr) : \ + sizeof(*(ptr)) == sizeof(uint16) ? _LTOH16_UA((const uint8 *)(ptr)) : \ + sizeof(*(ptr)) == sizeof(uint32) ? _LTOH32_UA((const uint8 *)(ptr)) : \ + *(uint8 *)0) + +#define ntoh_ua(ptr) \ + (sizeof(*(ptr)) == sizeof(uint8) ? *(const uint8 *)(ptr) : \ + sizeof(*(ptr)) == sizeof(uint16) ? _NTOH16_UA((const uint8 *)(ptr)) : \ + sizeof(*(ptr)) == sizeof(uint32) ? _NTOH32_UA((const uint8 *)(ptr)) : \ + *(uint8 *)0) + +#ifdef __GNUC__ + +#define bcmswap16(val) ({ \ + uint16 _val = (val); \ + BCMSWAP16(_val); \ +}) + +#define bcmswap32(val) ({ \ + uint32 _val = (val); \ + BCMSWAP32(_val); \ +}) + +#define bcmswap64(val) ({ \ + uint64 _val = (val); \ + BCMSWAP64(_val); \ +}) + +#define bcmswap32by16(val) ({ \ + uint32 _val = (val); \ + BCMSWAP32BY16(_val); \ +}) + +#define bcmswap16_buf(buf, len) ({ \ + uint16 *_buf = (uint16 *)(buf); \ + uint _wds = (len) / 2; \ + while (_wds--) { \ + *_buf = bcmswap16(*_buf); \ + _buf++; \ + } \ +}) + +#define htol16_ua_store(val, bytes) ({ \ + uint16 _val = (val); \ + uint8 *_bytes = (uint8 *)(bytes); \ + _bytes[0] = _val & 0xff; \ + _bytes[1] = _val >> 8; \ +}) + +#define htol32_ua_store(val, bytes) ({ \ + uint32 _val = (val); \ + uint8 *_bytes = (uint8 *)(bytes); \ + _bytes[0] = _val & 0xff; \ + _bytes[1] = (_val >> 8) & 0xff; \ + _bytes[2] = (_val >> 16) & 0xff; \ + _bytes[3] = _val >> 24; \ +}) + +#define hton16_ua_store(val, bytes) ({ \ + uint16 _val = (val); \ + uint8 *_bytes = (uint8 *)(bytes); \ + _bytes[0] = _val >> 8; \ + _bytes[1] = _val & 0xff; \ +}) + +#define hton32_ua_store(val, bytes) ({ \ + uint32 _val = (val); \ + uint8 *_bytes = (uint8 *)(bytes); \ + _bytes[0] = _val >> 24; \ + _bytes[1] = (_val >> 16) & 0xff; \ + _bytes[2] = (_val >> 8) & 0xff; \ + _bytes[3] = _val & 0xff; \ +}) + +#define ltoh16_ua(bytes) ({ \ + const uint8 *_bytes = (const uint8 *)(bytes); \ + _LTOH16_UA(_bytes); \ +}) + +#define ltoh32_ua(bytes) ({ \ + const uint8 *_bytes = (const uint8 *)(bytes); \ + _LTOH32_UA(_bytes); \ +}) + +#define ntoh16_ua(bytes) ({ \ + const uint8 *_bytes = (const uint8 *)(bytes); \ + _NTOH16_UA(_bytes); \ +}) + +#define ntoh32_ua(bytes) ({ \ + const uint8 *_bytes = (const uint8 *)(bytes); \ + _NTOH32_UA(_bytes); \ +}) + +#else + +static INLINE uint16 +bcmswap16(uint16 val) +{ + return BCMSWAP16(val); +} + +static INLINE uint32 +bcmswap32(uint32 val) +{ + return BCMSWAP32(val); +} + +static INLINE uint64 +bcmswap64(uint64 val) +{ + return BCMSWAP64(val); +} + +static INLINE uint32 +bcmswap32by16(uint32 val) +{ + return BCMSWAP32BY16(val); +} + +static INLINE void +bcmswap16_buf(uint16 *buf, uint len) +{ + len = len / 2; + + while (len--) { + *buf = bcmswap16(*buf); + buf++; + } +} + +static INLINE void +htol16_ua_store(uint16 val, uint8 *bytes) +{ + bytes[0] = val & 0xff; + bytes[1] = val >> 8; +} + +static INLINE void +htol32_ua_store(uint32 val, uint8 *bytes) +{ + bytes[0] = val & 0xff; + bytes[1] = (val >> 8) & 0xff; + bytes[2] = (val >> 16) & 0xff; + bytes[3] = val >> 24; +} + +static INLINE void +hton16_ua_store(uint16 val, uint8 *bytes) +{ + bytes[0] = val >> 8; + bytes[1] = val & 0xff; +} + +static INLINE void +hton32_ua_store(uint32 val, uint8 *bytes) +{ + bytes[0] = val >> 24; + bytes[1] = (val >> 16) & 0xff; + bytes[2] = (val >> 8) & 0xff; + bytes[3] = val & 0xff; +} + +static INLINE uint16 +ltoh16_ua(const void *bytes) +{ + return _LTOH16_UA((const uint8 *)bytes); +} + +static INLINE uint32 +ltoh32_ua(const void *bytes) +{ + return _LTOH32_UA((const uint8 *)bytes); +} + +static INLINE uint16 +ntoh16_ua(const void *bytes) +{ + return _NTOH16_UA((const uint8 *)bytes); +} + +static INLINE uint32 +ntoh32_ua(const void *bytes) +{ + return _NTOH32_UA((const uint8 *)bytes); +} + +#endif +#endif diff --git a/drivers/custom/broadcom-wl/src/include/bcmutils.h b/drivers/custom/broadcom-wl/src/include/bcmutils.h new file mode 100644 index 000000000000..5fafe3d38f1e --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/bcmutils.h @@ -0,0 +1,657 @@ +/* + * Misc useful os-independent macros and functions. + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: bcmutils.h 410746 2013-07-02 23:56:47Z $ + */ + +#ifndef _bcmutils_h_ +#define _bcmutils_h_ + +#define bcm_strcpy_s(dst, noOfElements, src) strcpy((dst), (src)) +#define bcm_strncpy_s(dst, noOfElements, src, count) strncpy((dst), (src), (count)) +#define bcm_strcat_s(dst, noOfElements, src) strcat((dst), (src)) +#define bcm_sprintf_s snprintf + +#define _BCM_U 0x01 +#define _BCM_L 0x02 +#define _BCM_D 0x04 +#define _BCM_C 0x08 +#define _BCM_P 0x10 +#define _BCM_S 0x20 +#define _BCM_X 0x40 +#define _BCM_SP 0x80 + +extern const unsigned char bcm_ctype[]; +#define bcm_ismask(x) (bcm_ctype[(int)(unsigned char)(x)]) + +#define bcm_isalnum(c) ((bcm_ismask(c)&(_BCM_U|_BCM_L|_BCM_D)) != 0) +#define bcm_isalpha(c) ((bcm_ismask(c)&(_BCM_U|_BCM_L)) != 0) +#define bcm_iscntrl(c) ((bcm_ismask(c)&(_BCM_C)) != 0) +#define bcm_isdigit(c) ((bcm_ismask(c)&(_BCM_D)) != 0) +#define bcm_isgraph(c) ((bcm_ismask(c)&(_BCM_P|_BCM_U|_BCM_L|_BCM_D)) != 0) +#define bcm_islower(c) ((bcm_ismask(c)&(_BCM_L)) != 0) +#define bcm_isprint(c) ((bcm_ismask(c)&(_BCM_P|_BCM_U|_BCM_L|_BCM_D|_BCM_SP)) != 0) +#define bcm_ispunct(c) ((bcm_ismask(c)&(_BCM_P)) != 0) +#define bcm_isspace(c) ((bcm_ismask(c)&(_BCM_S)) != 0) +#define bcm_isupper(c) ((bcm_ismask(c)&(_BCM_U)) != 0) +#define bcm_isxdigit(c) ((bcm_ismask(c)&(_BCM_D|_BCM_X)) != 0) +#define bcm_tolower(c) (bcm_isupper((c)) ? ((c) + 'a' - 'A') : (c)) +#define bcm_toupper(c) (bcm_islower((c)) ? ((c) + 'A' - 'a') : (c)) + +struct bcmstrbuf { + char *buf; + unsigned int size; + char *origbuf; + unsigned int origsize; +}; + +#include + +#define GPIO_PIN_NOTDEFINED 0x20 + +#define SPINWAIT_POLL_PERIOD 10 + +#define SPINWAIT(exp, us) { \ + uint countdown = (us) + (SPINWAIT_POLL_PERIOD - 1); \ + while ((exp) && (countdown >= SPINWAIT_POLL_PERIOD)) {\ + OSL_DELAY(SPINWAIT_POLL_PERIOD); \ + countdown -= SPINWAIT_POLL_PERIOD; \ + } \ +} + +#ifndef PKTQ_LEN_DEFAULT +#define PKTQ_LEN_DEFAULT 128 +#endif +#ifndef PKTQ_MAX_PREC +#define PKTQ_MAX_PREC 16 +#endif + +typedef struct pktq_prec { + void *head; + void *tail; + uint16 len; + uint16 max; +} pktq_prec_t; + +#define PKTQ_COMMON \ + uint16 num_prec; \ + uint16 hi_prec; \ + uint16 max; \ + uint16 len; + +struct pktq { + PKTQ_COMMON + + struct pktq_prec q[PKTQ_MAX_PREC]; +}; + +struct spktq { + PKTQ_COMMON + + struct pktq_prec q[1]; +}; + +#define PKTQ_PREC_ITER(pq, prec) for (prec = (pq)->num_prec - 1; prec >= 0; prec--) + +typedef bool (*ifpkt_cb_t)(void*, int); + +#define POOL_ENAB(bus) 0 +#define SHARED_POOL ((struct pktpool *)NULL) + +#ifndef PKTPOOL_LEN_MAX +#define PKTPOOL_LEN_MAX 40 +#endif +#define PKTPOOL_CB_MAX 3 + +struct pktpool; +typedef void (*pktpool_cb_t)(struct pktpool *pool, void *arg); +typedef struct { + pktpool_cb_t cb; + void *arg; +} pktpool_cbinfo_t; + +typedef struct pktpool { + bool inited; + uint16 r; + uint16 w; + uint16 len; + uint16 maxlen; + uint16 plen; + bool istx; + bool empty; + uint8 cbtoggle; + uint8 cbcnt; + uint8 ecbcnt; + bool emptycb_disable; + pktpool_cbinfo_t *availcb_excl; + pktpool_cbinfo_t cbs[PKTPOOL_CB_MAX]; + pktpool_cbinfo_t ecbs[PKTPOOL_CB_MAX]; + void *q[PKTPOOL_LEN_MAX + 1]; + +} pktpool_t; + +#if defined(BCM4329C0) +extern pktpool_t *pktpool_shared_ptr; +#else +extern pktpool_t *pktpool_shared; +#endif + +extern int pktpool_init(osl_t *osh, pktpool_t *pktp, int *pktplen, int plen, bool istx); +extern int pktpool_deinit(osl_t *osh, pktpool_t *pktp); +extern int pktpool_fill(osl_t *osh, pktpool_t *pktp, bool minimal); +extern void* pktpool_get(pktpool_t *pktp); +extern void pktpool_free(pktpool_t *pktp, void *p); +extern int pktpool_add(pktpool_t *pktp, void *p); +extern uint16 pktpool_avail(pktpool_t *pktp); +extern int pktpool_avail_notify_normal(osl_t *osh, pktpool_t *pktp); +extern int pktpool_avail_notify_exclusive(osl_t *osh, pktpool_t *pktp, pktpool_cb_t cb); +extern int pktpool_avail_register(pktpool_t *pktp, pktpool_cb_t cb, void *arg); +extern int pktpool_empty_register(pktpool_t *pktp, pktpool_cb_t cb, void *arg); +extern int pktpool_setmaxlen(pktpool_t *pktp, uint16 maxlen); +extern int pktpool_setmaxlen_strict(osl_t *osh, pktpool_t *pktp, uint16 maxlen); +extern void pktpool_emptycb_disable(pktpool_t *pktp, bool disable); +extern bool pktpool_emptycb_disabled(pktpool_t *pktp); + +#define POOLPTR(pp) ((pktpool_t *)(pp)) +#define pktpool_len(pp) (POOLPTR(pp)->len - 1) +#define pktpool_plen(pp) (POOLPTR(pp)->plen) +#define pktpool_maxlen(pp) (POOLPTR(pp)->maxlen) + +struct ether_addr; + +extern int ether_isbcast(const void *ea); +extern int ether_isnulladdr(const void *ea); + +#define pktq_psetmax(pq, prec, _max) ((pq)->q[prec].max = (_max)) +#define pktq_pmax(pq, prec) ((pq)->q[prec].max) +#define pktq_plen(pq, prec) ((pq)->q[prec].len) +#define pktq_pavail(pq, prec) ((pq)->q[prec].max - (pq)->q[prec].len) +#define pktq_pfull(pq, prec) ((pq)->q[prec].len >= (pq)->q[prec].max) +#define pktq_pempty(pq, prec) ((pq)->q[prec].len == 0) + +#define pktq_ppeek(pq, prec) ((pq)->q[prec].head) +#define pktq_ppeek_tail(pq, prec) ((pq)->q[prec].tail) + +extern void *pktq_penq(struct pktq *pq, int prec, void *p); +extern void *pktq_penq_head(struct pktq *pq, int prec, void *p); +extern void *pktq_pdeq(struct pktq *pq, int prec); +extern void *pktq_pdeq_prev(struct pktq *pq, int prec, void *prev_p); +extern void *pktq_pdeq_tail(struct pktq *pq, int prec); + +extern void pktq_pflush(osl_t *osh, struct pktq *pq, int prec, bool dir, + ifpkt_cb_t fn, int arg); + +extern bool pktq_pdel(struct pktq *pq, void *p, int prec); + +extern int pktq_mlen(struct pktq *pq, uint prec_bmp); +extern void *pktq_mdeq(struct pktq *pq, uint prec_bmp, int *prec_out); +extern void *pktq_mpeek(struct pktq *pq, uint prec_bmp, int *prec_out); + +#define pktq_len(pq) ((int)(pq)->len) +#define pktq_max(pq) ((int)(pq)->max) +#define pktq_avail(pq) ((int)((pq)->max - (pq)->len)) +#define pktq_full(pq) ((pq)->len >= (pq)->max) +#define pktq_empty(pq) ((pq)->len == 0) + +#define pktenq(pq, p) pktq_penq(((struct pktq *)(void *)pq), 0, (p)) +#define pktenq_head(pq, p) pktq_penq_head(((struct pktq *)(void *)pq), 0, (p)) +#define pktdeq(pq) pktq_pdeq(((struct pktq *)(void *)pq), 0) +#define pktdeq_tail(pq) pktq_pdeq_tail(((struct pktq *)(void *)pq), 0) +#define pktqinit(pq, len) pktq_init(((struct pktq *)(void *)pq), 1, len) + +extern void pktq_init(struct pktq *pq, int num_prec, int max_len); +extern void pktq_set_max_plen(struct pktq *pq, int prec, int max_len); + +extern void *pktq_deq(struct pktq *pq, int *prec_out); +extern void *pktq_deq_tail(struct pktq *pq, int *prec_out); +extern void *pktq_peek(struct pktq *pq, int *prec_out); +extern void *pktq_peek_tail(struct pktq *pq, int *prec_out); +extern void pktq_flush(osl_t *osh, struct pktq *pq, bool dir, ifpkt_cb_t fn, int arg); + +extern uint pktcopy(osl_t *osh, void *p, uint offset, int len, uchar *buf); +extern uint pktfrombuf(osl_t *osh, void *p, uint offset, int len, uchar *buf); +extern uint pkttotlen(osl_t *osh, void *p); +extern void *pktlast(osl_t *osh, void *p); +extern uint pktsegcnt(osl_t *osh, void *p); +extern uint pktsegcnt_war(osl_t *osh, void *p); +extern uint8 *pktdataoffset(osl_t *osh, void *p, uint offset); +extern void *pktoffset(osl_t *osh, void *p, uint offset); + +#define PKTPRIO_VDSCP 0x100 +#define PKTPRIO_VLAN 0x200 +#define PKTPRIO_UPD 0x400 +#define PKTPRIO_DSCP 0x800 + +extern uint pktsetprio(void *pkt, bool update_vtag); + +extern int BCMROMFN(bcm_atoi)(const char *s); +extern ulong BCMROMFN(bcm_strtoul)(const char *cp, char **endp, uint base); +extern char *BCMROMFN(bcmstrstr)(const char *haystack, const char *needle); +extern char *BCMROMFN(bcmstrcat)(char *dest, const char *src); +extern char *BCMROMFN(bcmstrncat)(char *dest, const char *src, uint size); +extern ulong wchar2ascii(char *abuf, ushort *wbuf, ushort wbuflen, ulong abuflen); +char* bcmstrtok(char **string, const char *delimiters, char *tokdelim); +int bcmstricmp(const char *s1, const char *s2); +int bcmstrnicmp(const char* s1, const char* s2, int cnt); + +extern char *bcm_ether_ntoa(const struct ether_addr *ea, char *buf); +extern int BCMROMFN(bcm_ether_atoe)(const char *p, struct ether_addr *ea); + +struct ipv4_addr; +extern char *bcm_ip_ntoa(struct ipv4_addr *ia, char *buf); +extern char *bcm_ipv6_ntoa(void *ipv6, char *buf); + +extern void bcm_mdelay(uint ms); + +#define NVRAM_RECLAIM_CHECK(name) + +extern char *getvar(char *vars, const char *name); +extern int getintvar(char *vars, const char *name); +extern int getintvararray(char *vars, const char *name, int index); +extern int getintvararraysize(char *vars, const char *name); +extern uint getgpiopin(char *vars, char *pin_name, uint def_pin); +#ifdef BCMDBG +extern void prpkt(const char *msg, osl_t *osh, void *p0); +#endif +#define bcm_perf_enable() +#define bcmstats(fmt) +#define bcmlog(fmt, a1, a2) +#define bcmdumplog(buf, size) *buf = '\0' +#define bcmdumplogent(buf, idx) -1 + +#define TSF_TICKS_PER_MS 1024 +#define bcmtslog(tstamp, fmt, a1, a2) +#define bcmprinttslogs() +#define bcmprinttstamp(us) +#define bcmdumptslog(buf, size) + +extern char *bcm_nvram_vars(uint *length); +extern int bcm_nvram_cache(void *sih); + +typedef struct bcm_iovar { + const char *name; + uint16 varid; + uint16 flags; + uint16 type; + uint16 minlen; +} bcm_iovar_t; + +#define IOV_GET 0 +#define IOV_SET 1 + +#define IOV_GVAL(id) ((id) * 2) +#define IOV_SVAL(id) ((id) * 2 + IOV_SET) +#define IOV_ISSET(actionid) ((actionid & IOV_SET) == IOV_SET) +#define IOV_ID(actionid) (actionid >> 1) + +extern const bcm_iovar_t *bcm_iovar_lookup(const bcm_iovar_t *table, const char *name); +extern int bcm_iovar_lencheck(const bcm_iovar_t *table, void *arg, int len, bool set); +#if defined(BCMDBG) +extern int bcm_format_ssid(char* buf, const uchar ssid[], uint ssid_len); +#endif + +#define IOVT_VOID 0 +#define IOVT_BOOL 1 +#define IOVT_INT8 2 +#define IOVT_UINT8 3 +#define IOVT_INT16 4 +#define IOVT_UINT16 5 +#define IOVT_INT32 6 +#define IOVT_UINT32 7 +#define IOVT_BUFFER 8 +#define BCM_IOVT_VALID(type) (((unsigned int)(type)) <= IOVT_BUFFER) + +#define BCM_IOV_TYPE_INIT { \ + "void", \ + "bool", \ + "int8", \ + "uint8", \ + "int16", \ + "uint16", \ + "int32", \ + "uint32", \ + "buffer", \ + "" } + +#define BCM_IOVT_IS_INT(type) (\ + (type == IOVT_BOOL) || \ + (type == IOVT_INT8) || \ + (type == IOVT_UINT8) || \ + (type == IOVT_INT16) || \ + (type == IOVT_UINT16) || \ + (type == IOVT_INT32) || \ + (type == IOVT_UINT32)) + +#define BCME_STRLEN 64 +#define VALID_BCMERROR(e) ((e <= 0) && (e >= BCME_LAST)) + +#define BCME_OK 0 +#define BCME_ERROR -1 +#define BCME_BADARG -2 +#define BCME_BADOPTION -3 +#define BCME_NOTUP -4 +#define BCME_NOTDOWN -5 +#define BCME_NOTAP -6 +#define BCME_NOTSTA -7 +#define BCME_BADKEYIDX -8 +#define BCME_RADIOOFF -9 +#define BCME_NOTBANDLOCKED -10 +#define BCME_NOCLK -11 +#define BCME_BADRATESET -12 +#define BCME_BADBAND -13 +#define BCME_BUFTOOSHORT -14 +#define BCME_BUFTOOLONG -15 +#define BCME_BUSY -16 +#define BCME_NOTASSOCIATED -17 +#define BCME_BADSSIDLEN -18 +#define BCME_OUTOFRANGECHAN -19 +#define BCME_BADCHAN -20 +#define BCME_BADADDR -21 +#define BCME_NORESOURCE -22 +#define BCME_UNSUPPORTED -23 +#define BCME_BADLEN -24 +#define BCME_NOTREADY -25 +#define BCME_EPERM -26 +#define BCME_NOMEM -27 +#define BCME_ASSOCIATED -28 +#define BCME_RANGE -29 +#define BCME_NOTFOUND -30 +#define BCME_WME_NOT_ENABLED -31 +#define BCME_TSPEC_NOTFOUND -32 +#define BCME_ACM_NOTSUPPORTED -33 +#define BCME_NOT_WME_ASSOCIATION -34 +#define BCME_SDIO_ERROR -35 +#define BCME_DONGLE_DOWN -36 +#define BCME_VERSION -37 +#define BCME_TXFAIL -38 +#define BCME_RXFAIL -39 +#define BCME_NODEVICE -40 +#define BCME_NMODE_DISABLED -41 +#define BCME_NONRESIDENT -42 +#define BCME_SCANREJECT -43 +#define BCME_LAST BCME_SCANREJECT + +#define BCMERRSTRINGTABLE { \ + "OK", \ + "Undefined error", \ + "Bad Argument", \ + "Bad Option", \ + "Not up", \ + "Not down", \ + "Not AP", \ + "Not STA", \ + "Bad Key Index", \ + "Radio Off", \ + "Not band locked", \ + "No clock", \ + "Bad Rate valueset", \ + "Bad Band", \ + "Buffer too short", \ + "Buffer too long", \ + "Busy", \ + "Not Associated", \ + "Bad SSID len", \ + "Out of Range Channel", \ + "Bad Channel", \ + "Bad Address", \ + "Not Enough Resources", \ + "Unsupported", \ + "Bad length", \ + "Not Ready", \ + "Not Permitted", \ + "No Memory", \ + "Associated", \ + "Not In Range", \ + "Not Found", \ + "WME Not Enabled", \ + "TSPEC Not Found", \ + "ACM Not Supported", \ + "Not WME Association", \ + "SDIO Bus Error", \ + "Dongle Not Accessible", \ + "Incorrect version", \ + "TX Failure", \ + "RX Failure", \ + "Device Not Present", \ + "NMODE Disabled", \ + "Nonresident overlay access", \ + "Scan Rejected", \ +} + +#ifndef ABS +#define ABS(a) (((a) < 0) ? -(a) : (a)) +#endif + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef LIMIT_TO_RANGE +#define LIMIT_TO_RANGE(x, min, max) \ + ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) +#endif + +#ifndef LIMIT_TO_MAX +#define LIMIT_TO_MAX(x, max) \ + (((x) > (max) ? (max) : (x))) +#endif + +#ifndef LIMIT_TO_MIN +#define LIMIT_TO_MIN(x, min) \ + (((x) < (min) ? (min) : (x))) +#endif + +#define CEIL(x, y) (((x) + ((y) - 1)) / (y)) +#define ROUNDUP(x, y) ((((x) + ((y) - 1)) / (y)) * (y)) +#define ISALIGNED(a, x) (((uintptr)(a) & ((x) - 1)) == 0) +#define ALIGN_ADDR(addr, boundary) (void *)(((uintptr)(addr) + (boundary) - 1) \ + & ~((boundary) - 1)) +#define ALIGN_SIZE(size, boundary) (((size) + (boundary) - 1) \ + & ~((boundary) - 1)) +#define ISPOWEROF2(x) ((((x) - 1) & (x)) == 0) +#define VALID_MASK(mask) !((mask) & ((mask) + 1)) + +#ifndef OFFSETOF +#ifdef __ARMCC_VERSION + +#include +#define OFFSETOF(type, member) offsetof(type, member) +#elif __GNUC__ >= 4 + +#define OFFSETOF(type, member) __builtin_offsetof(type, member) +#else +#define OFFSETOF(type, member) ((uint)(uintptr)&((type *)0)->member) +#endif +#endif + +#ifndef ARRAYSIZE +#define ARRAYSIZE(a) (sizeof(a) / sizeof(a[0])) +#endif + +extern void *_bcmutils_dummy_fn; +#define REFERENCE_FUNCTION(f) (_bcmutils_dummy_fn = (void *)(f)) + +#ifndef setbit +#ifndef NBBY +#define NBBY 8 +#endif +#define setbit(a, i) (((uint8 *)a)[(i) / NBBY] |= 1 << ((i) % NBBY)) +#define clrbit(a, i) (((uint8 *)a)[(i) / NBBY] &= ~(1 << ((i) % NBBY))) +#define isset(a, i) (((const uint8 *)a)[(i) / NBBY] & (1 << ((i) % NBBY))) +#define isclr(a, i) ((((const uint8 *)a)[(i) / NBBY] & (1 << ((i) % NBBY))) == 0) +#endif + +#define isbitset(a, i) (((a) & (1 << (i))) != 0) + +#define NBITS(type) (sizeof(type) * 8) +#define NBITVAL(nbits) (1 << (nbits)) +#define MAXBITVAL(nbits) ((1 << (nbits)) - 1) +#define NBITMASK(nbits) MAXBITVAL(nbits) +#define MAXNBVAL(nbyte) MAXBITVAL((nbyte) * 8) + +#define MUX(pred, true, false) ((pred) ? (true) : (false)) + +#define MODDEC(x, bound) MUX((x) == 0, (bound) - 1, (x) - 1) +#define MODINC(x, bound) MUX((x) == (bound) - 1, 0, (x) + 1) + +#define MODDEC_POW2(x, bound) (((x) - 1) & ((bound) - 1)) +#define MODINC_POW2(x, bound) (((x) + 1) & ((bound) - 1)) + +#define MODADD(x, y, bound) \ + MUX((x) + (y) >= (bound), (x) + (y) - (bound), (x) + (y)) +#define MODSUB(x, y, bound) \ + MUX(((int)(x)) - ((int)(y)) < 0, (x) - (y) + (bound), (x) - (y)) + +#define MODADD_POW2(x, y, bound) (((x) + (y)) & ((bound) - 1)) +#define MODSUB_POW2(x, y, bound) (((x) - (y)) & ((bound) - 1)) + +#define CRC8_INIT_VALUE 0xff +#define CRC8_GOOD_VALUE 0x9f +#define CRC16_INIT_VALUE 0xffff +#define CRC16_GOOD_VALUE 0xf0b8 +#define CRC32_INIT_VALUE 0xffffffff +#define CRC32_GOOD_VALUE 0xdebb20e3 + +#define MACF "%02x:%02x:%02x:%02x:%02x:%02x" +#define ETHERP_TO_MACF(ea) ((struct ether_addr *) (ea))->octet[0], \ + ((struct ether_addr *) (ea))->octet[1], \ + ((struct ether_addr *) (ea))->octet[2], \ + ((struct ether_addr *) (ea))->octet[3], \ + ((struct ether_addr *) (ea))->octet[4], \ + ((struct ether_addr *) (ea))->octet[5] + +#define ETHER_TO_MACF(ea) (ea).octet[0], \ + (ea).octet[1], \ + (ea).octet[2], \ + (ea).octet[3], \ + (ea).octet[4], \ + (ea).octet[5] + +typedef struct bcm_bit_desc { + uint32 bit; + const char* name; +} bcm_bit_desc_t; + +typedef struct bcm_bit_desc_ex { + uint32 mask; + const bcm_bit_desc_t *bitfield; +} bcm_bit_desc_ex_t; + +typedef struct bcm_tlv { + uint8 id; + uint8 len; + uint8 data[1]; +} bcm_tlv_t; + +#define bcm_valid_tlv(elt, buflen) ((buflen) >= 2 && (int)(buflen) >= (int)(2 + (elt)->len)) + +#define ETHER_ADDR_STR_LEN 18 + +static INLINE void +xor_128bit_block(const uint8 *src1, const uint8 *src2, uint8 *dst) +{ + if ( +#ifdef __i386__ + 1 || +#endif + (((uintptr)src1 | (uintptr)src2 | (uintptr)dst) & 3) == 0) { + + ((uint32 *)dst)[0] = ((const uint32 *)src1)[0] ^ ((const uint32 *)src2)[0]; + ((uint32 *)dst)[1] = ((const uint32 *)src1)[1] ^ ((const uint32 *)src2)[1]; + ((uint32 *)dst)[2] = ((const uint32 *)src1)[2] ^ ((const uint32 *)src2)[2]; + ((uint32 *)dst)[3] = ((const uint32 *)src1)[3] ^ ((const uint32 *)src2)[3]; + } else { + + int k; + for (k = 0; k < 16; k++) + dst[k] = src1[k] ^ src2[k]; + } +} + +extern uint8 BCMROMFN(hndcrc8)(uint8 *p, uint nbytes, uint8 crc); +extern uint16 BCMROMFN(hndcrc16)(uint8 *p, uint nbytes, uint16 crc); +extern uint32 BCMROMFN(hndcrc32)(uint8 *p, uint nbytes, uint32 crc); + +#if defined(BCMDBG) || defined(BCMDBG_ERR) || defined(BCMDBG_DUMP) + +extern int bcm_format_field(const bcm_bit_desc_ex_t *bd, uint32 field, char* buf, int len); + +extern int bcm_format_flags(const bcm_bit_desc_t *bd, uint32 flags, char* buf, int len); +#endif + +#if defined(BCMDBG) || defined(BCMDBG_ERR) || defined(BCMDBG_DUMP) || \ + defined(WLMEDIA_PEAKRATE) +extern int bcm_format_hex(char *str, const void *bytes, int len); +#endif + +#ifdef BCMDBG +extern void deadbeef(void *p, uint len); +#endif +extern const char *bcm_crypto_algo_name(uint algo); +extern char *bcm_chipname(uint chipid, char *buf, uint len); +extern char *bcm_brev_str(uint32 brev, char *buf); +extern void printbig(char *buf); +extern void prhex(const char *msg, uchar *buf, uint len); + +extern bcm_tlv_t *BCMROMFN(bcm_next_tlv)(bcm_tlv_t *elt, int *buflen); +extern bcm_tlv_t *BCMROMFN(bcm_parse_tlvs)(void *buf, int buflen, uint key); +extern bcm_tlv_t *BCMROMFN(bcm_parse_ordered_tlvs)(void *buf, int buflen, uint key); + +extern const char *bcmerrorstr(int bcmerror); +extern bcm_tlv_t *BCMROMFN(bcm_parse_tlvs)(void *buf, int buflen, uint key); + +typedef uint32 mbool; +#define mboolset(mb, bit) ((mb) |= (bit)) +#define mboolclr(mb, bit) ((mb) &= ~(bit)) +#define mboolisset(mb, bit) (((mb) & (bit)) != 0) +#define mboolmaskset(mb, mask, val) ((mb) = (((mb) & ~(mask)) | (val))) + +struct fielddesc { + const char *nameandfmt; + uint32 offset; + uint32 len; +}; + +extern void bcm_binit(struct bcmstrbuf *b, char *buf, uint size); +extern void bcm_bprhex(struct bcmstrbuf *b, const char *msg, bool newline, uint8 *buf, int len); + +extern void bcm_inc_bytes(uchar *num, int num_bytes, uint8 amount); +extern int bcm_cmp_bytes(const uchar *arg1, const uchar *arg2, uint8 nbytes); +extern void bcm_print_bytes(const char *name, const uchar *cdata, int len); + +typedef uint32 (*bcmutl_rdreg_rtn)(void *arg0, uint arg1, uint32 offset); +extern uint bcmdumpfields(bcmutl_rdreg_rtn func_ptr, void *arg0, uint arg1, struct fielddesc *str, + char *buf, uint32 bufsize); +extern uint BCMROMFN(bcm_bitcount)(uint8 *bitmap, uint bytelength); + +extern int bcm_bprintf(struct bcmstrbuf *b, const char *fmt, ...); + +extern uint16 BCMROMFN(bcm_qdbm_to_mw)(uint8 qdbm); +extern uint8 BCMROMFN(bcm_mw_to_qdbm)(uint16 mw); +extern uint bcm_mkiovar(char *name, char *data, uint datalen, char *buf, uint len); + +unsigned int process_nvram_vars(char *varbuf, unsigned int len); + +extern void bcm_uint64_multiple_add(uint32* r_high, uint32* r_low, uint32 a, uint32 b, uint32 c); + +extern void bcm_uint64_divide(uint32* r, uint32 a_high, uint32 a_low, uint32 b); + +#endif diff --git a/drivers/custom/broadcom-wl/src/include/epivers.h b/drivers/custom/broadcom-wl/src/include/epivers.h new file mode 100644 index 000000000000..df6f248d4434 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/epivers.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: epivers.h.in,v 13.33 2010-09-08 22:08:53 $ + * +*/ + +#ifndef _epivers_h_ +#define _epivers_h_ + +#define EPI_MAJOR_VERSION 6 + +#define EPI_MINOR_VERSION 30 + +#define EPI_RC_NUMBER 223 + +#define EPI_INCREMENTAL_NUMBER 271 + +#define EPI_BUILD_NUMBER 0 + +#define EPI_VERSION 6, 30, 223, 271 + +#define EPI_VERSION_NUM 0x061edf10 + +#define EPI_VERSION_DEV 6.30.223 + +#define EPI_VERSION_STR "6.30.223.271 (r587334)" + +#endif diff --git a/drivers/custom/broadcom-wl/src/include/linux_osl.h b/drivers/custom/broadcom-wl/src/include/linux_osl.h new file mode 100644 index 000000000000..bae45bb3e933 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/linux_osl.h @@ -0,0 +1,338 @@ +/* + * Linux OS Independent Layer + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: linux_osl.h 383331 2013-02-06 10:27:24Z $ + */ + +#ifndef _linux_osl_h_ +#define _linux_osl_h_ + +#include + +extern void * osl_os_open_image(char * filename); +extern int osl_os_get_image_block(char * buf, int len, void * image); +extern void osl_os_close_image(void * image); +extern int osl_os_image_size(void *image); + +extern osl_t *osl_attach(void *pdev, uint bustype, bool pkttag); +extern void osl_detach(osl_t *osh); + +extern uint32 g_assert_type; + +#if defined(BCMDBG_ASSERT) + #define ASSERT(exp) \ + do { if (!(exp)) osl_assert(#exp, __FILE__, __LINE__); } while (0) +extern void osl_assert(const char *exp, const char *file, int line); +#else + #ifdef __GNUC__ + #define GCC_VERSION \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) + #if GCC_VERSION > 30100 + #define ASSERT(exp) do {} while (0) + #else + + #define ASSERT(exp) + #endif + #endif +#endif + +#define OSL_DELAY(usec) osl_delay(usec) +extern void osl_delay(uint usec); + +#define OSL_PCMCIA_READ_ATTR(osh, offset, buf, size) \ + osl_pcmcia_read_attr((osh), (offset), (buf), (size)) +#define OSL_PCMCIA_WRITE_ATTR(osh, offset, buf, size) \ + osl_pcmcia_write_attr((osh), (offset), (buf), (size)) +extern void osl_pcmcia_read_attr(osl_t *osh, uint offset, void *buf, int size); +extern void osl_pcmcia_write_attr(osl_t *osh, uint offset, void *buf, int size); + +#define OSL_PCI_READ_CONFIG(osh, offset, size) \ + osl_pci_read_config((osh), (offset), (size)) +#define OSL_PCI_WRITE_CONFIG(osh, offset, size, val) \ + osl_pci_write_config((osh), (offset), (size), (val)) +extern uint32 osl_pci_read_config(osl_t *osh, uint offset, uint size); +extern void osl_pci_write_config(osl_t *osh, uint offset, uint size, uint val); + +#define OSL_PCI_BUS(osh) osl_pci_bus(osh) +#define OSL_PCI_SLOT(osh) osl_pci_slot(osh) +extern uint osl_pci_bus(osl_t *osh); +extern uint osl_pci_slot(osl_t *osh); +extern struct pci_dev *osl_pci_device(osl_t *osh); + +typedef struct { + bool pkttag; + bool mmbus; + pktfree_cb_fn_t tx_fn; + void *tx_ctx; + void *unused[3]; +} osl_pubinfo_t; + +#define PKTFREESETCB(osh, _tx_fn, _tx_ctx) \ + do { \ + ((osl_pubinfo_t*)osh)->tx_fn = _tx_fn; \ + ((osl_pubinfo_t*)osh)->tx_ctx = _tx_ctx; \ + } while (0) + +#define BUS_SWAP32(v) (v) + +#ifdef BCMDBG_MEM + #define MALLOC(osh, size) osl_debug_malloc((osh), (size), __LINE__, __FILE__) + #define MFREE(osh, addr, size) osl_debug_mfree((osh), (addr), (size), __LINE__, __FILE__) + #define MALLOCED(osh) osl_malloced((osh)) + #define MALLOC_DUMP(osh, b) osl_debug_memdump((osh), (b)) + extern void *osl_debug_malloc(osl_t *osh, uint size, int line, const char* file); + extern void osl_debug_mfree(osl_t *osh, void *addr, uint size, int line, const char* file); + extern uint osl_malloced(osl_t *osh); + struct bcmstrbuf; + extern int osl_debug_memdump(osl_t *osh, struct bcmstrbuf *b); +#else + #define MALLOC(osh, size) osl_malloc((osh), (size)) + #define MFREE(osh, addr, size) osl_mfree((osh), (addr), (size)) + #define MALLOCED(osh) osl_malloced((osh)) + extern void *osl_malloc(osl_t *osh, uint size); + extern void osl_mfree(osl_t *osh, void *addr, uint size); + extern uint osl_malloced(osl_t *osh); +#endif + +#define NATIVE_MALLOC(osh, size) kmalloc(size, GFP_ATOMIC) +#define NATIVE_MFREE(osh, addr, size) kfree(addr) + +#define MALLOC_FAILED(osh) osl_malloc_failed((osh)) +extern uint osl_malloc_failed(osl_t *osh); + +#define DMA_CONSISTENT_ALIGN osl_dma_consistent_align() +#define DMA_ALLOC_CONSISTENT(osh, size, align, tot, pap, dmah) \ + osl_dma_alloc_consistent((osh), (size), (align), (tot), (pap)) +#define DMA_FREE_CONSISTENT(osh, va, size, pa, dmah) \ + osl_dma_free_consistent((osh), (void*)(va), (size), (pa)) + +#define DMA_ALLOC_CONSISTENT_FORCE32(osh, size, align, tot, pap, dmah) \ + osl_dma_alloc_consistent((osh), (size), (align), (tot), (pap)) +#define DMA_FREE_CONSISTENT_FORCE32(osh, va, size, pa, dmah) \ + osl_dma_free_consistent((osh), (void*)(va), (size), (pa)) + +extern uint osl_dma_consistent_align(void); +extern void *osl_dma_alloc_consistent(osl_t *osh, uint size, uint16 align, uint *tot, ulong *pap); +extern void osl_dma_free_consistent(osl_t *osh, void *va, uint size, ulong pa); + +#define DMA_TX 1 +#define DMA_RX 2 + +#define DMA_UNMAP(osh, pa, size, direction, p, dmah) \ + osl_dma_unmap((osh), (pa), (size), (direction)) +extern uint osl_dma_map(osl_t *osh, void *va, uint size, int direction, void *p, + hnddma_seg_map_t *txp_dmah); +extern void osl_dma_unmap(osl_t *osh, uint pa, uint size, int direction); + +#define OSL_DMADDRWIDTH(osh, addrwidth) do {} while (0) + + #define SELECT_BUS_WRITE(osh, mmap_op, bus_op) mmap_op + #define SELECT_BUS_READ(osh, mmap_op, bus_op) mmap_op + +#define OSL_ERROR(bcmerror) osl_error(bcmerror) +extern int osl_error(int bcmerror); + +#define PKTBUFSZ 2048 + +#if defined(LINUX_PORT) + #define printf(fmt, args...) printk(fmt , ## args) + #include + #include + + #define bcopy(src, dst, len) memcpy((dst), (src), (len)) + #define bcmp(b1, b2, len) memcmp((b1), (b2), (len)) + #define bzero(b, len) memset((b), '\0', (len)) + + extern int osl_printf(const char *format, ...); + extern int osl_sprintf(char *buf, const char *format, ...); + extern int osl_snprintf(char *buf, size_t n, const char *format, ...); + extern int osl_vsprintf(char *buf, const char *format, va_list ap); + extern int osl_vsnprintf(char *buf, size_t n, const char *format, va_list ap); + extern int osl_strcmp(const char *s1, const char *s2); + extern int osl_strncmp(const char *s1, const char *s2, uint n); + extern int osl_strlen(const char *s); + extern char* osl_strcpy(char *d, const char *s); + extern char* osl_strncpy(char *d, const char *s, uint n); + extern char* osl_strchr(const char *s, int c); + extern char* osl_strrchr(const char *s, int c); + extern void *osl_memset(void *d, int c, size_t n); + extern void *osl_memcpy(void *d, const void *s, size_t n); + extern void *osl_memmove(void *d, const void *s, size_t n); + extern int osl_memcmp(const void *s1, const void *s2, size_t n); +#else + + #include + #include + #undef printf + #undef sprintf + #undef snprintf + #undef vsprintf + #undef vsnprintf + #define printf(fmt, args...) osl_printf((fmt) , ## args) + #define sprintf(buf, fmt, args...) osl_sprintf((buf), (fmt) , ## args) + #define snprintf(buf, n, fmt, args...) osl_snprintf((buf), (n), (fmt) , ## args) + #define vsprintf(buf, fmt, ap) osl_vsprintf((buf), (fmt), (ap)) + #define vsnprintf(buf, n, fmt, ap) osl_vsnprintf((buf), (n), (fmt), (ap)) + extern int osl_printf(const char *format, ...); + extern int osl_sprintf(char *buf, const char *format, ...); + extern int osl_snprintf(char *buf, size_t n, const char *format, ...); + extern int osl_vsprintf(char *buf, const char *format, va_list ap); + extern int osl_vsnprintf(char *buf, size_t n, const char *format, va_list ap); + + #undef strcmp + #undef strncmp + #undef strlen + #undef strcpy + #undef strncpy + #undef strchr + #undef strrchr + #define strcmp(s1, s2) osl_strcmp((s1), (s2)) + #define strncmp(s1, s2, n) osl_strncmp((s1), (s2), (n)) + #define strlen(s) osl_strlen((s)) + #define strcpy(d, s) osl_strcpy((d), (s)) + #define strncpy(d, s, n) osl_strncpy((d), (s), (n)) + #define strchr(s, c) osl_strchr((s), (c)) + #define strrchr(s, c) osl_strrchr((s), (c)) + extern int osl_strcmp(const char *s1, const char *s2); + extern int osl_strncmp(const char *s1, const char *s2, uint n); + extern int osl_strlen(const char *s); + extern char* osl_strcpy(char *d, const char *s); + extern char* osl_strncpy(char *d, const char *s, uint n); + extern char* osl_strchr(const char *s, int c); + extern char* osl_strrchr(const char *s, int c); + + #undef memset + #undef memcpy + #undef memcmp + #define memset(d, c, n) osl_memset((d), (c), (n)) + #define memcpy(d, s, n) osl_memcpy((d), (s), (n)) + #define memmove(d, s, n) osl_memmove((d), (s), (n)) + #define memcmp(s1, s2, n) osl_memcmp((s1), (s2), (n)) + extern void *osl_memset(void *d, int c, size_t n); + extern void *osl_memcpy(void *d, const void *s, size_t n); + extern void *osl_memmove(void *d, const void *s, size_t n); + extern int osl_memcmp(const void *s1, const void *s2, size_t n); + + #undef bcopy + #undef bcmp + #undef bzero + #define bcopy(src, dst, len) osl_memcpy((dst), (src), (len)) + #define bcmp(b1, b2, len) osl_memcmp((b1), (b2), (len)) + #define bzero(b, len) osl_memset((b), '\0', (len)) +#endif + +#define R_REG(osh, r) (\ + sizeof(*(r)) == sizeof(uint8) ? osl_readb((volatile uint8*)(r)) : \ + sizeof(*(r)) == sizeof(uint16) ? osl_readw((volatile uint16*)(r)) : \ + osl_readl((volatile uint32*)(r)) \ +) +#define W_REG(osh, r, v) do { \ + switch (sizeof(*(r))) { \ + case sizeof(uint8): osl_writeb((uint8)(v), (volatile uint8*)(r)); break; \ + case sizeof(uint16): osl_writew((uint16)(v), (volatile uint16*)(r)); break; \ + case sizeof(uint32): osl_writel((uint32)(v), (volatile uint32*)(r)); break; \ + } \ +} while (0) + +#define AND_REG(osh, r, v) W_REG(osh, (r), R_REG(osh, r) & (v)) +#define OR_REG(osh, r, v) W_REG(osh, (r), R_REG(osh, r) | (v)) +extern uint8 osl_readb(volatile uint8 *r); +extern uint16 osl_readw(volatile uint16 *r); +extern uint32 osl_readl(volatile uint32 *r); +extern void osl_writeb(uint8 v, volatile uint8 *r); +extern void osl_writew(uint16 v, volatile uint16 *r); +extern void osl_writel(uint32 v, volatile uint32 *r); + +#define OSL_SYSUPTIME() osl_sysuptime() +extern uint32 osl_sysuptime(void); + +#define OSL_UNCACHED(va) osl_uncached((va)) +extern void *osl_uncached(void *va); +#define OSL_CACHED(va) osl_cached((va)) +extern void *osl_cached(void *va); + +#define OSL_PREF_RANGE_LD(va, sz) +#define OSL_PREF_RANGE_ST(va, sz) + +#define OSL_GETCYCLES(x) ((x) = osl_getcycles()) +extern uint osl_getcycles(void); + +#define BUSPROBE(val, addr) osl_busprobe(&(val), (addr)) +extern int osl_busprobe(uint32 *val, uint32 addr); + +#define REG_MAP(pa, size) osl_reg_map((pa), (size)) +#define REG_UNMAP(va) osl_reg_unmap((va)) +extern void *osl_reg_map(uint32 pa, uint size); +extern void osl_reg_unmap(void *va); + +#define R_SM(r) *(r) +#define W_SM(r, v) (*(r) = (v)) +#define BZERO_SM(r, len) bzero((r), (len)) + +#define PKTGET(osh, len, send) osl_pktget((osh), (len)) +#define PKTDUP(osh, skb) osl_pktdup((osh), (skb)) +#define PKTFRMNATIVE(osh, skb) osl_pkt_frmnative((osh), (skb)) +#define PKTLIST_DUMP(osh, buf) +#define PKTDBG_TRACE(osh, pkt, bit) +#define PKTFREE(osh, skb, send) osl_pktfree((osh), (skb), (send)) +#define PKTDATA(osh, skb) osl_pktdata((osh), (skb)) +#define PKTLEN(osh, skb) osl_pktlen((osh), (skb)) +#define PKTHEADROOM(osh, skb) osl_pktheadroom((osh), (skb)) +#define PKTTAILROOM(osh, skb) osl_pkttailroom((osh), (skb)) +#define PKTNEXT(osh, skb) osl_pktnext((osh), (skb)) +#define PKTSETNEXT(osh, skb, x) osl_pktsetnext((skb), (x)) +#define PKTSETLEN(osh, skb, len) osl_pktsetlen((osh), (skb), (len)) +#define PKTPUSH(osh, skb, bytes) osl_pktpush((osh), (skb), (bytes)) +#define PKTPULL(osh, skb, bytes) osl_pktpull((osh), (skb), (bytes)) +#define PKTTAG(skb) osl_pkttag((skb)) +#define PKTTONATIVE(osh, pkt) osl_pkt_tonative((osh), (pkt)) +#define PKTLINK(skb) osl_pktlink((skb)) +#define PKTSETLINK(skb, x) osl_pktsetlink((skb), (x)) +#define PKTPRIO(skb) osl_pktprio((skb)) +#define PKTSETPRIO(skb, x) osl_pktsetprio((skb), (x)) +#define PKTSHARED(skb) osl_pktshared((skb)) +#define PKTSETPOOL(osh, skb, x, y) do {} while (0) +#define PKTPOOL(osh, skb) FALSE + +extern void *osl_pktget(osl_t *osh, uint len); +extern void *osl_pktdup(osl_t *osh, void *skb); +extern void *osl_pkt_frmnative(osl_t *osh, void *skb); +extern void osl_pktfree(osl_t *osh, void *skb, bool send); +extern uchar *osl_pktdata(osl_t *osh, void *skb); +extern uint osl_pktlen(osl_t *osh, void *skb); +extern uint osl_pktheadroom(osl_t *osh, void *skb); +extern uint osl_pkttailroom(osl_t *osh, void *skb); +extern void *osl_pktnext(osl_t *osh, void *skb); +extern void osl_pktsetnext(void *skb, void *x); +extern void osl_pktsetlen(osl_t *osh, void *skb, uint len); +extern uchar *osl_pktpush(osl_t *osh, void *skb, int bytes); +extern uchar *osl_pktpull(osl_t *osh, void *skb, int bytes); +extern void *osl_pkttag(void *skb); +extern void *osl_pktlink(void *skb); +extern void osl_pktsetlink(void *skb, void *x); +extern uint osl_pktprio(void *skb); +extern void osl_pktsetprio(void *skb, uint x); +extern struct sk_buff *osl_pkt_tonative(osl_t *osh, void *pkt); +extern bool osl_pktshared(void *skb); + +#define PKTALLOCED(osh) osl_pktalloced(osh) +extern uint osl_pktalloced(osl_t *osh); + +#define DMA_MAP(osh, va, size, direction, p, dmah) \ + osl_dma_map((osh), (va), (size), (direction), (p), (dmah)) + +#endif diff --git a/drivers/custom/broadcom-wl/src/include/linuxver.h b/drivers/custom/broadcom-wl/src/include/linuxver.h new file mode 100644 index 000000000000..bbe1d3c77bd5 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/linuxver.h @@ -0,0 +1,594 @@ +/* + * Linux-specific abstractions to gain some independence from linux kernel versions. + * Pave over some 2.2 versus 2.4 versus 2.6 kernel differences. + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: linuxver.h 372519 2012-12-04 01:21:16Z $ + */ + +#ifndef _linuxver_h_ +#define _linuxver_h_ + +#include +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) +#include +#else +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33)) +#include +#else +#include +#endif +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0)) +#include +#endif + +#include + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 0)) + +#ifdef __UNDEF_NO_VERSION__ +#undef __NO_VERSION__ +#else +#define __NO_VERSION__ +#endif +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) +#define module_param(_name_, _type_, _perm_) MODULE_PARM(_name_, "i") +#define module_param_string(_name_, _string_, _size_, _perm_) \ + MODULE_PARM(_string_, "c" __MODULE_STRING(_size_)) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 9)) +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) +#include +#else +#include +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28)) +#undef IP_TOS +#endif +#include + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 5, 41)) +#include +#else +#include +#ifndef work_struct +#define work_struct tq_struct +#endif +#ifndef INIT_WORK +#define INIT_WORK(_work, _func, _data) INIT_TQUEUE((_work), (_func), (_data)) +#endif +#ifndef schedule_work +#define schedule_work(_work) schedule_task((_work)) +#endif +#ifndef flush_scheduled_work +#define flush_scheduled_work() flush_scheduled_tasks() +#endif +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) +#define DAEMONIZE(a) daemonize(a); \ + allow_signal(SIGKILL); \ + allow_signal(SIGTERM); +#else +#define RAISE_RX_SOFTIRQ() \ + cpu_raise_softirq(smp_processor_id(), NET_RX_SOFTIRQ) +#define DAEMONIZE(a) daemonize(); \ + do { if (a) \ + strncpy(current->comm, a, MIN(sizeof(current->comm), (strlen(a)))); \ + } while (0); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) +#define MY_INIT_WORK(_work, _func) INIT_WORK(_work, _func) +#else +#define MY_INIT_WORK(_work, _func) INIT_WORK(_work, _func, _work) +#if !(LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 18) && defined(RHEL_MAJOR) && \ + (RHEL_MAJOR == 5)) + +typedef void (*work_func_t)(void *work); +#endif +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) + +#ifndef IRQ_NONE +typedef void irqreturn_t; +#define IRQ_NONE +#define IRQ_HANDLED +#define IRQ_RETVAL(x) +#endif +#else +typedef irqreturn_t(*FN_ISR) (int irq, void *dev_id, struct pt_regs *ptregs); +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) +#define IRQF_SHARED SA_SHIRQ +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 17) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 67) +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32) +#include +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) && LINUX_VERSION_CODE < KERNEL_VERSION(6, 13, 0) +#include +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) +#include +#else +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) +#include +#endif +#endif + +#ifdef CUSTOMER_HW4 +#include +#endif + +#ifndef __exit +#define __exit +#endif +#ifndef __devexit +#define __devexit +#endif +#ifndef __devinit +#define __devinit __init +#endif +#ifndef __devinitdata +#define __devinitdata +#endif +#ifndef __devexit_p +#define __devexit_p(x) x +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 0)) + +#define pci_get_drvdata(dev) (dev)->sysdata +#define pci_set_drvdata(dev, value) (dev)->sysdata = (value) + +struct pci_device_id { + unsigned int vendor, device; + unsigned int subvendor, subdevice; + unsigned int class, class_mask; + unsigned long driver_data; +}; + +struct pci_driver { + struct list_head node; + char *name; + const struct pci_device_id *id_table; + int (*probe)(struct pci_dev *dev, + const struct pci_device_id *id); + void (*remove)(struct pci_dev *dev); + void (*suspend)(struct pci_dev *dev); + void (*resume)(struct pci_dev *dev); +}; + +#define MODULE_DEVICE_TABLE(type, name) +#define PCI_ANY_ID (~0) + +#define pci_module_init pci_register_driver +extern int pci_register_driver(struct pci_driver *drv); +extern void pci_unregister_driver(struct pci_driver *drv); + +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)) +#define pci_module_init pci_register_driver +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 2, 18)) +#define module_init(x) __initcall(x); +#define module_exit(x) __exitcall(x); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 31) +#define WL_USE_NETDEV_OPS +#else +#undef WL_USE_NETDEV_OPS +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 31)) && defined(CONFIG_RFKILL) +#define WL_CONFIG_RFKILL +#else +#undef WL_CONFIG_RFKILL +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 48)) +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 13)) +#define pci_resource_start(dev, bar) ((dev)->base_address[(bar)]) +#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 44)) +#define pci_resource_start(dev, bar) ((dev)->resource[(bar)].start) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 23)) +#define pci_enable_device(dev) do { } while (0) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 14)) +#define net_device device +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 42)) + +#ifndef PCI_DMA_TODEVICE +#define PCI_DMA_TODEVICE 1 +#define PCI_DMA_FROMDEVICE 2 +#endif + +typedef u32 dma_addr_t; + +static inline int get_order(unsigned long size) +{ + int order; + + size = (size-1) >> (PAGE_SHIFT-1); + order = -1; + do { + size >>= 1; + order++; + } while (size); + return order; +} + +static inline void *pci_alloc_consistent(struct pci_dev *hwdev, size_t size, + dma_addr_t *dma_handle) +{ + void *ret; + int gfp = GFP_ATOMIC | GFP_DMA; + + ret = (void *)__get_free_pages(gfp, get_order(size)); + + if (ret != NULL) { + memset(ret, 0, size); + *dma_handle = virt_to_bus(ret); + } + return ret; +} +static inline void pci_free_consistent(struct pci_dev *hwdev, size_t size, + void *vaddr, dma_addr_t dma_handle) +{ + free_pages((unsigned long)vaddr, get_order(size)); +} +#define pci_map_single(cookie, address, size, dir) virt_to_bus(address) +#define pci_unmap_single(cookie, address, size, dir) + +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 43)) + +#define dev_kfree_skb_any(a) dev_kfree_skb(a) +#define netif_down(dev) do { (dev)->start = 0; } while (0) + +#ifndef _COMPAT_NETDEVICE_H + +#define dev_kfree_skb_irq(a) dev_kfree_skb(a) +#define netif_wake_queue(dev) \ + do { clear_bit(0, &(dev)->tbusy); mark_bh(NET_BH); } while (0) +#define netif_stop_queue(dev) set_bit(0, &(dev)->tbusy) + +static inline void netif_start_queue(struct net_device *dev) +{ + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; +} + +#define netif_queue_stopped(dev) (dev)->tbusy +#define netif_running(dev) (dev)->start + +#endif + +#define netif_device_attach(dev) netif_start_queue(dev) +#define netif_device_detach(dev) netif_stop_queue(dev) + +#define tasklet_struct tq_struct +static inline void tasklet_schedule(struct tasklet_struct *tasklet) +{ + queue_task(tasklet, &tq_immediate); + mark_bh(IMMEDIATE_BH); +} + +static inline void tasklet_init(struct tasklet_struct *tasklet, + void (*func)(unsigned long), + unsigned long data) +{ + tasklet->next = NULL; + tasklet->sync = 0; + tasklet->routine = (void (*)(void *))func; + tasklet->data = (void *)data; +} +#define tasklet_kill(tasklet) { do {} while (0); } + +#define del_timer_sync(timer) del_timer(timer) + +#else + +#define netif_down(dev) + +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 3)) + +#define PREPARE_TQUEUE(_tq, _routine, _data) \ + do { \ + (_tq)->routine = _routine; \ + (_tq)->data = _data; \ + } while (0) + +#define INIT_TQUEUE(_tq, _routine, _data) \ + do { \ + INIT_LIST_HEAD(&(_tq)->list); \ + (_tq)->sync = 0; \ + PREPARE_TQUEUE((_tq), (_routine), (_data)); \ + } while (0) + +#endif + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 9) +#define PCI_SAVE_STATE(a, b) pci_save_state(a) +#define PCI_RESTORE_STATE(a, b) pci_restore_state(a) +#else +#define PCI_SAVE_STATE(a, b) pci_save_state(a, b) +#define PCI_RESTORE_STATE(a, b) pci_restore_state(a, b) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 6)) +static inline int +pci_save_state(struct pci_dev *dev, u32 *buffer) +{ + int i; + if (buffer) { + for (i = 0; i < 16; i++) + pci_read_config_dword(dev, i * 4, &buffer[i]); + } + return 0; +} + +static inline int +pci_restore_state(struct pci_dev *dev, u32 *buffer) +{ + int i; + + if (buffer) { + for (i = 0; i < 16; i++) + pci_write_config_dword(dev, i * 4, buffer[i]); + } + + else { + for (i = 0; i < 6; i ++) + pci_write_config_dword(dev, + PCI_BASE_ADDRESS_0 + (i * 4), + pci_resource_start(dev, i)); + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq); + } + return 0; +} +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 19)) +#define read_c0_count() read_32bit_cp0_register(CP0_COUNT) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) +#ifndef SET_MODULE_OWNER +#define SET_MODULE_OWNER(dev) do {} while (0) +#define OLD_MOD_INC_USE_COUNT MOD_INC_USE_COUNT +#define OLD_MOD_DEC_USE_COUNT MOD_DEC_USE_COUNT +#else +#define OLD_MOD_INC_USE_COUNT do {} while (0) +#define OLD_MOD_DEC_USE_COUNT do {} while (0) +#endif +#else +#ifndef SET_MODULE_OWNER +#define SET_MODULE_OWNER(dev) do {} while (0) +#endif +#ifndef MOD_INC_USE_COUNT +#define MOD_INC_USE_COUNT do {} while (0) +#endif +#ifndef MOD_DEC_USE_COUNT +#define MOD_DEC_USE_COUNT do {} while (0) +#endif +#define OLD_MOD_INC_USE_COUNT MOD_INC_USE_COUNT +#define OLD_MOD_DEC_USE_COUNT MOD_DEC_USE_COUNT +#endif + +#ifndef SET_NETDEV_DEV +#define SET_NETDEV_DEV(net, pdev) do {} while (0) +#endif + +#ifndef HAVE_FREE_NETDEV +#define free_netdev(dev) kfree(dev) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) + +#define af_packet_priv data +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11) +#define DRV_SUSPEND_STATE_TYPE pm_message_t +#else +#define DRV_SUSPEND_STATE_TYPE uint32 +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) +#define CHECKSUM_HW CHECKSUM_PARTIAL +#endif + +typedef struct { + void *parent; + struct task_struct *p_task; + long thr_pid; + int prio; + struct semaphore sema; + int terminated; + struct completion completed; +} tsk_ctl_t; + +#define DBG_THR(x) + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) +#define SMP_RD_BARRIER_DEPENDS(x) smp_read_barrier_depends(x) +#else +#define SMP_RD_BARRIER_DEPENDS(x) smp_rmb(x) +#endif + +#define PROC_START(thread_func, owner, tsk_ctl, flags) \ +{ \ + sema_init(&((tsk_ctl)->sema), 0); \ + init_completion(&((tsk_ctl)->completed)); \ + (tsk_ctl)->parent = owner; \ + (tsk_ctl)->terminated = FALSE; \ + (tsk_ctl)->thr_pid = kernel_thread(thread_func, tsk_ctl, flags); \ + if ((tsk_ctl)->thr_pid > 0) \ + wait_for_completion(&((tsk_ctl)->completed)); \ + DBG_THR(("%s thr:%lx started\n", __FUNCTION__, (tsk_ctl)->thr_pid)); \ +} + +#ifdef USE_KTHREAD_API +#define PROC_START2(thread_func, owner, tsk_ctl, flags, name) \ +{ \ + sema_init(&((tsk_ctl)->sema), 0); \ + init_completion(&((tsk_ctl)->completed)); \ + (tsk_ctl)->parent = owner; \ + (tsk_ctl)->terminated = FALSE; \ + (tsk_ctl)->p_task = kthread_run(thread_func, tsk_ctl, (char*)name); \ + (tsk_ctl)->thr_pid = (tsk_ctl)->p_task->pid; \ + DBG_THR(("%s thr:%lx created\n", __FUNCTION__, (tsk_ctl)->thr_pid)); \ +} +#endif + +#define PROC_STOP(tsk_ctl) \ +{ \ + (tsk_ctl)->terminated = TRUE; \ + smp_wmb(); \ + up(&((tsk_ctl)->sema)); \ + wait_for_completion(&((tsk_ctl)->completed)); \ + DBG_THR(("%s thr:%lx terminated OK\n", __FUNCTION__, (tsk_ctl)->thr_pid)); \ + (tsk_ctl)->thr_pid = -1; \ +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 31)) +#define KILL_PROC(nr, sig) \ +{ \ +struct task_struct *tsk; \ +struct pid *pid; \ +pid = find_get_pid((pid_t)nr); \ +tsk = pid_task(pid, PIDTYPE_PID); \ +if (tsk) send_sig(sig, tsk, 1); \ +} +#else +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && (LINUX_VERSION_CODE <= \ + KERNEL_VERSION(2, 6, 30)) +#define KILL_PROC(pid, sig) \ +{ \ + struct task_struct *tsk; \ + tsk = find_task_by_vpid(pid); \ + if (tsk) send_sig(sig, tsk, 1); \ +} +#else +#define KILL_PROC(pid, sig) \ +{ \ + kill_proc(pid, sig, 1); \ +} +#endif +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) +#include +#include +#else +#include + +#define __wait_event_interruptible_timeout(wq, condition, ret) \ +do { \ + wait_queue_t __wait; \ + init_waitqueue_entry(&__wait, current); \ + \ + add_wait_queue(&wq, &__wait); \ + for (;;) { \ + set_current_state(TASK_INTERRUPTIBLE); \ + if (condition) \ + break; \ + if (!signal_pending(current)) { \ + ret = schedule_timeout(ret); \ + if (!ret) \ + break; \ + continue; \ + } \ + ret = -ERESTARTSYS; \ + break; \ + } \ + current->state = TASK_RUNNING; \ + remove_wait_queue(&wq, &__wait); \ +} while (0) + +#define wait_event_interruptible_timeout(wq, condition, timeout) \ +({ \ + long __ret = timeout; \ + if (!(condition)) \ + __wait_event_interruptible_timeout(wq, condition, __ret); \ + __ret; \ +}) + +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) +#define DEV_PRIV(dev) (dev->priv) +#else +#define DEV_PRIV(dev) netdev_priv(dev) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) +#define WL_ISR(i, d, p) wl_isr((i), (d)) +#else +#define WL_ISR(i, d, p) wl_isr((i), (d), (p)) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) +#define netdev_priv(dev) dev->priv +#endif + +#endif diff --git a/drivers/custom/broadcom-wl/src/include/osl.h b/drivers/custom/broadcom-wl/src/include/osl.h new file mode 100644 index 000000000000..407b257e9840 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/osl.h @@ -0,0 +1,132 @@ +/* + * OS Abstraction Layer + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: osl.h 431503 2013-10-23 21:42:47Z $ + */ + +#ifndef _osl_h_ +#define _osl_h_ + +typedef struct osl_info osl_t; +typedef struct osl_dmainfo osldma_t; + +#define OSL_PKTTAG_SZ 32 + +typedef void (*pktfree_cb_fn_t)(void *ctx, void *pkt, unsigned int status); + +typedef unsigned int (*osl_rreg_fn_t)(void *ctx, volatile void *reg, unsigned int size); +typedef void (*osl_wreg_fn_t)(void *ctx, volatile void *reg, unsigned int val, unsigned int size); + +#ifdef __mips__ +#define PREF_LOAD 0 +#define PREF_STORE 1 +#define PREF_LOAD_STREAMED 4 +#define PREF_STORE_STREAMED 5 +#define PREF_LOAD_RETAINED 6 +#define PREF_STORE_RETAINED 7 +#define PREF_WBACK_INV 25 +#define PREF_PREPARE4STORE 30 + +#define MAKE_PREFETCH_FN(hint) \ +static inline void prefetch_##hint(const void *addr) \ +{ \ + __asm__ __volatile__(\ + " .set mips4 \n" \ + " pref %0, (%1) \n" \ + " .set mips0 \n" \ + : \ + : "i" (hint), "r" (addr)); \ +} + +#define MAKE_PREFETCH_RANGE_FN(hint) \ +static inline void prefetch_range_##hint(const void *addr, int len) \ +{ \ + int size = len; \ + while (size > 0) { \ + prefetch_##hint(addr); \ + size -= 32; \ + } \ +} + +MAKE_PREFETCH_FN(PREF_LOAD) +MAKE_PREFETCH_RANGE_FN(PREF_LOAD) +MAKE_PREFETCH_FN(PREF_STORE) +MAKE_PREFETCH_RANGE_FN(PREF_STORE) +MAKE_PREFETCH_FN(PREF_LOAD_STREAMED) +MAKE_PREFETCH_RANGE_FN(PREF_LOAD_STREAMED) +MAKE_PREFETCH_FN(PREF_STORE_STREAMED) +MAKE_PREFETCH_RANGE_FN(PREF_STORE_STREAMED) +MAKE_PREFETCH_FN(PREF_LOAD_RETAINED) +MAKE_PREFETCH_RANGE_FN(PREF_LOAD_RETAINED) +MAKE_PREFETCH_FN(PREF_STORE_RETAINED) +MAKE_PREFETCH_RANGE_FN(PREF_STORE_RETAINED) +#endif + +#include + +#ifndef PKTDBG_TRACE +#define PKTDBG_TRACE(osh, pkt, bit) +#endif + +#define PKTCTFMAP(osh, p) + +#define SET_REG(osh, r, mask, val) W_REG((osh), (r), ((R_REG((osh), r) & ~(mask)) | (val))) + +#ifndef AND_REG +#define AND_REG(osh, r, v) W_REG(osh, (r), R_REG(osh, r) & (v)) +#endif + +#ifndef OR_REG +#define OR_REG(osh, r, v) W_REG(osh, (r), R_REG(osh, r) | (v)) +#endif + +#if !defined(OSL_SYSUPTIME) +#define OSL_SYSUPTIME() (0) +#define OSL_SYSUPTIME_SUPPORT FALSE +#else +#define OSL_SYSUPTIME_SUPPORT TRUE +#endif + +#if !defined(PKTC_DONGLE) +#define PKTCGETATTR(s) (0) +#define PKTCSETATTR(skb, f, p, b) +#define PKTCCLRATTR(skb) +#define PKTCCNT(skb) (1) +#define PKTCLEN(skb) PKTLEN(NULL, skb) +#define PKTCGETFLAGS(skb) (0) +#define PKTCSETFLAGS(skb, f) +#define PKTCCLRFLAGS(skb) +#define PKTCFLAGS(skb) (0) +#define PKTCSETCNT(skb, c) +#define PKTCINCRCNT(skb) +#define PKTCADDCNT(skb, c) +#define PKTCSETLEN(skb, l) +#define PKTCADDLEN(skb, l) +#define PKTCSETFLAG(skb, fb) +#define PKTCCLRFLAG(skb, fb) +#define PKTCLINK(skb) NULL +#define PKTSETCLINK(skb, x) +#define FOREACH_CHAINED_PKT(skb, nskb) \ + for ((nskb) = NULL; (skb) != NULL; (skb) = (nskb)) +#define PKTCFREE PKTFREE +#endif + +#define PKTSETCHAINED(osh, skb) +#define PKTCLRCHAINED(osh, skb) +#define PKTISCHAINED(skb) (FALSE) + +#endif diff --git a/drivers/custom/broadcom-wl/src/include/packed_section_end.h b/drivers/custom/broadcom-wl/src/include/packed_section_end.h new file mode 100644 index 000000000000..8a98795bff70 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/packed_section_end.h @@ -0,0 +1,41 @@ +/* + * Declare directives for structure packing. No padding will be provided + * between the members of packed structures, and therefore, there is no + * guarantee that structure members will be aligned. + * + * Declaring packed structures is compiler specific. In order to handle all + * cases, packed structures should be delared as: + * + * #include + * + * typedef BWL_PRE_PACKED_STRUCT struct foobar_t { + * some_struct_members; + * } BWL_POST_PACKED_STRUCT foobar_t; + * + * #include + * + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * $Id: packed_section_end.h 395414 2013-04-07 19:26:09Z $ + */ + +#ifdef BWL_PACKED_SECTION + #undef BWL_PACKED_SECTION +#else + #error "BWL_PACKED_SECTION is NOT defined!" +#endif + +#undef BWL_PRE_PACKED_STRUCT +#undef BWL_POST_PACKED_STRUCT diff --git a/drivers/custom/broadcom-wl/src/include/packed_section_start.h b/drivers/custom/broadcom-wl/src/include/packed_section_start.h new file mode 100644 index 000000000000..49228508fba9 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/packed_section_start.h @@ -0,0 +1,48 @@ +/* + * Declare directives for structure packing. No padding will be provided + * between the members of packed structures, and therefore, there is no + * guarantee that structure members will be aligned. + * + * Declaring packed structures is compiler specific. In order to handle all + * cases, packed structures should be delared as: + * + * #include + * + * typedef BWL_PRE_PACKED_STRUCT struct foobar_t { + * some_struct_members; + * } BWL_POST_PACKED_STRUCT foobar_t; + * + * #include + * + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * $Id: packed_section_start.h 395414 2013-04-07 19:26:09Z $ + */ + +#ifdef BWL_PACKED_SECTION + #error "BWL_PACKED_SECTION is already defined!" +#else + #define BWL_PACKED_SECTION +#endif + +#if defined(__GNUC__) || defined(__lint) + #define BWL_PRE_PACKED_STRUCT + #define BWL_POST_PACKED_STRUCT __attribute__ ((packed)) +#elif defined(__CC_ARM) + #define BWL_PRE_PACKED_STRUCT __packed + #define BWL_POST_PACKED_STRUCT +#else + #error "Unknown compiler!" +#endif diff --git a/drivers/custom/broadcom-wl/src/include/pcicfg.h b/drivers/custom/broadcom-wl/src/include/pcicfg.h new file mode 100644 index 000000000000..784b56f823ea --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/pcicfg.h @@ -0,0 +1,83 @@ +/* + * pcicfg.h: PCI configuration constants and structures. + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: pcicfg.h 392809 2013-03-24 22:49:08Z $ + */ + +#ifndef _h_pcicfg_ +#define _h_pcicfg_ + +#define PCI_CFG_VID 0 +#define PCI_CFG_DID 2 +#define PCI_CFG_CMD 4 +#define PCI_CFG_STAT 6 +#define PCI_CFG_REV 8 +#define PCI_CFG_PROGIF 9 +#define PCI_CFG_SUBCL 0xa +#define PCI_CFG_BASECL 0xb +#define PCI_CFG_CLSZ 0xc +#define PCI_CFG_LATTIM 0xd +#define PCI_CFG_HDR 0xe +#define PCI_CFG_BIST 0xf +#define PCI_CFG_BAR0 0x10 +#define PCI_CFG_BAR1 0x14 +#define PCI_CFG_BAR2 0x18 +#define PCI_CFG_BAR3 0x1c +#define PCI_CFG_BAR4 0x20 +#define PCI_CFG_BAR5 0x24 +#define PCI_CFG_CIS 0x28 +#define PCI_CFG_SVID 0x2c +#define PCI_CFG_SSID 0x2e +#define PCI_CFG_ROMBAR 0x30 +#define PCI_CFG_CAPPTR 0x34 +#define PCI_CFG_INT 0x3c +#define PCI_CFG_PIN 0x3d +#define PCI_CFG_MINGNT 0x3e +#define PCI_CFG_MAXLAT 0x3f +#define PCI_CFG_DEVCTRL 0xd8 +#define PCI_BAR0_WIN 0x80 +#define PCI_BAR1_WIN 0x84 +#define PCI_SPROM_CONTROL 0x88 +#define PCI_BAR1_CONTROL 0x8c +#define PCI_INT_STATUS 0x90 +#define PCI_INT_MASK 0x94 +#define PCI_TO_SB_MB 0x98 +#define PCI_BACKPLANE_ADDR 0xa0 +#define PCI_BACKPLANE_DATA 0xa4 +#define PCI_CLK_CTL_ST 0xa8 +#define PCI_BAR0_WIN2 0xac +#define PCI_GPIO_IN 0xb0 +#define PCI_GPIO_OUT 0xb4 +#define PCI_GPIO_OUTEN 0xb8 + +#define PCI_BAR0_SHADOW_OFFSET (2 * 1024) +#define PCI_BAR0_SPROM_OFFSET (4 * 1024) +#define PCI_BAR0_PCIREGS_OFFSET (6 * 1024) +#define PCI_BAR0_PCISBR_OFFSET (4 * 1024) + +#define PCIE2_BAR0_WIN2 0x70 +#define PCIE2_BAR0_CORE2_WIN 0x74 +#define PCIE2_BAR0_CORE2_WIN2 0x78 + +#define PCI_BAR0_WINSZ (16 * 1024) + +#define PCI_16KB0_PCIREGS_OFFSET (8 * 1024) +#define PCI_16KB0_CCREGS_OFFSET (12 * 1024) +#define PCI_16KBB0_WINSZ (16 * 1024) + +#define PCI_CONFIG_SPACE_SIZE 256 +#endif diff --git a/drivers/custom/broadcom-wl/src/include/siutils.h b/drivers/custom/broadcom-wl/src/include/siutils.h new file mode 100644 index 000000000000..fad8f624e49a --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/siutils.h @@ -0,0 +1,409 @@ +/* + * Misc utility routines for accessing the SOC Interconnects + * of Broadcom HNBU chips. + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: siutils.h 434387 2013-11-06 04:50:08Z $ + */ + +#ifndef _siutils_h_ +#define _siutils_h_ + +#include + +struct si_pub { + uint socitype; + + uint bustype; + uint buscoretype; + uint buscorerev; + uint buscoreidx; + int ccrev; + uint32 cccaps; + uint32 cccaps_ext; + int pmurev; + uint32 pmucaps; + uint boardtype; + uint boardrev; + uint boardvendor; + uint boardflags; + uint boardflags2; + uint chip; + uint chiprev; + uint chippkg; + uint32 chipst; + bool issim; + uint socirev; + bool pci_pr32414; + +#ifdef SI_ENUM_BASE_VARIABLE + uint32 si_enum_base; +#endif +}; + +typedef const struct si_pub si_t; + +#ifdef ATE_BUILD +typedef struct _ate_params { + void* wl; + uint8 gpio_input; + uint8 gpio_output; + bool cmd_proceed; + uint16 cmd_idx; + bool ate_cmd_done; +} ate_params_t; +#endif + +#define SI_OSH NULL + +#define BADIDX (SI_MAXCORES + 1) + +#define XTAL 0x1 +#define PLL 0x2 + +#define CLK_FAST 0 +#define CLK_DYNAMIC 2 + +#define GPIO_DRV_PRIORITY 0 +#define GPIO_APP_PRIORITY 1 +#define GPIO_HI_PRIORITY 2 + +#define GPIO_PULLUP 0 +#define GPIO_PULLDN 1 + +#define GPIO_REGEVT 0 +#define GPIO_REGEVT_INTMSK 1 +#define GPIO_REGEVT_INTPOL 2 + +#define SI_DEVPATH_BUFSZ 16 + +#define SI_DOATTACH 1 +#define SI_PCIDOWN 2 +#define SI_PCIUP 3 + +#define ISSIM_ENAB(sih) FALSE + +#if defined(BCMPMUCTL) +#define PMUCTL_ENAB(sih) (BCMPMUCTL) +#else +#define PMUCTL_ENAB(sih) ((sih)->cccaps & CC_CAP_PMU) +#endif + +#if defined(BCMPMUCTL) && BCMPMUCTL +#define CCCTL_ENAB(sih) (0) +#define CCPLL_ENAB(sih) (0) +#else +#define CCCTL_ENAB(sih) ((sih)->cccaps & CC_CAP_PWR_CTL) +#define CCPLL_ENAB(sih) ((sih)->cccaps & CC_CAP_PLL_MASK) +#endif + +typedef void (*gpio_handler_t)(uint32 stat, void *arg); + +#define CC_BTCOEX_EN_MASK 0x01 + +#define GPIO_CTRL_EPA_EN_MASK 0x40 + +#define GPIO_CTRL_5_6_EN_MASK 0x60 +#define GPIO_CTRL_7_6_EN_MASK 0xC0 +#define GPIO_OUT_7_EN_MASK 0x80 + +#define SI_CR4_CAP (0x04) +#define SI_CR4_BANKIDX (0x40) +#define SI_CR4_BANKINFO (0x44) + +#define ARMCR4_TCBBNB_MASK 0xf0 +#define ARMCR4_TCBBNB_SHIFT 4 +#define ARMCR4_TCBANB_MASK 0xf +#define ARMCR4_TCBANB_SHIFT 0 + +#define SICF_CPUHALT (0x0020) +#define ARMCR4_BSZ_MASK 0x3f +#define ARMCR4_BSZ_MULT 8192 + +extern si_t *si_attach(uint pcidev, osl_t *osh, void *regs, uint bustype, + void *sdh, char **vars, uint *varsz); +extern si_t *si_kattach(osl_t *osh); +extern void si_detach(si_t *sih); +extern bool si_pci_war16165(si_t *sih); + +extern uint si_corelist(si_t *sih, uint coreid[]); +extern uint si_coreid(si_t *sih); +extern uint si_flag(si_t *sih); +extern uint si_flag_alt(si_t *sih); +extern uint si_intflag(si_t *sih); +extern uint si_coreidx(si_t *sih); +extern uint si_coreunit(si_t *sih); +extern uint si_corevendor(si_t *sih); +extern uint si_corerev(si_t *sih); +extern void *si_osh(si_t *sih); +extern void si_setosh(si_t *sih, osl_t *osh); +extern uint si_corereg(si_t *sih, uint coreidx, uint regoff, uint mask, uint val); +extern void *si_coreregs(si_t *sih); +extern uint si_wrapperreg(si_t *sih, uint32 offset, uint32 mask, uint32 val); +extern uint si_core_wrapperreg(si_t *sih, uint32 coreidx, uint32 offset, uint32 mask, uint32 val); +extern void *si_wrapperregs(si_t *sih); +extern uint32 si_core_cflags(si_t *sih, uint32 mask, uint32 val); +extern void si_core_cflags_wo(si_t *sih, uint32 mask, uint32 val); +extern uint32 si_core_sflags(si_t *sih, uint32 mask, uint32 val); +extern bool si_iscoreup(si_t *sih); +extern uint si_findcoreidx(si_t *sih, uint coreid, uint coreunit); +extern void *si_setcoreidx(si_t *sih, uint coreidx); +extern void *si_setcore(si_t *sih, uint coreid, uint coreunit); +extern void *si_switch_core(si_t *sih, uint coreid, uint *origidx, uint *intr_val); +extern void si_restore_core(si_t *sih, uint coreid, uint intr_val); +extern int si_numaddrspaces(si_t *sih); +extern uint32 si_addrspace(si_t *sih, uint asidx); +extern uint32 si_addrspacesize(si_t *sih, uint asidx); +extern void si_coreaddrspaceX(si_t *sih, uint asidx, uint32 *addr, uint32 *size); +extern int si_corebist(si_t *sih); +extern void si_core_reset(si_t *sih, uint32 bits, uint32 resetbits); +extern void si_core_disable(si_t *sih, uint32 bits); +extern uint32 si_clock_rate(uint32 pll_type, uint32 n, uint32 m); +extern uint si_chip_hostif(si_t *sih); +extern bool si_read_pmu_autopll(si_t *sih); +extern uint32 si_clock(si_t *sih); +extern uint32 si_alp_clock(si_t *sih); +extern uint32 si_ilp_clock(si_t *sih); +extern void si_pci_setup(si_t *sih, uint coremask); +extern void si_pcmcia_init(si_t *sih); +extern void si_setint(si_t *sih, int siflag); +extern bool si_backplane64(si_t *sih); +extern void si_register_intr_callback(si_t *sih, void *intrsoff_fn, void *intrsrestore_fn, + void *intrsenabled_fn, void *intr_arg); +extern void si_deregister_intr_callback(si_t *sih); +extern void si_clkctl_init(si_t *sih); +extern uint16 si_clkctl_fast_pwrup_delay(si_t *sih); +extern bool si_clkctl_cc(si_t *sih, uint mode); +extern int si_clkctl_xtal(si_t *sih, uint what, bool on); +extern uint32 si_gpiotimerval(si_t *sih, uint32 mask, uint32 val); +extern void si_btcgpiowar(si_t *sih); +extern bool si_deviceremoved(si_t *sih); +extern uint32 si_socram_size(si_t *sih); +extern uint32 si_socdevram_size(si_t *sih); +extern void si_socdevram(si_t *sih, bool set, uint8 *ennable, uint8 *protect, uint8 *remap); +extern bool si_socdevram_pkg(si_t *sih); +extern bool si_socdevram_remap_isenb(si_t *sih); +extern uint32 si_socdevram_remap_size(si_t *sih); + +extern void si_watchdog(si_t *sih, uint ticks); +extern void si_watchdog_ms(si_t *sih, uint32 ms); +extern uint32 si_watchdog_msticks(void); +extern void *si_gpiosetcore(si_t *sih); +extern uint32 si_gpiocontrol(si_t *sih, uint32 mask, uint32 val, uint8 priority); +extern uint32 si_gpioouten(si_t *sih, uint32 mask, uint32 val, uint8 priority); +extern uint32 si_gpioout(si_t *sih, uint32 mask, uint32 val, uint8 priority); +extern uint32 si_gpioin(si_t *sih); +extern uint32 si_gpiointpolarity(si_t *sih, uint32 mask, uint32 val, uint8 priority); +extern uint32 si_gpiointmask(si_t *sih, uint32 mask, uint32 val, uint8 priority); +extern uint32 si_gpioled(si_t *sih, uint32 mask, uint32 val); +extern uint32 si_gpioreserve(si_t *sih, uint32 gpio_num, uint8 priority); +extern uint32 si_gpiorelease(si_t *sih, uint32 gpio_num, uint8 priority); +extern uint32 si_gpiopull(si_t *sih, bool updown, uint32 mask, uint32 val); +extern uint32 si_gpioevent(si_t *sih, uint regtype, uint32 mask, uint32 val); +extern uint32 si_gpio_int_enable(si_t *sih, bool enable); + +extern void *si_gpio_handler_register(si_t *sih, uint32 e, bool lev, gpio_handler_t cb, void *arg); +extern void si_gpio_handler_unregister(si_t *sih, void* gpioh); +extern void si_gpio_handler_process(si_t *sih); + +extern bool si_pci_pmecap(si_t *sih); +struct osl_info; +extern bool si_pci_fastpmecap(struct osl_info *osh); +extern bool si_pci_pmestat(si_t *sih); +extern void si_pci_pmeclr(si_t *sih); +extern void si_pci_pmeen(si_t *sih); +extern void si_pci_pmestatclr(si_t *sih); +extern uint si_pcie_readreg(void *sih, uint addrtype, uint offset); +extern uint si_pcie_writereg(void *sih, uint addrtype, uint offset, uint val); + +extern uint16 si_d11_devid(si_t *sih); +extern int si_corepciid(si_t *sih, uint func, uint16 *pcivendor, uint16 *pcidevice, + uint8 *pciclass, uint8 *pcisubclass, uint8 *pciprogif, uint8 *pciheader); + +#if defined(BCMECICOEX) +extern bool si_eci(si_t *sih); +extern int si_eci_init(si_t *sih); +extern void si_eci_notify_bt(si_t *sih, uint32 mask, uint32 val, bool interrupt); +extern bool si_seci(si_t *sih); +extern void* si_seci_init(si_t *sih, uint8 seci_mode); +extern void* si_gci_init(si_t *sih); +extern void si_seci_down(si_t *sih); +extern void si_seci_upd(si_t *sih, bool enable); +extern bool si_gci(si_t *sih); +#else +#define si_eci(sih) 0 +static INLINE void * si_eci_init(si_t *sih) {return NULL;} +#define si_eci_notify_bt(sih, type, val) (0) +#define si_seci(sih) 0 +#define si_seci_upd(sih, a) do {} while (0) +static INLINE void * si_seci_init(si_t *sih, uint8 use_seci) {return NULL;} +static INLINE void * si_gci_init(si_t *sih) {return NULL;} +#define si_seci_down(sih) do {} while (0) +#define si_gci(sih) 0 +#endif + +extern bool si_is_otp_disabled(si_t *sih); +extern bool si_is_otp_powered(si_t *sih); +extern void si_otp_power(si_t *sih, bool on); + +extern bool si_is_sprom_available(si_t *sih); +extern bool si_is_sprom_enabled(si_t *sih); +extern void si_sprom_enable(si_t *sih, bool enable); +#ifdef SI_SPROM_PROBE +extern void si_sprom_init(si_t *sih); +#endif + +extern int si_cis_source(si_t *sih); +#define CIS_DEFAULT 0 +#define CIS_SROM 1 +#define CIS_OTP 2 + +#define DEFAULT_FAB 0x0 +#define CSM_FAB7 0x1 +#define TSMC_FAB12 0x2 +#define SMIC_FAB4 0x3 + +extern int BCMINITFN(si_otp_fabid)(si_t *sih, uint16 *fabid, bool rw); +extern uint16 BCMATTACHFN(si_fabid)(si_t *sih); + +extern int si_devpath(si_t *sih, char *path, int size); + +extern char *si_getdevpathvar(si_t *sih, const char *name); +extern int si_getdevpathintvar(si_t *sih, const char *name); +extern char *si_coded_devpathvar(si_t *sih, char *varname, int var_len, const char *name); + +extern uint8 si_pcieclkreq(si_t *sih, uint32 mask, uint32 val); +extern uint32 si_pcielcreg(si_t *sih, uint32 mask, uint32 val); +extern uint8 si_pcieltrenable(si_t *sih, uint32 mask, uint32 val); +extern uint8 si_pcieobffenable(si_t *sih, uint32 mask, uint32 val); +extern uint32 si_pcieltr_reg(si_t *sih, uint32 reg, uint32 mask, uint32 val); +extern uint32 si_pcieltrspacing_reg(si_t *sih, uint32 mask, uint32 val); +extern uint32 si_pcieltrhysteresiscnt_reg(si_t *sih, uint32 mask, uint32 val); +extern void si_pcie_set_error_injection(si_t *sih, uint32 mode); +extern void si_pcie_set_L1substate(si_t *sih, uint32 substate); +extern uint32 si_pcie_get_L1substate(si_t *sih); +extern void si_war42780_clkreq(si_t *sih, bool clkreq); +extern void si_pci_down(si_t *sih); +extern void si_pci_up(si_t *sih); +extern void si_pci_sleep(si_t *sih); +extern void si_pcie_war_ovr_update(si_t *sih, uint8 aspm); +extern void si_pcie_power_save_enable(si_t *sih, bool enable); +extern void si_pcie_extendL1timer(si_t *sih, bool extend); +extern int si_pci_fixcfg(si_t *sih); +extern bool si_ldo_war(si_t *sih, uint devid); +extern void si_chippkg_set(si_t *sih, uint); + +extern void si_chipcontrl_btshd0_4331(si_t *sih, bool on); +extern void si_chipcontrl_restore(si_t *sih, uint32 val); +extern uint32 si_chipcontrl_read(si_t *sih); +extern void si_chipcontrl_epa4331(si_t *sih, bool on); +extern void si_chipcontrl_epa4331_wowl(si_t *sih, bool enter_wowl); +extern void si_chipcontrl_srom4360(si_t *sih, bool on); + +extern void si_epa_4313war(si_t *sih); +extern void si_btc_enable_chipcontrol(si_t *sih); + +extern void si_btcombo_p250_4313_war(si_t *sih); +extern void si_btcombo_43228_war(si_t *sih); +extern void si_clk_pmu_htavail_set(si_t *sih, bool set_clear); +extern void si_pmu_synth_pwrsw_4313_war(si_t *sih); +extern uint si_pll_reset(si_t *sih); + +extern bool si_taclear(si_t *sih, bool details); + +#ifdef BCMDBG +extern void si_view(si_t *sih, bool verbose); +extern void si_viewall(si_t *sih, bool verbose); +#if defined(BCM_BACKPLANE_TIMEOUT) +extern void si_setup_backplanetimeout(osl_t *osh, si_t *sih, uint val); +extern void si_setup_curmap(osl_t *osh, si_t *sih); +#endif +#endif +#if defined(BCMDBG) || defined(BCMDBG_DUMP) +extern int si_dump_pcieinfo(si_t *sih, struct bcmstrbuf *b); +#endif + +#if defined(BCMDBG) || defined(BCMDBG_DUMP) +struct bcmstrbuf; +extern void si_dumpregs(si_t *sih, struct bcmstrbuf *b); +#endif +extern uint32 si_ccreg(si_t *sih, uint32 offset, uint32 mask, uint32 val); +extern uint32 si_pciereg(si_t *sih, uint32 offset, uint32 mask, uint32 val, uint type); +extern uint32 write_ccreg(si_t *sih, uint32 offset, uint32 mask, uint32 val); +extern uint32 si_pcieserdesreg(si_t *sih, uint32 mdioslave, uint32 offset, uint32 mask, uint32 val); +extern void si_pcie_set_request_size(si_t *sih, uint16 size); +extern uint16 si_pcie_get_request_size(si_t *sih); +extern void si_pcie_set_maxpayload_size(si_t *sih, uint16 size); +extern uint16 si_pcie_get_maxpayload_size(si_t *sih); +extern uint16 si_pcie_get_ssid(si_t *sih); +extern uint32 si_pcie_get_bar0(si_t *sih); +extern int si_pcie_configspace_cache(si_t *sih); +extern int si_pcie_configspace_restore(si_t *sih); +extern int si_pcie_configspace_get(si_t *sih, uint8 *buf, uint size); + +char *si_getnvramflvar(si_t *sih, const char *name); + +extern void BCMATTACHFN(si_muxenab)(si_t *sih, uint32 w); + +extern uint32 si_tcm_size(si_t *sih); +extern bool si_has_flops(si_t *sih); + +extern uint32 si_gci_direct(si_t *sih, uint offset, uint32 mask, uint32 val); +extern uint32 si_gci_indirect(si_t *sih, uint regidx, uint offset, uint32 mask, uint32 val); +extern uint32 si_gci_output(si_t *sih, uint reg, uint32 mask, uint32 val); +extern uint32 si_gci_input(si_t *sih, uint reg); +extern uint32 si_gci_int_enable(si_t *sih, bool enable); +extern void si_gci_reset(si_t *sih); +extern void si_ercx_init(si_t *sih); +extern void si_wci2_init(si_t *sih); +extern void si_gci_seci_init(si_t *sih); +extern void si_gci_set_functionsel(si_t *sih, uint32 pin, uint8 fnsel); +extern uint8 si_gci_get_chipctrlreg_idx(uint32 pin, uint32 *regidx, uint32 *pos); +extern uint32 si_gci_chipcontrol(si_t *sih, uint reg, uint32 mask, uint32 val); +extern int si_set_sromctl(si_t *sih, uint32 value); +extern uint32 si_get_sromctl(si_t *sih); + +extern uint16 si_cc_get_reg16(uint32 reg_offs); +extern uint32 si_cc_get_reg32(uint32 reg_offs); +extern uint32 si_cc_set_reg32(uint32 reg_offs, uint32 val); +extern uint32 si_gci_preinit_upd_indirect(uint32 regidx, uint32 setval, uint32 mask); + +#define CHIPCTRLREG1 0x1 +#define CHIPCTRLREG2 0x2 +#define CHIPCTRLREG3 0x3 +#define CHIPCTRLREG4 0x4 +#define CHIPCTRLREG5 0x5 +#define REGCTRLREG4 0x4 +#define MINRESMASKREG 0x618 +#define MAXRESMASKREG 0x61c +#define CHIPCTRLADDR 0x650 +#define CHIPCTRLDATA 0x654 +#define RSRCTABLEADDR 0x620 +#define RSRCUPDWNTIME 0x628 +#define PMUREG_RESREQ_MASK 0x68c + +extern void si_update_masks(si_t *sih); + +extern void si_force_islanding(si_t *sih, bool enable); + +extern uint32 si_pmu_res_req_timer_clr(si_t *sih); +extern void si_pmu_rfldo(si_t *sih, bool on); +extern void si_survive_perst_war(si_t *sih, bool reset, uint32 sperst_mask, uint32 spert_val); +extern void si_pcie_ltr_war(si_t *sih); +extern uint si_jtag_ureg_read(si_t *sih, uint num); +extern void si_jtag_ureg_write(si_t *sih, uint num, uint data); +extern uint si_bbpll_war(si_t *sih, uint state); +#endif diff --git a/drivers/custom/broadcom-wl/src/include/typedefs.h b/drivers/custom/broadcom-wl/src/include/typedefs.h new file mode 100644 index 000000000000..d26ab140a60c --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/typedefs.h @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * $Id: typedefs.h 382996 2013-02-05 06:55:24Z $ + */ + +#ifndef _TYPEDEFS_H_ +#define _TYPEDEFS_H_ + +#if defined(__x86_64__) +#define TYPEDEF_UINTPTR +typedef unsigned long long int uintptr; +#endif + +#if defined(__sparc__) +#define TYPEDEF_ULONG +#endif + +#if defined(LINUX_PORT) +#define TYPEDEF_UINT +#define TYPEDEF_USHORT +#define TYPEDEF_ULONG +#ifdef __KERNEL__ +#include +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)) +#define TYPEDEF_BOOL +#endif + +#if (LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 18)) +#include +#endif +#endif +#endif + +#if defined(__GNUC__) && defined(__STRICT_ANSI__) +#define TYPEDEF_INT64 +#define TYPEDEF_UINT64 +#endif + +#if !defined(__BOB__) + +#if defined(__KERNEL__) + +#if defined(LINUX_PORT) +#include +#endif + +#else + +#include + +#endif + +#endif + +#define USE_TYPEDEF_DEFAULTS + +#ifdef USE_TYPEDEF_DEFAULTS +#undef USE_TYPEDEF_DEFAULTS + +#ifndef TYPEDEF_BOOL +typedef unsigned char bool; +#endif + +#ifndef TYPEDEF_UCHAR +typedef unsigned char uchar; +#endif + +#ifndef TYPEDEF_USHORT +typedef unsigned short ushort; +#endif + +#ifndef TYPEDEF_UINT +typedef unsigned int uint; +#endif + +#ifndef TYPEDEF_ULONG +typedef unsigned long ulong; +#endif + +#ifndef TYPEDEF_UINT8 +typedef unsigned char uint8; +#endif + +#ifndef TYPEDEF_UINT16 +typedef unsigned short uint16; +#endif + +#ifndef TYPEDEF_UINT32 +typedef unsigned int uint32; +#endif + +#ifndef TYPEDEF_UINT64 +typedef unsigned long long uint64; +#endif + +#ifndef TYPEDEF_UINTPTR +typedef unsigned int uintptr; +#endif + +#ifndef TYPEDEF_INT8 +typedef signed char int8; +#endif + +#ifndef TYPEDEF_INT16 +typedef signed short int16; +#endif + +#ifndef TYPEDEF_INT32 +typedef signed int int32; +#endif + +#ifndef TYPEDEF_INT64 +typedef signed long long int64; +#endif + +#ifndef TYPEDEF_FLOAT32 +typedef float float32; +#endif + +#ifndef TYPEDEF_FLOAT64 +typedef double float64; +#endif + +#ifndef TYPEDEF_FLOAT_T + +#if defined(FLOAT32) +typedef float32 float_t; +#else +typedef float64 float_t; +#endif + +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef OFF +#define OFF 0 +#endif + +#ifndef ON +#define ON 1 +#endif + +#define AUTO (-1) + +#ifndef PTRSZ +#define PTRSZ sizeof(char*) +#endif + +#if defined(__GNUC__) || defined(__lint) + #define BWL_COMPILER_GNU +#elif defined(__CC_ARM) && __CC_ARM + #define BWL_COMPILER_ARMCC +#else + #error "Unknown compiler!" +#endif + +#ifndef INLINE + #if defined(BWL_COMPILER_MICROSOFT) + #define INLINE __inline + #elif defined(BWL_COMPILER_GNU) + #define INLINE __inline__ + #elif defined(BWL_COMPILER_ARMCC) + #define INLINE __inline + #else + #define INLINE + #endif +#endif + +#undef TYPEDEF_BOOL +#undef TYPEDEF_UCHAR +#undef TYPEDEF_USHORT +#undef TYPEDEF_UINT +#undef TYPEDEF_ULONG +#undef TYPEDEF_UINT8 +#undef TYPEDEF_UINT16 +#undef TYPEDEF_UINT32 +#undef TYPEDEF_UINT64 +#undef TYPEDEF_UINTPTR +#undef TYPEDEF_INT8 +#undef TYPEDEF_INT16 +#undef TYPEDEF_INT32 +#undef TYPEDEF_INT64 +#undef TYPEDEF_FLOAT32 +#undef TYPEDEF_FLOAT64 +#undef TYPEDEF_FLOAT_T + +#endif + +#define UNUSED_PARAMETER(x) (void)(x) + +#define DISCARD_QUAL(ptr, type) ((type *)(uintptr)(ptr)) + +#include +#endif diff --git a/drivers/custom/broadcom-wl/src/include/wlioctl.h b/drivers/custom/broadcom-wl/src/include/wlioctl.h new file mode 100644 index 000000000000..2c05c7cc1d99 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/include/wlioctl.h @@ -0,0 +1,1165 @@ +/* + * Custom OID/ioctl definitions for + * Broadcom 802.11abg Networking Device Driver + * + * Definitions subject to change without notice. + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wlioctl.h 464835 2014-03-26 01:35:40Z $ + */ + +#ifndef _wlioctl_h_ +#define _wlioctl_h_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define BWL_DEFAULT_PACKING +#include + +#define WL_BSS_INFO_VERSION 109 + +typedef struct wl_bss_info { + uint32 version; + uint32 length; + struct ether_addr BSSID; + uint16 beacon_period; + uint16 capability; + uint8 SSID_len; + uint8 SSID[32]; + struct { + uint count; + uint8 rates[16]; + } rateset; + chanspec_t chanspec; + uint16 atim_window; + uint8 dtim_period; + int16 RSSI; + int8 phy_noise; + + uint8 n_cap; + uint32 nbss_cap; + uint8 ctl_ch; + uint8 padding1[3]; + uint16 vht_rxmcsmap; + uint16 vht_txmcsmap; + uint8 flags; + uint8 vht_cap; + uint8 reserved[2]; + uint8 basic_mcs[MCSSET_LEN]; + + uint16 ie_offset; + uint32 ie_length; + int16 SNR; + +} wl_bss_info_t; + +#define WL_BSS_FLAGS_FROM_BEACON 0x01 +#define WL_BSS_FLAGS_FROM_CACHE 0x02 +#define WL_BSS_FLAGS_RSSI_ONCHANNEL 0x04 + +#define VHT_BI_SGI_80MHZ 0x00000100 +#define VHT_BI_80MHZ 0x00000200 +#define VHT_BI_160MHZ 0x00000400 +#define VHT_BI_8080MHZ 0x00000800 + +typedef struct wlc_ssid { + uint32 SSID_len; + uchar SSID[32]; +} wlc_ssid_t; + +typedef struct wl_scan_results { + uint32 buflen; + uint32 version; + uint32 count; + wl_bss_info_t bss_info[1]; +} wl_scan_results_t; + +#define WL_MAXRATES_IN_SET 16 +typedef struct wl_rateset { + uint32 count; + uint8 rates[WL_MAXRATES_IN_SET]; +} wl_rateset_t; + +typedef struct wl_rateset_args { + uint32 count; + uint8 rates[WL_MAXRATES_IN_SET]; + uint8 mcs[MCSSET_LEN]; + uint16 vht_mcs[VHT_CAP_MCS_MAP_NSS_MAX]; +} wl_rateset_args_t; + +#define TXBF_RATE_MCS_ALL 4 +#define TXBF_RATE_VHT_ALL 4 +#define TXBF_RATE_OFDM_ALL 8 + +typedef struct wl_txbf_rateset { + uint8 txbf_rate_mcs[TXBF_RATE_MCS_ALL]; + uint8 txbf_rate_mcs_bcm[TXBF_RATE_MCS_ALL]; + uint16 txbf_rate_vht[TXBF_RATE_VHT_ALL]; + uint16 txbf_rate_vht_bcm[TXBF_RATE_VHT_ALL]; + uint8 txbf_rate_ofdm[TXBF_RATE_OFDM_ALL]; + uint8 txbf_rate_ofdm_bcm[TXBF_RATE_OFDM_ALL]; + uint8 txbf_rate_ofdm_cnt; + uint8 txbf_rate_ofdm_cnt_bcm; +} wl_txbf_rateset_t; + +#define OFDM_RATE_MASK 0x0000007f +typedef uint8 ofdm_rates_t; + +typedef struct wl_uint32_list { + + uint32 count; + + uint32 element[1]; +} wl_uint32_list_t; + +typedef struct wl_assoc_params { + struct ether_addr bssid; + int32 chanspec_num; + chanspec_t chanspec_list[1]; +} wl_assoc_params_t; +#define WL_ASSOC_PARAMS_FIXED_SIZE OFFSETOF(wl_assoc_params_t, chanspec_list) + +typedef wl_assoc_params_t wl_reassoc_params_t; +#define WL_REASSOC_PARAMS_FIXED_SIZE WL_ASSOC_PARAMS_FIXED_SIZE + +typedef wl_assoc_params_t wl_join_assoc_params_t; +#define WL_JOIN_ASSOC_PARAMS_FIXED_SIZE WL_ASSOC_PARAMS_FIXED_SIZE + +typedef struct wl_join_params { + wlc_ssid_t ssid; + wl_assoc_params_t params; +} wl_join_params_t; + +#define WLC_CNTRY_BUF_SZ 4 + +#define CRYPTO_ALGO_OFF 0 +#define CRYPTO_ALGO_WEP1 1 +#define CRYPTO_ALGO_TKIP 2 +#define CRYPTO_ALGO_WEP128 3 +#define CRYPTO_ALGO_AES_CCM 4 +#define CRYPTO_ALGO_AES_OCB_MSDU 5 +#define CRYPTO_ALGO_AES_OCB_MPDU 6 +#define CRYPTO_ALGO_NALG 7 +#define CRYPTO_ALGO_PMK 12 +#define CRYPTO_ALGO_BIP 13 + +#define WSEC_GEN_MIC_ERROR 0x0001 +#define WSEC_GEN_REPLAY 0x0002 +#define WSEC_GEN_ICV_ERROR 0x0004 +#define WSEC_GEN_MFP_ACT_ERROR 0x0008 +#define WSEC_GEN_MFP_DISASSOC_ERROR 0x0010 +#define WSEC_GEN_MFP_DEAUTH_ERROR 0x0020 + +#define WL_SOFT_KEY (1 << 0) +#define WL_PRIMARY_KEY (1 << 1) +#define WL_KF_RES_4 (1 << 4) +#define WL_KF_RES_5 (1 << 5) +#define WL_IBSS_PEER_GROUP_KEY (1 << 6) + +typedef struct wl_wsec_key { + uint32 index; + uint32 len; + uint8 data[DOT11_MAX_KEY_SIZE]; + uint32 pad_1[18]; + uint32 algo; + uint32 flags; + uint32 pad_2[2]; + int pad_3; + int iv_initialized; + int pad_4; + + struct { + uint32 hi; + uint16 lo; + } rxiv; + uint32 pad_5[2]; + struct ether_addr ea; +} wl_wsec_key_t; + +#define WSEC_MIN_PSK_LEN 8 +#define WSEC_MAX_PSK_LEN 64 + +#define WSEC_PASSPHRASE (1<<0) + +typedef struct { + ushort key_len; + ushort flags; + uint8 key[WSEC_MAX_PSK_LEN]; +} wsec_pmk_t; + +#define WEP_ENABLED 0x0001 +#define TKIP_ENABLED 0x0002 +#define AES_ENABLED 0x0004 +#define WSEC_SWFLAG 0x0008 +#define SES_OW_ENABLED 0x0040 + +#define WSEC_WEP_ENABLED(wsec) ((wsec) & WEP_ENABLED) +#define WSEC_TKIP_ENABLED(wsec) ((wsec) & TKIP_ENABLED) +#define WSEC_AES_ENABLED(wsec) ((wsec) & AES_ENABLED) + +#define WSEC_ENABLED(wsec) ((wsec) & (WEP_ENABLED | TKIP_ENABLED | AES_ENABLED)) +#define WSEC_SES_OW_ENABLED(wsec) ((wsec) & SES_OW_ENABLED) + +#define MFP_CAPABLE 0x0200 +#define MFP_REQUIRED 0x0400 +#define MFP_SHA256 0x0800 + +#define WPA_AUTH_DISABLED 0x0000 +#define WPA_AUTH_NONE 0x0001 +#define WPA_AUTH_UNSPECIFIED 0x0002 +#define WPA_AUTH_PSK 0x0004 + +#define WPA2_AUTH_UNSPECIFIED 0x0040 +#define WPA2_AUTH_PSK 0x0080 +#define BRCM_AUTH_PSK 0x0100 +#define BRCM_AUTH_DPT 0x0200 +#define WPA2_AUTH_MFP 0x1000 +#define WPA2_AUTH_TPK 0x2000 +#define WPA2_AUTH_FT 0x4000 +#define WPA_AUTH_PFN_ANY 0xffffffff + +#define MAXPMKID 16 + +typedef struct _pmkid { + struct ether_addr BSSID; + uint8 PMKID[WPA2_PMKID_LEN]; +} pmkid_t; + +typedef struct _pmkid_list { + uint32 npmkid; + pmkid_t pmkid[1]; +} pmkid_list_t; + +typedef struct _pmkid_cand { + struct ether_addr BSSID; + uint8 preauth; +} pmkid_cand_t; + +typedef struct _pmkid_cand_list { + uint32 npmkid_cand; + pmkid_cand_t pmkid_cand[1]; +} pmkid_cand_list_t; + +typedef struct { + uint32 val; + struct ether_addr ea; +} scb_val_t; + +typedef struct { + uint32 code; + scb_val_t ioctl_args; +} authops_t; + +typedef struct channel_info { + int hw_channel; + int target_channel; + int scan_channel; +} channel_info_t; + +struct maclist { + uint count; + struct ether_addr ea[1]; +}; + +typedef struct wl_ioctl { + uint cmd; + void *buf; + uint len; + uint8 set; + uint used; + uint needed; +} wl_ioctl_t; + +#define ioctl_subtype set +#define ioctl_pid used +#define ioctl_status needed + +typedef struct wlc_rev_info { + uint vendorid; + uint deviceid; + uint radiorev; + uint chiprev; + uint corerev; + uint boardid; + uint boardvendor; + uint boardrev; + uint driverrev; + uint ucoderev; + uint bus; + uint chipnum; + uint phytype; + uint phyrev; + uint anarev; + uint chippkg; + uint nvramrev; +} wlc_rev_info_t; + +#define WL_REV_INFO_LEGACY_LENGTH 48 + +#define WLC_IOCTL_MAXLEN 8192 +#define WLC_IOCTL_SMLEN 256 +#define WLC_IOCTL_MEDLEN 1536 +#if defined(LCNCONF) || defined(LCN40CONF) +#define WLC_SAMPLECOLLECT_MAXLEN 8192 +#else +#define WLC_SAMPLECOLLECT_MAXLEN 10240 +#endif +#define WLC_SAMPLECOLLECT_MAXLEN_LCN40 8192 + +#define WLC_GET_MAGIC 0 +#define WLC_GET_VERSION 1 +#define WLC_UP 2 +#define WLC_DOWN 3 +#define WLC_GET_LOOP 4 +#define WLC_SET_LOOP 5 +#define WLC_DUMP 6 +#define WLC_GET_MSGLEVEL 7 +#define WLC_SET_MSGLEVEL 8 +#define WLC_GET_PROMISC 9 +#define WLC_SET_PROMISC 10 + +#define WLC_GET_RATE 12 +#define WLC_GET_MAX_RATE 13 +#define WLC_GET_INSTANCE 14 + +#define WLC_GET_INFRA 19 +#define WLC_SET_INFRA 20 +#define WLC_GET_AUTH 21 +#define WLC_SET_AUTH 22 +#define WLC_GET_BSSID 23 +#define WLC_SET_BSSID 24 +#define WLC_GET_SSID 25 +#define WLC_SET_SSID 26 +#define WLC_RESTART 27 +#define WLC_TERMINATED 28 + +#define WLC_GET_CHANNEL 29 +#define WLC_SET_CHANNEL 30 +#define WLC_GET_SRL 31 +#define WLC_SET_SRL 32 +#define WLC_GET_LRL 33 +#define WLC_SET_LRL 34 +#define WLC_GET_PLCPHDR 35 +#define WLC_SET_PLCPHDR 36 +#define WLC_GET_RADIO 37 +#define WLC_SET_RADIO 38 +#define WLC_GET_PHYTYPE 39 +#define WLC_DUMP_RATE 40 +#define WLC_SET_RATE_PARAMS 41 +#define WLC_GET_FIXRATE 42 +#define WLC_SET_FIXRATE 43 + +#define WLC_GET_KEY 44 +#define WLC_SET_KEY 45 +#define WLC_GET_REGULATORY 46 +#define WLC_SET_REGULATORY 47 +#define WLC_GET_PASSIVE_SCAN 48 +#define WLC_SET_PASSIVE_SCAN 49 +#define WLC_SCAN 50 +#define WLC_SCAN_RESULTS 51 +#define WLC_DISASSOC 52 +#define WLC_REASSOC 53 +#define WLC_GET_ROAM_TRIGGER 54 +#define WLC_SET_ROAM_TRIGGER 55 +#define WLC_GET_ROAM_DELTA 56 +#define WLC_SET_ROAM_DELTA 57 +#define WLC_GET_ROAM_SCAN_PERIOD 58 +#define WLC_SET_ROAM_SCAN_PERIOD 59 +#define WLC_EVM 60 +#define WLC_GET_TXANT 61 +#define WLC_SET_TXANT 62 +#define WLC_GET_ANTDIV 63 +#define WLC_SET_ANTDIV 64 + +#define WLC_GET_CLOSED 67 +#define WLC_SET_CLOSED 68 +#define WLC_GET_MACLIST 69 +#define WLC_SET_MACLIST 70 +#define WLC_GET_RATESET 71 +#define WLC_SET_RATESET 72 + +#define WLC_LONGTRAIN 74 +#define WLC_GET_BCNPRD 75 +#define WLC_SET_BCNPRD 76 +#define WLC_GET_DTIMPRD 77 +#define WLC_SET_DTIMPRD 78 +#define WLC_GET_SROM 79 +#define WLC_SET_SROM 80 +#define WLC_GET_WEP_RESTRICT 81 +#define WLC_SET_WEP_RESTRICT 82 +#define WLC_GET_COUNTRY 83 +#define WLC_SET_COUNTRY 84 +#define WLC_GET_PM 85 +#define WLC_SET_PM 86 +#define WLC_GET_WAKE 87 +#define WLC_SET_WAKE 88 + +#define WLC_GET_FORCELINK 90 +#define WLC_SET_FORCELINK 91 +#define WLC_FREQ_ACCURACY 92 +#define WLC_CARRIER_SUPPRESS 93 +#define WLC_GET_PHYREG 94 +#define WLC_SET_PHYREG 95 +#define WLC_GET_RADIOREG 96 +#define WLC_SET_RADIOREG 97 +#define WLC_GET_REVINFO 98 +#define WLC_GET_UCANTDIV 99 +#define WLC_SET_UCANTDIV 100 +#define WLC_R_REG 101 +#define WLC_W_REG 102 + +#define WLC_GET_MACMODE 105 +#define WLC_SET_MACMODE 106 +#define WLC_GET_MONITOR 107 +#define WLC_SET_MONITOR 108 +#define WLC_GET_GMODE 109 +#define WLC_SET_GMODE 110 +#define WLC_GET_LEGACY_ERP 111 +#define WLC_SET_LEGACY_ERP 112 +#define WLC_GET_RX_ANT 113 +#define WLC_GET_CURR_RATESET 114 +#define WLC_GET_SCANSUPPRESS 115 +#define WLC_SET_SCANSUPPRESS 116 +#define WLC_GET_AP 117 +#define WLC_SET_AP 118 +#define WLC_GET_EAP_RESTRICT 119 +#define WLC_SET_EAP_RESTRICT 120 +#define WLC_SCB_AUTHORIZE 121 +#define WLC_SCB_DEAUTHORIZE 122 +#define WLC_GET_WDSLIST 123 +#define WLC_SET_WDSLIST 124 +#define WLC_GET_ATIM 125 +#define WLC_SET_ATIM 126 +#define WLC_GET_RSSI 127 +#define WLC_GET_PHYANTDIV 128 +#define WLC_SET_PHYANTDIV 129 +#define WLC_AP_RX_ONLY 130 +#define WLC_GET_TX_PATH_PWR 131 +#define WLC_SET_TX_PATH_PWR 132 +#define WLC_GET_WSEC 133 +#define WLC_SET_WSEC 134 +#define WLC_GET_PHY_NOISE 135 +#define WLC_GET_BSS_INFO 136 +#define WLC_GET_PKTCNTS 137 +#define WLC_GET_LAZYWDS 138 +#define WLC_SET_LAZYWDS 139 +#define WLC_GET_BANDLIST 140 + +#define WLC_GET_PHYLIST 180 +#define WLC_SCB_DEAUTHENTICATE_FOR_REASON 201 +#define WLC_GET_VALID_CHANNELS 217 +#define WLC_GET_FAKEFRAG 218 +#define WLC_SET_FAKEFRAG 219 +#define WLC_GET_PWROUT_PERCENTAGE 220 +#define WLC_SET_PWROUT_PERCENTAGE 221 +#define WLC_SET_BAD_FRAME_PREEMPT 222 +#define WLC_GET_BAD_FRAME_PREEMPT 223 +#define WLC_SET_LEAP_LIST 224 +#define WLC_GET_LEAP_LIST 225 +#define WLC_GET_CWMIN 226 +#define WLC_SET_CWMIN 227 +#define WLC_GET_CWMAX 228 +#define WLC_SET_CWMAX 229 +#define WLC_GET_WET 230 +#define WLC_SET_WET 231 +#define WLC_GET_PUB 232 + +#define WLC_GET_KEY_PRIMARY 235 +#define WLC_SET_KEY_PRIMARY 236 + +#define WLC_GET_VAR 262 +#define WLC_SET_VAR 263 + +#define WL_RADIO_SW_DISABLE (1<<0) +#define WL_RADIO_HW_DISABLE (1<<1) +#define WL_RADIO_MPC_DISABLE (1<<2) +#define WL_RADIO_COUNTRY_DISABLE (1<<3) + +#define WL_SPURAVOID_OFF 0 +#define WL_SPURAVOID_ON1 1 +#define WL_SPURAVOID_ON2 2 + +#define WL_4335_SPURAVOID_ON1 1 +#define WL_4335_SPURAVOID_ON2 2 +#define WL_4335_SPURAVOID_ON3 3 +#define WL_4335_SPURAVOID_ON4 4 +#define WL_4335_SPURAVOID_ON5 5 +#define WL_4335_SPURAVOID_ON6 6 +#define WL_4335_SPURAVOID_ON7 7 +#define WL_4335_SPURAVOID_ON8 8 +#define WL_4335_SPURAVOID_ON9 9 + +#define WL_TXPWR_OVERRIDE (1U<<31) +#define WL_TXPWR_NEG (1U<<30) + +#define WLC_PHY_TYPE_A 0 +#define WLC_PHY_TYPE_B 1 +#define WLC_PHY_TYPE_G 2 +#define WLC_PHY_TYPE_N 4 +#define WLC_PHY_TYPE_LP 5 +#define WLC_PHY_TYPE_SSN 6 +#define WLC_PHY_TYPE_HT 7 +#define WLC_PHY_TYPE_LCN 8 +#define WLC_PHY_TYPE_LCN40 10 +#define WLC_PHY_TYPE_AC 11 +#define WLC_PHY_TYPE_NULL 0xf + +#define PM_OFF 0 +#define PM_MAX 1 +#define PM_FAST 2 +#define PM_FORCE_OFF 3 + +#define NFIFO 6 +#define NREINITREASONCOUNT 8 +#define REINITREASONIDX(_x) (((_x) < NREINITREASONCOUNT) ? (_x) : 0) + +#define WL_CNT_T_VERSION 10 + +typedef struct { + uint16 version; + uint16 length; + + uint32 txframe; + uint32 txbyte; + uint32 txretrans; + uint32 txerror; + uint32 txctl; + uint32 txprshort; + uint32 txserr; + uint32 txnobuf; + uint32 txnoassoc; + uint32 txrunt; + uint32 txchit; + uint32 txcmiss; + + uint32 txuflo; + uint32 txphyerr; + uint32 txphycrs; + + uint32 rxframe; + uint32 rxbyte; + uint32 rxerror; + uint32 rxctl; + uint32 rxnobuf; + uint32 rxnondata; + uint32 rxbadds; + uint32 rxbadcm; + uint32 rxfragerr; + uint32 rxrunt; + uint32 rxgiant; + uint32 rxnoscb; + uint32 rxbadproto; + uint32 rxbadsrcmac; + uint32 rxbadda; + uint32 rxfilter; + + uint32 rxoflo; + uint32 rxuflo[NFIFO]; + + uint32 d11cnt_txrts_off; + uint32 d11cnt_rxcrc_off; + uint32 d11cnt_txnocts_off; + + uint32 dmade; + uint32 dmada; + uint32 dmape; + uint32 reset; + uint32 tbtt; + uint32 txdmawar; + uint32 pkt_callback_reg_fail; + + uint32 txallfrm; + uint32 txrtsfrm; + uint32 txctsfrm; + uint32 txackfrm; + uint32 txdnlfrm; + uint32 txbcnfrm; + uint32 txfunfl[8]; + uint32 txtplunfl; + uint32 txphyerror; + uint32 rxfrmtoolong; + uint32 rxfrmtooshrt; + uint32 rxinvmachdr; + uint32 rxbadfcs; + uint32 rxbadplcp; + uint32 rxcrsglitch; + uint32 rxstrt; + uint32 rxdfrmucastmbss; + uint32 rxmfrmucastmbss; + uint32 rxcfrmucast; + uint32 rxrtsucast; + uint32 rxctsucast; + uint32 rxackucast; + uint32 rxdfrmocast; + uint32 rxmfrmocast; + uint32 rxcfrmocast; + uint32 rxrtsocast; + uint32 rxctsocast; + uint32 rxdfrmmcast; + uint32 rxmfrmmcast; + uint32 rxcfrmmcast; + uint32 rxbeaconmbss; + uint32 rxdfrmucastobss; + uint32 rxbeaconobss; + uint32 rxrsptmout; + uint32 bcntxcancl; + uint32 rxf0ovfl; + uint32 rxf1ovfl; + uint32 rxf2ovfl; + uint32 txsfovfl; + uint32 pmqovfl; + uint32 rxcgprqfrm; + uint32 rxcgprsqovfl; + uint32 txcgprsfail; + uint32 txcgprssuc; + uint32 prs_timeout; + uint32 rxnack; + uint32 frmscons; + uint32 txnack; + uint32 txglitch_nack; + uint32 txburst; + + uint32 txfrag; + uint32 txmulti; + uint32 txfail; + uint32 txretry; + uint32 txretrie; + uint32 rxdup; + uint32 txrts; + uint32 txnocts; + uint32 txnoack; + uint32 rxfrag; + uint32 rxmulti; + uint32 rxcrc; + uint32 txfrmsnt; + uint32 rxundec; + + uint32 tkipmicfaill; + uint32 tkipcntrmsr; + uint32 tkipreplay; + uint32 ccmpfmterr; + uint32 ccmpreplay; + uint32 ccmpundec; + uint32 fourwayfail; + uint32 wepundec; + uint32 wepicverr; + uint32 decsuccess; + uint32 tkipicverr; + uint32 wepexcluded; + + uint32 txchanrej; + uint32 psmwds; + uint32 phywatchdog; + + uint32 prq_entries_handled; + uint32 prq_undirected_entries; + uint32 prq_bad_entries; + uint32 atim_suppress_count; + uint32 bcn_template_not_ready; + uint32 bcn_template_not_ready_done; + uint32 late_tbtt_dpc; + + uint32 rx1mbps; + uint32 rx2mbps; + uint32 rx5mbps5; + uint32 rx6mbps; + uint32 rx9mbps; + uint32 rx11mbps; + uint32 rx12mbps; + uint32 rx18mbps; + uint32 rx24mbps; + uint32 rx36mbps; + uint32 rx48mbps; + uint32 rx54mbps; + uint32 rx108mbps; + uint32 rx162mbps; + uint32 rx216mbps; + uint32 rx270mbps; + uint32 rx324mbps; + uint32 rx378mbps; + uint32 rx432mbps; + uint32 rx486mbps; + uint32 rx540mbps; + + uint32 pktengrxducast; + uint32 pktengrxdmcast; + + uint32 rfdisable; + uint32 bphy_rxcrsglitch; + uint32 bphy_badplcp; + + uint32 txexptime; + + uint32 txmpdu_sgi; + uint32 rxmpdu_sgi; + uint32 txmpdu_stbc; + uint32 rxmpdu_stbc; + + uint32 rxundec_mcst; + + uint32 tkipmicfaill_mcst; + uint32 tkipcntrmsr_mcst; + uint32 tkipreplay_mcst; + uint32 ccmpfmterr_mcst; + uint32 ccmpreplay_mcst; + uint32 ccmpundec_mcst; + uint32 fourwayfail_mcst; + uint32 wepundec_mcst; + uint32 wepicverr_mcst; + uint32 decsuccess_mcst; + uint32 tkipicverr_mcst; + uint32 wepexcluded_mcst; + + uint32 dma_hang; + uint32 reinit; + + uint32 pstatxucast; + uint32 pstatxnoassoc; + uint32 pstarxucast; + uint32 pstarxbcmc; + uint32 pstatxbcmc; + + uint32 cso_passthrough; + uint32 cso_normal; + uint32 chained; + uint32 chainedsz1; + uint32 unchained; + uint32 maxchainsz; + uint32 currchainsz; + uint32 pciereset; + uint32 cfgrestore; + uint32 reinitreason[NREINITREASONCOUNT]; +} wl_cnt_t; + +typedef struct { + bool auto_en; + uint8 active_ant; + uint32 rxcount; + int16 avg_snr_per_ant0; + int16 avg_snr_per_ant1; + uint32 swap_ge_rxcount0; + uint32 swap_ge_rxcount1; + uint32 swap_ge_snrthresh0; + uint32 swap_ge_snrthresh1; + uint32 swap_txfail0; + uint32 swap_txfail1; + uint32 swap_timer0; + uint32 swap_timer1; + uint32 swap_alivecheck0; + uint32 swap_alivecheck1; + uint32 rxcount_per_ant0; + uint32 rxcount_per_ant1; + uint32 acc_rxcount; + uint32 acc_rxcount_per_ant0; + uint32 acc_rxcount_per_ant1; +} wlc_swdiv_stats_t; + +#define WL_WME_CNT_VERSION 1 + +typedef struct { + uint32 packets; + uint32 bytes; +} wl_traffic_stats_t; + +typedef struct { + uint16 version; + uint16 length; + + wl_traffic_stats_t tx[AC_COUNT]; + wl_traffic_stats_t tx_failed[AC_COUNT]; + wl_traffic_stats_t rx[AC_COUNT]; + wl_traffic_stats_t rx_failed[AC_COUNT]; + + wl_traffic_stats_t forward[AC_COUNT]; + + wl_traffic_stats_t tx_expired[AC_COUNT]; + +} wl_wme_cnt_t; + +typedef struct wl_mkeep_alive_pkt { + uint16 version; + uint16 length; + uint32 period_msec; + uint16 len_bytes; + uint8 keep_alive_id; + uint8 data[1]; +} wl_mkeep_alive_pkt_t; + +#define WL_MKEEP_ALIVE_VERSION 1 +#define WL_MKEEP_ALIVE_FIXED_LEN OFFSETOF(wl_mkeep_alive_pkt_t, data) +#define WL_MKEEP_ALIVE_PRECISION 500 + +typedef struct wl_mtcpkeep_alive_conn_pkt { + struct ether_addr saddr; + struct ether_addr daddr; + struct ipv4_addr sipaddr; + struct ipv4_addr dipaddr; + uint16 sport; + uint16 dport; + uint32 seq; + uint32 ack; + uint16 tcpwin; +} wl_mtcpkeep_alive_conn_pkt_t; + +typedef struct wl_mtcpkeep_alive_timers_pkt { + uint16 interval; + uint16 retry_interval; + uint16 retry_count; +} wl_mtcpkeep_alive_timers_pkt_t; + +#ifndef ETHER_MAX_DATA +#define ETHER_MAX_DATA 1500 +#endif + +typedef struct wake_info { + uint32 wake_reason; + uint32 wake_info_len; + uchar packet[1]; +} wake_info_t; + +typedef struct wake_pkt { + uint32 wake_pkt_len; + uchar packet[1]; +} wake_pkt_t; + +#define WL_MTCPKEEP_ALIVE_VERSION 1 + +#ifdef WLBA + +#define WLC_BA_CNT_VERSION 1 + +typedef struct wlc_ba_cnt { + uint16 version; + uint16 length; + + uint32 txpdu; + uint32 txsdu; + uint32 txfc; + uint32 txfci; + uint32 txretrans; + uint32 txbatimer; + uint32 txdrop; + uint32 txaddbareq; + uint32 txaddbaresp; + uint32 txdelba; + uint32 txba; + uint32 txbar; + uint32 txpad[4]; + + uint32 rxpdu; + uint32 rxqed; + uint32 rxdup; + uint32 rxnobuf; + uint32 rxaddbareq; + uint32 rxaddbaresp; + uint32 rxdelba; + uint32 rxba; + uint32 rxbar; + uint32 rxinvba; + uint32 rxbaholes; + uint32 rxunexp; + uint32 rxpad[4]; +} wlc_ba_cnt_t; +#endif + +struct ampdu_tid_control { + uint8 tid; + uint8 enable; +}; + +struct ampdu_ea_tid { + struct ether_addr ea; + uint8 tid; +}; + +struct ampdu_retry_tid { + uint8 tid; + uint8 retry; +}; + +#define TOE_TX_CSUM_OL 0x00000001 +#define TOE_RX_CSUM_OL 0x00000002 + +#define TOE_ERRTEST_TX_CSUM 0x00000001 +#define TOE_ERRTEST_RX_CSUM 0x00000002 +#define TOE_ERRTEST_RX_CSUM2 0x00000004 + +struct toe_ol_stats_t { + + uint32 tx_summed; + + uint32 tx_iph_fill; + uint32 tx_tcp_fill; + uint32 tx_udp_fill; + uint32 tx_icmp_fill; + + uint32 rx_iph_good; + uint32 rx_iph_bad; + uint32 rx_tcp_good; + uint32 rx_tcp_bad; + uint32 rx_udp_good; + uint32 rx_udp_bad; + uint32 rx_icmp_good; + uint32 rx_icmp_bad; + + uint32 tx_tcp_errinj; + uint32 tx_udp_errinj; + uint32 tx_icmp_errinj; + + uint32 rx_tcp_errinj; + uint32 rx_udp_errinj; + uint32 rx_icmp_errinj; +}; + +#define ARP_OL_AGENT 0x00000001 +#define ARP_OL_SNOOP 0x00000002 +#define ARP_OL_HOST_AUTO_REPLY 0x00000004 +#define ARP_OL_PEER_AUTO_REPLY 0x00000008 + +#define ARP_ERRTEST_REPLY_PEER 0x1 +#define ARP_ERRTEST_REPLY_HOST 0x2 + +#define ARP_MULTIHOMING_MAX 8 +#define ND_MULTIHOMING_MAX 10 + +struct arp_ol_stats_t { + uint32 host_ip_entries; + uint32 host_ip_overflow; + + uint32 arp_table_entries; + uint32 arp_table_overflow; + + uint32 host_request; + uint32 host_reply; + uint32 host_service; + + uint32 peer_request; + uint32 peer_request_drop; + uint32 peer_reply; + uint32 peer_reply_drop; + uint32 peer_service; +}; + +struct nd_ol_stats_t { + uint32 host_ip_entries; + uint32 host_ip_overflow; + uint32 peer_request; + uint32 peer_request_drop; + uint32 peer_reply_drop; + uint32 peer_service; +}; + +typedef struct wl_keep_alive_pkt { + uint32 period_msec; + uint16 len_bytes; + uint8 data[1]; +} wl_keep_alive_pkt_t; + +#define WL_KEEP_ALIVE_FIXED_LEN OFFSETOF(wl_keep_alive_pkt_t, data) + +typedef enum wl_pkt_filter_type { + WL_PKT_FILTER_TYPE_PATTERN_MATCH +} wl_pkt_filter_type_t; + +#define WL_PKT_FILTER_TYPE wl_pkt_filter_type_t + +typedef struct wl_pkt_filter_pattern { + uint32 offset; + uint32 size_bytes; + uint8 mask_and_pattern[1]; + +} wl_pkt_filter_pattern_t; + +typedef struct wl_pkt_filter { + uint32 id; + uint32 type; + uint32 negate_match; + union { + wl_pkt_filter_pattern_t pattern; + } u; +} wl_pkt_filter_t; + +typedef struct wl_tcp_keep_set { + uint32 val1; + uint32 val2; +} wl_tcp_keep_set_t; + +#define WL_PKT_FILTER_FIXED_LEN OFFSETOF(wl_pkt_filter_t, u) +#define WL_PKT_FILTER_PATTERN_FIXED_LEN OFFSETOF(wl_pkt_filter_pattern_t, mask_and_pattern) + +typedef struct wl_pkt_filter_enable { + uint32 id; + uint32 enable; +} wl_pkt_filter_enable_t; + +typedef struct wl_pkt_filter_list { + uint32 num; + wl_pkt_filter_t filter[1]; +} wl_pkt_filter_list_t; + +#define WL_PKT_FILTER_LIST_FIXED_LEN OFFSETOF(wl_pkt_filter_list_t, filter) + +typedef struct wl_pkt_filter_stats { + uint32 num_pkts_matched; + uint32 num_pkts_forwarded; + uint32 num_pkts_discarded; +} wl_pkt_filter_stats_t; + +#define WL_WOWL_MAGIC (1 << 0) +#define WL_WOWL_NET (1 << 1) +#define WL_WOWL_DIS (1 << 2) +#define WL_WOWL_RETR (1 << 3) +#define WL_WOWL_BCN (1 << 4) +#define WL_WOWL_TST (1 << 5) +#define WL_WOWL_M1 (1 << 6) +#define WL_WOWL_EAPID (1 << 7) +#define WL_WOWL_PME_GPIO (1 << 8) +#define WL_WOWL_NEEDTKIP1 (1 << 9) +#define WL_WOWL_GTK_FAILURE (1 << 10) +#define WL_WOWL_EXTMAGPAT (1 << 11) +#define WL_WOWL_ARPOFFLOAD (1 << 12) +#define WL_WOWL_WPA2 (1 << 13) +#define WL_WOWL_KEYROT (1 << 14) +#define WL_WOWL_BCAST (1 << 15) +#define WL_WOWL_SCANOL (1 << 16) +#define WL_WOWL_TCPKEEP_TIME (1 << 17) +#define WL_WOWL_MDNS_CONFLICT (1 << 18) +#define WL_WOWL_MDNS_SERVICE (1 << 19) +#define WL_WOWL_TCPKEEP_DATA (1 << 20) +#define WL_WOWL_FW_HALT (1 << 21) +#define WL_WOWL_ENAB_HWRADIO (1 << 22) +#define WL_WOWL_MIC_FAIL (1 << 23) +#define WL_WOWL_LINKDOWN (1 << 31) + +#define MAGIC_PKT_MINLEN 102 +#define MAGIC_PKT_NUM_MAC_ADDRS 16 + +typedef enum { + wowl_pattern_type_bitmap = 0, + wowl_pattern_type_arp, + wowl_pattern_type_na +} wowl_pattern_type_t; + +typedef struct wl_wowl_pattern { + uint32 masksize; + uint32 offset; + uint32 patternoffset; + uint32 patternsize; + uint32 id; + uint32 reasonsize; + wowl_pattern_type_t type; + +} wl_wowl_pattern_t; + +typedef struct wl_wowl_pattern_list { + uint count; + wl_wowl_pattern_t pattern[1]; +} wl_wowl_pattern_list_t; + +typedef struct wl_wowl_wakeind { + uint8 pci_wakeind; + uint32 ucode_wakeind; +} wl_wowl_wakeind_t; + +typedef struct { + uint32 pktlen; + void *sdu; +} tcp_keepalive_wake_pkt_infop_t; + +typedef BWL_PRE_PACKED_STRUCT struct nbr_element { + uint8 id; + uint8 len; + struct ether_addr bssid; + uint32 bssid_info; + uint8 reg; + uint8 channel; + uint8 phytype; +} BWL_POST_PACKED_STRUCT nbr_element_t; + +#include + +#include + +#include + +typedef struct bcnreq { + uint8 bcn_mode; + int dur; + int channel; + struct ether_addr da; + uint16 random_int; + wlc_ssid_t ssid; + uint16 reps; +} bcnreq_t; + +typedef struct rrmreq { + struct ether_addr da; + uint8 reg; + uint8 chan; + uint16 random_int; + uint16 dur; + uint16 reps; +} rrmreq_t; + +typedef struct framereq { + struct ether_addr da; + uint8 reg; + uint8 chan; + uint16 random_int; + uint16 dur; + struct ether_addr ta; + uint16 reps; +} framereq_t; + +typedef struct statreq { + struct ether_addr da; + struct ether_addr peer; + uint16 random_int; + uint16 dur; + uint8 group_id; + uint16 reps; +} statreq_t; + +typedef struct wlc_l2keepalive_ol_params { + uint8 flags; + uint8 prio; + uint16 period_ms; +} wlc_l2keepalive_ol_params_t; + +typedef struct wlc_dwds_config { + uint32 mode; + struct ether_addr ea; +} wlc_dwds_config_t; + +#define WLC_KCK_LEN 16 +#define WLC_KEK_LEN 16 +#define WLC_REPLAY_CTR_LEN 8 + +typedef struct wlc_rekey_info { + uint32 offload_id; + uint8 kek[WLC_KEK_LEN]; + uint8 kck[WLC_KCK_LEN]; + uint8 replay_counter[WLC_REPLAY_CTR_LEN]; +} wlc_rekey_info_t; + +#endif diff --git a/drivers/custom/broadcom-wl/src/shared/bcmwifi/include/bcmwifi_channels.h b/drivers/custom/broadcom-wl/src/shared/bcmwifi/include/bcmwifi_channels.h new file mode 100644 index 000000000000..550c4fdc0785 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/shared/bcmwifi/include/bcmwifi_channels.h @@ -0,0 +1,353 @@ +/* + * Misc utility routines for WL and Apps + * This header file housing the define and function prototype use by + * both the wl driver, tools & Apps. + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * + * <> + * + * $Id: bcmwifi_channels.h 309193 2012-01-19 00:03:57Z $ + */ + +#ifndef _bcmwifi_channels_h_ +#define _bcmwifi_channels_h_ + +#if defined(__FreeBSD__) +#include +#endif + +typedef uint16 chanspec_t; + +#define CH_UPPER_SB 0x01 +#define CH_LOWER_SB 0x02 +#define CH_EWA_VALID 0x04 +#define CH_80MHZ_APART 16 +#define CH_40MHZ_APART 8 +#define CH_20MHZ_APART 4 +#define CH_10MHZ_APART 2 +#define CH_5MHZ_APART 1 +#define CH_MAX_2G_CHANNEL 14 +#define MAXCHANNEL 224 +#define MAXCHANNEL_NUM (MAXCHANNEL - 1) + +#define CH_NUM_VALID_RANGE(ch_num) ((ch_num) > 0 && (ch_num) <= MAXCHANNEL_NUM) + +#define CHSPEC_CTLOVLP(sp1, sp2, sep) (ABS(wf_chspec_ctlchan(sp1) - wf_chspec_ctlchan(sp2)) < \ + (sep)) + +#undef D11AC_IOTYPES +#define D11AC_IOTYPES + +#define WL_CHANSPEC_CHAN_MASK 0x00ff +#define WL_CHANSPEC_CHAN_SHIFT 0 +#define WL_CHANSPEC_CHAN1_MASK 0x000f +#define WL_CHANSPEC_CHAN1_SHIFT 0 +#define WL_CHANSPEC_CHAN2_MASK 0x00f0 +#define WL_CHANSPEC_CHAN2_SHIFT 4 + +#define WL_CHANSPEC_CTL_SB_MASK 0x0700 +#define WL_CHANSPEC_CTL_SB_SHIFT 8 +#define WL_CHANSPEC_CTL_SB_LLL 0x0000 +#define WL_CHANSPEC_CTL_SB_LLU 0x0100 +#define WL_CHANSPEC_CTL_SB_LUL 0x0200 +#define WL_CHANSPEC_CTL_SB_LUU 0x0300 +#define WL_CHANSPEC_CTL_SB_ULL 0x0400 +#define WL_CHANSPEC_CTL_SB_ULU 0x0500 +#define WL_CHANSPEC_CTL_SB_UUL 0x0600 +#define WL_CHANSPEC_CTL_SB_UUU 0x0700 +#define WL_CHANSPEC_CTL_SB_LL WL_CHANSPEC_CTL_SB_LLL +#define WL_CHANSPEC_CTL_SB_LU WL_CHANSPEC_CTL_SB_LLU +#define WL_CHANSPEC_CTL_SB_UL WL_CHANSPEC_CTL_SB_LUL +#define WL_CHANSPEC_CTL_SB_UU WL_CHANSPEC_CTL_SB_LUU +#define WL_CHANSPEC_CTL_SB_L WL_CHANSPEC_CTL_SB_LLL +#define WL_CHANSPEC_CTL_SB_U WL_CHANSPEC_CTL_SB_LLU +#define WL_CHANSPEC_CTL_SB_LOWER WL_CHANSPEC_CTL_SB_LLL +#define WL_CHANSPEC_CTL_SB_UPPER WL_CHANSPEC_CTL_SB_LLU +#define WL_CHANSPEC_CTL_SB_NONE WL_CHANSPEC_CTL_SB_LLL + +#define WL_CHANSPEC_BW_MASK 0x3800 +#define WL_CHANSPEC_BW_SHIFT 11 +#define WL_CHANSPEC_BW_5 0x0000 +#define WL_CHANSPEC_BW_10 0x0800 +#define WL_CHANSPEC_BW_20 0x1000 +#define WL_CHANSPEC_BW_40 0x1800 +#define WL_CHANSPEC_BW_80 0x2000 +#define WL_CHANSPEC_BW_160 0x2800 +#define WL_CHANSPEC_BW_8080 0x3000 +#define WL_CHANSPEC_BW_2P5 0x3800 + +#define WL_CHANSPEC_BAND_MASK 0xc000 +#define WL_CHANSPEC_BAND_SHIFT 14 +#define WL_CHANSPEC_BAND_2G 0x0000 +#define WL_CHANSPEC_BAND_3G 0x4000 +#define WL_CHANSPEC_BAND_4G 0x8000 +#define WL_CHANSPEC_BAND_5G 0xc000 +#define INVCHANSPEC 255 +#define MAX_CHANSPEC 0xFFFF + +#define LOWER_20_SB(channel) (((channel) > CH_10MHZ_APART) ? \ + ((channel) - CH_10MHZ_APART) : 0) +#define UPPER_20_SB(channel) (((channel) < (MAXCHANNEL - CH_10MHZ_APART)) ? \ + ((channel) + CH_10MHZ_APART) : 0) + +#define LL_20_SB(channel) (((channel) > 3 * CH_10MHZ_APART) ? ((channel) - 3 * CH_10MHZ_APART) : 0) +#define UU_20_SB(channel) (((channel) < (MAXCHANNEL - 3 * CH_10MHZ_APART)) ? \ + ((channel) + 3 * CH_10MHZ_APART) : 0) +#define LU_20_SB(channel) LOWER_20_SB(channel) +#define UL_20_SB(channel) UPPER_20_SB(channel) + +#define LOWER_40_SB(channel) ((channel) - CH_20MHZ_APART) +#define UPPER_40_SB(channel) ((channel) + CH_20MHZ_APART) +#define CHSPEC_WLCBANDUNIT(chspec) (CHSPEC_IS5G(chspec) ? BAND_5G_INDEX : BAND_2G_INDEX) +#define CH20MHZ_CHSPEC(channel) (chanspec_t)((chanspec_t)(channel) | WL_CHANSPEC_BW_20 | \ + (((channel) <= CH_MAX_2G_CHANNEL) ? \ + WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G)) +#define CH2P5MHZ_CHSPEC(channel) (chanspec_t)((chanspec_t)(channel) | WL_CHANSPEC_BW_2P5 | \ + (((channel) <= CH_MAX_2G_CHANNEL) ? \ + WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G)) +#define CH5MHZ_CHSPEC(channel) (chanspec_t)((chanspec_t)(channel) | WL_CHANSPEC_BW_5 | \ + (((channel) <= CH_MAX_2G_CHANNEL) ? \ + WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G)) +#define CH10MHZ_CHSPEC(channel) (chanspec_t)((chanspec_t)(channel) | WL_CHANSPEC_BW_10 | \ + (((channel) <= CH_MAX_2G_CHANNEL) ? \ + WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G)) +#define NEXT_20MHZ_CHAN(channel) (((channel) < (MAXCHANNEL - CH_20MHZ_APART)) ? \ + ((channel) + CH_20MHZ_APART) : 0) +#define CH40MHZ_CHSPEC(channel, ctlsb) (chanspec_t) \ + ((channel) | (ctlsb) | WL_CHANSPEC_BW_40 | \ + ((channel) <= CH_MAX_2G_CHANNEL ? WL_CHANSPEC_BAND_2G : \ + WL_CHANSPEC_BAND_5G)) +#define CH80MHZ_CHSPEC(channel, ctlsb) (chanspec_t) \ + ((channel) | (ctlsb) | \ + WL_CHANSPEC_BW_80 | WL_CHANSPEC_BAND_5G) +#define CH160MHZ_CHSPEC(channel, ctlsb) (chanspec_t) \ + ((channel) | (ctlsb) | \ + WL_CHANSPEC_BW_160 | WL_CHANSPEC_BAND_5G) +#define CHBW_CHSPEC(bw, channel) (chanspec_t)((chanspec_t)(channel) | (bw) | \ + (((channel) <= CH_MAX_2G_CHANNEL) ? \ + WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G)) + +#ifdef WL11AC_80P80 +#define CHSPEC_CHANNEL(chspec) wf_chspec_channel(chspec) +#else +#define CHSPEC_CHANNEL(chspec) ((uint8)((chspec) & WL_CHANSPEC_CHAN_MASK)) +#endif +#define CHSPEC_CHAN1(chspec) ((chspec) & WL_CHANSPEC_CHAN1_MASK) >> WL_CHANSPEC_CHAN1_SHIFT +#define CHSPEC_CHAN2(chspec) ((chspec) & WL_CHANSPEC_CHAN2_MASK) >> WL_CHANSPEC_CHAN2_SHIFT +#define CHSPEC_BAND(chspec) ((chspec) & WL_CHANSPEC_BAND_MASK) +#define CHSPEC_CTL_SB(chspec) ((chspec) & WL_CHANSPEC_CTL_SB_MASK) +#define CHSPEC_BW(chspec) ((chspec) & WL_CHANSPEC_BW_MASK) + +#define CHSPEC_IS2P5(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_2P5) +#define CHSPEC_IS5(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_5) +#define CHSPEC_IS10(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_10) +#define CHSPEC_IS20(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_20) +#ifndef CHSPEC_IS40 +#define CHSPEC_IS40(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_40) +#endif +#ifndef CHSPEC_IS80 +#define CHSPEC_IS80(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_80) +#endif +#ifndef CHSPEC_IS160 +#define CHSPEC_IS160(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_160) +#endif +#ifndef CHSPEC_IS8080 +#define CHSPEC_IS8080(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_8080) +#endif + +#ifdef WL11ULB + +#define BW_LE20(bw) (((bw) == WL_CHANSPEC_BW_2P5) || \ + ((bw) == WL_CHANSPEC_BW_5) || \ + ((bw) == WL_CHANSPEC_BW_10) || \ + ((bw) == WL_CHANSPEC_BW_20)) +#define CHSPEC_ISLE20(chspec) (CHSPEC_IS2P5(chspec) || CHSPEC_IS5(chspec) || \ + CHSPEC_IS10(chspec) || CHSPEC_IS20(chspec)) +#else +#define BW_LE20(bw) ((bw) == WL_CHANSPEC_BW_20) +#define CHSPEC_ISLE20(chspec) (CHSPEC_IS20(chspec)) +#endif + +#define BW_LE40(bw) (BW_LE20(bw) || ((bw) == WL_CHANSPEC_BW_40)) +#define BW_LE80(bw) (BW_LE40(bw) || ((bw) == WL_CHANSPEC_BW_80)) +#define BW_LE160(bw) (BW_LE80(bw) || ((bw) == WL_CHANSPEC_BW_160)) +#define CHSPEC_BW_LE20(chspec) (BW_LE20(CHSPEC_BW(chspec))) +#define CHSPEC_IS5G(chspec) (((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_5G) +#define CHSPEC_IS2G(chspec) (((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_2G) +#define CHSPEC_SB_UPPER(chspec) \ + ((((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_UPPER) && \ + (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_40)) +#define CHSPEC_SB_LOWER(chspec) \ + ((((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_LOWER) && \ + (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_40)) +#define CHSPEC2WLC_BAND(chspec) (CHSPEC_IS5G(chspec) ? WLC_BAND_5G : WLC_BAND_2G) + +#define CHANSPEC_STR_LEN 20 + +#define CHSPEC_IS_BW_160_WIDE(chspec) (CHSPEC_BW(chspec) == WL_CHANSPEC_BW_160 ||\ + CHSPEC_BW(chspec) == WL_CHANSPEC_BW_8080) + +#ifdef WL11ULB +#define CHSPEC_BW_GE(chspec, bw) \ + (((CHSPEC_IS_BW_160_WIDE(chspec) &&\ + ((bw) == WL_CHANSPEC_BW_160 || (bw) == WL_CHANSPEC_BW_8080)) ||\ + (CHSPEC_BW(chspec) >= (bw))) && \ + (!(CHSPEC_BW(chspec) == WL_CHANSPEC_BW_2P5 && (bw) != WL_CHANSPEC_BW_2P5))) +#else +#define CHSPEC_BW_GE(chspec, bw) \ + ((CHSPEC_IS_BW_160_WIDE(chspec) &&\ + ((bw) == WL_CHANSPEC_BW_160 || (bw) == WL_CHANSPEC_BW_8080)) ||\ + (CHSPEC_BW(chspec) >= (bw))) +#endif + +#ifdef WL11ULB +#define CHSPEC_BW_LE(chspec, bw) \ + (((CHSPEC_IS_BW_160_WIDE(chspec) &&\ + ((bw) == WL_CHANSPEC_BW_160 || (bw) == WL_CHANSPEC_BW_8080)) ||\ + (CHSPEC_BW(chspec) <= (bw))) || \ + (CHSPEC_BW(chspec) == WL_CHANSPEC_BW_2P5)) +#else +#define CHSPEC_BW_LE(chspec, bw) \ + ((CHSPEC_IS_BW_160_WIDE(chspec) &&\ + ((bw) == WL_CHANSPEC_BW_160 || (bw) == WL_CHANSPEC_BW_8080)) ||\ + (CHSPEC_BW(chspec) <= (bw))) +#endif + +#ifdef WL11ULB +#define CHSPEC_BW_GT(chspec, bw) \ + ((!(CHSPEC_IS_BW_160_WIDE(chspec) &&\ + ((bw) == WL_CHANSPEC_BW_160 || (bw) == WL_CHANSPEC_BW_8080)) &&\ + (CHSPEC_BW(chspec) > (bw))) && \ + (CHSPEC_BW(chspec) != WL_CHANSPEC_BW_2P5)) +#else +#define CHSPEC_BW_GT(chspec, bw) \ + (!(CHSPEC_IS_BW_160_WIDE(chspec) &&\ + ((bw) == WL_CHANSPEC_BW_160 || (bw) == WL_CHANSPEC_BW_8080)) &&\ + (CHSPEC_BW(chspec) > (bw))) +#endif + +#ifdef WL11ULB +#define CHSPEC_BW_LT(chspec, bw) \ + ((!(CHSPEC_IS_BW_160_WIDE(chspec) &&\ + ((bw) == WL_CHANSPEC_BW_160 || (bw) == WL_CHANSPEC_BW_8080)) &&\ + (CHSPEC_BW(chspec) < (bw))) || \ + ((CHSPEC_BW(chspec) == WL_CHANSPEC_BW_2P5 && (bw) != WL_CHANSPEC_BW_2P5))) +#else +#define CHSPEC_BW_LT(chspec, bw) \ + (!(CHSPEC_IS_BW_160_WIDE(chspec) &&\ + ((bw) == WL_CHANSPEC_BW_160 || (bw) == WL_CHANSPEC_BW_8080)) &&\ + (CHSPEC_BW(chspec) < (bw))) +#endif + +#define WL_LCHANSPEC_CHAN_MASK 0x00ff +#define WL_LCHANSPEC_CHAN_SHIFT 0 + +#define WL_LCHANSPEC_CTL_SB_MASK 0x0300 +#define WL_LCHANSPEC_CTL_SB_SHIFT 8 +#define WL_LCHANSPEC_CTL_SB_LOWER 0x0100 +#define WL_LCHANSPEC_CTL_SB_UPPER 0x0200 +#define WL_LCHANSPEC_CTL_SB_NONE 0x0300 + +#define WL_LCHANSPEC_BW_MASK 0x0C00 +#define WL_LCHANSPEC_BW_SHIFT 10 +#define WL_LCHANSPEC_BW_10 0x0400 +#define WL_LCHANSPEC_BW_20 0x0800 +#define WL_LCHANSPEC_BW_40 0x0C00 + +#define WL_LCHANSPEC_BAND_MASK 0xf000 +#define WL_LCHANSPEC_BAND_SHIFT 12 +#define WL_LCHANSPEC_BAND_5G 0x1000 +#define WL_LCHANSPEC_BAND_2G 0x2000 + +#define LCHSPEC_CHANNEL(chspec) ((uint8)((chspec) & WL_LCHANSPEC_CHAN_MASK)) +#define LCHSPEC_BAND(chspec) ((chspec) & WL_LCHANSPEC_BAND_MASK) +#define LCHSPEC_CTL_SB(chspec) ((chspec) & WL_LCHANSPEC_CTL_SB_MASK) +#define LCHSPEC_BW(chspec) ((chspec) & WL_LCHANSPEC_BW_MASK) +#define LCHSPEC_IS10(chspec) (((chspec) & WL_LCHANSPEC_BW_MASK) == WL_LCHANSPEC_BW_10) +#define LCHSPEC_IS20(chspec) (((chspec) & WL_LCHANSPEC_BW_MASK) == WL_LCHANSPEC_BW_20) +#define LCHSPEC_IS40(chspec) (((chspec) & WL_LCHANSPEC_BW_MASK) == WL_LCHANSPEC_BW_40) +#define LCHSPEC_IS5G(chspec) (((chspec) & WL_LCHANSPEC_BAND_MASK) == WL_LCHANSPEC_BAND_5G) +#define LCHSPEC_IS2G(chspec) (((chspec) & WL_LCHANSPEC_BAND_MASK) == WL_LCHANSPEC_BAND_2G) + +#define LCHSPEC_SB_UPPER(chspec) \ + ((((chspec) & WL_LCHANSPEC_CTL_SB_MASK) == WL_LCHANSPEC_CTL_SB_UPPER) && \ + (((chspec) & WL_LCHANSPEC_BW_MASK) == WL_LCHANSPEC_BW_40)) +#define LCHSPEC_SB_LOWER(chspec) \ + ((((chspec) & WL_LCHANSPEC_CTL_SB_MASK) == WL_LCHANSPEC_CTL_SB_LOWER) && \ + (((chspec) & WL_LCHANSPEC_BW_MASK) == WL_LCHANSPEC_BW_40)) + +#define LCHSPEC_CREATE(chan, band, bw, sb) ((uint16)((chan) | (sb) | (bw) | (band))) + +#define CH20MHZ_LCHSPEC(channel) \ + (chanspec_t)((chanspec_t)(channel) | WL_LCHANSPEC_BW_20 | \ + WL_LCHANSPEC_CTL_SB_NONE | (((channel) <= CH_MAX_2G_CHANNEL) ? \ + WL_LCHANSPEC_BAND_2G : WL_LCHANSPEC_BAND_5G)) + +#define WF_CHAN_FACTOR_2_4_G 4814 + +#define WF_CHAN_FACTOR_5_G 10000 + +#define WF_CHAN_FACTOR_4_G 8000 + +#define WLC_2G_25MHZ_OFFSET 5 + +#define WF_NUM_SIDEBANDS_40MHZ 2 +#define WF_NUM_SIDEBANDS_80MHZ 4 +#define WF_NUM_SIDEBANDS_8080MHZ 4 +#define WF_NUM_SIDEBANDS_160MHZ 8 + +extern char * wf_chspec_ntoa_ex(chanspec_t chspec, char *buf); + +extern char * wf_chspec_ntoa(chanspec_t chspec, char *buf); + +extern chanspec_t wf_chspec_aton(const char *a); + +extern bool wf_chspec_malformed(chanspec_t chanspec); + +extern bool wf_chspec_valid(chanspec_t chanspec); + +extern uint8 wf_chspec_ctlchan(chanspec_t chspec); + +extern chanspec_t wf_chspec_ctlchspec(chanspec_t chspec); + +extern chanspec_t wf_chspec_primary40_chspec(chanspec_t chspec); + +extern int wf_mhz2channel(uint freq, uint start_factor); + +extern int wf_channel2mhz(uint channel, uint start_factor); + +extern chanspec_t wf_chspec_80(uint8 center_channel, uint8 primary_channel); + +extern uint16 wf_channel2chspec(uint ctl_ch, uint bw); + +extern uint wf_channel2freq(uint channel); +extern uint wf_freq2channel(uint freq); + +extern chanspec_t wf_chspec_get8080_chspec(uint8 primary_20mhz, + uint8 chan0_80Mhz, uint8 chan1_80Mhz); + +extern uint8 wf_chspec_primary80_channel(chanspec_t chanspec); + +extern uint8 wf_chspec_secondary80_channel(chanspec_t chanspec); + +extern chanspec_t wf_chspec_primary80_chspec(chanspec_t chspec); + +#ifdef WL11AC_80P80 + +extern uint8 wf_chspec_channel(chanspec_t chspec); +#endif +#endif diff --git a/drivers/custom/broadcom-wl/src/shared/bcmwifi/include/bcmwifi_rates.h b/drivers/custom/broadcom-wl/src/shared/bcmwifi/include/bcmwifi_rates.h new file mode 100644 index 000000000000..a2b800961965 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/shared/bcmwifi/include/bcmwifi_rates.h @@ -0,0 +1,705 @@ +/* + * Indices for 802.11 a/b/g/n/ac 1-3 chain symmetric transmit rates + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * + * <> + * + * $Id: bcmwifi_rates.h 5187 2012-06-29 06:17:50Z $ + */ + +#ifndef _bcmwifi_rates_h_ +#define _bcmwifi_rates_h_ + +#define WL_RATESET_SZ_DSSS 4 +#define WL_RATESET_SZ_OFDM 8 +#define WL_RATESET_SZ_VHT_MCS 10 +#define WL_RATESET_SZ_VHT_MCS_P 12 + +#if defined(WLPROPRIETARY_11N_RATES) +#define WL_RATESET_SZ_HT_MCS WL_RATESET_SZ_VHT_MCS +#else +#define WL_RATESET_SZ_HT_MCS 8 +#endif + +#define WL_RATESET_SZ_HT_IOCTL 8 + +#define WL_TX_CHAINS_MAX 4 + +#define WL_RATE_DISABLED (-128) + +typedef enum wl_tx_bw { + WL_TX_BW_20, + WL_TX_BW_40, + WL_TX_BW_80, + WL_TX_BW_20IN40, + WL_TX_BW_20IN80, + WL_TX_BW_40IN80, + WL_TX_BW_160, + WL_TX_BW_20IN160, + WL_TX_BW_40IN160, + WL_TX_BW_80IN160, + WL_TX_BW_ALL, + WL_TX_BW_8080, + WL_TX_BW_8080CHAN2, + WL_TX_BW_20IN8080, + WL_TX_BW_40IN8080, + WL_TX_BW_80IN8080, + WL_TX_BW_2P5, + WL_TX_BW_5, + WL_TX_BW_10 +} wl_tx_bw_t; + +typedef enum wl_tx_mode { + WL_TX_MODE_NONE, + WL_TX_MODE_STBC, + WL_TX_MODE_CDD, + WL_TX_MODE_TXBF, + WL_NUM_TX_MODES +} wl_tx_mode_t; + +typedef enum wl_tx_chains { + WL_TX_CHAINS_1 = 1, + WL_TX_CHAINS_2, + WL_TX_CHAINS_3, + WL_TX_CHAINS_4 +} wl_tx_chains_t; + +typedef enum wl_tx_nss { + WL_TX_NSS_1 = 1, + WL_TX_NSS_2, + WL_TX_NSS_3, + WL_TX_NSS_4 +} wl_tx_nss_t; + +typedef enum clm_rates { + + WL_RATE_1X1_DSSS_1 = 0, + WL_RATE_1X1_DSSS_2 = 1, + WL_RATE_1X1_DSSS_5_5 = 2, + WL_RATE_1X1_DSSS_11 = 3, + + WL_RATE_1X1_OFDM_6 = 4, + WL_RATE_1X1_OFDM_9 = 5, + WL_RATE_1X1_OFDM_12 = 6, + WL_RATE_1X1_OFDM_18 = 7, + WL_RATE_1X1_OFDM_24 = 8, + WL_RATE_1X1_OFDM_36 = 9, + WL_RATE_1X1_OFDM_48 = 10, + WL_RATE_1X1_OFDM_54 = 11, + + WL_RATE_1X1_MCS0 = 12, + WL_RATE_1X1_MCS1 = 13, + WL_RATE_1X1_MCS2 = 14, + WL_RATE_1X1_MCS3 = 15, + WL_RATE_1X1_MCS4 = 16, + WL_RATE_1X1_MCS5 = 17, + WL_RATE_1X1_MCS6 = 18, + WL_RATE_1X1_MCS7 = 19, + WL_RATE_P_1X1_MCS87 = 20, + WL_RATE_P_1X1_MCS88 = 21, + + WL_RATE_1X1_VHT0SS1 = 12, + WL_RATE_1X1_VHT1SS1 = 13, + WL_RATE_1X1_VHT2SS1 = 14, + WL_RATE_1X1_VHT3SS1 = 15, + WL_RATE_1X1_VHT4SS1 = 16, + WL_RATE_1X1_VHT5SS1 = 17, + WL_RATE_1X1_VHT6SS1 = 18, + WL_RATE_1X1_VHT7SS1 = 19, + WL_RATE_1X1_VHT8SS1 = 20, + WL_RATE_1X1_VHT9SS1 = 21, + WL_RATE_P_1X1_VHT10SS1 = 22, + WL_RATE_P_1X1_VHT11SS1 = 23, + + WL_RATE_1X2_DSSS_1 = 24, + WL_RATE_1X2_DSSS_2 = 25, + WL_RATE_1X2_DSSS_5_5 = 26, + WL_RATE_1X2_DSSS_11 = 27, + + WL_RATE_1X2_CDD_OFDM_6 = 28, + WL_RATE_1X2_CDD_OFDM_9 = 29, + WL_RATE_1X2_CDD_OFDM_12 = 30, + WL_RATE_1X2_CDD_OFDM_18 = 31, + WL_RATE_1X2_CDD_OFDM_24 = 32, + WL_RATE_1X2_CDD_OFDM_36 = 33, + WL_RATE_1X2_CDD_OFDM_48 = 34, + WL_RATE_1X2_CDD_OFDM_54 = 35, + + WL_RATE_1X2_CDD_MCS0 = 36, + WL_RATE_1X2_CDD_MCS1 = 37, + WL_RATE_1X2_CDD_MCS2 = 38, + WL_RATE_1X2_CDD_MCS3 = 39, + WL_RATE_1X2_CDD_MCS4 = 40, + WL_RATE_1X2_CDD_MCS5 = 41, + WL_RATE_1X2_CDD_MCS6 = 42, + WL_RATE_1X2_CDD_MCS7 = 43, + WL_RATE_P_1X2_CDD_MCS87 = 44, + WL_RATE_P_1X2_CDD_MCS88 = 45, + + WL_RATE_1X2_VHT0SS1 = 36, + WL_RATE_1X2_VHT1SS1 = 37, + WL_RATE_1X2_VHT2SS1 = 38, + WL_RATE_1X2_VHT3SS1 = 39, + WL_RATE_1X2_VHT4SS1 = 40, + WL_RATE_1X2_VHT5SS1 = 41, + WL_RATE_1X2_VHT6SS1 = 42, + WL_RATE_1X2_VHT7SS1 = 43, + WL_RATE_1X2_VHT8SS1 = 44, + WL_RATE_1X2_VHT9SS1 = 45, + WL_RATE_P_1X2_VHT10SS1 = 46, + WL_RATE_P_1X2_VHT11SS1 = 47, + + WL_RATE_2X2_STBC_MCS0 = 48, + WL_RATE_2X2_STBC_MCS1 = 49, + WL_RATE_2X2_STBC_MCS2 = 50, + WL_RATE_2X2_STBC_MCS3 = 51, + WL_RATE_2X2_STBC_MCS4 = 52, + WL_RATE_2X2_STBC_MCS5 = 53, + WL_RATE_2X2_STBC_MCS6 = 54, + WL_RATE_2X2_STBC_MCS7 = 55, + WL_RATE_P_2X2_STBC_MCS87 = 56, + WL_RATE_P_2X2_STBC_MCS88 = 57, + + WL_RATE_2X2_STBC_VHT0SS1 = 48, + WL_RATE_2X2_STBC_VHT1SS1 = 49, + WL_RATE_2X2_STBC_VHT2SS1 = 50, + WL_RATE_2X2_STBC_VHT3SS1 = 51, + WL_RATE_2X2_STBC_VHT4SS1 = 52, + WL_RATE_2X2_STBC_VHT5SS1 = 53, + WL_RATE_2X2_STBC_VHT6SS1 = 54, + WL_RATE_2X2_STBC_VHT7SS1 = 55, + WL_RATE_2X2_STBC_VHT8SS1 = 56, + WL_RATE_2X2_STBC_VHT9SS1 = 57, + WL_RATE_P_2X2_STBC_VHT10SS1 = 58, + WL_RATE_P_2X2_STBC_VHT11SS1 = 59, + + WL_RATE_2X2_SDM_MCS8 = 60, + WL_RATE_2X2_SDM_MCS9 = 61, + WL_RATE_2X2_SDM_MCS10 = 62, + WL_RATE_2X2_SDM_MCS11 = 63, + WL_RATE_2X2_SDM_MCS12 = 64, + WL_RATE_2X2_SDM_MCS13 = 65, + WL_RATE_2X2_SDM_MCS14 = 66, + WL_RATE_2X2_SDM_MCS15 = 67, + WL_RATE_P_2X2_SDM_MCS99 = 68, + WL_RATE_P_2X2_SDM_MCS100 = 69, + + WL_RATE_2X2_VHT0SS2 = 60, + WL_RATE_2X2_VHT1SS2 = 61, + WL_RATE_2X2_VHT2SS2 = 62, + WL_RATE_2X2_VHT3SS2 = 63, + WL_RATE_2X2_VHT4SS2 = 64, + WL_RATE_2X2_VHT5SS2 = 65, + WL_RATE_2X2_VHT6SS2 = 66, + WL_RATE_2X2_VHT7SS2 = 67, + WL_RATE_2X2_VHT8SS2 = 68, + WL_RATE_2X2_VHT9SS2 = 69, + WL_RATE_P_2X2_VHT10SS2 = 70, + WL_RATE_P_2X2_VHT11SS2 = 71, + + WL_RATE_1X2_TXBF_OFDM_6 = 72, + WL_RATE_1X2_TXBF_OFDM_9 = 73, + WL_RATE_1X2_TXBF_OFDM_12 = 74, + WL_RATE_1X2_TXBF_OFDM_18 = 75, + WL_RATE_1X2_TXBF_OFDM_24 = 76, + WL_RATE_1X2_TXBF_OFDM_36 = 77, + WL_RATE_1X2_TXBF_OFDM_48 = 78, + WL_RATE_1X2_TXBF_OFDM_54 = 79, + + WL_RATE_1X2_TXBF_MCS0 = 80, + WL_RATE_1X2_TXBF_MCS1 = 81, + WL_RATE_1X2_TXBF_MCS2 = 82, + WL_RATE_1X2_TXBF_MCS3 = 83, + WL_RATE_1X2_TXBF_MCS4 = 84, + WL_RATE_1X2_TXBF_MCS5 = 85, + WL_RATE_1X2_TXBF_MCS6 = 86, + WL_RATE_1X2_TXBF_MCS7 = 87, + WL_RATE_P_1X2_TXBF_MCS87 = 88, + WL_RATE_P_1X2_TXBF_MCS88 = 89, + + WL_RATE_1X2_TXBF_VHT0SS1 = 80, + WL_RATE_1X2_TXBF_VHT1SS1 = 81, + WL_RATE_1X2_TXBF_VHT2SS1 = 82, + WL_RATE_1X2_TXBF_VHT3SS1 = 83, + WL_RATE_1X2_TXBF_VHT4SS1 = 84, + WL_RATE_1X2_TXBF_VHT5SS1 = 85, + WL_RATE_1X2_TXBF_VHT6SS1 = 86, + WL_RATE_1X2_TXBF_VHT7SS1 = 87, + WL_RATE_1X2_TXBF_VHT8SS1 = 88, + WL_RATE_1X2_TXBF_VHT9SS1 = 89, + WL_RATE_P_1X2_TXBF_VHT10SS1 = 90, + WL_RATE_P_1X2_TXBF_VHT11SS1 = 91, + + WL_RATE_2X2_TXBF_SDM_MCS8 = 92, + WL_RATE_2X2_TXBF_SDM_MCS9 = 93, + WL_RATE_2X2_TXBF_SDM_MCS10 = 94, + WL_RATE_2X2_TXBF_SDM_MCS11 = 95, + WL_RATE_2X2_TXBF_SDM_MCS12 = 96, + WL_RATE_2X2_TXBF_SDM_MCS13 = 97, + WL_RATE_2X2_TXBF_SDM_MCS14 = 98, + WL_RATE_2X2_TXBF_SDM_MCS15 = 99, + WL_RATE_P_2X2_TXBF_SDM_MCS99 = 100, + WL_RATE_P_2X2_TXBF_SDM_MCS100 = 101, + + WL_RATE_2X2_TXBF_VHT0SS2 = 92, + WL_RATE_2X2_TXBF_VHT1SS2 = 93, + WL_RATE_2X2_TXBF_VHT2SS2 = 94, + WL_RATE_2X2_TXBF_VHT3SS2 = 95, + WL_RATE_2X2_TXBF_VHT4SS2 = 96, + WL_RATE_2X2_TXBF_VHT5SS2 = 97, + WL_RATE_2X2_TXBF_VHT6SS2 = 98, + WL_RATE_2X2_TXBF_VHT7SS2 = 99, + WL_RATE_2X2_TXBF_VHT8SS2 = 100, + WL_RATE_2X2_TXBF_VHT9SS2 = 101, + WL_RATE_P_2X2_TXBF_VHT10SS2 = 102, + WL_RATE_P_2X2_TXBF_VHT11SS2 = 103, + + WL_RATE_1X3_DSSS_1 = 104, + WL_RATE_1X3_DSSS_2 = 105, + WL_RATE_1X3_DSSS_5_5 = 106, + WL_RATE_1X3_DSSS_11 = 107, + + WL_RATE_1X3_CDD_OFDM_6 = 108, + WL_RATE_1X3_CDD_OFDM_9 = 109, + WL_RATE_1X3_CDD_OFDM_12 = 110, + WL_RATE_1X3_CDD_OFDM_18 = 111, + WL_RATE_1X3_CDD_OFDM_24 = 112, + WL_RATE_1X3_CDD_OFDM_36 = 113, + WL_RATE_1X3_CDD_OFDM_48 = 114, + WL_RATE_1X3_CDD_OFDM_54 = 115, + + WL_RATE_1X3_CDD_MCS0 = 116, + WL_RATE_1X3_CDD_MCS1 = 117, + WL_RATE_1X3_CDD_MCS2 = 118, + WL_RATE_1X3_CDD_MCS3 = 119, + WL_RATE_1X3_CDD_MCS4 = 120, + WL_RATE_1X3_CDD_MCS5 = 121, + WL_RATE_1X3_CDD_MCS6 = 122, + WL_RATE_1X3_CDD_MCS7 = 123, + WL_RATE_P_1X3_CDD_MCS87 = 124, + WL_RATE_P_1X3_CDD_MCS88 = 125, + + WL_RATE_1X3_VHT0SS1 = 116, + WL_RATE_1X3_VHT1SS1 = 117, + WL_RATE_1X3_VHT2SS1 = 118, + WL_RATE_1X3_VHT3SS1 = 119, + WL_RATE_1X3_VHT4SS1 = 120, + WL_RATE_1X3_VHT5SS1 = 121, + WL_RATE_1X3_VHT6SS1 = 122, + WL_RATE_1X3_VHT7SS1 = 123, + WL_RATE_1X3_VHT8SS1 = 124, + WL_RATE_1X3_VHT9SS1 = 125, + WL_RATE_P_1X3_VHT10SS1 = 126, + WL_RATE_P_1X3_VHT11SS1 = 127, + + WL_RATE_2X3_STBC_MCS0 = 128, + WL_RATE_2X3_STBC_MCS1 = 129, + WL_RATE_2X3_STBC_MCS2 = 130, + WL_RATE_2X3_STBC_MCS3 = 131, + WL_RATE_2X3_STBC_MCS4 = 132, + WL_RATE_2X3_STBC_MCS5 = 133, + WL_RATE_2X3_STBC_MCS6 = 134, + WL_RATE_2X3_STBC_MCS7 = 135, + WL_RATE_P_2X3_STBC_MCS87 = 136, + WL_RATE_P_2X3_STBC_MCS88 = 137, + + WL_RATE_2X3_STBC_VHT0SS1 = 128, + WL_RATE_2X3_STBC_VHT1SS1 = 129, + WL_RATE_2X3_STBC_VHT2SS1 = 130, + WL_RATE_2X3_STBC_VHT3SS1 = 131, + WL_RATE_2X3_STBC_VHT4SS1 = 132, + WL_RATE_2X3_STBC_VHT5SS1 = 133, + WL_RATE_2X3_STBC_VHT6SS1 = 134, + WL_RATE_2X3_STBC_VHT7SS1 = 135, + WL_RATE_2X3_STBC_VHT8SS1 = 136, + WL_RATE_2X3_STBC_VHT9SS1 = 137, + WL_RATE_P_2X3_STBC_VHT10SS1 = 138, + WL_RATE_P_2X3_STBC_VHT11SS1 = 139, + + WL_RATE_2X3_SDM_MCS8 = 140, + WL_RATE_2X3_SDM_MCS9 = 141, + WL_RATE_2X3_SDM_MCS10 = 142, + WL_RATE_2X3_SDM_MCS11 = 143, + WL_RATE_2X3_SDM_MCS12 = 144, + WL_RATE_2X3_SDM_MCS13 = 145, + WL_RATE_2X3_SDM_MCS14 = 146, + WL_RATE_2X3_SDM_MCS15 = 147, + WL_RATE_P_2X3_SDM_MCS99 = 148, + WL_RATE_P_2X3_SDM_MCS100 = 149, + + WL_RATE_2X3_VHT0SS2 = 140, + WL_RATE_2X3_VHT1SS2 = 141, + WL_RATE_2X3_VHT2SS2 = 142, + WL_RATE_2X3_VHT3SS2 = 143, + WL_RATE_2X3_VHT4SS2 = 144, + WL_RATE_2X3_VHT5SS2 = 145, + WL_RATE_2X3_VHT6SS2 = 146, + WL_RATE_2X3_VHT7SS2 = 147, + WL_RATE_2X3_VHT8SS2 = 148, + WL_RATE_2X3_VHT9SS2 = 149, + WL_RATE_P_2X3_VHT10SS2 = 150, + WL_RATE_P_2X3_VHT11SS2 = 151, + + WL_RATE_3X3_SDM_MCS16 = 152, + WL_RATE_3X3_SDM_MCS17 = 153, + WL_RATE_3X3_SDM_MCS18 = 154, + WL_RATE_3X3_SDM_MCS19 = 155, + WL_RATE_3X3_SDM_MCS20 = 156, + WL_RATE_3X3_SDM_MCS21 = 157, + WL_RATE_3X3_SDM_MCS22 = 158, + WL_RATE_3X3_SDM_MCS23 = 159, + WL_RATE_P_3X3_SDM_MCS101 = 160, + WL_RATE_P_3X3_SDM_MCS102 = 161, + + WL_RATE_3X3_VHT0SS3 = 152, + WL_RATE_3X3_VHT1SS3 = 153, + WL_RATE_3X3_VHT2SS3 = 154, + WL_RATE_3X3_VHT3SS3 = 155, + WL_RATE_3X3_VHT4SS3 = 156, + WL_RATE_3X3_VHT5SS3 = 157, + WL_RATE_3X3_VHT6SS3 = 158, + WL_RATE_3X3_VHT7SS3 = 159, + WL_RATE_3X3_VHT8SS3 = 160, + WL_RATE_3X3_VHT9SS3 = 161, + WL_RATE_P_3X3_VHT10SS3 = 162, + WL_RATE_P_3X3_VHT11SS3 = 163, + + WL_RATE_1X3_TXBF_OFDM_6 = 164, + WL_RATE_1X3_TXBF_OFDM_9 = 165, + WL_RATE_1X3_TXBF_OFDM_12 = 166, + WL_RATE_1X3_TXBF_OFDM_18 = 167, + WL_RATE_1X3_TXBF_OFDM_24 = 168, + WL_RATE_1X3_TXBF_OFDM_36 = 169, + WL_RATE_1X3_TXBF_OFDM_48 = 170, + WL_RATE_1X3_TXBF_OFDM_54 = 171, + + WL_RATE_1X3_TXBF_MCS0 = 172, + WL_RATE_1X3_TXBF_MCS1 = 173, + WL_RATE_1X3_TXBF_MCS2 = 174, + WL_RATE_1X3_TXBF_MCS3 = 175, + WL_RATE_1X3_TXBF_MCS4 = 176, + WL_RATE_1X3_TXBF_MCS5 = 177, + WL_RATE_1X3_TXBF_MCS6 = 178, + WL_RATE_1X3_TXBF_MCS7 = 179, + WL_RATE_P_1X3_TXBF_MCS87 = 180, + WL_RATE_P_1X3_TXBF_MCS88 = 181, + + WL_RATE_1X3_TXBF_VHT0SS1 = 172, + WL_RATE_1X3_TXBF_VHT1SS1 = 173, + WL_RATE_1X3_TXBF_VHT2SS1 = 174, + WL_RATE_1X3_TXBF_VHT3SS1 = 175, + WL_RATE_1X3_TXBF_VHT4SS1 = 176, + WL_RATE_1X3_TXBF_VHT5SS1 = 177, + WL_RATE_1X3_TXBF_VHT6SS1 = 178, + WL_RATE_1X3_TXBF_VHT7SS1 = 179, + WL_RATE_1X3_TXBF_VHT8SS1 = 180, + WL_RATE_1X3_TXBF_VHT9SS1 = 181, + WL_RATE_P_1X3_TXBF_VHT10SS1 = 182, + WL_RATE_P_1X3_TXBF_VHT11SS1 = 183, + + WL_RATE_2X3_TXBF_SDM_MCS8 = 184, + WL_RATE_2X3_TXBF_SDM_MCS9 = 185, + WL_RATE_2X3_TXBF_SDM_MCS10 = 186, + WL_RATE_2X3_TXBF_SDM_MCS11 = 187, + WL_RATE_2X3_TXBF_SDM_MCS12 = 188, + WL_RATE_2X3_TXBF_SDM_MCS13 = 189, + WL_RATE_2X3_TXBF_SDM_MCS14 = 190, + WL_RATE_2X3_TXBF_SDM_MCS15 = 191, + WL_RATE_P_2X3_TXBF_SDM_MCS99 = 192, + WL_RATE_P_2X3_TXBF_SDM_MCS100 = 193, + + WL_RATE_2X3_TXBF_VHT0SS2 = 184, + WL_RATE_2X3_TXBF_VHT1SS2 = 185, + WL_RATE_2X3_TXBF_VHT2SS2 = 186, + WL_RATE_2X3_TXBF_VHT3SS2 = 187, + WL_RATE_2X3_TXBF_VHT4SS2 = 188, + WL_RATE_2X3_TXBF_VHT5SS2 = 189, + WL_RATE_2X3_TXBF_VHT6SS2 = 190, + WL_RATE_2X3_TXBF_VHT7SS2 = 191, + WL_RATE_2X3_TXBF_VHT8SS2 = 192, + WL_RATE_2X3_TXBF_VHT9SS2 = 193, + WL_RATE_P_2X3_TXBF_VHT10SS2 = 194, + WL_RATE_P_2X3_TXBF_VHT11SS2 = 195, + + WL_RATE_3X3_TXBF_SDM_MCS16 = 196, + WL_RATE_3X3_TXBF_SDM_MCS17 = 197, + WL_RATE_3X3_TXBF_SDM_MCS18 = 198, + WL_RATE_3X3_TXBF_SDM_MCS19 = 199, + WL_RATE_3X3_TXBF_SDM_MCS20 = 200, + WL_RATE_3X3_TXBF_SDM_MCS21 = 201, + WL_RATE_3X3_TXBF_SDM_MCS22 = 202, + WL_RATE_3X3_TXBF_SDM_MCS23 = 203, + WL_RATE_P_3X3_TXBF_SDM_MCS101 = 204, + WL_RATE_P_3X3_TXBF_SDM_MCS102 = 205, + + WL_RATE_3X3_TXBF_VHT0SS3 = 196, + WL_RATE_3X3_TXBF_VHT1SS3 = 197, + WL_RATE_3X3_TXBF_VHT2SS3 = 198, + WL_RATE_3X3_TXBF_VHT3SS3 = 199, + WL_RATE_3X3_TXBF_VHT4SS3 = 200, + WL_RATE_3X3_TXBF_VHT5SS3 = 201, + WL_RATE_3X3_TXBF_VHT6SS3 = 202, + WL_RATE_3X3_TXBF_VHT7SS3 = 203, + WL_RATE_3X3_TXBF_VHT8SS3 = 204, + WL_RATE_3X3_TXBF_VHT9SS3 = 205, + WL_RATE_P_3X3_TXBF_VHT10SS3 = 206, + WL_RATE_P_3X3_TXBF_VHT11SS3 = 207, + + WL_RATE_1X4_DSSS_1 = 208, + WL_RATE_1X4_DSSS_2 = 209, + WL_RATE_1X4_DSSS_5_5 = 210, + WL_RATE_1X4_DSSS_11 = 211, + + WL_RATE_1X4_CDD_OFDM_6 = 212, + WL_RATE_1X4_CDD_OFDM_9 = 213, + WL_RATE_1X4_CDD_OFDM_12 = 214, + WL_RATE_1X4_CDD_OFDM_18 = 215, + WL_RATE_1X4_CDD_OFDM_24 = 216, + WL_RATE_1X4_CDD_OFDM_36 = 217, + WL_RATE_1X4_CDD_OFDM_48 = 218, + WL_RATE_1X4_CDD_OFDM_54 = 219, + + WL_RATE_1X4_CDD_MCS0 = 220, + WL_RATE_1X4_CDD_MCS1 = 221, + WL_RATE_1X4_CDD_MCS2 = 222, + WL_RATE_1X4_CDD_MCS3 = 223, + WL_RATE_1X4_CDD_MCS4 = 224, + WL_RATE_1X4_CDD_MCS5 = 225, + WL_RATE_1X4_CDD_MCS6 = 226, + WL_RATE_1X4_CDD_MCS7 = 227, + WL_RATE_P_1X4_CDD_MCS87 = 228, + WL_RATE_P_1X4_CDD_MCS88 = 229, + + WL_RATE_1X4_VHT0SS1 = 220, + WL_RATE_1X4_VHT1SS1 = 221, + WL_RATE_1X4_VHT2SS1 = 222, + WL_RATE_1X4_VHT3SS1 = 223, + WL_RATE_1X4_VHT4SS1 = 224, + WL_RATE_1X4_VHT5SS1 = 225, + WL_RATE_1X4_VHT6SS1 = 226, + WL_RATE_1X4_VHT7SS1 = 227, + WL_RATE_1X4_VHT8SS1 = 228, + WL_RATE_1X4_VHT9SS1 = 229, + WL_RATE_P_1X4_VHT10SS1 = 230, + WL_RATE_P_1X4_VHT11SS1 = 231, + + WL_RATE_2X4_STBC_MCS0 = 232, + WL_RATE_2X4_STBC_MCS1 = 233, + WL_RATE_2X4_STBC_MCS2 = 234, + WL_RATE_2X4_STBC_MCS3 = 235, + WL_RATE_2X4_STBC_MCS4 = 236, + WL_RATE_2X4_STBC_MCS5 = 237, + WL_RATE_2X4_STBC_MCS6 = 238, + WL_RATE_2X4_STBC_MCS7 = 239, + WL_RATE_P_2X4_STBC_MCS87 = 240, + WL_RATE_P_2X4_STBC_MCS88 = 241, + + WL_RATE_2X4_STBC_VHT0SS1 = 232, + WL_RATE_2X4_STBC_VHT1SS1 = 233, + WL_RATE_2X4_STBC_VHT2SS1 = 234, + WL_RATE_2X4_STBC_VHT3SS1 = 235, + WL_RATE_2X4_STBC_VHT4SS1 = 236, + WL_RATE_2X4_STBC_VHT5SS1 = 237, + WL_RATE_2X4_STBC_VHT6SS1 = 238, + WL_RATE_2X4_STBC_VHT7SS1 = 239, + WL_RATE_2X4_STBC_VHT8SS1 = 240, + WL_RATE_2X4_STBC_VHT9SS1 = 241, + WL_RATE_P_2X4_STBC_VHT10SS1 = 242, + WL_RATE_P_2X4_STBC_VHT11SS1 = 243, + + WL_RATE_2X4_SDM_MCS8 = 244, + WL_RATE_2X4_SDM_MCS9 = 245, + WL_RATE_2X4_SDM_MCS10 = 246, + WL_RATE_2X4_SDM_MCS11 = 247, + WL_RATE_2X4_SDM_MCS12 = 248, + WL_RATE_2X4_SDM_MCS13 = 249, + WL_RATE_2X4_SDM_MCS14 = 250, + WL_RATE_2X4_SDM_MCS15 = 251, + WL_RATE_P_2X4_SDM_MCS99 = 252, + WL_RATE_P_2X4_SDM_MCS100 = 253, + + WL_RATE_2X4_VHT0SS2 = 244, + WL_RATE_2X4_VHT1SS2 = 245, + WL_RATE_2X4_VHT2SS2 = 246, + WL_RATE_2X4_VHT3SS2 = 247, + WL_RATE_2X4_VHT4SS2 = 248, + WL_RATE_2X4_VHT5SS2 = 249, + WL_RATE_2X4_VHT6SS2 = 250, + WL_RATE_2X4_VHT7SS2 = 251, + WL_RATE_2X4_VHT8SS2 = 252, + WL_RATE_2X4_VHT9SS2 = 253, + WL_RATE_P_2X4_VHT10SS2 = 254, + WL_RATE_P_2X4_VHT11SS2 = 255, + + WL_RATE_3X4_SDM_MCS16 = 256, + WL_RATE_3X4_SDM_MCS17 = 257, + WL_RATE_3X4_SDM_MCS18 = 258, + WL_RATE_3X4_SDM_MCS19 = 259, + WL_RATE_3X4_SDM_MCS20 = 260, + WL_RATE_3X4_SDM_MCS21 = 261, + WL_RATE_3X4_SDM_MCS22 = 262, + WL_RATE_3X4_SDM_MCS23 = 263, + WL_RATE_P_3X4_SDM_MCS101 = 264, + WL_RATE_P_3X4_SDM_MCS102 = 265, + + WL_RATE_3X4_VHT0SS3 = 256, + WL_RATE_3X4_VHT1SS3 = 257, + WL_RATE_3X4_VHT2SS3 = 258, + WL_RATE_3X4_VHT3SS3 = 259, + WL_RATE_3X4_VHT4SS3 = 260, + WL_RATE_3X4_VHT5SS3 = 261, + WL_RATE_3X4_VHT6SS3 = 262, + WL_RATE_3X4_VHT7SS3 = 263, + WL_RATE_3X4_VHT8SS3 = 264, + WL_RATE_3X4_VHT9SS3 = 265, + WL_RATE_P_3X4_VHT10SS3 = 266, + WL_RATE_P_3X4_VHT11SS3 = 267, + + WL_RATE_4X4_SDM_MCS24 = 268, + WL_RATE_4X4_SDM_MCS25 = 269, + WL_RATE_4X4_SDM_MCS26 = 270, + WL_RATE_4X4_SDM_MCS27 = 271, + WL_RATE_4X4_SDM_MCS28 = 272, + WL_RATE_4X4_SDM_MCS29 = 273, + WL_RATE_4X4_SDM_MCS30 = 274, + WL_RATE_4X4_SDM_MCS31 = 275, + WL_RATE_P_4X4_SDM_MCS103 = 276, + WL_RATE_P_4X4_SDM_MCS104 = 277, + + WL_RATE_4X4_VHT0SS4 = 268, + WL_RATE_4X4_VHT1SS4 = 269, + WL_RATE_4X4_VHT2SS4 = 270, + WL_RATE_4X4_VHT3SS4 = 271, + WL_RATE_4X4_VHT4SS4 = 272, + WL_RATE_4X4_VHT5SS4 = 273, + WL_RATE_4X4_VHT6SS4 = 274, + WL_RATE_4X4_VHT7SS4 = 275, + WL_RATE_4X4_VHT8SS4 = 276, + WL_RATE_4X4_VHT9SS4 = 277, + WL_RATE_P_4X4_VHT10SS4 = 278, + WL_RATE_P_4X4_VHT11SS4 = 279, + + WL_RATE_1X4_TXBF_OFDM_6 = 280, + WL_RATE_1X4_TXBF_OFDM_9 = 281, + WL_RATE_1X4_TXBF_OFDM_12 = 282, + WL_RATE_1X4_TXBF_OFDM_18 = 283, + WL_RATE_1X4_TXBF_OFDM_24 = 284, + WL_RATE_1X4_TXBF_OFDM_36 = 285, + WL_RATE_1X4_TXBF_OFDM_48 = 286, + WL_RATE_1X4_TXBF_OFDM_54 = 287, + + WL_RATE_1X4_TXBF_MCS0 = 288, + WL_RATE_1X4_TXBF_MCS1 = 289, + WL_RATE_1X4_TXBF_MCS2 = 290, + WL_RATE_1X4_TXBF_MCS3 = 291, + WL_RATE_1X4_TXBF_MCS4 = 292, + WL_RATE_1X4_TXBF_MCS5 = 293, + WL_RATE_1X4_TXBF_MCS6 = 294, + WL_RATE_1X4_TXBF_MCS7 = 295, + WL_RATE_P_1X4_TXBF_MCS87 = 296, + WL_RATE_P_1X4_TXBF_MCS88 = 297, + + WL_RATE_1X4_TXBF_VHT0SS1 = 288, + WL_RATE_1X4_TXBF_VHT1SS1 = 289, + WL_RATE_1X4_TXBF_VHT2SS1 = 290, + WL_RATE_1X4_TXBF_VHT3SS1 = 291, + WL_RATE_1X4_TXBF_VHT4SS1 = 292, + WL_RATE_1X4_TXBF_VHT5SS1 = 293, + WL_RATE_1X4_TXBF_VHT6SS1 = 294, + WL_RATE_1X4_TXBF_VHT7SS1 = 295, + WL_RATE_1X4_TXBF_VHT8SS1 = 296, + WL_RATE_1X4_TXBF_VHT9SS1 = 297, + WL_RATE_P_1X4_TXBF_VHT10SS1 = 298, + WL_RATE_P_1X4_TXBF_VHT11SS1 = 299, + + WL_RATE_2X4_TXBF_SDM_MCS8 = 300, + WL_RATE_2X4_TXBF_SDM_MCS9 = 301, + WL_RATE_2X4_TXBF_SDM_MCS10 = 302, + WL_RATE_2X4_TXBF_SDM_MCS11 = 303, + WL_RATE_2X4_TXBF_SDM_MCS12 = 304, + WL_RATE_2X4_TXBF_SDM_MCS13 = 305, + WL_RATE_2X4_TXBF_SDM_MCS14 = 306, + WL_RATE_2X4_TXBF_SDM_MCS15 = 307, + WL_RATE_P_2X4_TXBF_SDM_MCS99 = 308, + WL_RATE_P_2X4_TXBF_SDM_MCS100 = 309, + + WL_RATE_2X4_TXBF_VHT0SS2 = 300, + WL_RATE_2X4_TXBF_VHT1SS2 = 301, + WL_RATE_2X4_TXBF_VHT2SS2 = 302, + WL_RATE_2X4_TXBF_VHT3SS2 = 303, + WL_RATE_2X4_TXBF_VHT4SS2 = 304, + WL_RATE_2X4_TXBF_VHT5SS2 = 305, + WL_RATE_2X4_TXBF_VHT6SS2 = 306, + WL_RATE_2X4_TXBF_VHT7SS2 = 307, + WL_RATE_2X4_TXBF_VHT8SS2 = 308, + WL_RATE_2X4_TXBF_VHT9SS2 = 309, + WL_RATE_P_2X4_TXBF_VHT10SS2 = 310, + WL_RATE_P_2X4_TXBF_VHT11SS2 = 311, + + WL_RATE_3X4_TXBF_SDM_MCS16 = 312, + WL_RATE_3X4_TXBF_SDM_MCS17 = 313, + WL_RATE_3X4_TXBF_SDM_MCS18 = 314, + WL_RATE_3X4_TXBF_SDM_MCS19 = 315, + WL_RATE_3X4_TXBF_SDM_MCS20 = 316, + WL_RATE_3X4_TXBF_SDM_MCS21 = 317, + WL_RATE_3X4_TXBF_SDM_MCS22 = 318, + WL_RATE_3X4_TXBF_SDM_MCS23 = 319, + WL_RATE_P_3X4_TXBF_SDM_MCS101 = 320, + WL_RATE_P_3X4_TXBF_SDM_MCS102 = 321, + + WL_RATE_3X4_TXBF_VHT0SS3 = 312, + WL_RATE_3X4_TXBF_VHT1SS3 = 313, + WL_RATE_3X4_TXBF_VHT2SS3 = 314, + WL_RATE_3X4_TXBF_VHT3SS3 = 315, + WL_RATE_3X4_TXBF_VHT4SS3 = 316, + WL_RATE_3X4_TXBF_VHT5SS3 = 317, + WL_RATE_3X4_TXBF_VHT6SS3 = 318, + WL_RATE_3X4_TXBF_VHT7SS3 = 319, + WL_RATE_P_3X4_TXBF_VHT8SS3 = 320, + WL_RATE_P_3X4_TXBF_VHT9SS3 = 321, + WL_RATE_P_3X4_TXBF_VHT10SS3 = 322, + WL_RATE_P_3X4_TXBF_VHT11SS3 = 323, + + WL_RATE_4X4_TXBF_SDM_MCS24 = 324, + WL_RATE_4X4_TXBF_SDM_MCS25 = 325, + WL_RATE_4X4_TXBF_SDM_MCS26 = 326, + WL_RATE_4X4_TXBF_SDM_MCS27 = 327, + WL_RATE_4X4_TXBF_SDM_MCS28 = 328, + WL_RATE_4X4_TXBF_SDM_MCS29 = 329, + WL_RATE_4X4_TXBF_SDM_MCS30 = 330, + WL_RATE_4X4_TXBF_SDM_MCS31 = 331, + WL_RATE_P_4X4_TXBF_SDM_MCS103 = 332, + WL_RATE_P_4X4_TXBF_SDM_MCS104 = 333, + + WL_RATE_4X4_TXBF_VHT0SS4 = 324, + WL_RATE_4X4_TXBF_VHT1SS4 = 325, + WL_RATE_4X4_TXBF_VHT2SS4 = 326, + WL_RATE_4X4_TXBF_VHT3SS4 = 327, + WL_RATE_4X4_TXBF_VHT4SS4 = 328, + WL_RATE_4X4_TXBF_VHT5SS4 = 329, + WL_RATE_4X4_TXBF_VHT6SS4 = 330, + WL_RATE_4X4_TXBF_VHT7SS4 = 331, + WL_RATE_P_4X4_TXBF_VHT8SS4 = 332, + WL_RATE_P_4X4_TXBF_VHT9SS4 = 333, + WL_RATE_P_4X4_TXBF_VHT10SS4 = 334, + WL_RATE_P_4X4_TXBF_VHT11SS4 = 335 + +} clm_rates_t; + +#define WL_NUMRATES 336 + +#endif diff --git a/drivers/custom/broadcom-wl/src/shared/linux_osl.c b/drivers/custom/broadcom-wl/src/shared/linux_osl.c new file mode 100644 index 000000000000..c7b350cc127a --- /dev/null +++ b/drivers/custom/broadcom-wl/src/shared/linux_osl.c @@ -0,0 +1,1142 @@ +/* + * Linux OS Independent Layer + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: linux_osl.c 383331 2013-02-06 10:27:24Z $ + */ + +#define LINUX_PORT + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PCI_CFG_RETRY 10 + +#define OS_HANDLE_MAGIC 0x1234abcd +#define BCM_MEM_FILENAME_LEN 24 + +typedef struct bcm_mem_link { + struct bcm_mem_link *prev; + struct bcm_mem_link *next; + uint size; + int line; + void *osh; + char file[BCM_MEM_FILENAME_LEN]; +} bcm_mem_link_t; + +struct osl_info { + osl_pubinfo_t pub; + uint magic; + void *pdev; + atomic_t malloced; + atomic_t pktalloced; + uint failed; + uint bustype; + bcm_mem_link_t *dbgmem_list; + spinlock_t dbgmem_lock; + spinlock_t pktalloc_lock; +}; + +#define OSL_PKTTAG_CLEAR(p) \ +do { \ + struct sk_buff *s = (struct sk_buff *)(p); \ + ASSERT(OSL_PKTTAG_SZ == 32); \ + *(uint32 *)(&s->cb[0]) = 0; *(uint32 *)(&s->cb[4]) = 0; \ + *(uint32 *)(&s->cb[8]) = 0; *(uint32 *)(&s->cb[12]) = 0; \ + *(uint32 *)(&s->cb[16]) = 0; *(uint32 *)(&s->cb[20]) = 0; \ + *(uint32 *)(&s->cb[24]) = 0; *(uint32 *)(&s->cb[28]) = 0; \ +} while (0) + +uint32 g_assert_type = FALSE; + +static int16 linuxbcmerrormap[] = +{ 0, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -E2BIG, + -E2BIG, + -EBUSY, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EFAULT, + -ENOMEM, + -EOPNOTSUPP, + -EMSGSIZE, + -EINVAL, + -EPERM, + -ENOMEM, + -EINVAL, + -ERANGE, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EIO, + -ENODEV, + -EINVAL, + -EIO, + -EIO, + -ENODEV, + -EINVAL, + -ENODATA, + -EINVAL, + +#if BCME_LAST != -43 +#error "You need to add a OS error translation in the linuxbcmerrormap \ + for new error code defined in bcmutils.h" +#endif +}; + +int +osl_error(int bcmerror) +{ + if (bcmerror > 0) + bcmerror = 0; + else if (bcmerror < BCME_LAST) + bcmerror = BCME_ERROR; + + return linuxbcmerrormap[-bcmerror]; +} + +extern uint8* dhd_os_prealloc(void *osh, int section, int size); + +osl_t * +osl_attach(void *pdev, uint bustype, bool pkttag) +{ + osl_t *osh; + + osh = kmalloc(sizeof(osl_t), GFP_ATOMIC); + ASSERT(osh); + + bzero(osh, sizeof(osl_t)); + + ASSERT(ABS(BCME_LAST) == (ARRAYSIZE(linuxbcmerrormap) - 1)); + + osh->magic = OS_HANDLE_MAGIC; + atomic_set(&osh->malloced, 0); + osh->failed = 0; + osh->dbgmem_list = NULL; + spin_lock_init(&(osh->dbgmem_lock)); + osh->pdev = pdev; + osh->pub.pkttag = pkttag; + osh->bustype = bustype; + + switch (bustype) { + case PCI_BUS: + case SI_BUS: + case PCMCIA_BUS: + osh->pub.mmbus = TRUE; + break; + case JTAG_BUS: + case SDIO_BUS: + case USB_BUS: + case SPI_BUS: + case RPC_BUS: + osh->pub.mmbus = FALSE; + break; + default: + ASSERT(FALSE); + break; + } + + spin_lock_init(&(osh->pktalloc_lock)); + +#ifdef BCMDBG + if (pkttag) { + struct sk_buff *skb; + BCM_REFERENCE(skb); + ASSERT(OSL_PKTTAG_SZ <= sizeof(skb->cb)); + } +#endif + return osh; +} + +void +osl_detach(osl_t *osh) +{ + if (osh == NULL) + return; + + ASSERT(osh->magic == OS_HANDLE_MAGIC); + kfree(osh); +} + +static struct sk_buff *osl_alloc_skb(unsigned int len) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + gfp_t flags = GFP_ATOMIC; + struct sk_buff *skb; + + skb = __dev_alloc_skb(len, flags); + return skb; +#else + return dev_alloc_skb(len); +#endif +} + +struct sk_buff * BCMFASTPATH +osl_pkt_tonative(osl_t *osh, void *pkt) +{ + struct sk_buff *nskb; + + if (osh->pub.pkttag) + OSL_PKTTAG_CLEAR(pkt); + + for (nskb = (struct sk_buff *)pkt; nskb; nskb = nskb->next) { + atomic_sub(PKTISCHAINED(nskb) ? PKTCCNT(nskb) : 1, &osh->pktalloced); + + } + return (struct sk_buff *)pkt; +} + +void * BCMFASTPATH +osl_pkt_frmnative(osl_t *osh, void *pkt) +{ + struct sk_buff *nskb; + + if (osh->pub.pkttag) + OSL_PKTTAG_CLEAR(pkt); + + for (nskb = (struct sk_buff *)pkt; nskb; nskb = nskb->next) { + atomic_add(PKTISCHAINED(nskb) ? PKTCCNT(nskb) : 1, &osh->pktalloced); + + } + return (void *)pkt; +} + +void * BCMFASTPATH +osl_pktget(osl_t *osh, uint len) +{ + struct sk_buff *skb; + + if ((skb = osl_alloc_skb(len))) { +#ifdef BCMDBG + skb_put(skb, len); +#else + skb->tail += len; + skb->len += len; +#endif + skb->priority = 0; + + atomic_inc(&osh->pktalloced); + } + + return ((void*) skb); +} + +void BCMFASTPATH +osl_pktfree(osl_t *osh, void *p, bool send) +{ + struct sk_buff *skb, *nskb; + + skb = (struct sk_buff*) p; + + if (send && osh->pub.tx_fn) + osh->pub.tx_fn(osh->pub.tx_ctx, p, 0); + + PKTDBG_TRACE(osh, (void *) skb, PKTLIST_PKTFREE); + + while (skb) { + nskb = skb->next; + skb->next = NULL; + + { + if (skb->destructor) + + dev_kfree_skb_any(skb); + else + + dev_kfree_skb(skb); + } + atomic_dec(&osh->pktalloced); + skb = nskb; + } +} + +uint32 +osl_pci_read_config(osl_t *osh, uint offset, uint size) +{ + uint val = 0; + uint retry = PCI_CFG_RETRY; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + + ASSERT(size == 4); + + do { + pci_read_config_dword(osh->pdev, offset, &val); + if (val != 0xffffffff) + break; + } while (retry--); + +#ifdef BCMDBG + if (retry < PCI_CFG_RETRY) + printk("PCI CONFIG READ access to %d required %d retries\n", offset, + (PCI_CFG_RETRY - retry)); +#endif + + return (val); +} + +void +osl_pci_write_config(osl_t *osh, uint offset, uint size, uint val) +{ + uint retry = PCI_CFG_RETRY; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + + ASSERT(size == 4); + + do { + pci_write_config_dword(osh->pdev, offset, val); + if (offset != PCI_BAR0_WIN) + break; + if (osl_pci_read_config(osh, offset, size) == val) + break; + } while (retry--); + +#ifdef BCMDBG + if (retry < PCI_CFG_RETRY) + printk("PCI CONFIG WRITE access to %d required %d retries\n", offset, + (PCI_CFG_RETRY - retry)); +#endif +} + +uint +osl_pci_bus(osl_t *osh) +{ + ASSERT(osh && (osh->magic == OS_HANDLE_MAGIC) && osh->pdev); + + return ((struct pci_dev *)osh->pdev)->bus->number; +} + +uint +osl_pci_slot(osl_t *osh) +{ + ASSERT(osh && (osh->magic == OS_HANDLE_MAGIC) && osh->pdev); + +#if defined(__ARM_ARCH_7A__) && LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 35) + return PCI_SLOT(((struct pci_dev *)osh->pdev)->devfn) + 1; +#else + return PCI_SLOT(((struct pci_dev *)osh->pdev)->devfn); +#endif +} + +struct pci_dev * +osl_pci_device(osl_t *osh) +{ + ASSERT(osh && (osh->magic == OS_HANDLE_MAGIC) && osh->pdev); + + return osh->pdev; +} + +static void +osl_pcmcia_attr(osl_t *osh, uint offset, char *buf, int size, bool write) +{ +} + +void +osl_pcmcia_read_attr(osl_t *osh, uint offset, void *buf, int size) +{ + osl_pcmcia_attr(osh, offset, (char *) buf, size, FALSE); +} + +void +osl_pcmcia_write_attr(osl_t *osh, uint offset, void *buf, int size) +{ + osl_pcmcia_attr(osh, offset, (char *) buf, size, TRUE); +} + +#ifdef BCMDBG_MEM + +static +#endif +void * +osl_malloc(osl_t *osh, uint size) +{ + void *addr; + + if (osh) + ASSERT(osh->magic == OS_HANDLE_MAGIC); + + if ((addr = kmalloc(size, GFP_ATOMIC)) == NULL) { + if (osh) + osh->failed++; + return (NULL); + } + if (osh) + atomic_add(size, &osh->malloced); + + return (addr); +} + +#ifdef BCMDBG_MEM + +static +#endif +void +osl_mfree(osl_t *osh, void *addr, uint size) +{ + if (osh) { + ASSERT(osh->magic == OS_HANDLE_MAGIC); + atomic_sub(size, &osh->malloced); + } + kfree(addr); +} + +uint +osl_malloced(osl_t *osh) +{ + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + return (atomic_read(&osh->malloced)); +} + +uint +osl_malloc_failed(osl_t *osh) +{ + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + return (osh->failed); +} + +#ifdef BCMDBG_MEM +#define MEMLIST_LOCK(osh, flags) spin_lock_irqsave(&(osh)->dbgmem_lock, flags) +#define MEMLIST_UNLOCK(osh, flags) spin_unlock_irqrestore(&(osh)->dbgmem_lock, flags) + +void * +osl_debug_malloc(osl_t *osh, uint size, int line, const char* file) +{ + bcm_mem_link_t *p; + const char* basename; + unsigned long flags = 0; + + if (!size) { + printk("%s: allocating zero sized mem at %s line %d\n", __FUNCTION__, file, line); + ASSERT(0); + } + + if (osh) { + MEMLIST_LOCK(osh, flags); + } + if ((p = (bcm_mem_link_t*)osl_malloc(osh, sizeof(bcm_mem_link_t) + size)) == NULL) { + if (osh) { + MEMLIST_UNLOCK(osh, flags); + } + return (NULL); + } + + p->size = size; + p->line = line; + p->osh = (void *)osh; + + basename = strrchr(file, '/'); + + if (basename) + basename++; + + if (!basename) + basename = file; + + strncpy(p->file, basename, BCM_MEM_FILENAME_LEN); + p->file[BCM_MEM_FILENAME_LEN - 1] = '\0'; + + if (osh) { + p->prev = NULL; + p->next = osh->dbgmem_list; + if (p->next) + p->next->prev = p; + osh->dbgmem_list = p; + MEMLIST_UNLOCK(osh, flags); + } + + return p + 1; +} + +void +osl_debug_mfree(osl_t *osh, void *addr, uint size, int line, const char* file) +{ + bcm_mem_link_t *p = (bcm_mem_link_t *)((int8*)addr - sizeof(bcm_mem_link_t)); + unsigned long flags = 0; + + ASSERT(osh == NULL || osh->magic == OS_HANDLE_MAGIC); + + if (p->size == 0) { + printk("osl_debug_mfree: double free on addr %p size %d at line %d file %s\n", + addr, size, line, file); + ASSERT(p->size); + return; + } + + if (p->size != size) { + printk("%s: dealloca size does not match alloc size\n", __FUNCTION__); + printk("Dealloc addr %p size %d at line %d file %s\n", addr, size, line, file); + printk("Alloc size %d line %d file %s\n", p->size, p->line, p->file); + ASSERT(p->size == size); + return; + } + + if (p->osh != (void *)osh) { + printk("osl_debug_mfree: alloc osh %p does not match dealloc osh %p\n", + p->osh, osh); + printk("Dealloc addr %p size %d at line %d file %s\n", addr, size, line, file); + printk("Alloc size %d line %d file %s\n", p->size, p->line, p->file); + ASSERT(p->osh == (void *)osh); + return; + } + + if (osh) { + MEMLIST_LOCK(osh, flags); + if (p->prev) + p->prev->next = p->next; + if (p->next) + p->next->prev = p->prev; + if (osh->dbgmem_list == p) + osh->dbgmem_list = p->next; + p->next = p->prev = NULL; + } + p->size = 0; + + osl_mfree(osh, p, size + sizeof(bcm_mem_link_t)); + if (osh) { + MEMLIST_UNLOCK(osh, flags); + } +} + +int +osl_debug_memdump(osl_t *osh, struct bcmstrbuf *b) +{ + bcm_mem_link_t *p; + unsigned long flags = 0; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + + MEMLIST_LOCK(osh, flags); + if (osh->dbgmem_list) { + if (b != NULL) + bcm_bprintf(b, " Address Size File:line\n"); + else + printf(" Address Size File:line\n"); + + for (p = osh->dbgmem_list; p; p = p->next) { + if (b != NULL) + bcm_bprintf(b, "%p %6d %s:%d\n", (char*)p + sizeof(bcm_mem_link_t), + p->size, p->file, p->line); + else + printf("%p %6d %s:%d\n", (char*)p + sizeof(bcm_mem_link_t), + p->size, p->file, p->line); + + if (p == p->next) { + if (b != NULL) + bcm_bprintf(b, "WARNING: loop-to-self " + "p %p p->next %p\n", p, p->next); + else + printf("WARNING: loop-to-self " + "p %p p->next %p\n", p, p->next); + + break; + } + } + } + MEMLIST_UNLOCK(osh, flags); + + return 0; +} + +#endif + +uint +osl_dma_consistent_align(void) +{ + return (PAGE_SIZE); +} + +void* +osl_dma_alloc_consistent(osl_t *osh, uint size, uint16 align_bits, uint *alloced, ulong *pap) +{ + void *va; + uint16 align = (1 << align_bits); + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + + if (!ISALIGNED(DMA_CONSISTENT_ALIGN, align)) + size += align; + *alloced = size; + +#ifdef __ARM_ARCH_7A__ + va = kmalloc(size, GFP_ATOMIC | __GFP_ZERO); + if (va) + *pap = (ulong)__virt_to_phys(va); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + va = dma_alloc_coherent(&((struct pci_dev *)osh->pdev)->dev, size, (dma_addr_t*)pap, GFP_ATOMIC); +#else + va = pci_alloc_consistent(osh->pdev, size, (dma_addr_t*)pap); +#endif + return va; +} + +void +osl_dma_free_consistent(osl_t *osh, void *va, uint size, ulong pa) +{ + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + +#ifdef __ARM_ARCH_7A__ + kfree(va); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + dma_free_coherent(&((struct pci_dev *)osh->pdev)->dev, size, va, (dma_addr_t)pa); +#else + pci_free_consistent(osh->pdev, size, va, (dma_addr_t)pa); +#endif +} + +uint BCMFASTPATH +osl_dma_map(osl_t *osh, void *va, uint size, int direction, void *p, hnddma_seg_map_t *dmah) +{ + int dir; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + dir = (direction == DMA_TX)? DMA_TO_DEVICE: DMA_FROM_DEVICE; +#else + dir = (direction == DMA_TX)? PCI_DMA_TODEVICE: PCI_DMA_FROMDEVICE; +#endif + +#if defined(__ARM_ARCH_7A__) && defined(BCMDMASGLISTOSL) + if (dmah != NULL) { + int32 nsegs, i, totsegs = 0, totlen = 0; + struct scatterlist *sg, _sg[MAX_DMA_SEGS * 2]; + struct sk_buff *skb; + for (skb = (struct sk_buff *)p; skb != NULL; skb = PKTNEXT(osh, skb)) { + sg = &_sg[totsegs]; + if (skb_is_nonlinear(skb)) { + nsegs = skb_to_sgvec(skb, sg, 0, PKTLEN(osh, skb)); + ASSERT((nsegs > 0) && (totsegs + nsegs <= MAX_DMA_SEGS)); + pci_map_sg(osh->pdev, sg, nsegs, dir); + } else { + nsegs = 1; + ASSERT(totsegs + nsegs <= MAX_DMA_SEGS); + sg->page_link = 0; + sg_set_buf(sg, PKTDATA(osh, skb), PKTLEN(osh, skb)); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + dma_map_single(&((struct pci_dev *)osh->pdev)->dev, PKTDATA(osh, skb), PKTLEN(osh, skb), dir); +#else + pci_map_single(osh->pdev, PKTDATA(osh, skb), PKTLEN(osh, skb), dir); +#endif + } + totsegs += nsegs; + totlen += PKTLEN(osh, skb); + } + dmah->nsegs = totsegs; + dmah->origsize = totlen; + for (i = 0, sg = _sg; i < totsegs; i++, sg++) { + dmah->segs[i].addr = sg_phys(sg); + dmah->segs[i].length = sg->length; + } + return dmah->segs[0].addr; + } +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + return (dma_map_single(&((struct pci_dev *)osh->pdev)->dev, va, size, dir)); +#else + return (pci_map_single(osh->pdev, va, size, dir)); +#endif +} + +void BCMFASTPATH +osl_dma_unmap(osl_t *osh, uint pa, uint size, int direction) +{ + int dir; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + dir = (direction == DMA_TX)? DMA_TO_DEVICE: DMA_FROM_DEVICE; + dma_unmap_single(&((struct pci_dev *)osh->pdev)->dev, (uint32)pa, size, dir); +#else + dir = (direction == DMA_TX)? PCI_DMA_TODEVICE: PCI_DMA_FROMDEVICE; + pci_unmap_single(osh->pdev, (uint32)pa, size, dir); +#endif +} + +#if defined(BCMDBG_ASSERT) +void +osl_assert(const char *exp, const char *file, int line) +{ + char tempbuf[256]; + const char *basename; + + basename = strrchr(file, '/'); + + if (basename) + basename++; + + if (!basename) + basename = file; + +#ifdef BCMDBG_ASSERT + snprintf(tempbuf, 256, "assertion \"%s\" failed: file \"%s\", line %d\n", + exp, basename, line); + + if (!in_interrupt() && g_assert_type != 1) { + const int delay = 3; + printk("%s", tempbuf); + printk("panic in %d seconds\n", delay); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(delay * HZ); + } + + switch (g_assert_type) { + case 0: + panic("%s", tempbuf); + break; + case 1: + printk("%s", tempbuf); + break; + case 2: + printk("%s", tempbuf); + BUG(); + break; + default: + break; + } +#endif + +} +#endif + +void +osl_delay(uint usec) +{ + uint d; + + while (usec > 0) { + d = MIN(usec, 1000); + udelay(d); + usec -= d; + } +} + +void * +osl_pktdup(osl_t *osh, void *skb) +{ + void * p; + + ASSERT(!PKTISCHAINED(skb)); + + PKTCTFMAP(osh, skb); + + if ((p = skb_clone((struct sk_buff *)skb, GFP_ATOMIC)) == NULL) + return NULL; + + PKTSETCLINK(p, NULL); + PKTCCLRFLAGS(p); + PKTCSETCNT(p, 1); + PKTCSETLEN(p, PKTLEN(osh, skb)); + + if (osh->pub.pkttag) + OSL_PKTTAG_CLEAR(p); + + atomic_inc(&osh->pktalloced); + return (p); +} + +uint32 +osl_sysuptime(void) +{ + return ((uint32)jiffies * (1000 / HZ)); +} + +int +osl_printf(const char *format, ...) +{ + va_list args; + static char printbuf[1024]; + int len; + + va_start(args, format); + len = vsnprintf(printbuf, 1024, format, args); + va_end(args); + + if (len > sizeof(printbuf)) { + printk("osl_printf: buffer overrun\n"); + return (0); + } + + return (printk("%s", printbuf)); +} + +int +osl_sprintf(char *buf, const char *format, ...) +{ + va_list args; + int rc; + + va_start(args, format); + rc = vsprintf(buf, format, args); + va_end(args); + return (rc); +} + +int +osl_snprintf(char *buf, size_t n, const char *format, ...) +{ + va_list args; + int rc; + + va_start(args, format); + rc = vsnprintf(buf, n, format, args); + va_end(args); + return (rc); +} + +int +osl_vsprintf(char *buf, const char *format, va_list ap) +{ + return (vsprintf(buf, format, ap)); +} + +int +osl_vsnprintf(char *buf, size_t n, const char *format, va_list ap) +{ + return (vsnprintf(buf, n, format, ap)); +} + +int +osl_strcmp(const char *s1, const char *s2) +{ + return (strcmp(s1, s2)); +} + +int +osl_strncmp(const char *s1, const char *s2, uint n) +{ + return (strncmp(s1, s2, n)); +} + +int +osl_strlen(const char *s) +{ + return (strlen(s)); +} + +char* +osl_strcpy(char *d, const char *s) +{ + return (strcpy(d, s)); +} + +char* +osl_strncpy(char *d, const char *s, uint n) +{ + return (strncpy(d, s, n)); +} + +char* +osl_strchr(const char *s, int c) +{ + return (strchr(s, c)); +} + +char* +osl_strrchr(const char *s, int c) +{ + return (strrchr(s, c)); +} + +void* +osl_memset(void *d, int c, size_t n) +{ + return memset(d, c, n); +} + +void* +osl_memcpy(void *d, const void *s, size_t n) +{ + return memcpy(d, s, n); +} + +void* +osl_memmove(void *d, const void *s, size_t n) +{ + return memmove(d, s, n); +} + +int +osl_memcmp(const void *s1, const void *s2, size_t n) +{ + return memcmp(s1, s2, n); +} + +uint32 +osl_readl(volatile uint32 *r) +{ + return (readl(r)); +} + +uint16 +osl_readw(volatile uint16 *r) +{ + return (readw(r)); +} + +uint8 +osl_readb(volatile uint8 *r) +{ + return (readb(r)); +} + +void +osl_writel(uint32 v, volatile uint32 *r) +{ + writel(v, r); +} + +void +osl_writew(uint16 v, volatile uint16 *r) +{ + writew(v, r); +} + +void +osl_writeb(uint8 v, volatile uint8 *r) +{ + writeb(v, r); +} + +void * +osl_uncached(void *va) +{ + return ((void*)va); +} + +void * +osl_cached(void *va) +{ + return ((void*)va); +} + +uint +osl_getcycles(void) +{ + uint cycles; + +#if defined(__i386__) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0) + cycles = (u32)rdtsc(); +#else + rdtscl(cycles); +#endif +#else + cycles = 0; +#endif + return cycles; +} + +void * +osl_reg_map(uint32 pa, uint size) +{ + return (ioremap((unsigned long)pa, (unsigned long)size)); +} + +void +osl_reg_unmap(void *va) +{ + iounmap(va); +} + +int +osl_busprobe(uint32 *val, uint32 addr) +{ + *val = readl((uint32 *)(uintptr)addr); + return 0; +} + +bool +osl_pktshared(void *skb) +{ + return (((struct sk_buff*)skb)->cloned); +} + +uchar* +osl_pktdata(osl_t *osh, void *skb) +{ + return (((struct sk_buff*)skb)->data); +} + +uint +osl_pktlen(osl_t *osh, void *skb) +{ + return (((struct sk_buff*)skb)->len); +} + +uint +osl_pktheadroom(osl_t *osh, void *skb) +{ + return (uint) skb_headroom((struct sk_buff *) skb); +} + +uint +osl_pkttailroom(osl_t *osh, void *skb) +{ + return (uint) skb_tailroom((struct sk_buff *) skb); +} + +void* +osl_pktnext(osl_t *osh, void *skb) +{ + return (((struct sk_buff*)skb)->next); +} + +void +osl_pktsetnext(void *skb, void *x) +{ + ((struct sk_buff*)skb)->next = (struct sk_buff*)x; +} + +void +osl_pktsetlen(osl_t *osh, void *skb, uint len) +{ + __skb_trim((struct sk_buff*)skb, len); +} + +uchar* +osl_pktpush(osl_t *osh, void *skb, int bytes) +{ + return (skb_push((struct sk_buff*)skb, bytes)); +} + +uchar* +osl_pktpull(osl_t *osh, void *skb, int bytes) +{ + return (skb_pull((struct sk_buff*)skb, bytes)); +} + +void* +osl_pkttag(void *skb) +{ + return ((void*)(((struct sk_buff*)skb)->cb)); +} + +void* +osl_pktlink(void *skb) +{ + return (((struct sk_buff*)skb)->prev); +} + +void +osl_pktsetlink(void *skb, void *x) +{ + ((struct sk_buff*)skb)->prev = (struct sk_buff*)x; +} + +uint +osl_pktprio(void *skb) +{ + return (((struct sk_buff*)skb)->priority); +} + +void +osl_pktsetprio(void *skb, uint x) +{ + ((struct sk_buff*)skb)->priority = x; +} + +uint +osl_pktalloced(osl_t *osh) +{ + return (atomic_read(&osh->pktalloced)); +} + +void * +osl_os_open_image(char *filename) +{ + struct file *fp; + + fp = filp_open(filename, O_RDONLY, 0); + + if (IS_ERR(fp)) + fp = NULL; + + return fp; +} + +int +osl_os_get_image_block(char *buf, int len, void *image) +{ + struct file *fp = (struct file *)image; + int rdlen; + loff_t pos; + + if (!image) + return 0; + + pos = fp->f_pos; + rdlen = kernel_read(fp, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) + pos, +#endif + buf, len +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) + ,&pos +#endif + ); + if (rdlen > 0) + fp->f_pos += rdlen; + + return rdlen; +} + +void +osl_os_close_image(void *image) +{ + if (image) + filp_close((struct file *)image, NULL); +} + +int +osl_os_image_size(void *image) +{ + int len = 0, curroffset; + + if (image) { + + curroffset = generic_file_llseek(image, 0, 1); + + len = generic_file_llseek(image, 0, 2); + + generic_file_llseek(image, curroffset, 0); + } + return len; +} diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wl_cfg80211_hybrid.c b/drivers/custom/broadcom-wl/src/wl/sys/wl_cfg80211_hybrid.c new file mode 100644 index 000000000000..02c29a84e461 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wl_cfg80211_hybrid.c @@ -0,0 +1,3143 @@ +/* + * Linux-specific portion of Broadcom 802.11abg Networking Device Driver + * cfg80211 interface + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wl_cfg80211.c,v 1.1.6.4 2011-02-11 00:22:09 $ + */ + +#if defined(USE_CFG80211) + +#define LINUX_PORT +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +#include +#endif + +#define EVENT_TYPE(e) dtoh32((e)->event_type) +#define EVENT_FLAGS(e) dtoh16((e)->flags) +#define EVENT_STATUS(e) dtoh32((e)->status) + +#ifdef BCMDBG +u32 wl_dbg_level = WL_DBG_ERR | WL_DBG_INFO; +#else +u32 wl_dbg_level = WL_DBG_ERR; +#endif + +static s32 wl_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev, + enum nl80211_iftype type, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0) + u32 *flags, +#endif + struct vif_params *params); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0) +static s32 +wl_cfg80211_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request); +#else +static s32 wl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_scan_request *request); +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 17, 0) +static s32 wl_cfg80211_set_wiphy_params(struct wiphy *wiphy, s32 radio_idx, u32 changed); +#else +static s32 wl_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed); +#endif +static s32 wl_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ibss_params *params); +static s32 wl_cfg80211_leave_ibss(struct wiphy *wiphy, struct net_device *dev); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0) +static s32 wl_cfg80211_get_station(struct wiphy *wiphy, + struct net_device *dev, u8 *mac, struct station_info *sinfo); +#else +static s32 wl_cfg80211_get_station(struct wiphy *wiphy, + struct net_device *dev, const u8 *mac, struct station_info *sinfo); +#endif + +static s32 wl_cfg80211_set_power_mgmt(struct wiphy *wiphy, + struct net_device *dev, bool enabled, s32 timeout); +static int wl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_connect_params *sme); +static s32 wl_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev, u16 reason_code); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 17, 0) +static s32 wl_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + s32 radio_idx, enum nl80211_tx_power_setting type, s32 dbm); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) +static s32 +wl_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + enum nl80211_tx_power_setting type, s32 dbm); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) +static s32 wl_cfg80211_set_tx_power(struct wiphy *wiphy, + enum nl80211_tx_power_setting type, s32 dbm); +#else +static s32 wl_cfg80211_set_tx_power(struct wiphy *wiphy, + enum tx_power_setting type, s32 dbm); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 17, 0) +static s32 wl_cfg80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, s32 radio_idx, + u32 /*link_id*/, s32 *dbm); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0) +static s32 wl_cfg80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, u32 /*link_id*/, s32 *dbm); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) +static s32 wl_cfg80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, s32 *dbm); +#else +static s32 wl_cfg80211_get_tx_power(struct wiphy *wiphy, s32 *dbm); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +static s32 wl_cfg80211_config_default_key(struct wiphy *wiphy, + struct net_device *dev, int link_id, u8 key_idx, bool unicast, + bool multicast); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 38) +static s32 wl_cfg80211_config_default_key(struct wiphy *wiphy, + struct net_device *dev, u8 key_idx, bool unicast, bool multicast); +#else +static s32 wl_cfg80211_config_default_key(struct wiphy *wiphy, + struct net_device *dev, u8 key_idx); +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +static s32 wl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev, + int link_id, u8 key_idx, bool pairwise, const u8 *mac_addr, + struct key_params *params); +static s32 wl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev, + int link_id, u8 key_idx, bool pairwise, const u8 *mac_addr); +static s32 wl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev, + int link_id, u8 key_idx, bool pairwise, const u8 *mac_addr, + void *cookie, + void (*callback) (void *cookie, struct key_params *params)); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) +static s32 wl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_idx, bool pairwise, const u8 *mac_addr, struct key_params *params); +static s32 wl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_idx, bool pairwise, const u8 *mac_addr); +static s32 wl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_idx, bool pairwise, const u8 *mac_addr, + void *cookie, void (*callback) (void *cookie, struct key_params *params)); +#else +static s32 wl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_idx, const u8 *mac_addr, struct key_params *params); +static s32 wl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_idx, const u8 *mac_addr); +static s32 wl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_idx, const u8 *mac_addr, + void *cookie, void (*callback) (void *cookie, struct key_params *params)); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) +static s32 wl_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_pmksa *pmksa); +static s32 wl_cfg80211_del_pmksa(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_pmksa *pmksa); +static s32 wl_cfg80211_flush_pmksa(struct wiphy *wiphy, struct net_device *dev); +#endif + +static s32 wl_create_event_handler(struct wl_cfg80211_priv *wl); +static void wl_destroy_event_handler(struct wl_cfg80211_priv *wl); +static s32 wl_event_handler(void *data); +static void wl_init_eq(struct wl_cfg80211_priv *wl); +static void wl_flush_eq(struct wl_cfg80211_priv *wl); +static void wl_lock_eq(struct wl_cfg80211_priv *wl); +static void wl_unlock_eq(struct wl_cfg80211_priv *wl); +static void wl_init_eq_lock(struct wl_cfg80211_priv *wl); +static void wl_init_eloop_handler(struct wl_cfg80211_event_loop *el); +static struct wl_cfg80211_event_q *wl_deq_event(struct wl_cfg80211_priv *wl); +static s32 wl_enq_event(struct wl_cfg80211_priv *wl, u32 type, + const wl_event_msg_t *msg, void *data); +static void wl_put_event(struct wl_cfg80211_event_q *e); +static void wl_wakeup_event(struct wl_cfg80211_priv *wl); + +static s32 wl_notify_connect_status(struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data); +static s32 wl_notify_roaming_status(struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data); +static s32 wl_notify_scan_status(struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data); +static s32 wl_bss_connect_done(struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data, bool completed); +static s32 wl_bss_roaming_done(struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data); +static s32 wl_notify_mic_status(struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data); + +static s32 wl_dev_bufvar_get(struct net_device *dev, s8 *name, s8 *buf, s32 buf_len); +static __used s32 wl_dev_bufvar_set(struct net_device *dev, s8 *name, s8 *buf, s32 len); +static s32 wl_dev_intvar_set(struct net_device *dev, s8 *name, s32 val); +static s32 wl_dev_intvar_get(struct net_device *dev, s8 *name, s32 *retval); +static s32 wl_dev_ioctl(struct net_device *dev, u32 cmd, void *arg, u32 len); + +static s32 wl_set_frag(struct net_device *dev, u32 frag_threshold); +static s32 wl_set_rts(struct net_device *dev, u32 frag_threshold); +static s32 wl_set_retry(struct net_device *dev, u32 retry, bool l); + +static void wl_init_prof(struct wl_cfg80211_profile *prof); + +static s32 wl_set_wpa_version(struct net_device *dev, struct cfg80211_connect_params *sme); +static s32 wl_set_auth_type(struct net_device *dev, struct cfg80211_connect_params *sme); +static s32 wl_set_set_cipher(struct net_device *dev, struct cfg80211_connect_params *sme); +static s32 wl_set_key_mgmt(struct net_device *dev, struct cfg80211_connect_params *sme); +static s32 wl_set_set_sharedkey(struct net_device *dev, struct cfg80211_connect_params *sme); +static s32 wl_get_assoc_ies(struct wl_cfg80211_priv *wl); +static void wl_ch_to_chanspec(struct ieee80211_channel *chan, + struct wl_join_params *join_params, size_t *join_params_size); + +static void wl_rst_ie(struct wl_cfg80211_priv *wl); +static __used s32 wl_add_ie(struct wl_cfg80211_priv *wl, u8 t, u8 l, u8 *v); +static s32 wl_mrg_ie(struct wl_cfg80211_priv *wl, u8 *ie_stream, u16 ie_size); +static s32 wl_cp_ie(struct wl_cfg80211_priv *wl, u8 *dst, u16 dst_size); +static u32 wl_get_ielen(struct wl_cfg80211_priv *wl); + +static s32 wl_mode_to_nl80211_iftype(s32 mode); + +static s32 wl_alloc_wdev(struct device *dev, struct wireless_dev **rwdev); +static void wl_free_wdev(struct wl_cfg80211_priv *wl); + +static s32 wl_inform_bss(struct wl_cfg80211_priv *wl, struct wl_scan_results *bss_list); +static s32 wl_inform_single_bss(struct wl_cfg80211_priv *wl, struct wl_bss_info *bi); +static s32 wl_update_bss_info(struct wl_cfg80211_priv *wl); + +static void key_endian_to_device(struct wl_wsec_key *key); +static void key_endian_to_host(struct wl_wsec_key *key); + +static s32 wl_init_priv_mem(struct wl_cfg80211_priv *wl); +static void wl_deinit_priv_mem(struct wl_cfg80211_priv *wl); + +static bool wl_is_ibssmode(struct wl_cfg80211_priv *wl); + +static void wl_link_up(struct wl_cfg80211_priv *wl); +static void wl_link_down(struct wl_cfg80211_priv *wl); +static s32 wl_set_mode(struct net_device *ndev, s32 iftype); + +static void wl_init_conf(struct wl_cfg80211_conf *conf); + +static s32 wl_update_wiphybands(struct wl_cfg80211_priv *wl); + +static __used s32 wl_update_pmklist(struct net_device *dev, + struct wl_cfg80211_pmk_list *pmk_list, s32 err); + +#if defined(WL_DBGMSG_ENABLE) +#define WL_DBG_ESTR_MAX 32 +static s8 wl_dbg_estr[][WL_DBG_ESTR_MAX] = { + "SET_SSID", "JOIN", "START", "AUTH", "AUTH_IND", + "DEAUTH", "DEAUTH_IND", "ASSOC", "ASSOC_IND", "REASSOC", + "REASSOC_IND", "DISASSOC", "DISASSOC_IND", "QUIET_START", "QUIET_END", + "BEACON_RX", "LINK", "MIC_ERROR", "NDIS_LINK", "ROAM", + "TXFAIL", "PMKID_CACHE", "RETROGRADE_TSF", "PRUNE", "AUTOAUTH", + "EAPOL_MSG", "SCAN_COMPLETE", "ADDTS_IND", "DELTS_IND", "BCNSENT_IND", + "BCNRX_MSG", "BCNLOST_MSG", "ROAM_PREP", "PFN_NET_FOUND", + "PFN_NET_LOST", + "RESET_COMPLETE", "JOIN_START", "ROAM_START", "ASSOC_START", + "IBSS_ASSOC", + "RADIO", "PSM_WATCHDOG", + "PROBREQ_MSG", + "SCAN_CONFIRM_IND", "PSK_SUP", "COUNTRY_CODE_CHANGED", + "EXCEEDED_MEDIUM_TIME", "ICV_ERROR", + "UNICAST_DECODE_ERROR", "MULTICAST_DECODE_ERROR", "TRACE", + "IF", + "RSSI", "PFN_SCAN_COMPLETE", "ACTION_FRAME", "ACTION_FRAME_COMPLETE", +}; +#endif + +#define CHAN2G(_channel, _freq, _flags) { \ + .band = NL80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +#define CHAN5G(_channel, _flags) { \ + .band = NL80211_BAND_5GHZ, \ + .center_freq = 5000 + (5 * (_channel)), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +#define RATE_TO_BASE100KBPS(rate) (((rate) * 10) / 2) +#define RATETAB_ENT(_rateid, _flags) \ + { \ + .bitrate = RATE_TO_BASE100KBPS(_rateid), \ + .hw_value = (_rateid), \ + .flags = (_flags), \ + } + +static struct ieee80211_rate __wl_rates[] = { + RATETAB_ENT(DOT11_RATE_1M, 0), + RATETAB_ENT(DOT11_RATE_2M, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(DOT11_RATE_5M5, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(DOT11_RATE_11M, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(DOT11_RATE_6M, 0), + RATETAB_ENT(DOT11_RATE_9M, 0), + RATETAB_ENT(DOT11_RATE_12M, 0), + RATETAB_ENT(DOT11_RATE_18M, 0), + RATETAB_ENT(DOT11_RATE_24M, 0), + RATETAB_ENT(DOT11_RATE_36M, 0), + RATETAB_ENT(DOT11_RATE_48M, 0), + RATETAB_ENT(DOT11_RATE_54M, 0), +}; + +#define wl_a_rates (__wl_rates + 4) +#define wl_a_rates_size 8 +#define wl_g_rates (__wl_rates + 0) +#define wl_g_rates_size 12 + +static struct ieee80211_channel __wl_2ghz_channels[] = { + CHAN2G(1, 2412, 0), + CHAN2G(2, 2417, 0), + CHAN2G(3, 2422, 0), + CHAN2G(4, 2427, 0), + CHAN2G(5, 2432, 0), + CHAN2G(6, 2437, 0), + CHAN2G(7, 2442, 0), + CHAN2G(8, 2447, 0), + CHAN2G(9, 2452, 0), + CHAN2G(10, 2457, 0), + CHAN2G(11, 2462, 0), + CHAN2G(12, 2467, 0), + CHAN2G(13, 2472, 0), + CHAN2G(14, 2484, 0), +}; + +static struct ieee80211_channel __wl_5ghz_a_channels[] = { + CHAN5G(34, 0), CHAN5G(36, 0), + CHAN5G(38, 0), CHAN5G(40, 0), + CHAN5G(42, 0), CHAN5G(44, 0), + CHAN5G(46, 0), CHAN5G(48, 0), + CHAN5G(52, 0), CHAN5G(56, 0), + CHAN5G(60, 0), CHAN5G(64, 0), + CHAN5G(100, 0), CHAN5G(104, 0), + CHAN5G(108, 0), CHAN5G(112, 0), + CHAN5G(116, 0), CHAN5G(120, 0), + CHAN5G(124, 0), CHAN5G(128, 0), + CHAN5G(132, 0), CHAN5G(136, 0), + CHAN5G(140, 0), CHAN5G(149, 0), + CHAN5G(153, 0), CHAN5G(157, 0), + CHAN5G(161, 0), CHAN5G(165, 0), + CHAN5G(184, 0), CHAN5G(188, 0), + CHAN5G(192, 0), CHAN5G(196, 0), + CHAN5G(200, 0), CHAN5G(204, 0), + CHAN5G(208, 0), CHAN5G(212, 0), + CHAN5G(216, 0), +}; + +static struct ieee80211_channel __wl_5ghz_n_channels[] = { + CHAN5G(32, 0), CHAN5G(34, 0), + CHAN5G(36, 0), CHAN5G(38, 0), + CHAN5G(40, 0), CHAN5G(42, 0), + CHAN5G(44, 0), CHAN5G(46, 0), + CHAN5G(48, 0), CHAN5G(50, 0), + CHAN5G(52, 0), CHAN5G(54, 0), + CHAN5G(56, 0), CHAN5G(58, 0), + CHAN5G(60, 0), CHAN5G(62, 0), + CHAN5G(64, 0), CHAN5G(66, 0), + CHAN5G(68, 0), CHAN5G(70, 0), + CHAN5G(72, 0), CHAN5G(74, 0), + CHAN5G(76, 0), CHAN5G(78, 0), + CHAN5G(80, 0), CHAN5G(82, 0), + CHAN5G(84, 0), CHAN5G(86, 0), + CHAN5G(88, 0), CHAN5G(90, 0), + CHAN5G(92, 0), CHAN5G(94, 0), + CHAN5G(96, 0), CHAN5G(98, 0), + CHAN5G(100, 0), CHAN5G(102, 0), + CHAN5G(104, 0), CHAN5G(106, 0), + CHAN5G(108, 0), CHAN5G(110, 0), + CHAN5G(112, 0), CHAN5G(114, 0), + CHAN5G(116, 0), CHAN5G(118, 0), + CHAN5G(120, 0), CHAN5G(122, 0), + CHAN5G(124, 0), CHAN5G(126, 0), + CHAN5G(128, 0), CHAN5G(130, 0), + CHAN5G(132, 0), CHAN5G(134, 0), + CHAN5G(136, 0), CHAN5G(138, 0), + CHAN5G(140, 0), CHAN5G(142, 0), + CHAN5G(144, 0), CHAN5G(145, 0), + CHAN5G(146, 0), CHAN5G(147, 0), + CHAN5G(148, 0), CHAN5G(149, 0), + CHAN5G(150, 0), CHAN5G(151, 0), + CHAN5G(152, 0), CHAN5G(153, 0), + CHAN5G(154, 0), CHAN5G(155, 0), + CHAN5G(156, 0), CHAN5G(157, 0), + CHAN5G(158, 0), CHAN5G(159, 0), + CHAN5G(160, 0), CHAN5G(161, 0), + CHAN5G(162, 0), CHAN5G(163, 0), + CHAN5G(164, 0), CHAN5G(165, 0), + CHAN5G(166, 0), CHAN5G(168, 0), + CHAN5G(170, 0), CHAN5G(172, 0), + CHAN5G(174, 0), CHAN5G(176, 0), + CHAN5G(178, 0), CHAN5G(180, 0), + CHAN5G(182, 0), CHAN5G(184, 0), + CHAN5G(186, 0), CHAN5G(188, 0), + CHAN5G(190, 0), CHAN5G(192, 0), + CHAN5G(194, 0), CHAN5G(196, 0), + CHAN5G(198, 0), CHAN5G(200, 0), + CHAN5G(202, 0), CHAN5G(204, 0), + CHAN5G(206, 0), CHAN5G(208, 0), + CHAN5G(210, 0), CHAN5G(212, 0), + CHAN5G(214, 0), CHAN5G(216, 0), + CHAN5G(218, 0), CHAN5G(220, 0), + CHAN5G(222, 0), CHAN5G(224, 0), + CHAN5G(226, 0), CHAN5G(228, 0), +}; + +static struct ieee80211_supported_band __wl_band_2ghz = { + .band = NL80211_BAND_2GHZ, + .channels = __wl_2ghz_channels, + .n_channels = ARRAY_SIZE(__wl_2ghz_channels), + .bitrates = wl_g_rates, + .n_bitrates = wl_g_rates_size, +}; + +static struct ieee80211_supported_band __wl_band_5ghz_a = { + .band = NL80211_BAND_5GHZ, + .channels = __wl_5ghz_a_channels, + .n_channels = ARRAY_SIZE(__wl_5ghz_a_channels), + .bitrates = wl_a_rates, + .n_bitrates = wl_a_rates_size, +}; + +static struct ieee80211_supported_band __wl_band_5ghz_n = { + .band = NL80211_BAND_5GHZ, + .channels = __wl_5ghz_n_channels, + .n_channels = ARRAY_SIZE(__wl_5ghz_n_channels), + .bitrates = wl_a_rates, + .n_bitrates = wl_a_rates_size, +}; + +static const u32 __wl_cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + WLAN_CIPHER_SUITE_AES_CMAC, +}; + +static void key_endian_to_device(struct wl_wsec_key *key) +{ + key->index = htod32(key->index); + key->len = htod32(key->len); + key->algo = htod32(key->algo); + key->flags = htod32(key->flags); + key->rxiv.hi = htod32(key->rxiv.hi); + key->rxiv.lo = htod16(key->rxiv.lo); + key->iv_initialized = htod32(key->iv_initialized); +} + +static void key_endian_to_host(struct wl_wsec_key *key) +{ + key->index = dtoh32(key->index); + key->len = dtoh32(key->len); + key->algo = dtoh32(key->algo); + key->flags = dtoh32(key->flags); + key->rxiv.hi = dtoh32(key->rxiv.hi); + key->rxiv.lo = dtoh16(key->rxiv.lo); + key->iv_initialized = dtoh32(key->iv_initialized); +} + +static s32 +wl_dev_ioctl(struct net_device *dev, u32 cmd, void *arg, u32 len) +{ + return wlc_ioctl_internal(dev, cmd, arg, len); +} + +static s32 +wl_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev, + enum nl80211_iftype type, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0) + u32 *flags, +#endif + struct vif_params *params) +{ + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + struct wireless_dev *wdev; + s32 infra = 0; + s32 ap = 0; + s32 err = 0; + + switch (type) { + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_WDS: + WL_ERR(("type (%d) : currently we do not support this type\n", + type)); + return -EOPNOTSUPP; + case NL80211_IFTYPE_ADHOC: + wl->conf->mode = WL_MODE_IBSS; + break; + case NL80211_IFTYPE_STATION: + wl->conf->mode = WL_MODE_BSS; + infra = 1; + break; + default: + return -EINVAL; + } + infra = htod32(infra); + ap = htod32(ap); + wdev = ndev->ieee80211_ptr; + wdev->iftype = type; + WL_DBG(("%s : ap (%d), infra (%d)\n", ndev->name, ap, infra)); + err = wl_dev_ioctl(ndev, WLC_SET_INFRA, &infra, sizeof(infra)); + if (err) { + WL_ERR(("WLC_SET_INFRA error (%d)\n", err)); + return err; + } + err = wl_dev_ioctl(ndev, WLC_SET_AP, &ap, sizeof(ap)); + if (err) { + WL_ERR(("WLC_SET_AP error (%d)\n", err)); + return err; + } + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0) +static s32 +wl_cfg80211_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +#else +static s32 +wl_cfg80211_scan(struct wiphy *wiphy, + struct net_device *ndev, + struct cfg80211_scan_request *request) +#endif + +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0) + struct net_device *ndev = request->wdev->netdev; +#endif + struct wl_cfg80211_priv *wl = ndev_to_wl(ndev); + struct cfg80211_ssid *ssids; + struct wl_cfg80211_scan_req *sr = wl_to_sr(wl); + s32 passive_scan; + s32 err = 0; + + if (request) { + ssids = request->ssids; + } + else { + + ssids = NULL; + } + wl->scan_request = request; + + memset(&sr->ssid, 0, sizeof(sr->ssid)); + + if (ssids) { + WL_DBG(("ssid \"%s\", ssid_len (%d)\n", ssids->ssid, ssids->ssid_len)); + sr->ssid.SSID_len = min_t(u8, sizeof(sr->ssid.SSID), ssids->ssid_len); + } + + if (sr->ssid.SSID_len) { + memcpy(sr->ssid.SSID, ssids->ssid, sr->ssid.SSID_len); + sr->ssid.SSID_len = htod32(sr->ssid.SSID_len); + WL_DBG(("Specific scan ssid=\"%s\" len=%d\n", sr->ssid.SSID, sr->ssid.SSID_len)); + } else { + WL_DBG(("Broadcast scan\n")); + } + WL_DBG(("sr->ssid.SSID_len (%d)\n", sr->ssid.SSID_len)); + passive_scan = wl->active_scan ? 0 : 1; + err = wl_dev_ioctl(ndev, WLC_SET_PASSIVE_SCAN, &passive_scan, sizeof(passive_scan)); + if (err) { + WL_ERR(("WLC_SET_PASSIVE_SCAN error (%d)\n", err)); + goto scan_out; + } + err = wl_dev_ioctl(ndev, WLC_SCAN, &sr->ssid, sizeof(sr->ssid)); + if (err) { + if (err == -EBUSY) { + WL_INF(("system busy : scan for \"%s\" " + "canceled\n", sr->ssid.SSID)); + } else { + WL_ERR(("WLC_SCAN error (%d)\n", err)); + } + goto scan_out; + } + + return 0; + +scan_out: + wl->scan_request = NULL; + return err; +} + +static s32 wl_dev_intvar_set(struct net_device *dev, s8 *name, s32 val) +{ + s8 buf[WLC_IOCTL_SMLEN]; + u32 len; + s32 err = 0; + + val = htod32(val); + len = bcm_mkiovar(name, (char *)(&val), sizeof(val), buf, sizeof(buf)); + BUG_ON(!len); + + err = wl_dev_ioctl(dev, WLC_SET_VAR, buf, len); + if (err) { + WL_ERR(("error (%d)\n", err)); + } + + return err; +} + +static s32 +wl_dev_intvar_get(struct net_device *dev, s8 *name, s32 *retval) +{ + union { + s8 buf[WLC_IOCTL_SMLEN]; + s32 val; + } var; + u32 len; + u32 data_null; + s32 err = 0; + + len = bcm_mkiovar(name, (char *)(&data_null), 0, (char *)(&var), sizeof(var.buf)); + BUG_ON(!len); + err = wl_dev_ioctl(dev, WLC_GET_VAR, &var, len); + if (err) { + WL_ERR(("error (%d)\n", err)); + } + *retval = dtoh32(var.val); + + return err; +} + +static s32 wl_set_rts(struct net_device *dev, u32 rts_threshold) +{ + s32 err = 0; + + err = wl_dev_intvar_set(dev, "rtsthresh", rts_threshold); + if (err) { + WL_ERR(("Error (%d)\n", err)); + return err; + } + return err; +} + +static s32 wl_set_frag(struct net_device *dev, u32 frag_threshold) +{ + s32 err = 0; + + err = wl_dev_intvar_set(dev, "fragthresh", frag_threshold); + if (err) { + WL_ERR(("Error (%d)\n", err)); + return err; + } + return err; +} + +static s32 wl_set_retry(struct net_device *dev, u32 retry, bool l) +{ + s32 err = 0; + u32 cmd = (l ? WLC_SET_LRL : WLC_SET_SRL); + + retry = htod32(retry); + err = wl_dev_ioctl(dev, cmd, &retry, sizeof(retry)); + if (err) { + WL_ERR(("cmd (%d) , error (%d)\n", cmd, err)); + return err; + } + return err; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 17, 0) +static s32 wl_cfg80211_set_wiphy_params(struct wiphy *wiphy, s32 radio_idx, u32 changed) +#else +static s32 wl_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +#endif +{ + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + struct net_device *ndev = wl_to_ndev(wl); + s32 err = 0; + + if (changed & WIPHY_PARAM_RTS_THRESHOLD && + (wl->conf->rts_threshold != wiphy->rts_threshold)) { + wl->conf->rts_threshold = wiphy->rts_threshold; + err = wl_set_rts(ndev, wl->conf->rts_threshold); + if (!err) + return err; + } + if (changed & WIPHY_PARAM_FRAG_THRESHOLD && + (wl->conf->frag_threshold != wiphy->frag_threshold)) { + wl->conf->frag_threshold = wiphy->frag_threshold; + err = wl_set_frag(ndev, wl->conf->frag_threshold); + if (!err) + return err; + } + if (changed & WIPHY_PARAM_RETRY_LONG && (wl->conf->retry_long != wiphy->retry_long)) { + wl->conf->retry_long = wiphy->retry_long; + err = wl_set_retry(ndev, wl->conf->retry_long, true); + if (!err) + return err; + } + if (changed & WIPHY_PARAM_RETRY_SHORT && (wl->conf->retry_short != wiphy->retry_short)) { + wl->conf->retry_short = wiphy->retry_short; + err = wl_set_retry(ndev, wl->conf->retry_short, false); + if (!err) { + return err; + } + } + + return err; +} + +static s32 +wl_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ibss_params *params) +{ + struct wl_join_params join_params; + size_t join_params_size; + s32 val; + s32 err = 0; + + WL_DBG(("\n")); + + if (params->bssid) { + WL_ERR(("Invalid bssid\n")); + return -EOPNOTSUPP; + } + + if ((err = wl_dev_intvar_set(dev, "auth", 0))) { + return err; + } + if ((err = wl_dev_intvar_set(dev, "wpa_auth", WPA_AUTH_NONE))) { + return err; + } + if ((err = wl_dev_intvar_get(dev, "wsec", &val))) { + return err; + } + val &= ~(WEP_ENABLED | TKIP_ENABLED | AES_ENABLED); + if ((err = wl_dev_intvar_set(dev, "wsec", val))) { + return err; + } + + memset(&join_params, 0, sizeof(join_params)); + join_params_size = sizeof(join_params.ssid); + + memcpy((void *)join_params.ssid.SSID, (void *)params->ssid, params->ssid_len); + join_params.ssid.SSID_len = htod32(params->ssid_len); + if (params->bssid) + memcpy(&join_params.params.bssid, params->bssid, ETHER_ADDR_LEN); + else + memset(&join_params.params.bssid, 0, ETHER_ADDR_LEN); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) + wl_ch_to_chanspec(params->chandef.chan, &join_params, &join_params_size); +#else + wl_ch_to_chanspec(params->channel, &join_params, &join_params_size); +#endif + err = wl_dev_ioctl(dev, WLC_SET_SSID, &join_params, join_params_size); + if (err) { + WL_ERR(("Error (%d)\n", err)); + return err; + } + return err; +} + +static s32 wl_cfg80211_leave_ibss(struct wiphy *wiphy, struct net_device *dev) +{ + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + s32 err = 0; + + WL_DBG(("\n")); + + wl_link_down(wl); + + return err; +} + +static s32 +wl_set_wpa_version(struct net_device *dev, struct cfg80211_connect_params *sme) +{ + struct wl_cfg80211_priv *wl = ndev_to_wl(dev); + s32 val = 0; + s32 err = 0; + + if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1) + val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED; + else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2) + val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; + else + val = WPA_AUTH_DISABLED; + WL_DBG(("setting wpa_auth to 0x%0x\n", val)); + err = wl_dev_intvar_set(dev, "wpa_auth", val); + if (err) { + WL_ERR(("set wpa_auth failed (%d)\n", err)); + return err; + } + wl->profile->sec.wpa_versions = sme->crypto.wpa_versions; + return err; +} + +static s32 +wl_set_auth_type(struct net_device *dev, struct cfg80211_connect_params *sme) +{ + struct wl_cfg80211_priv *wl = ndev_to_wl(dev); + s32 val = 0; + s32 err = 0; + + switch (sme->auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + val = 0; + WL_DBG(("open system\n")); + break; + case NL80211_AUTHTYPE_SHARED_KEY: + val = 1; + WL_DBG(("shared key\n")); + break; + case NL80211_AUTHTYPE_AUTOMATIC: + val = 2; + WL_DBG(("automatic\n")); + break; + case NL80211_AUTHTYPE_NETWORK_EAP: + val = 2; + WL_DBG(("network eap\n")); + break; + default: + val = 2; + WL_ERR(("invalid auth type (%d)\n", sme->auth_type)); + break; + } + + err = wl_dev_intvar_set(dev, "auth", val); + if (err) { + WL_ERR(("set auth failed (%d)\n", err)); + return err; + } + + wl->profile->sec.auth_type = sme->auth_type; + return err; +} + +static s32 +wl_set_set_cipher(struct net_device *dev, struct cfg80211_connect_params *sme) +{ + struct wl_cfg80211_priv *wl = ndev_to_wl(dev); + s32 pval = 0; + s32 gval = 0; + s32 val = 0; + s32 err = 0; + + if (sme->crypto.n_ciphers_pairwise) { + switch (sme->crypto.ciphers_pairwise[0]) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + pval = WEP_ENABLED; + break; + case WLAN_CIPHER_SUITE_TKIP: + pval = TKIP_ENABLED; + break; + case WLAN_CIPHER_SUITE_CCMP: + pval = AES_ENABLED; + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + pval = AES_ENABLED; + break; + default: + WL_ERR(("invalid cipher pairwise (%d)\n", sme->crypto.ciphers_pairwise[0])); + return -EINVAL; + } + } + if (sme->crypto.cipher_group) { + switch (sme->crypto.cipher_group) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + gval = WEP_ENABLED; + break; + case WLAN_CIPHER_SUITE_TKIP: + gval = TKIP_ENABLED; + break; + case WLAN_CIPHER_SUITE_CCMP: + gval = AES_ENABLED; + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + gval = AES_ENABLED; + break; + default: + WL_ERR(("invalid cipher group (%d)\n", sme->crypto.cipher_group)); + return -EINVAL; + } + } + + if ((err = wl_dev_intvar_get(dev, "wsec", &val))) { + return err; + } + val &= ~(WEP_ENABLED | TKIP_ENABLED | AES_ENABLED); + val |= pval | gval; + WL_DBG(("set wsec to %d\n", val)); + err = wl_dev_intvar_set(dev, "wsec", val); + if (err) { + WL_ERR(("error (%d)\n", err)); + return err; + } + + wl->profile->sec.cipher_pairwise = sme->crypto.ciphers_pairwise[0]; + wl->profile->sec.cipher_group = sme->crypto.cipher_group; + + return err; +} + +static s32 +wl_set_key_mgmt(struct net_device *dev, struct cfg80211_connect_params *sme) +{ + struct wl_cfg80211_priv *wl = ndev_to_wl(dev); + s32 val = 0; + s32 err = 0; + + if (sme->crypto.n_akm_suites) { + err = wl_dev_intvar_get(dev, "wpa_auth", &val); + if (err) { + WL_ERR(("could not get wpa_auth (%d)\n", err)); + return err; + } + if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED)) { + switch (sme->crypto.akm_suites[0]) { + case WLAN_AKM_SUITE_8021X: + val = WPA_AUTH_UNSPECIFIED; + break; + case WLAN_AKM_SUITE_PSK: + val = WPA_AUTH_PSK; + break; + default: + WL_ERR(("invalid cipher group (%d)\n", sme->crypto.cipher_group)); + return -EINVAL; + } + } else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) { + switch (sme->crypto.akm_suites[0]) { + case WLAN_AKM_SUITE_8021X: + val = WPA2_AUTH_UNSPECIFIED; + break; + case WLAN_AKM_SUITE_PSK: + val = WPA2_AUTH_PSK; + break; + default: + WL_ERR(("invalid cipher group (%d)\n", sme->crypto.cipher_group)); + return -EINVAL; + } + } + + WL_DBG(("setting wpa_auth to %d\n", val)); + err = wl_dev_intvar_set(dev, "wpa_auth", val); + if (err) { + WL_ERR(("could not set wpa_auth (%d)\n", err)); + return err; + } + } + + wl->profile->sec.wpa_auth = sme->crypto.akm_suites[0]; + + return err; +} + +static s32 +wl_set_set_sharedkey(struct net_device *dev, struct cfg80211_connect_params *sme) +{ + struct wl_cfg80211_priv *wl = ndev_to_wl(dev); + struct wl_cfg80211_security *sec; + struct wl_wsec_key key; + s32 err = 0; + + WL_DBG(("key len (%d)\n", sme->key_len)); + if (sme->key_len) { + sec = &wl->profile->sec; + WL_DBG(("wpa_versions 0x%x cipher_pairwise 0x%x\n", + sec->wpa_versions, sec->cipher_pairwise)); + if (!(sec->wpa_versions & (NL80211_WPA_VERSION_1 | NL80211_WPA_VERSION_2)) && + (sec->cipher_pairwise & + (WLAN_CIPHER_SUITE_WEP40 | WLAN_CIPHER_SUITE_WEP104))) { + memset(&key, 0, sizeof(key)); + key.len = (u32) sme->key_len; + key.index = (u32) sme->key_idx; + if (key.len > sizeof(key.data)) { + WL_ERR(("Too long key length (%u)\n", key.len)); + return -EINVAL; + } + memcpy(key.data, sme->key, key.len); + key.flags = WL_PRIMARY_KEY; + switch (sec->cipher_pairwise) { + case WLAN_CIPHER_SUITE_WEP40: + key.algo = CRYPTO_ALGO_WEP1; + break; + case WLAN_CIPHER_SUITE_WEP104: + key.algo = CRYPTO_ALGO_WEP128; + break; + default: + WL_ERR(("Invalid algorithm (%d)\n", + sme->crypto.ciphers_pairwise[0])); + return -EINVAL; + } + + WL_DBG(("key length (%d) key index (%d) algo (%d)\n", key.len, + key.index, key.algo)); + WL_DBG(("key \"%s\"\n", key.data)); + key_endian_to_device(&key); + err = wl_dev_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)); + if (err) { + WL_ERR(("WLC_SET_KEY error (%d)\n", err)); + return err; + } + } + } + return err; +} + +static s32 +wl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_connect_params *sme) +{ + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + struct wl_join_params join_params; + size_t join_params_size; + char valc; + s32 err = 0; + + if (!sme->ssid) { + WL_ERR(("Invalid ssid\n")); + return -EOPNOTSUPP; + } + + WL_DBG(("ie (%p), ie_len (%zd)\n", sme->ie, sme->ie_len)); + + err = wl_set_auth_type(dev, sme); + if (err) + return err; + + err = wl_set_wpa_version(dev, sme); + if (err) + return err; + + err = wl_set_set_cipher(dev, sme); + if (err) + return err; + + err = wl_set_key_mgmt(dev, sme); + if (err) + return err; + + err = wl_set_set_sharedkey(dev, sme); + if (err) + return err; + + valc = 1; + wl_dev_bufvar_set(dev, "wsec_restrict", &valc, 1); + + if (sme->bssid) { + memcpy(wl->profile->bssid, sme->bssid, ETHER_ADDR_LEN); + } + else { + memset(wl->profile->bssid, 0, ETHER_ADDR_LEN); + } + + memset(&join_params, 0, sizeof(join_params)); + join_params_size = sizeof(join_params.ssid); + + join_params.ssid.SSID_len = min(sizeof(join_params.ssid.SSID), sme->ssid_len); + memcpy(&join_params.ssid.SSID, sme->ssid, join_params.ssid.SSID_len); + join_params.ssid.SSID_len = htod32(join_params.ssid.SSID_len); + memcpy(&join_params.params.bssid, ðer_bcast, ETHER_ADDR_LEN); + + memcpy(wl->profile->ssid.SSID, &join_params.ssid.SSID, join_params.ssid.SSID_len); + wl->profile->ssid.SSID_len = join_params.ssid.SSID_len; + + wl_ch_to_chanspec(sme->channel, &join_params, &join_params_size); + WL_DBG(("join_param_size %u\n", (unsigned int)join_params_size)); + + if (join_params.ssid.SSID_len < IEEE80211_MAX_SSID_LEN) { + WL_DBG(("ssid \"%s\", len (%d)\n", join_params.ssid.SSID, + join_params.ssid.SSID_len)); + } + err = wl_dev_ioctl(dev, WLC_SET_SSID, &join_params, join_params_size); + if (err) { + WL_ERR(("error (%d)\n", err)); + return err; + } + + set_bit(WL_STATUS_CONNECTING, &wl->status); + + return err; +} + +static s32 +wl_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev, u16 reason_code) +{ + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + scb_val_t scbval; + s32 err = 0; + + WL_DBG(("Reason %d\n", reason_code)); + + if (wl->profile->active) { + scbval.val = reason_code; + memcpy(&scbval.ea, &wl->bssid, ETHER_ADDR_LEN); + scbval.val = htod32(scbval.val); + err = wl_dev_ioctl(dev, WLC_DISASSOC, &scbval, sizeof(scb_val_t)); + if (err) { + WL_ERR(("error (%d)\n", err)); + return err; + } + } + + return err; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 17, 0) +static s32 wl_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + s32 radio_idx, enum nl80211_tx_power_setting type, s32 dbm) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) +static s32 +wl_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + enum nl80211_tx_power_setting type, s32 dbm) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) +static s32 +wl_cfg80211_set_tx_power(struct wiphy *wiphy, enum nl80211_tx_power_setting type, s32 dbm) +#else +#define NL80211_TX_POWER_AUTOMATIC TX_POWER_AUTOMATIC +#define NL80211_TX_POWER_LIMITED TX_POWER_LIMITED +#define NL80211_TX_POWER_FIXED TX_POWER_FIXED +static s32 +wl_cfg80211_set_tx_power(struct wiphy *wiphy, enum tx_power_setting type, s32 dbm) +#endif +{ + + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + struct net_device *ndev = wl_to_ndev(wl); + u16 txpwrmw; + s32 err = 0; + s32 disable = 0; + + switch (type) { + case NL80211_TX_POWER_AUTOMATIC: + break; + case NL80211_TX_POWER_LIMITED: + if (dbm < 0) { + WL_ERR(("TX_POWER_LIMITTED - dbm is negative\n")); + return -EINVAL; + } + break; + case NL80211_TX_POWER_FIXED: + if (dbm < 0) { + WL_ERR(("TX_POWER_FIXED - dbm is negative..\n")); + return -EINVAL; + } + break; + } + + disable = WL_RADIO_SW_DISABLE << 16; + disable = htod32(disable); + err = wl_dev_ioctl(ndev, WLC_SET_RADIO, &disable, sizeof(disable)); + if (err) { + WL_ERR(("WLC_SET_RADIO error (%d)\n", err)); + return err; + } + + if (dbm > 0xffff) + txpwrmw = 0xffff; + else + txpwrmw = (u16) dbm; + err = wl_dev_intvar_set(ndev, "qtxpower", (s32) (bcm_mw_to_qdbm(txpwrmw))); + if (err) { + WL_ERR(("qtxpower error (%d)\n", err)); + return err; + } + wl->conf->tx_power = dbm; + + return err; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 17, 0) +static s32 wl_cfg80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, s32 radio_idx, + u32 /*link_id*/, s32 *dbm) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0) +static s32 wl_cfg80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, u32 /*link_id*/, s32 *dbm) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) +static s32 wl_cfg80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, s32 *dbm) +#else +static s32 wl_cfg80211_get_tx_power(struct wiphy *wiphy, s32 *dbm) +#endif +{ + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + struct net_device *ndev = wl_to_ndev(wl); + s32 txpwrdbm; + u8 result; + s32 err = 0; + + err = wl_dev_intvar_get(ndev, "qtxpower", &txpwrdbm); + if (err) { + WL_ERR(("error (%d)\n", err)); + return err; + } + result = (u8) (txpwrdbm & ~WL_TXPWR_OVERRIDE); + *dbm = (s32) bcm_qdbm_to_mw(result); + + return err; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +static s32 +wl_cfg80211_config_default_key(struct wiphy *wiphy, + struct net_device *dev, int link_id, u8 key_idx, bool unicast, + bool multicast) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 38) +static s32 +wl_cfg80211_config_default_key(struct wiphy *wiphy, + struct net_device *dev, u8 key_idx, bool unicast, bool multicast) +#else +static s32 +wl_cfg80211_config_default_key(struct wiphy *wiphy, + struct net_device *dev, u8 key_idx) +#endif +{ + u32 index; + s32 err = 0; + + WL_DBG(("key index (%d)\n", key_idx)); + + index = (u32) key_idx; + index = htod32(index); + err = wl_dev_ioctl(dev, WLC_SET_KEY_PRIMARY, &index, sizeof(index)); + if (err) { + WL_DBG(("error (%d)\n", err)); + } + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +static s32 +wl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev, + int link_id, u8 key_idx, bool pairwise, const u8 *mac_addr, + struct key_params *params) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) +static s32 +wl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_idx, bool pairwise, const u8 *mac_addr, struct key_params *params) +#else +static s32 +wl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_idx, const u8 *mac_addr, struct key_params *params) +#endif +{ + struct wl_cfg80211_priv *wl = ndev_to_wl(dev); + struct wl_wsec_key key; + s32 secval, secnew = 0; + s32 err = 0; + + WL_DBG(("key index %u len %u\n", (unsigned)key_idx, params->key_len)); + + memset(&key, 0, sizeof(key)); + + key.index = (u32) key_idx; + + switch (params->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + key.algo = CRYPTO_ALGO_WEP1; + secnew = WEP_ENABLED; + WL_DBG(("WLAN_CIPHER_SUITE_WEP40\n")); + break; + case WLAN_CIPHER_SUITE_WEP104: + key.algo = CRYPTO_ALGO_WEP128; + secnew = WEP_ENABLED; + WL_DBG(("WLAN_CIPHER_SUITE_WEP104\n")); + break; + case WLAN_CIPHER_SUITE_TKIP: + key.algo = CRYPTO_ALGO_TKIP; + secnew = TKIP_ENABLED; + WL_DBG(("WLAN_CIPHER_SUITE_TKIP\n")); + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + key.algo = CRYPTO_ALGO_AES_CCM; + secnew = AES_ENABLED; + WL_DBG(("WLAN_CIPHER_SUITE_AES_CMAC\n")); + break; + case WLAN_CIPHER_SUITE_CCMP: + key.algo = CRYPTO_ALGO_AES_CCM; + secnew = AES_ENABLED; + WL_DBG(("WLAN_CIPHER_SUITE_CCMP\n")); + break; + default: + WL_ERR(("Invalid cipher (0x%x)\n", params->cipher)); + return -EINVAL; + } + + if (mac_addr) { + if (!ETHER_ISMULTI(mac_addr)) { + memcpy((char *)&key.ea, (void *)mac_addr, ETHER_ADDR_LEN); + } + } + + key.len = (u32) params->key_len; + if (key.len > sizeof(key.data)) { + WL_ERR(("Too long key length (%u)\n", key.len)); + return -EINVAL; + } + memcpy(key.data, params->key, key.len); + + if (params->cipher == WLAN_CIPHER_SUITE_TKIP) { + u8 keybuf[8]; + memcpy(keybuf, &key.data[24], sizeof(keybuf)); + memcpy(&key.data[24], &key.data[16], sizeof(keybuf)); + memcpy(&key.data[16], keybuf, sizeof(keybuf)); + } + + if (params->seq_len) { + u8 *ivptr; + if (params->seq_len != 6) { + WL_ERR(("seq_len %d is unexpected, check implementation.\n", + params->seq_len)); + } + ivptr = (u8 *) params->seq; + key.rxiv.hi = (ivptr[5] << 24) | (ivptr[4] << 16) | (ivptr[3] << 8) | ivptr[2]; + key.rxiv.lo = (ivptr[1] << 8) | ivptr[0]; + key.iv_initialized = true; + } + + key_endian_to_device(&key); + if (wl->passive) { + schedule(); + } + err = wl_dev_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)); + if (err) { + WL_ERR(("WLC_SET_KEY error (%d)\n", err)); + return err; + } + + if ((err = wl_dev_intvar_get(dev, "wsec", &secval))) { + return err; + } + if (secnew == WEP_ENABLED) { + secval &= ~(TKIP_ENABLED | AES_ENABLED); + } + else { + secval &= ~(WEP_ENABLED); + } + secval |= secnew; + WL_DBG(("set wsec to %d\n", secval)); + err = wl_dev_intvar_set(dev, "wsec", secval); + if (err) { + WL_ERR(("error (%d)\n", err)); + return err; + } + + if (mac_addr) { + wl->profile->sec.cipher_pairwise = params->cipher; + } + else { + wl->profile->sec.cipher_group = params->cipher; + } + + return err; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +static s32 +wl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev, + int link_id, u8 key_idx, bool pairwise, const u8 *mac_addr) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) +static s32 +wl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_idx, bool pairwise, const u8 *mac_addr) +#else +static s32 +wl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_idx, const u8 *mac_addr) +#endif +{ + struct wl_wsec_key key; + s32 err = 0; + + WL_DBG(("key index (%d)\n", key_idx)); + + memset(&key, 0, sizeof(key)); + + key.index = (u32) key_idx; + key.len = 0; + if (mac_addr) { + if (!ETHER_ISMULTI(mac_addr)) { + memcpy((char *)&key.ea, (void *)mac_addr, ETHER_ADDR_LEN); + } + } + key.algo = CRYPTO_ALGO_OFF; + + key_endian_to_device(&key); + err = wl_dev_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)); + if (err) { + if (err == -EINVAL) { + if (key.index >= DOT11_MAX_DEFAULT_KEYS) { + + WL_DBG(("invalid key index (%d)\n", key_idx)); + } + } else { + WL_ERR(("WLC_SET_KEY error (%d)\n", err)); + } + return err; + } + + return err; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +static s32 +wl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev, + int link_id, u8 key_idx, bool pairwise, const u8 *mac_addr, + void *cookie, + void (*callback) (void *cookie, struct key_params * params)) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) +static s32 +wl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_idx, bool pairwise, const u8 *mac_addr, void *cookie, + void (*callback) (void *cookie, struct key_params * params)) +#else +static s32 +wl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_idx, const u8 *mac_addr, void *cookie, + void (*callback) (void *cookie, struct key_params * params)) +#endif +{ + struct key_params params; + struct wl_wsec_key key; + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + struct wl_cfg80211_security *sec; + s32 wsec; + s32 err = 0; + + WL_DBG(("key index (%d)\n", key_idx)); + + memset(¶ms, 0, sizeof(params)); + + memset(&key, 0, sizeof(key)); + key.index = key_idx; + key_endian_to_device(&key); + + if ((err = wl_dev_ioctl(dev, WLC_GET_KEY, &key, sizeof(key)))) { + return err; + } + key_endian_to_host(&key); + + params.key_len = (u8) min_t(u8, DOT11_MAX_KEY_SIZE, key.len); + memcpy((char *)params.key, key.data, params.key_len); + + if ((err = wl_dev_ioctl(dev, WLC_GET_WSEC, &wsec, sizeof(wsec)))) { + return err; + } + wsec = dtoh32(wsec); + switch (wsec) { + case WEP_ENABLED: + sec = &wl->profile->sec; + if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP40) { + params.cipher = WLAN_CIPHER_SUITE_WEP40; + WL_DBG(("WLAN_CIPHER_SUITE_WEP40\n")); + } else if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP104) { + params.cipher = WLAN_CIPHER_SUITE_WEP104; + WL_DBG(("WLAN_CIPHER_SUITE_WEP104\n")); + } + break; + case TKIP_ENABLED: + params.cipher = WLAN_CIPHER_SUITE_TKIP; + WL_DBG(("WLAN_CIPHER_SUITE_TKIP\n")); + break; + case AES_ENABLED: + params.cipher = WLAN_CIPHER_SUITE_AES_CMAC; + WL_DBG(("WLAN_CIPHER_SUITE_AES_CMAC\n")); + break; + default: + WL_ERR(("Invalid algo (0x%x)\n", wsec)); + return -EINVAL; + } + + callback(cookie, ¶ms); + return err; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0) +static s32 +wl_cfg80211_get_station(struct wiphy *wiphy, struct net_device *dev, + u8 *mac, struct station_info *sinfo) +#else +static s32 +wl_cfg80211_get_station(struct wiphy *wiphy, struct net_device *dev, + const u8 *mac, struct station_info *sinfo) +#endif +{ + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + scb_val_t scb_val; + int rssi; + s32 rate; + s32 err = 0; + + if (memcmp(mac, wl->profile->bssid, ETHER_ADDR_LEN)) { + WL_ERR(("Wrong Mac address, mac = %pM profile =%pM\n", mac, wl->profile->bssid)); + } + + err = wl_dev_ioctl(dev, WLC_GET_RATE, &rate, sizeof(rate)); + if (err) { + WL_DBG(("Could not get rate (%d)\n", err)); + } else { + rate = dtoh32(rate); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) + sinfo->filled |= BIT(NL80211_STA_INFO_TX_BITRATE); +#else + sinfo->filled |= STATION_INFO_TX_BITRATE; +#endif + sinfo->txrate.legacy = rate * 5; + WL_DBG(("Rate %d Mbps\n", (rate / 2))); + } + + if (test_bit(WL_STATUS_CONNECTED, &wl->status)) { + memset(&scb_val, 0, sizeof(scb_val)); + err = wl_dev_ioctl(dev, WLC_GET_RSSI, &scb_val, sizeof(scb_val_t)); + if (err) { + WL_DBG(("Could not get rssi (%d)\n", err)); + return err; + } + rssi = dtoh32(scb_val.val); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) + sinfo->filled |= BIT(NL80211_STA_INFO_SIGNAL); +#else + sinfo->filled |= STATION_INFO_SIGNAL; +#endif + sinfo->signal = rssi; + WL_DBG(("RSSI %d dBm\n", rssi)); + } + + return err; +} + +static s32 +wl_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, + bool enabled, s32 timeout) +{ + s32 pm; + s32 err = 0; + + pm = enabled ? PM_FAST : PM_OFF; + pm = htod32(pm); + WL_DBG(("power save %s\n", (pm ? "enabled" : "disabled"))); + err = wl_dev_ioctl(dev, WLC_SET_PM, &pm, sizeof(pm)); + if (err) { + if (err == -ENODEV) + WL_DBG(("net_device is not ready yet\n")); + else + WL_ERR(("error (%d)\n", err)); + return err; + } + return err; +} + +static __used s32 +wl_update_pmklist(struct net_device *dev, struct wl_cfg80211_pmk_list *pmk_list, s32 err) +{ + int i, j; + + WL_DBG(("No of elements %d\n", pmk_list->pmkids.npmkid)); + for (i = 0; i < pmk_list->pmkids.npmkid; i++) { + WL_DBG(("PMKID[%d]: %pM =\n", i, + &pmk_list->pmkids.pmkid[i].BSSID)); + for (j = 0; j < WPA2_PMKID_LEN; j++) { + WL_DBG(("%02x\n", pmk_list->pmkids.pmkid[i].PMKID[j])); + } + } + if (!err) { + err = wl_dev_bufvar_set(dev, "pmkid_info", (char *)pmk_list, sizeof(*pmk_list)); + } + + return err; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) + +static s32 +wl_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_pmksa *pmksa) +{ + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + s32 err = 0; + int i; + + for (i = 0; i < wl->pmk_list->pmkids.npmkid; i++) + if (!memcmp(pmksa->bssid, &wl->pmk_list->pmkids.pmkid[i].BSSID, ETHER_ADDR_LEN)) + break; + if (i < WL_NUM_PMKIDS_MAX) { + memcpy(&wl->pmk_list->pmkids.pmkid[i].BSSID, pmksa->bssid, ETHER_ADDR_LEN); + memcpy(&wl->pmk_list->pmkids.pmkid[i].PMKID, pmksa->pmkid, WPA2_PMKID_LEN); + if (i == wl->pmk_list->pmkids.npmkid) + wl->pmk_list->pmkids.npmkid++; + } else { + err = -EINVAL; + } + WL_DBG(("set_pmksa,IW_PMKSA_ADD - PMKID: %pM =\n", + &wl->pmk_list->pmkids.pmkid[wl->pmk_list->pmkids.npmkid].BSSID)); + for (i = 0; i < WPA2_PMKID_LEN; i++) { + WL_DBG(("%02x\n", + wl->pmk_list->pmkids.pmkid[wl->pmk_list->pmkids.npmkid].PMKID[i])); + } + + err = wl_update_pmklist(dev, wl->pmk_list, err); + + return err; +} + +static s32 +wl_cfg80211_del_pmksa(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_pmksa *pmksa) +{ + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + struct _pmkid_list pmkid; + s32 err = 0; + int i; + + memcpy(&pmkid.pmkid[0].BSSID, pmksa->bssid, ETHER_ADDR_LEN); + memcpy(&pmkid.pmkid[0].PMKID, pmksa->pmkid, WPA2_PMKID_LEN); + + WL_DBG(("del_pmksa,IW_PMKSA_REMOVE - PMKID: %pM =\n", + &pmkid.pmkid[0].BSSID)); + for (i = 0; i < WPA2_PMKID_LEN; i++) { + WL_DBG(("%02x\n", pmkid.pmkid[0].PMKID[i])); + } + + for (i = 0; i < wl->pmk_list->pmkids.npmkid; i++) + if (!memcmp(pmksa->bssid, &wl->pmk_list->pmkids.pmkid[i].BSSID, ETHER_ADDR_LEN)) + break; + + if ((wl->pmk_list->pmkids.npmkid > 0) && (i < wl->pmk_list->pmkids.npmkid)) { + memset(&wl->pmk_list->pmkids.pmkid[i], 0, sizeof(pmkid_t)); + for (; i < (wl->pmk_list->pmkids.npmkid - 1); i++) { + memcpy(&wl->pmk_list->pmkids.pmkid[i].BSSID, + &wl->pmk_list->pmkids.pmkid[i + 1].BSSID, ETHER_ADDR_LEN); + memcpy(&wl->pmk_list->pmkids.pmkid[i].PMKID, + &wl->pmk_list->pmkids.pmkid[i + 1].PMKID, WPA2_PMKID_LEN); + } + wl->pmk_list->pmkids.npmkid--; + } else { + err = -EINVAL; + } + + err = wl_update_pmklist(dev, wl->pmk_list, err); + + return err; + +} + +static s32 +wl_cfg80211_flush_pmksa(struct wiphy *wiphy, struct net_device *dev) +{ + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + s32 err = 0; + + memset(wl->pmk_list, 0, sizeof(*wl->pmk_list)); + err = wl_update_pmklist(dev, wl->pmk_list, err); + return err; + +} + +#endif + +#ifdef CONFIG_PM +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) + +static int +wl_wowl_ind_wake_reason(struct wl_cfg80211_priv *wl, struct cfg80211_wowlan_wakeup *wakeup) +{ + wl_wowl_wakeind_t wowl_ind; + s32 err; + + err = wl_dev_bufvar_get(wl_to_ndev(wl), "wowl_wakeind", + (s8 *)&wowl_ind, sizeof(wowl_ind)); + if (err != 0) { + WL_ERR(("Unable to get wake reason, err = %d\n", err)); + return -1; + } + + if (wowl_ind.ucode_wakeind == 0) { + WL_DBG(("System woke, but not by us\n")); + return 0; + } + WL_DBG(("wake reason is 0x%x\n", wowl_ind.ucode_wakeind)); + + if (wowl_ind.ucode_wakeind & WL_WOWL_MAGIC) { + WL_ERR(("WOWLAN Woke for: Magic Pkt\n")); + wakeup->magic_pkt = true; + } + if (wowl_ind.ucode_wakeind & WL_WOWL_DIS) { + WL_ERR(("WOWLAN Woke for: Disconnect\n")); + wakeup->disconnect = true; + } + if (wowl_ind.ucode_wakeind & WL_WOWL_BCN) { + WL_ERR(("WOWLAN Woke for: Beacon Loss\n")); + wakeup->disconnect = true; + } + if (wowl_ind.ucode_wakeind & WL_WOWL_GTK_FAILURE) { + WL_ERR(("WOWLAN Woke for: GTK failure\n")); + wakeup->gtk_rekey_failure = true; + } + if (wowl_ind.ucode_wakeind & WL_WOWL_EAPID) { + WL_ERR(("WOWLAN Woke for: EAP identify request\n")); + wakeup->eap_identity_req = true; + } + if (wowl_ind.ucode_wakeind & WL_WOWL_M1) { + WL_ERR(("WOWLAN Woke for: 4-way handshake request\n")); + wakeup->four_way_handshake = true; + } + return 1; +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0) +static int +wl_cfg80211_rekey(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_gtk_rekey_data *data) +{ + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + wlc_rekey_info_t rekey; + s32 err; + + if (!wl->offloads) { + return 0; + } + + memset(&rekey, 0, sizeof(rekey)); + memcpy(&rekey.kek, data->kek, WLC_KEK_LEN); + memcpy(&rekey.kck, data->kck, WLC_KCK_LEN); + memcpy(&rekey.replay_counter, data->replay_ctr, WLC_REPLAY_CTR_LEN); + WL_INF(("Send down replay counter %x%x%x%x%x%x%x%x\n", + rekey.replay_counter[0], rekey.replay_counter[1], rekey.replay_counter[2], + rekey.replay_counter[3], rekey.replay_counter[4], rekey.replay_counter[5], + rekey.replay_counter[6], rekey.replay_counter[7])); + + err = wl_dev_bufvar_set(wl_to_ndev(wl), "wowl_replay", (s8 *)&rekey, sizeof(rekey)); + if (err) { + WL_ERR(("Error calling wowl_set_key\n")); + return err; + } + return err; +} +#endif + +static int wl_cfg80211_suspend(struct wiphy *wiphy, struct cfg80211_wowlan *wowlan) +{ + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + uint wowl = 0; + s32 err; + + if (!wowlan) { + WL_DBG(("No wowlan requested\n")); + return 0; + } + if (!test_bit(WL_STATUS_CONNECTED, &wl->status)) { + WL_INF(("No wowl when not associated.\n")); + return 0; + } + + err = wl_dev_intvar_get(wl_to_ndev(wl), "wowl", &wowl); + if (err) { + WL_ERR(("Error fetching WOWL %d\n", err)); + } + if (wowlan->disconnect) { + WL_INF(("Requesting wake on Disconnect\n")); + wowl |= WL_WOWL_DIS | WL_WOWL_BCN; + } + if (wowlan->magic_pkt) { + WL_INF(("Requesting wake on Magic Pkt\n")); + wowl |= WL_WOWL_MAGIC; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0) + if (wowlan->gtk_rekey_failure) { + WL_INF(("Requesting wake GTK rekey failure Pkt\n")); + wowl |= WL_WOWL_GTK_FAILURE; + } + if (wowlan->four_way_handshake) { + WL_INF(("Requesting wake on 4way handshake request\n")); + wowl |= WL_WOWL_M1; + } +#endif + + wowl |= WL_WOWL_KEYROT; + + err = wl_dev_intvar_set(wl_to_ndev(wl), "wowl", wowl); + if (err) { + WL_ERR(("Error enabling WOWL %d\n", err)); + } + + return err; +} +#else +static int wl_cfg80211_suspend(struct wiphy *wiphy) +{ + return 0; +} +#endif + +static int wl_cfg80211_resume(struct wiphy *wiphy) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0) + struct wl_cfg80211_priv *wl = wiphy_to_wl(wiphy); + wlc_rekey_info_t *rekey = (wlc_rekey_info_t *)wl->extra_buf; + s32 err; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) + struct cfg80211_wowlan_wakeup wakeup; + int result; + + memset(&wakeup, 0, sizeof(wakeup)); + wakeup.pattern_idx = -1; + + result = wl_wowl_ind_wake_reason(wl, &wakeup); + switch (result) { + case -1: + break; + case 0: + cfg80211_report_wowlan_wakeup(wl_to_wdev(wl), NULL, GFP_KERNEL); + break; + case 1: + cfg80211_report_wowlan_wakeup(wl_to_wdev(wl), &wakeup, GFP_KERNEL); + break; + } +#endif + + err = wl_dev_bufvar_get(wl_to_ndev(wl), "wowl_replay", (s8 *)rekey, + sizeof(wlc_rekey_info_t)); + if (!err) { + WL_INF(("Send up replay counter %x%x%x%x%x%x%x%x\n", + rekey->replay_counter[0], rekey->replay_counter[1], + rekey->replay_counter[2], rekey->replay_counter[3], + rekey->replay_counter[4], rekey->replay_counter[5], + rekey->replay_counter[6], rekey->replay_counter[7])); + cfg80211_gtk_rekey_notify(wl_to_ndev(wl), (u8 *)&wl->bssid.octet, + rekey->replay_counter, GFP_KERNEL); + } +#endif + return 0; +} +#endif + +static struct cfg80211_ops wl_cfg80211_ops = { + .change_virtual_intf = wl_cfg80211_change_iface, + .scan = wl_cfg80211_scan, + .set_wiphy_params = wl_cfg80211_set_wiphy_params, + .join_ibss = wl_cfg80211_join_ibss, + .leave_ibss = wl_cfg80211_leave_ibss, + .get_station = wl_cfg80211_get_station, + .set_tx_power = wl_cfg80211_set_tx_power, + .get_tx_power = wl_cfg80211_get_tx_power, + .add_key = wl_cfg80211_add_key, + .del_key = wl_cfg80211_del_key, + .get_key = wl_cfg80211_get_key, + .set_default_key = wl_cfg80211_config_default_key, + .set_power_mgmt = wl_cfg80211_set_power_mgmt, + .connect = wl_cfg80211_connect, + .disconnect = wl_cfg80211_disconnect, +#ifdef CONFIG_PM + .suspend = wl_cfg80211_suspend, + .resume = wl_cfg80211_resume, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0) + .set_rekey_data = wl_cfg80211_rekey, +#endif +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) + .set_pmksa = wl_cfg80211_set_pmksa, + .del_pmksa = wl_cfg80211_del_pmksa, + .flush_pmksa = wl_cfg80211_flush_pmksa +#endif +}; + +#ifdef CONFIG_PM +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) +static const struct wiphy_wowlan_support wl_wowlan_support = { +#else +static struct wiphy_wowlan_support wl_wowlan_support = { +#endif + .flags = WIPHY_WOWLAN_MAGIC_PKT +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0) + | WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | WIPHY_WOWLAN_GTK_REKEY_FAILURE | + WIPHY_WOWLAN_EAP_IDENTITY_REQ +#endif + | WIPHY_WOWLAN_DISCONNECT, +}; +#endif +#endif + +static s32 wl_mode_to_nl80211_iftype(s32 mode) +{ + s32 err = 0; + + switch (mode) { + case WL_MODE_BSS: + return NL80211_IFTYPE_STATION; + case WL_MODE_IBSS: + return NL80211_IFTYPE_ADHOC; + default: + return NL80211_IFTYPE_UNSPECIFIED; + } + + return err; +} + +static s32 wl_alloc_wdev(struct device *dev, struct wireless_dev **rwdev) +{ + struct wireless_dev *wdev; + s32 err = 0; + + wdev = kzalloc(sizeof(*wdev), GFP_KERNEL); + if (!wdev) { + WL_ERR(("Could not allocate wireless device\n")); + err = -ENOMEM; + goto early_out; + } + wdev->wiphy = wiphy_new(&wl_cfg80211_ops, sizeof(struct wl_cfg80211_priv)); + if (!wdev->wiphy) { + WL_ERR(("Couldn not allocate wiphy device\n")); + err = -ENOMEM; + goto wiphy_new_out; + } + set_wiphy_dev(wdev->wiphy, dev); + wdev->wiphy->max_scan_ssids = WL_NUM_SCAN_MAX; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) + wdev->wiphy->max_num_pmkids = WL_NUM_PMKIDS_MAX; +#endif + wdev->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_ADHOC); + wdev->wiphy->bands[NL80211_BAND_2GHZ] = &__wl_band_2ghz; + wdev->wiphy->bands[NL80211_BAND_5GHZ] = &__wl_band_5ghz_a; + wdev->wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + wdev->wiphy->cipher_suites = __wl_cipher_suites; + wdev->wiphy->n_cipher_suites = ARRAY_SIZE(__wl_cipher_suites); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) + + wdev->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; +#endif + +#ifdef CONFIG_PM +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) + wdev->wiphy->wowlan = &wl_wowlan_support; +#else + wdev->wiphy->wowlan = wl_wowlan_support; +#endif +#endif +#endif + + err = wiphy_register(wdev->wiphy); + if (err < 0) { + WL_ERR(("Couldn not register wiphy device (%d)\n", err)); + goto wiphy_register_out; + } + + *rwdev = wdev; + return err; + +wiphy_register_out: + wiphy_free(wdev->wiphy); + +wiphy_new_out: + kfree(wdev); + +early_out: + *rwdev = wdev; + return err; +} + +static void wl_free_wdev(struct wl_cfg80211_priv *wl) +{ + struct wireless_dev *wdev = wl_to_wdev(wl); + + if (!wdev) { + WL_ERR(("wdev is invalid\n")); + return; + } + wiphy_unregister(wdev->wiphy); + wiphy_free(wdev->wiphy); + kfree(wdev); + wl_to_wdev(wl) = NULL; +} + +static s32 wl_inform_bss(struct wl_cfg80211_priv *wl, struct wl_scan_results *bss_list) +{ + struct wl_bss_info *bi = NULL; + s32 err = 0; + int i; + + if (bss_list->version != WL_BSS_INFO_VERSION) { + WL_ERR(("Version %d != WL_BSS_INFO_VERSION\n", bss_list->version)); + return -EOPNOTSUPP; + } + WL_DBG(("scanned AP count (%d)\n", bss_list->count)); + bi = next_bss(bss_list, bi); + for_each_bss(bss_list, bi, i) { + err = wl_inform_single_bss(wl, bi); + if (err) + break; + } + return err; +} + +static s32 wl_inform_single_bss(struct wl_cfg80211_priv *wl, struct wl_bss_info *bi) +{ + struct wiphy *wiphy = wl_to_wiphy(wl); + struct ieee80211_mgmt *mgmt; + struct ieee80211_channel *channel; + struct wl_cfg80211_bss_info *notif_bss_info; + struct wl_cfg80211_scan_req *sr = wl_to_sr(wl); + struct beacon_proberesp *beacon_proberesp; + struct cfg80211_bss *cbss = NULL; + s32 mgmt_type; + u32 signal; + u32 freq; + s32 err = 0; + u8 *notify_ie; + size_t notify_ielen; + + if (dtoh32(bi->length) > WL_BSS_INFO_MAX) { + WL_DBG(("Beacon is larger than buffer. Discarding\n")); + return -E2BIG; + } + notif_bss_info = kzalloc(sizeof(*notif_bss_info) + sizeof(*mgmt) - sizeof(u8) + + WL_BSS_INFO_MAX, GFP_KERNEL); + if (!notif_bss_info) { + WL_ERR(("notif_bss_info alloc failed\n")); + return -ENOMEM; + } + mgmt = (struct ieee80211_mgmt *)notif_bss_info->frame_buf; + notif_bss_info->channel = bi->ctl_ch ? bi->ctl_ch : CHSPEC_CHANNEL(bi->chanspec); + + notif_bss_info->rssi = bi->RSSI; + memcpy(mgmt->bssid, &bi->BSSID, ETHER_ADDR_LEN); + mgmt_type = wl->active_scan ? IEEE80211_STYPE_PROBE_RESP : IEEE80211_STYPE_BEACON; + if (!memcmp(bi->SSID, sr->ssid.SSID, bi->SSID_len)) { + mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | mgmt_type); + } + beacon_proberesp = wl->active_scan ? (struct beacon_proberesp *)&mgmt->u.probe_resp : + (struct beacon_proberesp *)&mgmt->u.beacon; + beacon_proberesp->timestamp = 0; + beacon_proberesp->beacon_int = cpu_to_le16(bi->beacon_period); + beacon_proberesp->capab_info = cpu_to_le16(bi->capability); + wl_rst_ie(wl); + + err = wl_mrg_ie(wl, ((u8 *) bi) + bi->ie_offset, bi->ie_length); + if (err) + goto inform_single_bss_out; + + err = wl_cp_ie(wl, beacon_proberesp->variable, WL_BSS_INFO_MAX - + offsetof(struct wl_cfg80211_bss_info, frame_buf)); + if (err) + goto inform_single_bss_out; + + notif_bss_info->frame_len = offsetof(struct ieee80211_mgmt, u.beacon.variable) + + wl_get_ielen(wl); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) + freq = ieee80211_channel_to_frequency(notif_bss_info->channel, + (notif_bss_info->channel <= CH_MAX_2G_CHANNEL) ? + NL80211_BAND_2GHZ : NL80211_BAND_5GHZ); +#else + freq = ieee80211_channel_to_frequency(notif_bss_info->channel); +#endif + if (freq == 0) { + WL_ERR(("Invalid channel, fail to chcnage channel to freq\n")); + err = -EINVAL; + goto inform_single_bss_out; + } + channel = ieee80211_get_channel(wiphy, freq); + if (unlikely(!channel)) { + WL_ERR(("ieee80211_get_channel error\n")); + err = -EINVAL; + goto inform_single_bss_out; + } + + WL_DBG(("SSID : \"%s\", rssi %d, channel %d, capability : 0x04%x, bssid %pM\n", + bi->SSID, notif_bss_info->rssi, notif_bss_info->channel, + mgmt->u.beacon.capab_info, &bi->BSSID)); + + signal = notif_bss_info->rssi * 100; + + if (!wl->scan_request) { + cbss = cfg80211_inform_bss_frame(wiphy, channel, mgmt, + le16_to_cpu(notif_bss_info->frame_len), signal, GFP_KERNEL); + if (unlikely(!cbss)) { + WL_ERR(("cfg80211_inform_bss_frame error\n")); + err = -ENOMEM; + goto inform_single_bss_out; + } + } else { + notify_ie = (u8 *)bi + le16_to_cpu(bi->ie_offset); + notify_ielen = le32_to_cpu(bi->ie_length); +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0) + cbss = cfg80211_inform_bss(wiphy, channel, (const u8 *)(bi->BSSID.octet), + 0, beacon_proberesp->capab_info, beacon_proberesp->beacon_int, + (const u8 *)notify_ie, notify_ielen, signal, GFP_KERNEL); +#else + cbss = cfg80211_inform_bss(wiphy, channel, + wl->active_scan ? + CFG80211_BSS_FTYPE_PRESP : CFG80211_BSS_FTYPE_BEACON, + (const u8 *)(bi->BSSID.octet), 0, + beacon_proberesp->capab_info, + beacon_proberesp->beacon_int, + (const u8 *)notify_ie, notify_ielen, signal, GFP_KERNEL); +#endif + if (unlikely(!cbss)) { + WL_ERR(("cfg80211_inform_bss error\n")); + err = -ENOMEM; + goto inform_single_bss_out; + } + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) + cfg80211_put_bss(wiphy, cbss); +#else + cfg80211_put_bss(cbss); +#endif + +inform_single_bss_out: + kfree(notif_bss_info); + + return err; +} + +static s32 +wl_notify_connect_status(struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data) +{ + s32 err = 0; + u32 event = EVENT_TYPE(e); + u16 flags = EVENT_FLAGS(e); + u32 status = EVENT_STATUS(e); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0) + struct ieee80211_channel *channel = NULL; + struct wiphy *wiphy; + u32 chanspec, chan; + u32 freq, band; +#endif + + WL_DBG(("\n")); + + if (!wl_is_ibssmode(wl)) { + if (event == WLC_E_LINK && (flags & WLC_EVENT_MSG_LINK)) { + wl_link_up(wl); + wl_bss_connect_done(wl, ndev, e, data, true); + wl->profile->active = true; + } + else if ((event == WLC_E_LINK && ~(flags & WLC_EVENT_MSG_LINK)) || + event == WLC_E_DEAUTH_IND || event == WLC_E_DISASSOC_IND) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,2,0) + cfg80211_disconnected(ndev, 0, NULL, 0, GFP_KERNEL); +#else + cfg80211_disconnected(ndev, 0, NULL, 0, false, GFP_KERNEL); +#endif + clear_bit(WL_STATUS_CONNECTED, &wl->status); + wl_link_down(wl); + wl_init_prof(wl->profile); + } + else if (event == WLC_E_SET_SSID && status == WLC_E_STATUS_NO_NETWORKS) { + wl_bss_connect_done(wl, ndev, e, data, false); + } + else { + WL_DBG(("no action (BSS mode)\n")); + } + } + else { + if (event == WLC_E_JOIN) { + WL_DBG(("joined in IBSS network\n")); + } + if (event == WLC_E_START) { + WL_DBG(("started IBSS network\n")); + } + if (event == WLC_E_JOIN || event == WLC_E_START) { + wl_link_up(wl); + wl_get_assoc_ies(wl); + memcpy(&wl->bssid, &e->addr, ETHER_ADDR_LEN); + wl_update_bss_info(wl); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0) + wiphy = wl_to_wiphy(wl); + err = wl_dev_intvar_get(ndev, "chanspec", &chanspec); + if (err) { + WL_ERR(("Could not get chanspec, err %d\n", err)); + return err; + } + chan = wf_chspec_ctlchan(chanspec); + band = (chan <= CH_MAX_2G_CHANNEL) ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + freq = ieee80211_channel_to_frequency(chan, band); + channel = ieee80211_get_channel(wiphy, freq); + cfg80211_ibss_joined(ndev, (u8 *)&wl->bssid, channel, GFP_KERNEL); +#else + cfg80211_ibss_joined(ndev, (u8 *)&wl->bssid, GFP_KERNEL); +#endif + set_bit(WL_STATUS_CONNECTED, &wl->status); + wl->profile->active = true; + } + else if ((event == WLC_E_LINK && ~(flags & WLC_EVENT_MSG_LINK)) || + event == WLC_E_DEAUTH_IND || event == WLC_E_DISASSOC_IND) { + clear_bit(WL_STATUS_CONNECTED, &wl->status); + wl_link_down(wl); + wl_init_prof(wl->profile); + } + else if (event == WLC_E_SET_SSID && status == WLC_E_STATUS_NO_NETWORKS) { + WL_DBG(("no action - join fail (IBSS mode)\n")); + } + else { + WL_DBG(("no action (IBSS mode)\n")); + } + } + + return err; +} + +static s32 +wl_notify_roaming_status(struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data) +{ + s32 err = 0; + u32 status = EVENT_STATUS(e); + + WL_DBG(("\n")); + + if (status == WLC_E_STATUS_SUCCESS) { + err = wl_bss_roaming_done(wl, ndev, e, data); + wl->profile->active = true; + } + + return err; +} + +static __used s32 +wl_dev_bufvar_set(struct net_device *dev, s8 *name, s8 *buf, s32 len) +{ + struct wl_cfg80211_priv *wl = ndev_to_wl(dev); + u32 buflen; + + buflen = bcm_mkiovar(name, buf, len, wl->ioctl_buf, WL_IOCTL_LEN_MAX); + BUG_ON(!buflen); + + return wl_dev_ioctl(dev, WLC_SET_VAR, wl->ioctl_buf, buflen); +} + +static s32 +wl_dev_bufvar_get(struct net_device *dev, s8 *name, s8 *buf, s32 buf_len) +{ + struct wl_cfg80211_priv *wl = ndev_to_wl(dev); + u32 len; + s32 err = 0; + + len = bcm_mkiovar(name, NULL, 0, wl->ioctl_buf, WL_IOCTL_LEN_MAX); + BUG_ON(!len); + err = wl_dev_ioctl(dev, WLC_GET_VAR, (void *)wl->ioctl_buf, WL_IOCTL_LEN_MAX); + if (err) { + WL_INF(("error (%d)\n", err)); + return err; + } + memcpy(buf, wl->ioctl_buf, buf_len); + + return err; +} + +static s32 wl_get_assoc_ies(struct wl_cfg80211_priv *wl) +{ + struct net_device *ndev = wl_to_ndev(wl); + struct wl_cfg80211_assoc_ielen *assoc_info; + struct wl_cfg80211_connect_info *conn_info = wl_to_conn(wl); + u32 req_len; + u32 resp_len; + s32 err = 0; + + err = wl_dev_bufvar_get(ndev, "assoc_info", wl->extra_buf, WL_ASSOC_INFO_MAX); + if (err) { + WL_ERR(("could not get assoc info (%d)\n", err)); + return err; + } + assoc_info = (struct wl_cfg80211_assoc_ielen *)wl->extra_buf; + req_len = assoc_info->req_len; + resp_len = assoc_info->resp_len; + if (req_len) { + err = wl_dev_bufvar_get(ndev, "assoc_req_ies", wl->extra_buf, WL_ASSOC_INFO_MAX); + if (err) { + WL_ERR(("could not get assoc req (%d)\n", err)); + return err; + } + conn_info->req_ie_len = req_len; + conn_info->req_ie = + kmemdup(wl->extra_buf, conn_info->req_ie_len, GFP_KERNEL); + } else { + conn_info->req_ie_len = 0; + conn_info->req_ie = NULL; + } + if (resp_len) { + err = wl_dev_bufvar_get(ndev, "assoc_resp_ies", wl->extra_buf, WL_ASSOC_INFO_MAX); + if (err) { + WL_ERR(("could not get assoc resp (%d)\n", err)); + return err; + } + conn_info->resp_ie_len = resp_len; + conn_info->resp_ie = + kmemdup(wl->extra_buf, conn_info->resp_ie_len, GFP_KERNEL); + } else { + conn_info->resp_ie_len = 0; + conn_info->resp_ie = NULL; + } + WL_DBG(("req len (%d) resp len (%d)\n", conn_info->req_ie_len, + conn_info->resp_ie_len)); + + return err; +} + +static void wl_ch_to_chanspec(struct ieee80211_channel *chan, struct wl_join_params *join_params, + size_t *join_params_size) +{ + chanspec_t chanspec = 0; + + if (chan) { + join_params->params.chanspec_num = 1; + join_params->params.chanspec_list[0] = + ieee80211_frequency_to_channel(chan->center_freq); + + if (chan->band == NL80211_BAND_2GHZ) { + chanspec |= WL_CHANSPEC_BAND_2G; + } + else if (chan->band == NL80211_BAND_5GHZ) { + chanspec |= WL_CHANSPEC_BAND_5G; + } + else { + WL_ERR(("Unknown band\n")); + BUG(); + } + + chanspec |= WL_CHANSPEC_BW_20; + + *join_params_size += WL_ASSOC_PARAMS_FIXED_SIZE + + join_params->params.chanspec_num * sizeof(chanspec_t); + + join_params->params.chanspec_list[0] &= WL_CHANSPEC_CHAN_MASK; + join_params->params.chanspec_list[0] |= chanspec; + join_params->params.chanspec_list[0] = + htodchanspec(join_params->params.chanspec_list[0]); + + join_params->params.chanspec_num = htod32(join_params->params.chanspec_num); + + WL_DBG(("join_params->params.chanspec_list[0]= %#X, channel %d, chanspec %#X\n", + join_params->params.chanspec_list[0], + join_params->params.chanspec_list[0], chanspec)); + } +} + +static s32 wl_update_bss_info(struct wl_cfg80211_priv *wl) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) + struct wiphy *wiphy = wl_to_wiphy(wl); +#endif + struct cfg80211_bss *bss; + struct wl_bss_info *bi; + struct wlc_ssid *ssid; + struct bcm_tlv *tim; + s32 dtim_period; + size_t ie_len; + u8 *ie; + s32 err = 0; + + ssid = &wl->profile->ssid; + bss = cfg80211_get_bss(wl_to_wiphy(wl), NULL, (s8 *)&wl->bssid, + ssid->SSID, ssid->SSID_len, WLAN_CAPABILITY_ESS, WLAN_CAPABILITY_ESS); + + rtnl_lock(); + if (!bss) { + WL_DBG(("Could not find the AP\n")); + *(u32 *) wl->extra_buf = htod32(WL_EXTRA_BUF_MAX); + err = wl_dev_ioctl(wl_to_ndev(wl), WLC_GET_BSS_INFO, wl->extra_buf, + WL_EXTRA_BUF_MAX); + if (err) { + WL_ERR(("Could not get bss info %d\n", err)); + goto update_bss_info_out; + } + bi = (struct wl_bss_info *)(wl->extra_buf + 4); + if (memcmp(&bi->BSSID, &wl->bssid, ETHER_ADDR_LEN)) { + err = -EIO; + goto update_bss_info_out; + } + err = wl_inform_single_bss(wl, bi); + if (err) + goto update_bss_info_out; + + bss = cfg80211_get_bss(wl_to_wiphy(wl), NULL, (s8 *)&wl->bssid, + ssid->SSID, ssid->SSID_len, WLAN_CAPABILITY_ESS, WLAN_CAPABILITY_ESS); + + ie = ((u8 *)bi) + bi->ie_offset; + ie_len = bi->ie_length; + } else { + WL_DBG(("Found the AP in the list - BSSID %pM\n", bss->bssid)); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) + ie = (u8 *)(bss->ies->data); + ie_len = bss->ies->len; +#else + ie = bss->information_elements; + ie_len = bss->len_information_elements; +#endif + wl->conf->channel = *bss->channel; + } + + if (bss) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) + cfg80211_put_bss(wiphy, bss); +#else + cfg80211_put_bss(bss); +#endif + } else { + WL_DBG(("Could not update BSS\n")); + err = -EINVAL; + goto update_bss_info_out; + } + + tim = bcm_parse_tlvs(ie, ie_len, WLAN_EID_TIM); + if (tim) { + dtim_period = tim->data[1]; + } else { + + err = wl_dev_ioctl(wl_to_ndev(wl), WLC_GET_DTIMPRD, + &dtim_period, sizeof(dtim_period)); + if (err) { + WL_ERR(("WLC_GET_DTIMPRD error (%d)\n", err)); + goto update_bss_info_out; + } + } + +update_bss_info_out: + rtnl_unlock(); + return err; +} + +static s32 +wl_bss_roaming_done(struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data) +{ + struct wl_cfg80211_connect_info *conn_info = wl_to_conn(wl); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) + struct cfg80211_roam_info roam_info = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0) + .bssid = wl->profile->bssid, +#else + .links[0].bssid = wl->profile->bssid, +#endif + .req_ie = conn_info->req_ie, + .req_ie_len = conn_info->req_ie_len, + .resp_ie = conn_info->resp_ie, + .resp_ie_len = conn_info->resp_ie_len, + }; +#endif + s32 err = 0; + + err = wl_get_assoc_ies(wl); + if (err) + return err; + + memcpy(wl->profile->bssid, &e->addr, ETHER_ADDR_LEN); + memcpy(&wl->bssid, &e->addr, ETHER_ADDR_LEN); + + err = wl_update_bss_info(wl); + if (err) + return err; + + cfg80211_roamed(ndev, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) + &roam_info, +#else +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39) + &wl->conf->channel, +#endif + (u8 *)&wl->bssid, + conn_info->req_ie, conn_info->req_ie_len, + conn_info->resp_ie, conn_info->resp_ie_len, +#endif + GFP_KERNEL); + WL_DBG(("Report roaming result\n")); + + set_bit(WL_STATUS_CONNECTED, &wl->status); + + return err; +} + +static s32 +wl_bss_connect_done(struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data, bool completed) +{ + struct wl_cfg80211_connect_info *conn_info = wl_to_conn(wl); + s32 err = 0; + + if (wl->scan_request) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + struct cfg80211_scan_info info = { + .aborted = true, + }; + WL_DBG(("%s: Aborting scan\n", __FUNCTION__)); + cfg80211_scan_done(wl->scan_request, &info); +#else + WL_DBG(("%s: Aborting scan\n", __FUNCTION__)); + cfg80211_scan_done(wl->scan_request, true); +#endif + wl->scan_request = NULL; + } + + if (test_and_clear_bit(WL_STATUS_CONNECTING, &wl->status)) { + if (completed) { + wl_get_assoc_ies(wl); + memcpy(&wl->bssid, &e->addr, ETHER_ADDR_LEN); + memcpy(wl->profile->bssid, &e->addr, ETHER_ADDR_LEN); + wl_update_bss_info(wl); + set_bit(WL_STATUS_CONNECTED, &wl->status); + } + + WL_DBG(("Reporting BSS network join result \"%s\"\n", + wl->profile->ssid.SSID)); + cfg80211_connect_result(ndev, (u8 *)&wl->bssid, conn_info->req_ie, + conn_info->req_ie_len, conn_info->resp_ie, conn_info->resp_ie_len, + completed ? WLAN_STATUS_SUCCESS : WLAN_STATUS_AUTH_TIMEOUT, GFP_KERNEL); + WL_DBG(("Connection %s\n", completed ? "Succeeded" : "FAILed")); + } + + return err; +} + +static s32 +wl_notify_mic_status(struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data) +{ + u16 flags = EVENT_FLAGS(e); + enum nl80211_key_type key_type; + + WL_DBG(("\n")); + + rtnl_lock(); + if (flags & WLC_EVENT_MSG_GROUP) + key_type = NL80211_KEYTYPE_GROUP; + else + key_type = NL80211_KEYTYPE_PAIRWISE; + + cfg80211_michael_mic_failure(ndev, (u8 *)&e->addr, key_type, -1, NULL, GFP_KERNEL); + rtnl_unlock(); + + return 0; +} + +static s32 +wl_notify_scan_status(struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data) +{ + struct channel_info channel_inform; + struct wl_scan_results *bss_list; + u32 buflen; + s32 err = 0; + + WL_DBG(("\n")); + + rtnl_lock(); + err = wl_dev_ioctl(ndev, WLC_GET_CHANNEL, &channel_inform, sizeof(channel_inform)); + if (err) { + WL_ERR(("scan busy (%d)\n", err)); + goto scan_done_out; + } + channel_inform.scan_channel = dtoh32(channel_inform.scan_channel); + if (channel_inform.scan_channel) { + + WL_DBG(("channel_inform.scan_channel (%d)\n", channel_inform.scan_channel)); + } + + for (buflen = WL_SCAN_BUF_BASE; ; ) { + bss_list = (struct wl_scan_results *) kmalloc(buflen, GFP_KERNEL); + if (!bss_list) { + WL_ERR(("%s Out of memory for scan results, (%d)\n", ndev->name, err)); + goto scan_done_out; + } + memset(bss_list, 0, buflen); + bss_list->buflen = htod32(buflen); + err = wl_dev_ioctl(ndev, WLC_SCAN_RESULTS, bss_list, buflen); + if (!err) { + break; + } + else if (err == -E2BIG) { + kfree(bss_list); + buflen *= 2; + } + else { + WL_ERR(("%s Scan_results error (%d)\n", ndev->name, err)); + kfree(bss_list); + err = -EINVAL; + goto scan_done_out; + } + } + + bss_list->buflen = dtoh32(bss_list->buflen); + bss_list->version = dtoh32(bss_list->version); + bss_list->count = dtoh32(bss_list->count); + + err = wl_inform_bss(wl, bss_list); + kfree(bss_list); + +scan_done_out: + if (wl->scan_request) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + struct cfg80211_scan_info info = { + .aborted = false, + }; + cfg80211_scan_done(wl->scan_request, &info); +#else + cfg80211_scan_done(wl->scan_request, false); +#endif + wl->scan_request = NULL; + } + rtnl_unlock(); + return err; +} + +static void wl_init_conf(struct wl_cfg80211_conf *conf) +{ + conf->mode = (u32)-1; + conf->frag_threshold = (u32)-1; + conf->rts_threshold = (u32)-1; + conf->retry_short = (u32)-1; + conf->retry_long = (u32)-1; + conf->tx_power = -1; +} + +static void wl_init_prof(struct wl_cfg80211_profile *prof) +{ + memset(prof, 0, sizeof(*prof)); +} + +static void wl_init_eloop_handler(struct wl_cfg80211_event_loop *el) +{ + memset(el, 0, sizeof(*el)); + el->handler[WLC_E_SCAN_COMPLETE] = wl_notify_scan_status; + el->handler[WLC_E_JOIN] = wl_notify_connect_status; + el->handler[WLC_E_START] = wl_notify_connect_status; + el->handler[WLC_E_LINK] = wl_notify_connect_status; + el->handler[WLC_E_NDIS_LINK] = wl_notify_connect_status; + el->handler[WLC_E_SET_SSID] = wl_notify_connect_status; + el->handler[WLC_E_DISASSOC_IND] = wl_notify_connect_status; + el->handler[WLC_E_DEAUTH_IND] = wl_notify_connect_status; + el->handler[WLC_E_ROAM] = wl_notify_roaming_status; + el->handler[WLC_E_MIC_ERROR] = wl_notify_mic_status; +} + +static s32 wl_init_priv_mem(struct wl_cfg80211_priv *wl) +{ + wl->conf = (void *)kzalloc(sizeof(*wl->conf), GFP_KERNEL); + if (!wl->conf) { + WL_ERR(("wl_cfg80211_conf alloc failed\n")); + goto init_priv_mem_out; + } + wl->profile = (void *)kzalloc(sizeof(*wl->profile), GFP_KERNEL); + if (!wl->profile) { + WL_ERR(("wl_cfg80211_profile alloc failed\n")); + goto init_priv_mem_out; + } + wl->scan_req_int = (void *)kzalloc(sizeof(*wl->scan_req_int), GFP_KERNEL); + if (!wl->scan_req_int) { + WL_ERR(("Scan req alloc failed\n")); + goto init_priv_mem_out; + } + wl->ioctl_buf = (void *)kzalloc(WL_IOCTL_LEN_MAX, GFP_KERNEL); + if (!wl->ioctl_buf) { + WL_ERR(("Ioctl buf alloc failed\n")); + goto init_priv_mem_out; + } + wl->extra_buf = (void *)kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL); + if (!wl->extra_buf) { + WL_ERR(("Extra buf alloc failed\n")); + goto init_priv_mem_out; + } + + wl->pmk_list = (void *)kzalloc(sizeof(*wl->pmk_list), GFP_KERNEL); + if (!wl->pmk_list) { + WL_ERR(("pmk list alloc failed\n")); + goto init_priv_mem_out; + } + + return 0; + +init_priv_mem_out: + wl_deinit_priv_mem(wl); + + return -ENOMEM; +} + +static void wl_deinit_priv_mem(struct wl_cfg80211_priv *wl) +{ + kfree(wl->conf); + wl->conf = NULL; + kfree(wl->profile); + wl->profile = NULL; + kfree(wl->scan_req_int); + wl->scan_req_int = NULL; + kfree(wl->ioctl_buf); + wl->ioctl_buf = NULL; + kfree(wl->extra_buf); + wl->extra_buf = NULL; + kfree(wl->pmk_list); + wl->pmk_list = NULL; +} + +static s32 wl_create_event_handler(struct wl_cfg80211_priv *wl) +{ + sema_init(&wl->event_sync, 0); + wl->event_tsk = kthread_run(wl_event_handler, wl, "wl_event_handler"); + if (IS_ERR(wl->event_tsk)) { + wl->event_tsk = NULL; + WL_ERR(("failed to create event thread\n")); + return -ENOMEM; + } + return 0; +} + +static void wl_destroy_event_handler(struct wl_cfg80211_priv *wl) +{ + if (wl->event_tsk) { + send_sig(SIGTERM, wl->event_tsk, 1); + kthread_stop(wl->event_tsk); + wl->event_tsk = NULL; + } +} + +static s32 wl_init_cfg80211_priv(struct wl_cfg80211_priv *wl, struct wireless_dev *wdev) +{ + s32 err = 0; + + wl->wdev = wdev; + + wl->scan_request = NULL; + wl->active_scan = true; + wl_init_eq(wl); + err = wl_init_priv_mem(wl); + if (err) + return err; + + if (wl_create_event_handler(wl)) + return -ENOMEM; + + wl_init_eloop_handler(&wl->el); + + if (err) + return err; + + wl_init_conf(wl->conf); + wl_init_prof(wl->profile); + wl_link_down(wl); + + return err; +} + +static void wl_deinit_cfg80211_priv(struct wl_cfg80211_priv *wl) +{ + wl_destroy_event_handler(wl); + wl_flush_eq(wl); + wl_link_down(wl); + wl_deinit_priv_mem(wl); +} + +s32 wl_cfg80211_attach(struct net_device *ndev, struct device *dev, int passive) +{ + struct wireless_dev *wdev; + struct wl_cfg80211_priv *wl; + s32 err = 0; + + if (!ndev) { + WL_ERR(("ndev is invaild\n")); + return -ENODEV; + } + + err = wl_alloc_wdev(dev, &wdev); + if (err < 0) { + return err; + } + + wdev->iftype = wl_mode_to_nl80211_iftype(WL_MODE_BSS); + wl = wdev_to_wl(wdev); + ndev->ieee80211_ptr = wdev; + SET_NETDEV_DEV(ndev, wiphy_dev(wdev->wiphy)); + wdev->netdev = ndev; + err = wl_init_cfg80211_priv(wl, wdev); + if (err) { + WL_ERR(("Failed to init iwm_priv (%d)\n", err)); + goto cfg80211_attach_out; + } + wl->passive = !!passive; + + if (!err) { + WL_INF(("Registered CFG80211 phy\n")); + } + return err; + +cfg80211_attach_out: + wl_free_wdev(wl); + return err; +} + +void wl_cfg80211_detach(struct net_device *ndev) +{ + struct wl_cfg80211_priv *wl; + + if (ndev->ieee80211_ptr == NULL) { + WL_ERR(( "NULL ndev->ieee80211ptr, unable to deref wl\n")); + return; + } + wl = ndev_to_wl(ndev); + + wl_deinit_cfg80211_priv(wl); + wl_free_wdev(wl); +} + +static void wl_wakeup_event(struct wl_cfg80211_priv *wl) +{ + up(&wl->event_sync); +} + +static s32 wl_event_handler(void *data) +{ + struct wl_cfg80211_priv *wl = (struct wl_cfg80211_priv *)data; + struct wl_cfg80211_event_q *e; + + allow_signal(SIGTERM); + while (!down_interruptible(&wl->event_sync)) { + if (kthread_should_stop()) + break; + e = wl_deq_event(wl); + if (!e) { + WL_ERR(("eqeue empty..\n")); + BUG(); + } + if (wl->el.handler[e->etype]) { + WL_DBG(("event type (%d)\n", e->etype)); + wl->el.handler[e->etype] (wl, wl_to_ndev(wl), &e->emsg, e->edata); + } else { + WL_DBG(("Unknown Event (%d): ignoring\n", e->etype)); + } + wl_put_event(e); + } + WL_DBG(("%s was terminated\n", __func__)); + return 0; +} + +void +wl_cfg80211_event(struct net_device *ndev, const wl_event_msg_t * e, void *data) +{ + + u32 event_type = EVENT_TYPE(e); + + struct wl_cfg80211_priv *wl = ndev_to_wl(ndev); +#if defined(WL_DBGMSG_ENABLE) + s8 *estr = (event_type <= sizeof(wl_dbg_estr) / WL_DBG_ESTR_MAX - 1) ? + wl_dbg_estr[event_type] : (s8 *) "Unknown"; + WL_DBG(("event_type (%d):" "WLC_E_" "%s\n", event_type, estr)); +#endif + if (!wl_enq_event(wl, event_type, e, data)) + wl_wakeup_event(wl); +} + +static void wl_init_eq(struct wl_cfg80211_priv *wl) +{ + wl_init_eq_lock(wl); + INIT_LIST_HEAD(&wl->eq_list); +} + +static void wl_flush_eq(struct wl_cfg80211_priv *wl) +{ + struct wl_cfg80211_event_q *e; + + wl_lock_eq(wl); + while (!list_empty(&wl->eq_list)) { + e = list_first_entry(&wl->eq_list, struct wl_cfg80211_event_q, eq_list); + list_del(&e->eq_list); + kfree(e); + } + wl_unlock_eq(wl); +} + +static struct wl_cfg80211_event_q *wl_deq_event(struct wl_cfg80211_priv *wl) +{ + struct wl_cfg80211_event_q *e = NULL; + + wl_lock_eq(wl); + if (!list_empty(&wl->eq_list)) { + e = list_first_entry(&wl->eq_list, struct wl_cfg80211_event_q, eq_list); + list_del(&e->eq_list); + } + wl_unlock_eq(wl); + + return e; +} + +static s32 +wl_enq_event(struct wl_cfg80211_priv *wl, u32 event, const wl_event_msg_t *msg, void *data) +{ + struct wl_cfg80211_event_q *e; + s32 err = 0; + + e = kzalloc(sizeof(struct wl_cfg80211_event_q), GFP_ATOMIC); + if (!e) { + WL_ERR(("event alloc failed\n")); + return -ENOMEM; + } + + e->etype = event; + memcpy(&e->emsg, msg, sizeof(wl_event_msg_t)); + if (data) { + } + + spin_lock(&wl->eq_lock); + list_add_tail(&e->eq_list, &wl->eq_list); + spin_unlock(&wl->eq_lock); + + return err; +} + +static void wl_put_event(struct wl_cfg80211_event_q *e) +{ + kfree(e); +} + +static s32 wl_set_mode(struct net_device *ndev, s32 iftype) +{ + s32 infra = 0; + s32 ap = 0; + s32 err = 0; + + switch (iftype) { + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_WDS: + WL_ERR(("type (%d) : currently we do not support this mode\n", + iftype)); + err = -EINVAL; + return err; + case NL80211_IFTYPE_ADHOC: + break; + case NL80211_IFTYPE_STATION: + infra = 1; + break; + default: + err = -EINVAL; + WL_ERR(("invalid type (%d)\n", iftype)); + return err; + } + infra = htod32(infra); + ap = htod32(ap); + WL_DBG(("%s ap (%d), infra (%d)\n", ndev->name, ap, infra)); + err = wl_dev_ioctl(ndev, WLC_SET_INFRA, &infra, sizeof(infra)); + if (err) { + WL_ERR(("WLC_SET_INFRA error (%d)\n", err)); + return err; + } + err = wl_dev_ioctl(ndev, WLC_SET_AP, &ap, sizeof(ap)); + if (err) { + WL_ERR(("WLC_SET_AP error (%d)\n", err)); + return err; + } + + return 0; +} + +static void wl_update_wowl(struct net_device *ndev) +{ +#ifdef CONFIG_PM + struct wl_cfg80211_priv *wl = ndev_to_wl(ndev); + struct wireless_dev *wdev = ndev->ieee80211_ptr; + s32 offloads = 0; + s32 err = 0; + err = wl_dev_bufvar_get(wl_to_ndev(wl), "offloads", + (s8 *)&offloads, sizeof(offloads)); + if (err == 0 && offloads == 1) { + WL_INF(("Supports offloads\n")); + wl->offloads = true; + } else { + WL_INF(("No offloads supported\n")); + wl->offloads = false; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) + wdev->wiphy->wowlan = NULL; +#else + memset(&wdev->wiphy->wowlan, 0, sizeof(struct wiphy_wowlan_support)); +#endif +#endif + } +#endif +} + +static s32 wl_update_wiphybands(struct wl_cfg80211_priv *wl) +{ + struct wiphy *wiphy; + s32 phy_list; + s8 phy; + s32 err = 0; + + err = wl_dev_ioctl(wl_to_ndev(wl), WLC_GET_PHYLIST, &phy_list, sizeof(phy_list)); + if (err) { + WL_ERR(("error (%d)\n", err)); + return err; + } + + phy = ((char *)&phy_list)[0]; + WL_DBG(("%c phy\n", phy)); + + if (phy == 'n' || phy == 'a' || phy == 'v') { + wiphy = wl_to_wiphy(wl); + wiphy->bands[NL80211_BAND_5GHZ] = &__wl_band_5ghz_n; + } + + return err; +} + +s32 wl_cfg80211_up(struct net_device *ndev) +{ + struct wl_cfg80211_priv *wl = ndev_to_wl(ndev); + s32 err = 0; + struct wireless_dev *wdev = ndev->ieee80211_ptr; + + wl_set_mode(ndev, wdev->iftype); + err = wl_update_wiphybands(wl); + if (err) { + return err; + } + + wl_update_wowl(ndev); + return 0; +} + +s32 wl_cfg80211_down(struct net_device *ndev) +{ + struct wl_cfg80211_priv *wl = ndev_to_wl(ndev); + s32 err = 0; + + if (wl->scan_request) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + struct cfg80211_scan_info info = { + .aborted = true, + }; + cfg80211_scan_done(wl->scan_request, &info); +#else + cfg80211_scan_done(wl->scan_request, true); +#endif + wl->scan_request = NULL; + } + + return err; +} + +static bool wl_is_ibssmode(struct wl_cfg80211_priv *wl) +{ + return wl->conf->mode == WL_MODE_IBSS; +} + +static void wl_rst_ie(struct wl_cfg80211_priv *wl) +{ + struct wl_cfg80211_ie *ie = wl_to_ie(wl); + + ie->offset = 0; +} + +static __used s32 wl_add_ie(struct wl_cfg80211_priv *wl, u8 t, u8 l, u8 *v) +{ + struct wl_cfg80211_ie *ie = wl_to_ie(wl); + s32 err = 0; + + if (ie->offset + l + 2 > WL_TLV_INFO_MAX) { + WL_ERR(("ei crosses buffer boundary\n")); + return -ENOSPC; + } + ie->buf[ie->offset] = t; + ie->buf[ie->offset + 1] = l; + memcpy(&ie->buf[ie->offset + 2], v, l); + ie->offset += l + 2; + + return err; +} + +static s32 wl_mrg_ie(struct wl_cfg80211_priv *wl, u8 *ie_stream, u16 ie_size) +{ + struct wl_cfg80211_ie *ie = wl_to_ie(wl); + s32 err = 0; + + if (ie->offset + ie_size > WL_TLV_INFO_MAX) { + WL_ERR(("ei_stream crosses buffer boundary\n")); + return -ENOSPC; + } + memcpy(&ie->buf[ie->offset], ie_stream, ie_size); + ie->offset += ie_size; + + return err; +} + +static s32 wl_cp_ie(struct wl_cfg80211_priv *wl, u8 *dst, u16 dst_size) +{ + struct wl_cfg80211_ie *ie = wl_to_ie(wl); + s32 err = 0; + + if (ie->offset > dst_size) { + WL_ERR(("dst_size is not enough\n")); + return -ENOSPC; + } + memcpy(dst, &ie->buf[0], ie->offset); + + return err; +} + +static u32 wl_get_ielen(struct wl_cfg80211_priv *wl) +{ + struct wl_cfg80211_ie *ie = wl_to_ie(wl); + + return ie->offset; +} + +static void wl_link_up(struct wl_cfg80211_priv *wl) +{ + WL_DBG(("\n")); +} + +static void wl_link_down(struct wl_cfg80211_priv *wl) +{ + struct wl_cfg80211_connect_info *conn_info = wl_to_conn(wl); + + WL_DBG(("\n")); + + kfree(conn_info->req_ie); + conn_info->req_ie = NULL; + conn_info->req_ie_len = 0; + kfree(conn_info->resp_ie); + conn_info->resp_ie = NULL; + conn_info->resp_ie_len = 0; +} + +static void wl_lock_eq(struct wl_cfg80211_priv *wl) +{ + spin_lock_irq(&wl->eq_lock); +} + +static void wl_unlock_eq(struct wl_cfg80211_priv *wl) +{ + spin_unlock_irq(&wl->eq_lock); +} + +static void wl_init_eq_lock(struct wl_cfg80211_priv *wl) +{ + spin_lock_init(&wl->eq_lock); +} + +#endif diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wl_cfg80211_hybrid.h b/drivers/custom/broadcom-wl/src/wl/sys/wl_cfg80211_hybrid.h new file mode 100644 index 000000000000..bc6f3ad73889 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wl_cfg80211_hybrid.h @@ -0,0 +1,231 @@ +/* + * Linux-specific portion of Broadcom 802.11abg Networking Device Driver + * cfg80211 interface + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wl_cfg80211.h,v 1.1.8.1 2011-01-26 00:57:46 $ + */ + +#ifndef _wl_cfg80211_h_ +#define _wl_cfg80211_h_ + +#include +#include + +struct wl_cfg80211_conf; +struct wl_cfg80211_priv; +struct wl_cfg80211_security; + +#define htod32(i) i +#define htod16(i) i +#define dtoh32(i) i +#define dtoh16(i) i +#define htodchanspec(i) i +#define dtohchanspec(i) i +#define dtoh32(i) i +#define dtoh16(i) i + +#define WL_DBGMSG_ENABLE + +#define WL_DBG_NONE 0 +#define WL_DBG_DBG (1 << 2) +#define WL_DBG_INFO (1 << 1) +#define WL_DBG_ERR (1 << 0) +#define WL_DBG_MASK ((WL_DBG_DBG | WL_DBG_INFO | WL_DBG_ERR) << 1) + +#if defined(WL_DBGMSG_ENABLE) +#define WL_DBG(args) \ +do { \ + if (wl_dbg_level & WL_DBG_DBG) { \ + printk(KERN_ERR "DEBUG @%s :", __func__); \ + printk args; \ + } \ +} while (0) +#else +#define WL_DBG(args) +#endif + +#define WL_ERR(args) \ +do { \ + if (wl_dbg_level & WL_DBG_ERR) { \ + if (net_ratelimit()) { \ + printk(KERN_ERR "ERROR @%s : ", __func__); \ + printk args; \ + } \ + } \ +} while (0) + +#define WL_INF(args) \ +do { \ + if (wl_dbg_level & WL_DBG_INFO) { \ + if (net_ratelimit()) { \ + printk(KERN_ERR "INFO @%s : ", __func__); \ + printk args; \ + } \ + } \ +} while (0) + +#define WL_NUM_SCAN_MAX 1 +#define WL_NUM_PMKIDS_MAX MAXPMKID +#define WL_SCAN_BUF_BASE (16*1024) +#define WL_TLV_INFO_MAX 1024 +#define WL_BSS_INFO_MAX 2048 +#define WL_ASSOC_INFO_MAX 512 +#define WL_IOCTL_LEN_MAX 2048 +#define WL_EXTRA_BUF_MAX 2048 +#define WL_AP_MAX 256 + +enum wl_cfg80211_status { + WL_STATUS_CONNECTING, + WL_STATUS_CONNECTED +}; + +enum wl_cfg80211_mode { + WL_MODE_BSS, + WL_MODE_IBSS, + WL_MODE_AP +}; + +struct beacon_proberesp { + __le64 timestamp; + __le16 beacon_int; + __le16 capab_info; + u8 variable[0]; +} __attribute__ ((packed)); + +struct wl_cfg80211_conf { + u32 mode; + u32 frag_threshold; + u32 rts_threshold; + u32 retry_short; + u32 retry_long; + s32 tx_power; + struct ieee80211_channel channel; +}; + +struct wl_cfg80211_event_loop { + s32(*handler[WLC_E_LAST]) (struct wl_cfg80211_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data); +}; + +struct wl_cfg80211_bss_info { + u16 band; + u16 channel; + s16 rssi; + u16 frame_len; + u8 frame_buf[1]; +}; + +struct wl_cfg80211_scan_req { + struct wlc_ssid ssid; +}; + +struct wl_cfg80211_ie { + u16 offset; + u8 buf[WL_TLV_INFO_MAX]; +}; + +struct wl_cfg80211_event_q { + struct list_head eq_list; + u32 etype; + wl_event_msg_t emsg; + s8 edata[1]; +}; + +struct wl_cfg80211_security { + u32 wpa_versions; + u32 auth_type; + u32 cipher_pairwise; + u32 cipher_group; + u32 wpa_auth; +}; + +struct wl_cfg80211_profile { + struct wlc_ssid ssid; + u8 bssid[ETHER_ADDR_LEN]; + struct wl_cfg80211_security sec; + bool active; +}; + +struct wl_cfg80211_connect_info { + u8 *req_ie; + s32 req_ie_len; + u8 *resp_ie; + s32 resp_ie_len; +}; + +struct wl_cfg80211_assoc_ielen { + u32 req_len; + u32 resp_len; +}; + +struct wl_cfg80211_pmk_list { + pmkid_list_t pmkids; + pmkid_t foo[MAXPMKID - 1]; +}; + +struct wl_cfg80211_priv { + struct wireless_dev *wdev; + struct wl_cfg80211_conf *conf; + struct cfg80211_scan_request *scan_request; + struct wl_cfg80211_event_loop el; + struct list_head eq_list; + spinlock_t eq_lock; + struct wl_cfg80211_scan_req *scan_req_int; + struct wl_cfg80211_ie ie; + struct ether_addr bssid; + struct semaphore event_sync; + struct wl_cfg80211_profile *profile; + struct wl_cfg80211_connect_info conn_info; + struct wl_cfg80211_pmk_list *pmk_list; + struct task_struct *event_tsk; + unsigned long status; + bool active_scan; + bool passive; + bool offloads; + u8 *ioctl_buf; + u8 *extra_buf; + u8 ci[0] __attribute__ ((__aligned__(NETDEV_ALIGN))); +}; + +#define wl_to_dev(w) (wiphy_dev(wl->wdev->wiphy)) +#define wl_to_wiphy(w) (w->wdev->wiphy) +#define wiphy_to_wl(w) ((struct wl_cfg80211_priv *)(wiphy_priv(w))) +#define wl_to_wdev(w) (w->wdev) +#define wdev_to_wl(w) ((struct wl_cfg80211_priv *)(wdev_priv(w))) +#define wl_to_ndev(w) (w->wdev->netdev) +#define ndev_to_wl(n) (wdev_to_wl(n->ieee80211_ptr)) +#define wl_to_sr(w) (w->scan_req_int) +#define wl_to_ie(w) (&w->ie) +#define wl_to_conn(w) (&w->conn_info) + +static inline struct wl_bss_info *next_bss(struct wl_scan_results *list, struct wl_bss_info *bss) +{ + return bss = bss ? (struct wl_bss_info *)((unsigned long)bss + + dtoh32(bss->length)) : list->bss_info; +} + +#define for_each_bss(list, bss, __i) \ + for (__i = 0; __i < list->count && __i < WL_AP_MAX; __i++, bss = next_bss(list, bss)) + +extern s32 wl_cfg80211_attach(struct net_device *ndev, struct device *dev, int flags); +extern void wl_cfg80211_detach(struct net_device *ndev); + +extern void wl_cfg80211_event(struct net_device *ndev, const wl_event_msg_t *e, void *data); +extern s32 wl_cfg80211_up(struct net_device *ndev); +extern s32 wl_cfg80211_down(struct net_device *ndev); + +#endif diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wl_dbg.h b/drivers/custom/broadcom-wl/src/wl/sys/wl_dbg.h new file mode 100644 index 000000000000..dc8bf26fc8b3 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wl_dbg.h @@ -0,0 +1,74 @@ +/* + * Minimal debug/trace/assert driver definitions for + * Broadcom 802.11 Networking Adapter. + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wl_dbg.h 405851 2013-06-05 00:56:21Z $ + */ + +#ifndef _wl_dbg_h_ +#define _wl_dbg_h_ + +extern uint32 wl_msg_level; +extern uint32 wl_msg_level2; + +#if defined(BCMDBG) && !defined(BCMDBG_EXCLUDE_HW_TIMESTAMP) +extern char* wlc_dbg_get_hw_timestamp(void); + +#define WL_TIMESTAMP() do { if (wl_msg_level2 & WL_TIMESTAMP_VAL) {\ + printf(wlc_dbg_get_hw_timestamp()); }\ + } while (0) +#else +#define WL_TIMESTAMP() +#endif + +#if 0 && (VERSION_MAJOR > 9) +extern int osl_printf(const char *fmt, ...); +#include +#define WL_PRINT(args) do { osl_printf args; } while (0) +#define RELEASE_PRINT(args) do { WL_PRINT(args); IO8Log args; } while (0) +#else +#define WL_PRINT(args) do { WL_TIMESTAMP(); printf args; } while (0) +#endif + +#ifdef BCMDBG + +#define WL_NONE(args) do {if (wl_msg_level & 0) WL_PRINT(args);} while (0) + +#define WL_ERROR(args) do {if (wl_msg_level & WL_ERROR_VAL) WL_PRINT(args);} while (0) +#define WL_TRACE(args) do {if (wl_msg_level & WL_TRACE_VAL) WL_PRINT(args);} while (0) + +#else + +#define WL_NONE(args) + +#ifdef BCMDBG_ERR +#define WL_ERROR(args) WL_PRINT(args) +#else +#define WL_ERROR(args) +#endif +#define WL_TRACE(args) +#define WL_APSTA_UPDN(args) +#define WL_APSTA_RX(args) +#define WL_WSEC(args) +#define WL_WSEC_DUMP(args) +#define WL_PCIE(args) do {if (wl_msg_level2 & WL_PCIE_VAL) WL_PRINT(args);} while (0) +#define WL_PCIE_ON() (wl_msg_level2 & WL_PCIE_VAL) +#endif + +extern uint32 wl_msg_level; +extern uint32 wl_msg_level2; +#endif diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wl_export.h b/drivers/custom/broadcom-wl/src/wl/sys/wl_export.h new file mode 100644 index 000000000000..41d4251e3ea5 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wl_export.h @@ -0,0 +1,81 @@ +/* + * Required functions exported by the port-specific (os-dependent) driver + * to common (os-independent) driver code. + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wl_export.h 579954 2015-08-17 18:08:53Z $ + */ + +#ifndef _wl_export_h_ +#define _wl_export_h_ + +struct wl_info; +struct wl_if; +struct wlc_if; +extern void wl_init(struct wl_info *wl); +extern uint wl_reset(struct wl_info *wl); +extern void wl_intrson(struct wl_info *wl); +extern uint32 wl_intrsoff(struct wl_info *wl); +extern void wl_intrsrestore(struct wl_info *wl, uint32 macintmask); +extern void wl_event(struct wl_info *wl, char *ifname, wlc_event_t *e); +extern void wl_event_sync(struct wl_info *wl, char *ifname, wlc_event_t *e); +extern void wl_event_sendup(struct wl_info *wl, const wlc_event_t *e, uint8 *data, uint32 len); +extern int wl_up(struct wl_info *wl); +extern void wl_down(struct wl_info *wl); +extern void wl_dump_ver(struct wl_info *wl, struct bcmstrbuf *b); +extern void wl_txflowcontrol(struct wl_info *wl, struct wl_if *wlif, bool state, int prio); +extern bool wl_alloc_dma_resources(struct wl_info *wl, uint dmaddrwidth); +extern void wl_reclaim(void); +extern void wl_nocard_timer(void *arg); +extern void wl_recover_nocard(struct wl_info *wl); +extern void wl_devicerecovery(struct wl_info *wl ); + +extern uint32 wl_pcie_bar1(struct wl_info *wl, uchar** addr); + +struct wl_timer; +extern struct wl_timer *wl_init_timer(struct wl_info *wl, void (*fn)(void* arg), void *arg, + const char *name); +extern void wl_free_timer(struct wl_info *wl, struct wl_timer *timer); +extern void wl_add_timer(struct wl_info *wl, struct wl_timer *timer, uint ms, int periodic); +extern bool wl_del_timer(struct wl_info *wl, struct wl_timer *timer); + +extern void wl_sendup(struct wl_info *wl, struct wl_if *wlif, void *p, int numpkt); +extern char *wl_ifname(struct wl_info *wl, struct wl_if *wlif); +extern struct wl_if *wl_add_if(struct wl_info *wl, struct wlc_if* wlcif, uint unit, + struct ether_addr *remote); +extern void wl_del_if(struct wl_info *wl, struct wl_if *wlif); +#ifdef DWDS +extern void wl_dwds_del_if(struct wl_info *wl, struct wl_if *wlif, bool force); +#endif + +extern int wl_osl_pcie_rc(struct wl_info *wl, uint op, int param); + +extern void wl_monitor(struct wl_info *wl, wl_rxsts_t *rxsts, void *p); +extern void wl_set_monitor(struct wl_info *wl, int val); + +extern uint wl_buf_to_pktcopy(osl_t *osh, void *p, uchar *buf, int len, uint offset); +extern void * wl_get_pktbuffer(osl_t *osh, int len); +extern int wl_set_pktlen(osl_t *osh, void *p, int len); + +#define IFCTX_ARPI (1) +#define IFCTX_NDI (2) +#define IFCTX_NETDEV (3) +extern void *wl_get_ifctx(struct wl_info *wl, int ctx_id, wl_if_t *wlif); + +#define wl_sort_bsslist(a, b) FALSE + +#define wl_outputpacket_complete(a, b, c) do { } while (0) +#endif diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wl_iw.c b/drivers/custom/broadcom-wl/src/wl/sys/wl_iw.c new file mode 100644 index 000000000000..e346b153f38e --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wl_iw.c @@ -0,0 +1,2809 @@ +/* + * Linux Wireless Extensions support + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wl_iw.c 458427 2014-02-26 23:12:38Z $ + */ + +#if defined(USE_IW) +#define LINUX_PORT + +#include +#include +#include + +#include +#include +#include + +#include +#include + +typedef const struct si_pub si_t; +#include + +#include +#include +#include + +extern bool wl_iw_conn_status_str(uint32 event_type, uint32 status, + uint32 reason, char* stringBuf, uint buflen); + +#define MAX_WLIW_IOCTL_LEN 1024 + +#define htod32(i) i +#define htod16(i) i +#define dtoh32(i) i +#define dtoh16(i) i +#define htodchanspec(i) i +#define dtohchanspec(i) i + +extern struct iw_statistics *wl_get_wireless_stats(struct net_device *dev); + +#if WIRELESS_EXT < 19 +#define IW_IOCTL_IDX(cmd) ((cmd) - SIOCIWFIRST) +#define IW_EVENT_IDX(cmd) ((cmd) - IWEVFIRST) +#endif + +typedef struct priv_link { + wl_iw_t *wliw; +} priv_link_t; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) +#define WL_DEV_LINK(dev) (priv_link_t*)(dev->priv) +#else +#define WL_DEV_LINK(dev) (priv_link_t*)netdev_priv(dev) +#endif + +#define IW_DEV_IF(dev) ((wl_iw_t*)(WL_DEV_LINK(dev))->wliw) + +static void swap_key_from_BE( + wl_wsec_key_t *key +) +{ + key->index = htod32(key->index); + key->len = htod32(key->len); + key->algo = htod32(key->algo); + key->flags = htod32(key->flags); + key->rxiv.hi = htod32(key->rxiv.hi); + key->rxiv.lo = htod16(key->rxiv.lo); + key->iv_initialized = htod32(key->iv_initialized); +} + +static void swap_key_to_BE( + wl_wsec_key_t *key +) +{ + key->index = dtoh32(key->index); + key->len = dtoh32(key->len); + key->algo = dtoh32(key->algo); + key->flags = dtoh32(key->flags); + key->rxiv.hi = dtoh32(key->rxiv.hi); + key->rxiv.lo = dtoh16(key->rxiv.lo); + key->iv_initialized = dtoh32(key->iv_initialized); +} + +static int +dev_wlc_ioctl( + struct net_device *dev, + int cmd, + void *arg, + int len +) +{ + return wlc_ioctl_internal(dev, cmd, arg, len); +} + +static int +dev_wlc_intvar_set( + struct net_device *dev, + char *name, + int val) +{ + char buf[WLC_IOCTL_SMLEN]; + uint len; + + val = htod32(val); + len = bcm_mkiovar(name, (char *)(&val), sizeof(val), buf, sizeof(buf)); + ASSERT(len); + + return (dev_wlc_ioctl(dev, WLC_SET_VAR, buf, len)); +} + +#if WIRELESS_EXT > 17 +static int +dev_wlc_bufvar_set( + struct net_device *dev, + char *name, + char *buf, int len) +{ + char *ioctlbuf; + uint buflen; + int error; + + ioctlbuf = kmalloc(MAX_WLIW_IOCTL_LEN, GFP_KERNEL); + if (!ioctlbuf) + return -ENOMEM; + + buflen = bcm_mkiovar(name, buf, len, ioctlbuf, MAX_WLIW_IOCTL_LEN); + ASSERT(buflen); + error = dev_wlc_ioctl(dev, WLC_SET_VAR, ioctlbuf, buflen); + + kfree(ioctlbuf); + return error; +} +#endif + +static int +dev_wlc_bufvar_get( + struct net_device *dev, + char *name, + char *buf, int buflen) +{ + char *ioctlbuf; + int error; + + uint len; + + ioctlbuf = kmalloc(MAX_WLIW_IOCTL_LEN, GFP_KERNEL); + if (!ioctlbuf) + return -ENOMEM; + len = bcm_mkiovar(name, NULL, 0, ioctlbuf, MAX_WLIW_IOCTL_LEN); + ASSERT(len); + BCM_REFERENCE(len); + error = dev_wlc_ioctl(dev, WLC_GET_VAR, (void *)ioctlbuf, MAX_WLIW_IOCTL_LEN); + if (!error) + bcopy(ioctlbuf, buf, buflen); + + kfree(ioctlbuf); + return (error); +} + +static int +dev_wlc_intvar_get( + struct net_device *dev, + char *name, + int *retval) +{ + union { + char buf[WLC_IOCTL_SMLEN]; + int val; + } var; + int error; + + uint len; + uint data_null; + + len = bcm_mkiovar(name, (char *)(&data_null), 0, (char *)(&var), sizeof(var.buf)); + ASSERT(len); + error = dev_wlc_ioctl(dev, WLC_GET_VAR, (void *)&var, len); + + *retval = dtoh32(var.val); + + return (error); +} + +#if WIRELESS_EXT < 13 +struct iw_request_info +{ + __u16 cmd; + __u16 flags; +}; + +typedef int (*iw_handler)(struct net_device *dev, struct iw_request_info *info, + void *wrqu, char *extra); +#endif + +#if WIRELESS_EXT > 12 +static int +wl_iw_set_leddc( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int dc = *(int *)extra; + int error; + + error = dev_wlc_intvar_set(dev, "leddc", dc); + return error; +} + +static int +wl_iw_set_vlanmode( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int mode = *(int *)extra; + int error; + + mode = htod32(mode); + error = dev_wlc_intvar_set(dev, "vlan_mode", mode); + return error; +} + +static int +wl_iw_set_pm( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int pm = *(int *)extra; + int error; + + pm = htod32(pm); + error = dev_wlc_ioctl(dev, WLC_SET_PM, &pm, sizeof(pm)); + return error; +} +#endif + +int +wl_iw_send_priv_event( + struct net_device *dev, + char *flag +) +{ + union iwreq_data wrqu; + char extra[IW_CUSTOM_MAX + 1]; + int cmd; + + cmd = IWEVCUSTOM; + memset(&wrqu, 0, sizeof(wrqu)); + if (strlen(flag) > sizeof(extra)) + return -1; + + strcpy(extra, flag); + wrqu.data.length = strlen(extra); + wireless_send_event(dev, cmd, &wrqu, extra); + WL_TRACE(("Send IWEVCUSTOM Event as %s\n", extra)); + + return 0; +} + +static int +wl_iw_config_commit( + struct net_device *dev, + struct iw_request_info *info, + void *zwrq, + char *extra +) +{ + wlc_ssid_t ssid; + int error; + struct sockaddr bssid; + + WL_TRACE(("%s: SIOCSIWCOMMIT\n", dev->name)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_SSID, &ssid, sizeof(ssid)))) + return error; + + ssid.SSID_len = dtoh32(ssid.SSID_len); + + if (!ssid.SSID_len) + return 0; + + bzero(&bssid, sizeof(struct sockaddr)); + if ((error = dev_wlc_ioctl(dev, WLC_REASSOC, &bssid, ETHER_ADDR_LEN))) { + WL_ERROR(("%s: WLC_REASSOC failed (%d)\n", __FUNCTION__, error)); + return error; + } + + return 0; +} + +static int +wl_iw_get_name( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *cwrq, + char *extra +) +{ + int phytype, err; + uint band[3]; + char cap[5]; + + WL_TRACE(("%s: SIOCGIWNAME\n", dev->name)); + + cap[0] = 0; + if ((err = dev_wlc_ioctl(dev, WLC_GET_PHYTYPE, &phytype, sizeof(phytype))) < 0) + goto done; + if ((err = dev_wlc_ioctl(dev, WLC_GET_BANDLIST, band, sizeof(band))) < 0) + goto done; + + band[0] = dtoh32(band[0]); + switch (phytype) { + case WLC_PHY_TYPE_A: + strcpy(cap, "a"); + break; + case WLC_PHY_TYPE_B: + strcpy(cap, "b"); + break; + case WLC_PHY_TYPE_LP: + case WLC_PHY_TYPE_G: + if (band[0] >= 2) + strcpy(cap, "abg"); + else + strcpy(cap, "bg"); + break; + case WLC_PHY_TYPE_N: + if (band[0] >= 2) + strcpy(cap, "abgn"); + else + strcpy(cap, "bgn"); + break; + } +done: + snprintf(cwrq->name, IFNAMSIZ, "IEEE 802.11%s", cap); + return 0; +} + +static int +wl_iw_set_freq( + struct net_device *dev, + struct iw_request_info *info, + struct iw_freq *fwrq, + char *extra +) +{ + int error, chan; + uint sf = 0; + + WL_TRACE(("%s: SIOCSIWFREQ\n", dev->name)); + + if (fwrq->e == 0 && fwrq->m < MAXCHANNEL) { + chan = fwrq->m; + } + + else { + + if (fwrq->e >= 6) { + fwrq->e -= 6; + while (fwrq->e--) + fwrq->m *= 10; + } else if (fwrq->e < 6) { + while (fwrq->e++ < 6) + fwrq->m /= 10; + } + + if (fwrq->m > 4000 && fwrq->m < 5000) + sf = WF_CHAN_FACTOR_4_G; + + chan = wf_mhz2channel(fwrq->m, sf); + } + chan = htod32(chan); + if ((error = dev_wlc_ioctl(dev, WLC_SET_CHANNEL, &chan, sizeof(chan)))) + return error; + + return -EINPROGRESS; +} + +static int +wl_iw_get_freq( + struct net_device *dev, + struct iw_request_info *info, + struct iw_freq *fwrq, + char *extra +) +{ + channel_info_t ci; + int error; + + WL_TRACE(("%s: SIOCGIWFREQ\n", dev->name)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_CHANNEL, &ci, sizeof(ci)))) + return error; + + fwrq->m = dtoh32(ci.hw_channel); + fwrq->e = dtoh32(0); + return 0; +} + +static int +wl_iw_set_mode( + struct net_device *dev, + struct iw_request_info *info, + __u32 *uwrq, + char *extra +) +{ + int infra = 0, ap = 0, error = 0; + + WL_TRACE(("%s: SIOCSIWMODE\n", dev->name)); + + switch (*uwrq) { + case IW_MODE_MASTER: + infra = ap = 1; + break; + case IW_MODE_ADHOC: + case IW_MODE_AUTO: + break; + case IW_MODE_INFRA: + infra = 1; + break; + default: + return -EINVAL; + } + infra = htod32(infra); + ap = htod32(ap); + + if ((error = dev_wlc_ioctl(dev, WLC_SET_INFRA, &infra, sizeof(infra))) || + (error = dev_wlc_ioctl(dev, WLC_SET_AP, &ap, sizeof(ap)))) + return error; + + return -EINPROGRESS; +} + +static int +wl_iw_get_mode( + struct net_device *dev, + struct iw_request_info *info, + __u32 *uwrq, + char *extra +) +{ + int error, infra = 0, ap = 0; + + WL_TRACE(("%s: SIOCGIWMODE\n", dev->name)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_INFRA, &infra, sizeof(infra))) || + (error = dev_wlc_ioctl(dev, WLC_GET_AP, &ap, sizeof(ap)))) + return error; + + infra = dtoh32(infra); + ap = dtoh32(ap); + *uwrq = infra ? ap ? IW_MODE_MASTER : IW_MODE_INFRA : IW_MODE_ADHOC; + + return 0; +} + +static int +wl_iw_get_range( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + struct iw_range *range = (struct iw_range *) extra; + static int channels[MAXCHANNEL+1]; + wl_uint32_list_t *list = (wl_uint32_list_t *) channels; + wl_rateset_t rateset; + int error, i, k; + uint sf, ch; + + int phytype; + int bw_cap = 0, sgi_tx = 0, nmode = 0; + channel_info_t ci; + uint8 nrate_list2copy = 0; + uint16 nrate_list[4][8] = { {13, 26, 39, 52, 78, 104, 117, 130}, + {14, 29, 43, 58, 87, 116, 130, 144}, + {27, 54, 81, 108, 162, 216, 243, 270}, + {30, 60, 90, 120, 180, 240, 270, 300}}; + + WL_TRACE(("%s: SIOCGIWRANGE\n", dev->name)); + + if (!extra) + return -EINVAL; + + dwrq->length = sizeof(struct iw_range); + memset(range, 0, sizeof(*range)); + + range->min_nwid = range->max_nwid = 0; + + list->count = htod32(MAXCHANNEL); + if ((error = dev_wlc_ioctl(dev, WLC_GET_VALID_CHANNELS, channels, sizeof(channels)))) + return error; + for (i = 0; i < dtoh32(list->count) && i < IW_MAX_FREQUENCIES; i++) { + range->freq[i].i = dtoh32(list->element[i]); + + ch = dtoh32(list->element[i]); + if (ch <= CH_MAX_2G_CHANNEL) + sf = WF_CHAN_FACTOR_2_4_G; + else + sf = WF_CHAN_FACTOR_5_G; + + range->freq[i].m = wf_channel2mhz(ch, sf); + range->freq[i].e = 6; + } + range->num_frequency = range->num_channels = i; + + range->max_qual.qual = 5; + + range->max_qual.level = 0x100 - 200; + + range->max_qual.noise = 0x100 - 200; + + range->sensitivity = 65535; + +#if WIRELESS_EXT > 11 + + range->avg_qual.qual = 3; + + range->avg_qual.level = 0x100 + WL_IW_RSSI_GOOD; + + range->avg_qual.noise = 0x100 - 75; +#endif + + if ((error = dev_wlc_ioctl(dev, WLC_GET_CURR_RATESET, &rateset, sizeof(rateset)))) + return error; + rateset.count = dtoh32(rateset.count); + range->num_bitrates = rateset.count; + for (i = 0; i < rateset.count && i < IW_MAX_BITRATES; i++) + range->bitrate[i] = (rateset.rates[i] & 0x7f) * 500000; + dev_wlc_intvar_get(dev, "nmode", &nmode); + if ((error = dev_wlc_ioctl(dev, WLC_GET_PHYTYPE, &phytype, sizeof(phytype)))) + return error; + + if (nmode == 1 && ((phytype == WLC_PHY_TYPE_SSN) || (phytype == WLC_PHY_TYPE_LCN) || + (phytype == WLC_PHY_TYPE_LCN40))) { + dev_wlc_intvar_get(dev, "mimo_bw_cap", &bw_cap); + dev_wlc_intvar_get(dev, "sgi_tx", &sgi_tx); + dev_wlc_ioctl(dev, WLC_GET_CHANNEL, &ci, sizeof(channel_info_t)); + ci.hw_channel = dtoh32(ci.hw_channel); + + if (bw_cap == 0 || + (bw_cap == 2 && ci.hw_channel <= 14)) { + if (sgi_tx == 0) + nrate_list2copy = 0; + else + nrate_list2copy = 1; + } + if (bw_cap == 1 || + (bw_cap == 2 && ci.hw_channel >= 36)) { + if (sgi_tx == 0) + nrate_list2copy = 2; + else + nrate_list2copy = 3; + } + range->num_bitrates += 8; + for (k = 0; i < range->num_bitrates; k++, i++) { + + range->bitrate[i] = (nrate_list[nrate_list2copy][k]) * 500000; + } + } + + if ((error = dev_wlc_ioctl(dev, WLC_GET_PHYTYPE, &i, sizeof(i)))) + return error; + i = dtoh32(i); + if (i == WLC_PHY_TYPE_A) + range->throughput = 24000000; + else + range->throughput = 1500000; + + range->min_rts = 0; + range->max_rts = 2347; + range->min_frag = 256; + range->max_frag = 2346; + + range->max_encoding_tokens = DOT11_MAX_DEFAULT_KEYS; + range->num_encoding_sizes = 4; + range->encoding_size[0] = WEP1_KEY_SIZE; + range->encoding_size[1] = WEP128_KEY_SIZE; +#if WIRELESS_EXT > 17 + range->encoding_size[2] = TKIP_KEY_SIZE; +#else + range->encoding_size[2] = 0; +#endif + range->encoding_size[3] = AES_KEY_SIZE; + + range->min_pmp = 0; + range->max_pmp = 0; + range->min_pmt = 0; + range->max_pmt = 0; + range->pmp_flags = 0; + range->pm_capa = 0; + + range->num_txpower = 2; + range->txpower[0] = 1; + range->txpower[1] = 255; + range->txpower_capa = IW_TXPOW_MWATT; + +#if WIRELESS_EXT > 10 + range->we_version_compiled = WIRELESS_EXT; + range->we_version_source = 19; + + range->retry_capa = IW_RETRY_LIMIT; + range->retry_flags = IW_RETRY_LIMIT; + range->r_time_flags = 0; + + range->min_retry = 1; + range->max_retry = 255; + + range->min_r_time = 0; + range->max_r_time = 0; +#endif + +#if WIRELESS_EXT > 17 + range->enc_capa = IW_ENC_CAPA_WPA; + range->enc_capa |= IW_ENC_CAPA_CIPHER_TKIP; + range->enc_capa |= IW_ENC_CAPA_CIPHER_CCMP; + range->enc_capa |= IW_ENC_CAPA_WPA2; + + IW_EVENT_CAPA_SET_KERNEL(range->event_capa); + + IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWAP); + IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWSCAN); + IW_EVENT_CAPA_SET(range->event_capa, IWEVTXDROP); + IW_EVENT_CAPA_SET(range->event_capa, IWEVMICHAELMICFAILURE); + IW_EVENT_CAPA_SET(range->event_capa, IWEVASSOCREQIE); + IW_EVENT_CAPA_SET(range->event_capa, IWEVASSOCRESPIE); + IW_EVENT_CAPA_SET(range->event_capa, IWEVPMKIDCAND); + +#if WIRELESS_EXT >= 22 && defined(IW_SCAN_CAPA_ESSID) + + range->scan_capa = IW_SCAN_CAPA_ESSID; +#endif +#endif + + return 0; +} + +static int +rssi_to_qual(int rssi) +{ + if (rssi <= WL_IW_RSSI_NO_SIGNAL) + return 0; + else if (rssi <= WL_IW_RSSI_VERY_LOW) + return 1; + else if (rssi <= WL_IW_RSSI_LOW) + return 2; + else if (rssi <= WL_IW_RSSI_GOOD) + return 3; + else if (rssi <= WL_IW_RSSI_VERY_GOOD) + return 4; + else + return 5; +} + +static int +wl_iw_set_spy( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_iw_t *iw = IW_DEV_IF(dev); + struct sockaddr *addr = (struct sockaddr *) extra; + int i; + + WL_TRACE(("%s: SIOCSIWSPY\n", dev->name)); + + if (!extra) + return -EINVAL; + + iw->spy_num = MIN(ARRAYSIZE(iw->spy_addr), dwrq->length); + for (i = 0; i < iw->spy_num; i++) + memcpy(&iw->spy_addr[i], addr[i].sa_data, ETHER_ADDR_LEN); + memset(iw->spy_qual, 0, sizeof(iw->spy_qual)); + + return 0; +} + +static int +wl_iw_get_spy( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_iw_t *iw = IW_DEV_IF(dev); + struct sockaddr *addr = (struct sockaddr *) extra; + struct iw_quality *qual = (struct iw_quality *) &addr[iw->spy_num]; + int i; + + WL_TRACE(("%s: SIOCGIWSPY\n", dev->name)); + + if (!extra) + return -EINVAL; + + dwrq->length = iw->spy_num; + for (i = 0; i < iw->spy_num; i++) { + memcpy(addr[i].sa_data, &iw->spy_addr[i], ETHER_ADDR_LEN); + addr[i].sa_family = AF_UNIX; + memcpy(&qual[i], &iw->spy_qual[i], sizeof(struct iw_quality)); + iw->spy_qual[i].updated = 0; + } + + return 0; +} + +static int +wl_iw_set_wap( + struct net_device *dev, + struct iw_request_info *info, + struct sockaddr *awrq, + char *extra +) +{ + int error = -EINVAL; +#ifdef BCMDBG + +#endif + + WL_TRACE(("%s: SIOCSIWAP\n", dev->name)); + + if (awrq->sa_family != ARPHRD_ETHER) { + WL_ERROR(("%s: Invalid Header...sa_family\n", __FUNCTION__)); + return -EINVAL; + } + + if (ETHER_ISBCAST(awrq->sa_data) || ETHER_ISNULLADDR(awrq->sa_data)) { + scb_val_t scbval; + bzero(&scbval, sizeof(scb_val_t)); + if ((error = dev_wlc_ioctl(dev, WLC_DISASSOC, &scbval, sizeof(scb_val_t)))) { + WL_ERROR(("%s: WLC_DISASSOC failed (%d).\n", __FUNCTION__, error)); + } + return 0; + } + + if ((error = dev_wlc_ioctl(dev, WLC_REASSOC, awrq->sa_data, ETHER_ADDR_LEN))) { + WL_ERROR(("%s: WLC_REASSOC failed (%d).\n", __FUNCTION__, error)); + return error; + } + + return 0; +} + +static int +wl_iw_get_wap( + struct net_device *dev, + struct iw_request_info *info, + struct sockaddr *awrq, + char *extra +) +{ + WL_TRACE(("%s: SIOCGIWAP\n", dev->name)); + + awrq->sa_family = ARPHRD_ETHER; + memset(awrq->sa_data, 0, ETHER_ADDR_LEN); + + (void) dev_wlc_ioctl(dev, WLC_GET_BSSID, awrq->sa_data, ETHER_ADDR_LEN); + + return 0; +} + +#if WIRELESS_EXT > 17 +static int +wl_iw_mlme( + struct net_device *dev, + struct iw_request_info *info, + struct sockaddr *awrq, + char *extra +) +{ + struct iw_mlme *mlme; + scb_val_t scbval; + int error = -EINVAL; + + WL_TRACE(("%s: SIOCSIWMLME\n", dev->name)); + + mlme = (struct iw_mlme *)extra; + if (mlme == NULL) { + WL_ERROR(("Invalid ioctl data.\n")); + return error; + } + + scbval.val = mlme->reason_code; + bcopy(&mlme->addr.sa_data, &scbval.ea, ETHER_ADDR_LEN); + + if (mlme->cmd == IW_MLME_DISASSOC) { + scbval.val = htod32(scbval.val); + error = dev_wlc_ioctl(dev, WLC_DISASSOC, &scbval, sizeof(scb_val_t)); + } + else if (mlme->cmd == IW_MLME_DEAUTH) { + scbval.val = htod32(scbval.val); + error = dev_wlc_ioctl(dev, WLC_SCB_DEAUTHENTICATE_FOR_REASON, &scbval, + sizeof(scb_val_t)); + } + else { + WL_ERROR(("%s: Invalid ioctl data.\n", __FUNCTION__)); + return error; + } + + return error; +} +#endif + +static int +wl_iw_get_aplist( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_scan_results_t *list; + struct sockaddr *addr = (struct sockaddr *) extra; + struct iw_quality qual[IW_MAX_AP]; + wl_bss_info_t *bi = NULL; + int error, i; + uint buflen = dwrq->length; + + WL_TRACE(("%s: SIOCGIWAPLIST\n", dev->name)); + + if (!extra) + return -EINVAL; + + list = kmalloc(buflen, GFP_KERNEL); + if (!list) + return -ENOMEM; + memset(list, 0, buflen); + list->buflen = htod32(buflen); + if ((error = dev_wlc_ioctl(dev, WLC_SCAN_RESULTS, list, buflen))) { + WL_ERROR(("%d: Scan results error %d\n", __LINE__, error)); + kfree(list); + return error; + } + list->buflen = dtoh32(list->buflen); + list->version = dtoh32(list->version); + list->count = dtoh32(list->count); + ASSERT(list->version == WL_BSS_INFO_VERSION); + + for (i = 0, dwrq->length = 0; i < list->count && dwrq->length < IW_MAX_AP; i++) { + bi = bi ? (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)) : list->bss_info; + ASSERT(((uintptr)bi + dtoh32(bi->length)) <= ((uintptr)list + + buflen)); + + if (!(dtoh16(bi->capability) & DOT11_CAP_ESS)) + continue; + + memcpy(addr[dwrq->length].sa_data, &bi->BSSID, ETHER_ADDR_LEN); + addr[dwrq->length].sa_family = ARPHRD_ETHER; + qual[dwrq->length].qual = rssi_to_qual(dtoh16(bi->RSSI)); + qual[dwrq->length].level = 0x100 + dtoh16(bi->RSSI); + qual[dwrq->length].noise = 0x100 + bi->phy_noise; + +#if WIRELESS_EXT > 18 + qual[dwrq->length].updated = IW_QUAL_ALL_UPDATED | IW_QUAL_DBM; +#else + qual[dwrq->length].updated = 7; +#endif + + dwrq->length++; + } + + kfree(list); + + if (dwrq->length) { + memcpy(&addr[dwrq->length], qual, sizeof(struct iw_quality) * dwrq->length); + + dwrq->flags = 1; + } + + return 0; +} + +#if WIRELESS_EXT > 13 +static int +wl_iw_set_scan( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + wlc_ssid_t ssid; + + WL_TRACE(("%s: SIOCSIWSCAN\n", dev->name)); + + memset(&ssid, 0, sizeof(ssid)); + +#if WIRELESS_EXT > 17 + + if (wrqu->data.length == sizeof(struct iw_scan_req)) { + if (wrqu->data.flags & IW_SCAN_THIS_ESSID) { + struct iw_scan_req *req = (struct iw_scan_req *)extra; + ssid.SSID_len = MIN(sizeof(ssid.SSID), req->essid_len); + memcpy(ssid.SSID, req->essid, ssid.SSID_len); + ssid.SSID_len = htod32(ssid.SSID_len); + } + } +#endif + + (void) dev_wlc_ioctl(dev, WLC_SCAN, &ssid, sizeof(ssid)); + + return 0; +} + +#if WIRELESS_EXT > 17 +static bool +ie_is_wpa_ie(uint8 **wpaie, uint8 **tlvs, int *tlvs_len) +{ + + uint8 *ie = *wpaie; + + if ((ie[1] >= 6) && + !bcmp((const void *)&ie[2], (const void *)(WPA_OUI "\x01"), 4)) { + return TRUE; + } + + ie += ie[1] + 2; + + *tlvs_len -= (int)(ie - *tlvs); + + *tlvs = ie; + return FALSE; +} + +static bool +ie_is_wps_ie(uint8 **wpsie, uint8 **tlvs, int *tlvs_len) +{ + + uint8 *ie = *wpsie; + + if ((ie[1] >= 4) && + !bcmp((const void *)&ie[2], (const void *)(WPA_OUI "\x04"), 4)) { + return TRUE; + } + + ie += ie[1] + 2; + + *tlvs_len -= (int)(ie - *tlvs); + + *tlvs = ie; + return FALSE; +} +#endif + +static int +wl_iw_handle_scanresults_ies(char **event_p, char *end, + struct iw_request_info *info, wl_bss_info_t *bi) +{ +#if WIRELESS_EXT > 17 + struct iw_event iwe; + char *event; + + event = *event_p; + if (bi->ie_length) { + + bcm_tlv_t *ie; + uint8 *ptr = ((uint8 *)bi) + sizeof(wl_bss_info_t); + int ptr_len = bi->ie_length; + + if ((ie = bcm_parse_tlvs(ptr, ptr_len, DOT11_MNG_RSN_ID))) { + iwe.cmd = IWEVGENIE; + iwe.u.data.length = ie->len + 2; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, (char *)ie); + } + ptr = ((uint8 *)bi) + sizeof(wl_bss_info_t); + + while ((ie = bcm_parse_tlvs(ptr, ptr_len, DOT11_MNG_WPA_ID))) { + + if (ie_is_wps_ie(((uint8 **)&ie), &ptr, &ptr_len)) { + iwe.cmd = IWEVGENIE; + iwe.u.data.length = ie->len + 2; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, (char *)ie); + break; + } + } + + ptr = ((uint8 *)bi) + sizeof(wl_bss_info_t); + ptr_len = bi->ie_length; + while ((ie = bcm_parse_tlvs(ptr, ptr_len, DOT11_MNG_WPA_ID))) { + if (ie_is_wpa_ie(((uint8 **)&ie), &ptr, &ptr_len)) { + iwe.cmd = IWEVGENIE; + iwe.u.data.length = ie->len + 2; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, (char *)ie); + break; + } + } + + *event_p = event; + } + +#endif + return 0; +} +static int +wl_iw_get_scan( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + channel_info_t ci; + wl_scan_results_t *list; + struct iw_event iwe; + wl_bss_info_t *bi = NULL; + int error, i, j; + char *event = extra, *end = extra + dwrq->length, *value; + uint buflen = dwrq->length; + + WL_TRACE(("%s: SIOCGIWSCAN\n", dev->name)); + + if (!extra) + return -EINVAL; + + if ((error = dev_wlc_ioctl(dev, WLC_GET_CHANNEL, &ci, sizeof(ci)))) + return error; + ci.scan_channel = dtoh32(ci.scan_channel); + if (ci.scan_channel) + return -EAGAIN; + + list = kmalloc(buflen, GFP_KERNEL); + if (!list) + return -ENOMEM; + memset(list, 0, buflen); + list->buflen = htod32(buflen); + if ((error = dev_wlc_ioctl(dev, WLC_SCAN_RESULTS, list, buflen))) { + kfree(list); + return error; + } + list->buflen = dtoh32(list->buflen); + list->version = dtoh32(list->version); + list->count = dtoh32(list->count); + + ASSERT(list->version == WL_BSS_INFO_VERSION); + + for (i = 0; i < list->count && i < IW_MAX_AP; i++) { + bi = bi ? (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)) : list->bss_info; + ASSERT(((uintptr)bi + dtoh32(bi->length)) <= ((uintptr)list + + buflen)); + + iwe.cmd = SIOCGIWAP; + iwe.u.ap_addr.sa_family = ARPHRD_ETHER; + memcpy(iwe.u.ap_addr.sa_data, &bi->BSSID, ETHER_ADDR_LEN); + event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_ADDR_LEN); + + iwe.u.data.length = dtoh32(bi->SSID_len); + iwe.cmd = SIOCGIWESSID; + iwe.u.data.flags = 1; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, bi->SSID); + + if (dtoh16(bi->capability) & (DOT11_CAP_ESS | DOT11_CAP_IBSS)) { + iwe.cmd = SIOCGIWMODE; + if (dtoh16(bi->capability) & DOT11_CAP_ESS) + iwe.u.mode = IW_MODE_INFRA; + else + iwe.u.mode = IW_MODE_ADHOC; + event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_UINT_LEN); + } + + iwe.cmd = SIOCGIWFREQ; + iwe.u.freq.m = wf_channel2mhz(CHSPEC_CHANNEL(bi->chanspec), + CHSPEC_CHANNEL(bi->chanspec) <= CH_MAX_2G_CHANNEL ? + WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G); + iwe.u.freq.e = 6; + event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_FREQ_LEN); + + iwe.cmd = IWEVQUAL; + iwe.u.qual.qual = rssi_to_qual(dtoh16(bi->RSSI)); + iwe.u.qual.level = 0x100 + dtoh16(bi->RSSI); + iwe.u.qual.noise = 0x100 + bi->phy_noise; + event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_QUAL_LEN); + + wl_iw_handle_scanresults_ies(&event, end, info, bi); + + iwe.cmd = SIOCGIWENCODE; + if (dtoh16(bi->capability) & DOT11_CAP_PRIVACY) + iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; + else + iwe.u.data.flags = IW_ENCODE_DISABLED; + iwe.u.data.length = 0; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, (char *)event); + + if (bi->rateset.count) { + value = event + IW_EV_LCP_LEN; + iwe.cmd = SIOCGIWRATE; + + iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0; + for (j = 0; j < bi->rateset.count && j < IW_MAX_BITRATES; j++) { + iwe.u.bitrate.value = (bi->rateset.rates[j] & 0x7f) * 500000; + value = IWE_STREAM_ADD_VALUE(info, event, value, end, &iwe, + IW_EV_PARAM_LEN); + } + event = value; + } + } + + kfree(list); + + dwrq->length = event - extra; + dwrq->flags = 0; + + return 0; +} + +#endif + +static int +wl_iw_set_essid( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wlc_ssid_t ssid; + int error; + + WL_TRACE(("%s: SIOCSIWESSID\n", dev->name)); + + memset(&ssid, 0, sizeof(ssid)); + if (dwrq->length && extra) { +#if WIRELESS_EXT > 20 + ssid.SSID_len = MIN(sizeof(ssid.SSID), dwrq->length); +#else + ssid.SSID_len = MIN(sizeof(ssid.SSID), dwrq->length-1); +#endif + memcpy(ssid.SSID, extra, ssid.SSID_len); + ssid.SSID_len = htod32(ssid.SSID_len); + + if ((error = dev_wlc_ioctl(dev, WLC_SET_SSID, &ssid, sizeof(ssid)))) + return error; + } + + else { + scb_val_t scbval; + bzero(&scbval, sizeof(scb_val_t)); + if ((error = dev_wlc_ioctl(dev, WLC_DISASSOC, &scbval, sizeof(scb_val_t)))) + return error; + } + return 0; +} + +static int +wl_iw_get_essid( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wlc_ssid_t ssid; + int error; + + WL_TRACE(("%s: SIOCGIWESSID\n", dev->name)); + + if (!extra) + return -EINVAL; + + if ((error = dev_wlc_ioctl(dev, WLC_GET_SSID, &ssid, sizeof(ssid)))) { + WL_ERROR(("Error getting the SSID\n")); + return error; + } + + ssid.SSID_len = dtoh32(ssid.SSID_len); + + memcpy(extra, ssid.SSID, ssid.SSID_len); + + dwrq->length = ssid.SSID_len; + + dwrq->flags = 1; + + return 0; +} + +static int +wl_iw_set_nick( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_iw_t *iw = IW_DEV_IF(dev); + WL_TRACE(("%s: SIOCSIWNICKN\n", dev->name)); + + if (!extra) + return -EINVAL; + + if (dwrq->length > sizeof(iw->nickname)) + return -E2BIG; + + memcpy(iw->nickname, extra, dwrq->length); + iw->nickname[dwrq->length - 1] = '\0'; + + return 0; +} + +static int +wl_iw_get_nick( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_iw_t *iw = IW_DEV_IF(dev); + WL_TRACE(("%s: SIOCGIWNICKN\n", dev->name)); + + if (!extra) + return -EINVAL; + + strcpy(extra, iw->nickname); + dwrq->length = strlen(extra) + 1; + + return 0; +} + +static int wl_iw_set_rate( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + wl_rateset_t rateset; + int error, rate, i, error_bg, error_a; + + WL_TRACE(("%s: SIOCSIWRATE\n", dev->name)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_CURR_RATESET, &rateset, sizeof(rateset)))) + return error; + + rateset.count = dtoh32(rateset.count); + + if (vwrq->value < 0) { + + rate = rateset.rates[rateset.count - 1] & 0x7f; + } else if (vwrq->value < rateset.count) { + + rate = rateset.rates[vwrq->value] & 0x7f; + } else { + + rate = vwrq->value / 500000; + } + + if (vwrq->fixed) { + + error_bg = dev_wlc_intvar_set(dev, "bg_rate", rate); + error_a = dev_wlc_intvar_set(dev, "a_rate", rate); + + if (error_bg && error_a) + return (error_bg | error_a); + } else { + + error_bg = dev_wlc_intvar_set(dev, "bg_rate", 0); + + error_a = dev_wlc_intvar_set(dev, "a_rate", 0); + + if (error_bg && error_a) + return (error_bg | error_a); + + for (i = 0; i < rateset.count; i++) + if ((rateset.rates[i] & 0x7f) > rate) + break; + rateset.count = htod32(i); + + if ((error = dev_wlc_ioctl(dev, WLC_SET_RATESET, &rateset, sizeof(rateset)))) + return error; + } + + return 0; +} + +static int wl_iw_get_rate( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, rate; + + WL_TRACE(("%s: SIOCGIWRATE\n", dev->name)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_RATE, &rate, sizeof(rate)))) + return error; + rate = dtoh32(rate); + vwrq->value = rate * 500000; + + return 0; +} + +static int +wl_iw_set_rts( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, rts; + + WL_TRACE(("%s: SIOCSIWRTS\n", dev->name)); + + if (vwrq->disabled) + rts = DOT11_DEFAULT_RTS_LEN; + else if (vwrq->value < 0 || vwrq->value > DOT11_DEFAULT_RTS_LEN) + return -EINVAL; + else + rts = vwrq->value; + + if ((error = dev_wlc_intvar_set(dev, "rtsthresh", rts))) + return error; + + return 0; +} + +static int +wl_iw_get_rts( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, rts; + + WL_TRACE(("%s: SIOCGIWRTS\n", dev->name)); + + if ((error = dev_wlc_intvar_get(dev, "rtsthresh", &rts))) + return error; + + vwrq->value = rts; + vwrq->disabled = (rts >= DOT11_DEFAULT_RTS_LEN); + vwrq->fixed = 1; + + return 0; +} + +static int +wl_iw_set_frag( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, frag; + + WL_TRACE(("%s: SIOCSIWFRAG\n", dev->name)); + + if (vwrq->disabled) + frag = DOT11_DEFAULT_FRAG_LEN; + else if (vwrq->value < 0 || vwrq->value > DOT11_DEFAULT_FRAG_LEN) + return -EINVAL; + else + frag = vwrq->value; + + if ((error = dev_wlc_intvar_set(dev, "fragthresh", frag))) + return error; + + return 0; +} + +static int +wl_iw_get_frag( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, fragthreshold; + + WL_TRACE(("%s: SIOCGIWFRAG\n", dev->name)); + + if ((error = dev_wlc_intvar_get(dev, "fragthresh", &fragthreshold))) + return error; + + vwrq->value = fragthreshold; + vwrq->disabled = (fragthreshold >= DOT11_DEFAULT_FRAG_LEN); + vwrq->fixed = 1; + + return 0; +} + +static int +wl_iw_set_txpow( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, disable; + uint16 txpwrmw; + WL_TRACE(("%s: SIOCSIWTXPOW\n", dev->name)); + + disable = vwrq->disabled ? WL_RADIO_SW_DISABLE : 0; + disable += WL_RADIO_SW_DISABLE << 16; + + disable = htod32(disable); + if ((error = dev_wlc_ioctl(dev, WLC_SET_RADIO, &disable, sizeof(disable)))) + return error; + + if (disable & WL_RADIO_SW_DISABLE) + return 0; + + if (!(vwrq->flags & IW_TXPOW_MWATT)) + return -EINVAL; + + if (vwrq->value < 0) + return 0; + + if (vwrq->value > 0xffff) txpwrmw = 0xffff; + else txpwrmw = (uint16)vwrq->value; + + error = dev_wlc_intvar_set(dev, "qtxpower", (int)(bcm_mw_to_qdbm(txpwrmw))); + return error; +} + +static int +wl_iw_get_txpow( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, disable, txpwrdbm; + uint8 result; + + WL_TRACE(("%s: SIOCGIWTXPOW\n", dev->name)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_RADIO, &disable, sizeof(disable))) || + (error = dev_wlc_intvar_get(dev, "qtxpower", &txpwrdbm))) + return error; + + disable = dtoh32(disable); + result = (uint8)(txpwrdbm & ~WL_TXPWR_OVERRIDE); + vwrq->value = (int32)bcm_qdbm_to_mw(result); + vwrq->fixed = 0; + vwrq->disabled = (disable & (WL_RADIO_SW_DISABLE | WL_RADIO_HW_DISABLE)) ? 1 : 0; + vwrq->flags = IW_TXPOW_MWATT; + + return 0; +} + +#if WIRELESS_EXT > 10 +static int +wl_iw_set_retry( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, lrl, srl; + + WL_TRACE(("%s: SIOCSIWRETRY\n", dev->name)); + + if (vwrq->disabled || (vwrq->flags & IW_RETRY_LIFETIME)) + return -EINVAL; + + if (vwrq->flags & IW_RETRY_LIMIT) { + +#if WIRELESS_EXT > 20 + if ((vwrq->flags & IW_RETRY_LONG) ||(vwrq->flags & IW_RETRY_MAX) || + !((vwrq->flags & IW_RETRY_SHORT) || (vwrq->flags & IW_RETRY_MIN))) { +#else + if ((vwrq->flags & IW_RETRY_MAX) || !(vwrq->flags & IW_RETRY_MIN)) { +#endif + + lrl = htod32(vwrq->value); + if ((error = dev_wlc_ioctl(dev, WLC_SET_LRL, &lrl, sizeof(lrl)))) + return error; + } + +#if WIRELESS_EXT > 20 + if ((vwrq->flags & IW_RETRY_SHORT) ||(vwrq->flags & IW_RETRY_MIN) || + !((vwrq->flags & IW_RETRY_LONG) || (vwrq->flags & IW_RETRY_MAX))) { +#else + if ((vwrq->flags & IW_RETRY_MIN) || !(vwrq->flags & IW_RETRY_MAX)) { +#endif + + srl = htod32(vwrq->value); + if ((error = dev_wlc_ioctl(dev, WLC_SET_SRL, &srl, sizeof(srl)))) + return error; + } + } + + return 0; +} + +static int +wl_iw_get_retry( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, lrl, srl; + + WL_TRACE(("%s: SIOCGIWRETRY\n", dev->name)); + + vwrq->disabled = 0; + + if ((vwrq->flags & IW_RETRY_TYPE) == IW_RETRY_LIFETIME) + return -EINVAL; + + if ((error = dev_wlc_ioctl(dev, WLC_GET_LRL, &lrl, sizeof(lrl))) || + (error = dev_wlc_ioctl(dev, WLC_GET_SRL, &srl, sizeof(srl)))) + return error; + + lrl = dtoh32(lrl); + srl = dtoh32(srl); + + if (vwrq->flags & IW_RETRY_MAX) { + vwrq->flags = IW_RETRY_LIMIT | IW_RETRY_MAX; + vwrq->value = lrl; + } else { + vwrq->flags = IW_RETRY_LIMIT; + vwrq->value = srl; + if (srl != lrl) + vwrq->flags |= IW_RETRY_MIN; + } + + return 0; +} +#endif + +static int +wl_iw_set_encode( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_wsec_key_t key; + int error, val, wsec; + + WL_TRACE(("%s: SIOCSIWENCODE\n", dev->name)); + + memset(&key, 0, sizeof(key)); + + if ((dwrq->flags & IW_ENCODE_INDEX) == 0) { + + for (key.index = 0; key.index < DOT11_MAX_DEFAULT_KEYS; key.index++) { + val = htod32(key.index); + if ((error = dev_wlc_ioctl(dev, WLC_GET_KEY_PRIMARY, &val, sizeof(val)))) + return error; + val = dtoh32(val); + if (val) + break; + } + + if (key.index == DOT11_MAX_DEFAULT_KEYS) + key.index = 0; + } else { + key.index = (dwrq->flags & IW_ENCODE_INDEX) - 1; + if (key.index >= DOT11_MAX_DEFAULT_KEYS) + return -EINVAL; + } + + wsec = (dwrq->flags & IW_ENCODE_DISABLED) ? 0 : WEP_ENABLED; + + if ((error = dev_wlc_intvar_set(dev, "wsec", wsec))) + return error; + + if (!extra || !dwrq->length || (dwrq->flags & IW_ENCODE_NOKEY)) { + + val = htod32(key.index); + if ((error = dev_wlc_ioctl(dev, WLC_SET_KEY_PRIMARY, &val, sizeof(val)))) + return error; + } else { + key.len = dwrq->length; + + if (dwrq->length > sizeof(key.data)) + return -EINVAL; + + memcpy(key.data, extra, dwrq->length); + + key.flags = WL_PRIMARY_KEY; + switch (key.len) { + case WEP1_KEY_SIZE: + key.algo = CRYPTO_ALGO_WEP1; + break; + case WEP128_KEY_SIZE: + key.algo = CRYPTO_ALGO_WEP128; + break; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) + case TKIP_KEY_SIZE: + key.algo = CRYPTO_ALGO_TKIP; + break; +#endif + case AES_KEY_SIZE: + key.algo = CRYPTO_ALGO_AES_CCM; + break; + default: + return -EINVAL; + } + + swap_key_from_BE(&key); + if ((error = dev_wlc_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)))) + return error; + } + + val = (dwrq->flags & IW_ENCODE_RESTRICTED) ? 1 : 0; + val = htod32(val); + if ((error = dev_wlc_ioctl(dev, WLC_SET_AUTH, &val, sizeof(val)))) + return error; + + return 0; +} + +static int +wl_iw_get_encode( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_wsec_key_t key; + int error, val, wsec, auth; + + WL_TRACE(("%s: SIOCGIWENCODE\n", dev->name)); + + bzero(&key, sizeof(wl_wsec_key_t)); + + if ((dwrq->flags & IW_ENCODE_INDEX) == 0) { + + for (key.index = 0; key.index < DOT11_MAX_DEFAULT_KEYS; key.index++) { + val = key.index; + if ((error = dev_wlc_ioctl(dev, WLC_GET_KEY_PRIMARY, &val, sizeof(val)))) + return error; + val = dtoh32(val); + if (val) + break; + } + } else + key.index = (dwrq->flags & IW_ENCODE_INDEX) - 1; + + if (key.index >= DOT11_MAX_DEFAULT_KEYS) + key.index = 0; + + if ((error = dev_wlc_ioctl(dev, WLC_GET_WSEC, &wsec, sizeof(wsec))) || + (error = dev_wlc_ioctl(dev, WLC_GET_AUTH, &auth, sizeof(auth)))) + return error; + + swap_key_to_BE(&key); + + wsec = dtoh32(wsec); + auth = dtoh32(auth); + + dwrq->length = MIN(IW_ENCODING_TOKEN_MAX, key.len); + + dwrq->flags = key.index + 1; + if (!(wsec & (WEP_ENABLED | TKIP_ENABLED | AES_ENABLED))) { + + dwrq->flags |= IW_ENCODE_DISABLED; + } + if (auth) { + + dwrq->flags |= IW_ENCODE_RESTRICTED; + } + + if (dwrq->length && extra) + memcpy(extra, key.data, dwrq->length); + + return 0; +} + +static int +wl_iw_set_power( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, pm; + + WL_TRACE(("%s: SIOCSIWPOWER\n", dev->name)); + + pm = vwrq->disabled ? PM_OFF : PM_MAX; + + pm = htod32(pm); + if ((error = dev_wlc_ioctl(dev, WLC_SET_PM, &pm, sizeof(pm)))) + return error; + + return 0; +} + +static int +wl_iw_get_power( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, pm; + + WL_TRACE(("%s: SIOCGIWPOWER\n", dev->name)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_PM, &pm, sizeof(pm)))) + return error; + + pm = dtoh32(pm); + vwrq->disabled = pm ? 0 : 1; + vwrq->flags = IW_POWER_ALL_R; + + return 0; +} + +#if WIRELESS_EXT > 17 +static int +wl_iw_set_wpaie( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *iwp, + char *extra +) +{ + dev_wlc_bufvar_set(dev, "wpaie", extra, iwp->length); + + return 0; +} + +static int +wl_iw_get_wpaie( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *iwp, + char *extra +) +{ + WL_TRACE(("%s: SIOCGIWGENIE\n", dev->name)); + iwp->length = 64; + dev_wlc_bufvar_get(dev, "wpaie", extra, iwp->length); + return 0; +} + +static int +wl_iw_set_encodeext( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_wsec_key_t key; + int error; + struct iw_encode_ext *iwe; + + WL_TRACE(("%s: SIOCSIWENCODEEXT\n", dev->name)); + + memset(&key, 0, sizeof(key)); + iwe = (struct iw_encode_ext *)extra; + + if (dwrq->flags & IW_ENCODE_DISABLED) { + + } + + key.index = 0; + if (dwrq->flags & IW_ENCODE_INDEX) + key.index = (dwrq->flags & IW_ENCODE_INDEX) - 1; + + key.len = iwe->key_len; + + if (!ETHER_ISMULTI(iwe->addr.sa_data)) + bcopy((void *)&iwe->addr.sa_data, (char *)&key.ea, ETHER_ADDR_LEN); + + if (key.len == 0) { + if (iwe->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) { + WL_WSEC(("Changing the the primary Key to %d\n", key.index)); + + key.index = htod32(key.index); + error = dev_wlc_ioctl(dev, WLC_SET_KEY_PRIMARY, + &key.index, sizeof(key.index)); + if (error) + return error; + } + + else { + swap_key_from_BE(&key); + dev_wlc_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)); + } + } + + else { + if (iwe->key_len > sizeof(key.data)) + return -EINVAL; + + WL_WSEC(("Setting the key index %d\n", key.index)); + if (iwe->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) { + WL_WSEC(("key is a Primary Key\n")); + key.flags = WL_PRIMARY_KEY; + } + + bcopy((void *)iwe->key, key.data, iwe->key_len); + + if (iwe->alg == IW_ENCODE_ALG_TKIP) { + uint8 keybuf[8]; + bcopy(&key.data[24], keybuf, sizeof(keybuf)); + bcopy(&key.data[16], &key.data[24], sizeof(keybuf)); + bcopy(keybuf, &key.data[16], sizeof(keybuf)); + } + + if (iwe->ext_flags & IW_ENCODE_EXT_RX_SEQ_VALID) { + uchar *ivptr; + ivptr = (uchar *)iwe->rx_seq; + key.rxiv.hi = (ivptr[5] << 24) | (ivptr[4] << 16) | + (ivptr[3] << 8) | ivptr[2]; + key.rxiv.lo = (ivptr[1] << 8) | ivptr[0]; + key.iv_initialized = TRUE; + } + + switch (iwe->alg) { + case IW_ENCODE_ALG_NONE: + key.algo = CRYPTO_ALGO_OFF; + break; + case IW_ENCODE_ALG_WEP: + if (iwe->key_len == WEP1_KEY_SIZE) + key.algo = CRYPTO_ALGO_WEP1; + else + key.algo = CRYPTO_ALGO_WEP128; + break; + case IW_ENCODE_ALG_TKIP: + key.algo = CRYPTO_ALGO_TKIP; + break; + case IW_ENCODE_ALG_CCMP: + key.algo = CRYPTO_ALGO_AES_CCM; + break; + default: + break; + } + swap_key_from_BE(&key); + + error = dev_wlc_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)); + if (error) + return error; + } + return 0; +} + +#if WIRELESS_EXT > 17 +struct { + pmkid_list_t pmkids; + pmkid_t foo[MAXPMKID-1]; +} pmkid_list; +static int +wl_iw_set_pmksa( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + struct iw_pmksa *iwpmksa; + uint i; + char eabuf[ETHER_ADDR_STR_LEN]; + pmkid_t * pmkid_array = pmkid_list.pmkids.pmkid; + + WL_TRACE(("%s: SIOCSIWPMKSA\n", dev->name)); + iwpmksa = (struct iw_pmksa *)extra; + bzero((char *)eabuf, ETHER_ADDR_STR_LEN); + if (iwpmksa->cmd == IW_PMKSA_FLUSH) { + WL_TRACE(("wl_iw_set_pmksa - IW_PMKSA_FLUSH\n")); + bzero((char *)&pmkid_list, sizeof(pmkid_list)); + } + if (iwpmksa->cmd == IW_PMKSA_REMOVE) { + pmkid_list_t pmkid, *pmkidptr; + pmkidptr = &pmkid; + bcopy(&iwpmksa->bssid.sa_data[0], &pmkidptr->pmkid[0].BSSID, ETHER_ADDR_LEN); + bcopy(&iwpmksa->pmkid[0], &pmkidptr->pmkid[0].PMKID, WPA2_PMKID_LEN); + { + uint j; + WL_TRACE(("wl_iw_set_pmksa,IW_PMKSA_REMOVE - PMKID: %s = ", + bcm_ether_ntoa(&pmkidptr->pmkid[0].BSSID, + eabuf))); + for (j = 0; j < WPA2_PMKID_LEN; j++) + WL_TRACE(("%02x ", pmkidptr->pmkid[0].PMKID[j])); + WL_TRACE(("\n")); + } + for (i = 0; i < pmkid_list.pmkids.npmkid; i++) + if (!bcmp(&iwpmksa->bssid.sa_data[0], &pmkid_array[i].BSSID, + ETHER_ADDR_LEN)) + break; + for (; i < pmkid_list.pmkids.npmkid; i++) { + bcopy(&pmkid_array[i+1].BSSID, + &pmkid_array[i].BSSID, + ETHER_ADDR_LEN); + bcopy(&pmkid_array[i+1].PMKID, + &pmkid_array[i].PMKID, + WPA2_PMKID_LEN); + } + pmkid_list.pmkids.npmkid--; + } + if (iwpmksa->cmd == IW_PMKSA_ADD) { + bcopy(&iwpmksa->bssid.sa_data[0], + &pmkid_array[pmkid_list.pmkids.npmkid].BSSID, + ETHER_ADDR_LEN); + bcopy(&iwpmksa->pmkid[0], &pmkid_array[pmkid_list.pmkids.npmkid].PMKID, + WPA2_PMKID_LEN); + { + uint j; + uint k; + k = pmkid_list.pmkids.npmkid; + BCM_REFERENCE(k); + WL_TRACE(("wl_iw_set_pmksa,IW_PMKSA_ADD - PMKID: %s = ", + bcm_ether_ntoa(&pmkid_array[k].BSSID, + eabuf))); + for (j = 0; j < WPA2_PMKID_LEN; j++) + WL_TRACE(("%02x ", pmkid_array[k].PMKID[j])); + WL_TRACE(("\n")); + } + pmkid_list.pmkids.npmkid++; + } + WL_TRACE(("PRINTING pmkid LIST - No of elements %d\n", pmkid_list.pmkids.npmkid)); + for (i = 0; i < pmkid_list.pmkids.npmkid; i++) { + uint j; + WL_TRACE(("PMKID[%d]: %s = ", i, + bcm_ether_ntoa(&pmkid_array[i].BSSID, + eabuf))); + for (j = 0; j < WPA2_PMKID_LEN; j++) + WL_TRACE(("%02x ", pmkid_array[i].PMKID[j])); + printf("\n"); + } + WL_TRACE(("\n")); + dev_wlc_bufvar_set(dev, "pmkid_info", (char *)&pmkid_list, sizeof(pmkid_list)); + return 0; +} +#endif + +static int +wl_iw_get_encodeext( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + WL_TRACE(("%s: SIOCGIWENCODEEXT\n", dev->name)); + return 0; +} + +static int +wl_iw_set_wpaauth( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error = 0; + int paramid; + int paramval; + uint32 cipher_combined; + int val = 0; + wl_iw_t *iw = IW_DEV_IF(dev); + + WL_TRACE(("%s: SIOCSIWAUTH\n", dev->name)); + + paramid = vwrq->flags & IW_AUTH_INDEX; + paramval = vwrq->value; + + WL_TRACE(("%s: SIOCSIWAUTH, paramid = 0x%0x, paramval = 0x%0x\n", + dev->name, paramid, paramval)); + + switch (paramid) { + + case IW_AUTH_WPA_VERSION: + + if (paramval & IW_AUTH_WPA_VERSION_DISABLED) + val = WPA_AUTH_DISABLED; + else if (paramval & (IW_AUTH_WPA_VERSION_WPA)) + val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED; + else if (paramval & IW_AUTH_WPA_VERSION_WPA2) + val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; + WL_TRACE(("%s: %d: setting wpa_auth to 0x%0x\n", __FUNCTION__, __LINE__, val)); + if ((error = dev_wlc_intvar_set(dev, "wpa_auth", val))) + return error; + break; + + case IW_AUTH_CIPHER_PAIRWISE: + case IW_AUTH_CIPHER_GROUP: + + if (paramid == IW_AUTH_CIPHER_PAIRWISE) { + iw->pwsec = paramval; + } + else { + iw->gwsec = paramval; + } + + if ((error = dev_wlc_intvar_get(dev, "wsec", &val))) + return error; + + cipher_combined = iw->gwsec | iw->pwsec; + val &= ~(WEP_ENABLED | TKIP_ENABLED | AES_ENABLED); + if (cipher_combined & (IW_AUTH_CIPHER_WEP40 | IW_AUTH_CIPHER_WEP104)) + val |= WEP_ENABLED; + if (cipher_combined & IW_AUTH_CIPHER_TKIP) + val |= TKIP_ENABLED; + if (cipher_combined & IW_AUTH_CIPHER_CCMP) + val |= AES_ENABLED; + + if (iw->privacy_invoked && !val) { + WL_WSEC(("%s: %s: 'Privacy invoked' TRUE but clearing wsec, assuming " + "we're a WPS enrollee\n", dev->name, __FUNCTION__)); + if ((error = dev_wlc_intvar_set(dev, "is_WPS_enrollee", TRUE))) { + WL_WSEC(("Failed to set iovar is_WPS_enrollee\n")); + return error; + } + } else if (val) { + if ((error = dev_wlc_intvar_set(dev, "is_WPS_enrollee", FALSE))) { + WL_WSEC(("Failed to clear iovar is_WPS_enrollee\n")); + return error; + } + } + + if ((error = dev_wlc_intvar_set(dev, "wsec", val))) + return error; + break; + + case IW_AUTH_KEY_MGMT: + if ((error = dev_wlc_intvar_get(dev, "wpa_auth", &val))) + return error; + + if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED)) { + if (paramval & IW_AUTH_KEY_MGMT_PSK) + val = WPA_AUTH_PSK; + else + val = WPA_AUTH_UNSPECIFIED; + } + else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) { + if (paramval & IW_AUTH_KEY_MGMT_PSK) + val = WPA2_AUTH_PSK; + else + val = WPA2_AUTH_UNSPECIFIED; + } + WL_TRACE(("%s: %d: setting wpa_auth to %d\n", __FUNCTION__, __LINE__, val)); + if ((error = dev_wlc_intvar_set(dev, "wpa_auth", val))) + return error; + break; + + case IW_AUTH_TKIP_COUNTERMEASURES: + dev_wlc_bufvar_set(dev, "tkip_countermeasures", (char *)¶mval, 1); + break; + + case IW_AUTH_80211_AUTH_ALG: + + WL_ERROR(("Setting the D11auth %d\n", paramval)); + if (paramval & IW_AUTH_ALG_OPEN_SYSTEM) + val = 0; + else if (paramval & IW_AUTH_ALG_SHARED_KEY) + val = 1; + else + error = 1; + if (!error && (error = dev_wlc_intvar_set(dev, "auth", val))) + return error; + break; + + case IW_AUTH_WPA_ENABLED: + if (paramval == 0) { + val = 0; + WL_TRACE(("%s: %d: setting wpa_auth to %d\n", __FUNCTION__, __LINE__, val)); + error = dev_wlc_intvar_set(dev, "wpa_auth", val); + return error; + } + else { + + } + break; + + case IW_AUTH_DROP_UNENCRYPTED: + dev_wlc_bufvar_set(dev, "wsec_restrict", (char *)¶mval, 1); + break; + + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + dev_wlc_bufvar_set(dev, "rx_unencrypted_eapol", (char *)¶mval, 1); + break; + +#if WIRELESS_EXT > 17 + + case IW_AUTH_ROAMING_CONTROL: + WL_TRACE(("%s: IW_AUTH_ROAMING_CONTROL\n", __FUNCTION__)); + + break; + + case IW_AUTH_PRIVACY_INVOKED: { + int wsec; + + if (paramval == 0) { + iw->privacy_invoked = FALSE; + if ((error = dev_wlc_intvar_set(dev, "is_WPS_enrollee", FALSE))) { + WL_WSEC(("Failed to clear iovar is_WPS_enrollee\n")); + return error; + } + } else { + iw->privacy_invoked = TRUE; + if ((error = dev_wlc_intvar_get(dev, "wsec", &wsec))) + return error; + + if (!WSEC_ENABLED(wsec)) { + + if ((error = dev_wlc_intvar_set(dev, "is_WPS_enrollee", TRUE))) { + WL_WSEC(("Failed to set iovar is_WPS_enrollee\n")); + return error; + } + } else { + if ((error = dev_wlc_intvar_set(dev, "is_WPS_enrollee", FALSE))) { + WL_WSEC(("Failed to clear iovar is_WPS_enrollee\n")); + return error; + } + } + } + break; + } + +#endif + + default: + break; + } + return 0; +} +#define VAL_PSK(_val) (((_val) & WPA_AUTH_PSK) || ((_val) & WPA2_AUTH_PSK)) + +static int +wl_iw_get_wpaauth( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error; + int paramid; + int paramval = 0; + int val; + wl_iw_t *iw = IW_DEV_IF(dev); + + WL_TRACE(("%s: SIOCGIWAUTH\n", dev->name)); + + paramid = vwrq->flags & IW_AUTH_INDEX; + + switch (paramid) { + case IW_AUTH_WPA_VERSION: + + if ((error = dev_wlc_intvar_get(dev, "wpa_auth", &val))) + return error; + if (val & (WPA_AUTH_NONE | WPA_AUTH_DISABLED)) + paramval = IW_AUTH_WPA_VERSION_DISABLED; + else if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED)) + paramval = IW_AUTH_WPA_VERSION_WPA; + else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) + paramval = IW_AUTH_WPA_VERSION_WPA2; + break; + + case IW_AUTH_CIPHER_PAIRWISE: + paramval = iw->pwsec; + break; + + case IW_AUTH_CIPHER_GROUP: + paramval = iw->gwsec; + break; + + case IW_AUTH_KEY_MGMT: + + if ((error = dev_wlc_intvar_get(dev, "wpa_auth", &val))) + return error; + if (VAL_PSK(val)) + paramval = IW_AUTH_KEY_MGMT_PSK; + else + paramval = IW_AUTH_KEY_MGMT_802_1X; + + break; + case IW_AUTH_TKIP_COUNTERMEASURES: + dev_wlc_bufvar_get(dev, "tkip_countermeasures", (char *)¶mval, 1); + break; + + case IW_AUTH_DROP_UNENCRYPTED: + dev_wlc_bufvar_get(dev, "wsec_restrict", (char *)¶mval, 1); + break; + + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + dev_wlc_bufvar_get(dev, "rx_unencrypted_eapol", (char *)¶mval, 1); + break; + + case IW_AUTH_80211_AUTH_ALG: + + if ((error = dev_wlc_intvar_get(dev, "auth", &val))) + return error; + if (!val) + paramval = IW_AUTH_ALG_OPEN_SYSTEM; + else + paramval = IW_AUTH_ALG_SHARED_KEY; + break; + case IW_AUTH_WPA_ENABLED: + if ((error = dev_wlc_intvar_get(dev, "wpa_auth", &val))) + return error; + if (val) + paramval = TRUE; + else + paramval = FALSE; + break; + +#if WIRELESS_EXT > 17 + + case IW_AUTH_ROAMING_CONTROL: + WL_ERROR(("%s: IW_AUTH_ROAMING_CONTROL\n", __FUNCTION__)); + + break; + + case IW_AUTH_PRIVACY_INVOKED: + paramval = iw->privacy_invoked; + break; + +#endif + } + vwrq->value = paramval; + return 0; +} +#endif + +static const iw_handler wl_iw_handler[] = +{ + (iw_handler) wl_iw_config_commit, + (iw_handler) wl_iw_get_name, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) wl_iw_set_freq, + (iw_handler) wl_iw_get_freq, + (iw_handler) wl_iw_set_mode, + (iw_handler) wl_iw_get_mode, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) wl_iw_get_range, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) wl_iw_set_spy, + (iw_handler) wl_iw_get_spy, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) wl_iw_set_wap, + (iw_handler) wl_iw_get_wap, +#if WIRELESS_EXT > 17 + (iw_handler) wl_iw_mlme, +#else + (iw_handler) NULL, +#endif + (iw_handler) wl_iw_get_aplist, +#if WIRELESS_EXT > 13 + (iw_handler) wl_iw_set_scan, + (iw_handler) wl_iw_get_scan, +#else + (iw_handler) NULL, + (iw_handler) NULL, +#endif + (iw_handler) wl_iw_set_essid, + (iw_handler) wl_iw_get_essid, + (iw_handler) wl_iw_set_nick, + (iw_handler) wl_iw_get_nick, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) wl_iw_set_rate, + (iw_handler) wl_iw_get_rate, + (iw_handler) wl_iw_set_rts, + (iw_handler) wl_iw_get_rts, + (iw_handler) wl_iw_set_frag, + (iw_handler) wl_iw_get_frag, + (iw_handler) wl_iw_set_txpow, + (iw_handler) wl_iw_get_txpow, +#if WIRELESS_EXT > 10 + (iw_handler) wl_iw_set_retry, + (iw_handler) wl_iw_get_retry, +#endif + (iw_handler) wl_iw_set_encode, + (iw_handler) wl_iw_get_encode, + (iw_handler) wl_iw_set_power, + (iw_handler) wl_iw_get_power, +#if WIRELESS_EXT > 17 + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) wl_iw_set_wpaie, + (iw_handler) wl_iw_get_wpaie, + (iw_handler) wl_iw_set_wpaauth, + (iw_handler) wl_iw_get_wpaauth, + (iw_handler) wl_iw_set_encodeext, + (iw_handler) wl_iw_get_encodeext, + (iw_handler) wl_iw_set_pmksa, +#endif +}; + +#if WIRELESS_EXT > 12 +enum { + WL_IW_SET_LEDDC = SIOCIWFIRSTPRIV, + WL_IW_SET_VLANMODE, + WL_IW_SET_PM +}; + +static iw_handler wl_iw_priv_handler[] = { + wl_iw_set_leddc, + wl_iw_set_vlanmode, + wl_iw_set_pm +}; + +static struct iw_priv_args wl_iw_priv_args[] = { + { + WL_IW_SET_LEDDC, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + 0, + "set_leddc" + }, + { + WL_IW_SET_VLANMODE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + 0, + "set_vlanmode" + }, + { + WL_IW_SET_PM, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + 0, + "set_pm" + } +}; + +const struct iw_handler_def wl_iw_handler_def = +{ + .num_standard = ARRAYSIZE(wl_iw_handler), + .num_private = ARRAY_SIZE(wl_iw_priv_handler), + .num_private_args = ARRAY_SIZE(wl_iw_priv_args), + .standard = (iw_handler *) wl_iw_handler, + .private = wl_iw_priv_handler, + .private_args = wl_iw_priv_args, +#if WIRELESS_EXT >= 19 + get_wireless_stats: wl_get_wireless_stats, +#endif + }; +#endif + +int +wl_iw_ioctl( + struct net_device *dev, + struct ifreq *rq, + int cmd +) +{ + struct iwreq *wrq = (struct iwreq *) rq; + struct iw_request_info info; + iw_handler handler; + char *extra = NULL; + size_t token_size = 1; + int max_tokens = 0, ret = 0; + + if (cmd < SIOCIWFIRST || + IW_IOCTL_IDX(cmd) >= ARRAYSIZE(wl_iw_handler) || + !(handler = wl_iw_handler[IW_IOCTL_IDX(cmd)])) + return -EOPNOTSUPP; + + switch (cmd) { + + case SIOCSIWESSID: + case SIOCGIWESSID: + case SIOCSIWNICKN: + case SIOCGIWNICKN: + max_tokens = IW_ESSID_MAX_SIZE + 1; + break; + + case SIOCSIWENCODE: + case SIOCGIWENCODE: +#if WIRELESS_EXT > 17 + case SIOCSIWENCODEEXT: + case SIOCGIWENCODEEXT: +#endif + max_tokens = IW_ENCODING_TOKEN_MAX; + break; + + case SIOCGIWRANGE: + max_tokens = sizeof(struct iw_range); + break; + + case SIOCGIWAPLIST: + token_size = sizeof(struct sockaddr) + sizeof(struct iw_quality); + max_tokens = IW_MAX_AP; + break; + +#if WIRELESS_EXT > 13 + case SIOCGIWSCAN: + max_tokens = IW_SCAN_MAX_DATA; + break; +#endif + + case SIOCSIWSPY: + token_size = sizeof(struct sockaddr); + max_tokens = IW_MAX_SPY; + break; + + case SIOCGIWSPY: + token_size = sizeof(struct sockaddr) + sizeof(struct iw_quality); + max_tokens = IW_MAX_SPY; + break; + default: + break; + } + + if (max_tokens && wrq->u.data.pointer) { + if (wrq->u.data.length > max_tokens) + return -E2BIG; + + if (!(extra = kmalloc(max_tokens * token_size, GFP_KERNEL))) + return -ENOMEM; + + if (copy_from_user(extra, wrq->u.data.pointer, wrq->u.data.length * token_size)) { + kfree(extra); + return -EFAULT; + } + } + + info.cmd = cmd; + info.flags = 0; + + ret = handler(dev, &info, &wrq->u, extra); + + if (extra) { + if (copy_to_user(wrq->u.data.pointer, extra, wrq->u.data.length * token_size)) { + kfree(extra); + return -EFAULT; + } + + kfree(extra); + } + + return ret; +} + +bool +wl_iw_conn_status_str(uint32 event_type, uint32 status, uint32 reason, + char* stringBuf, uint buflen) +{ + typedef struct conn_fail_event_map_t { + uint32 inEvent; + uint32 inStatus; + uint32 inReason; + const char* outName; + const char* outCause; + } conn_fail_event_map_t; + +# define WL_IW_DONT_CARE 9999 + const conn_fail_event_map_t event_map [] = { + + {WLC_E_SET_SSID, WLC_E_STATUS_SUCCESS, WL_IW_DONT_CARE, + "Conn", "Success"}, + {WLC_E_SET_SSID, WLC_E_STATUS_NO_NETWORKS, WL_IW_DONT_CARE, + "Conn", "NoNetworks"}, + {WLC_E_SET_SSID, WLC_E_STATUS_FAIL, WL_IW_DONT_CARE, + "Conn", "ConfigMismatch"}, + {WLC_E_PRUNE, WL_IW_DONT_CARE, WLC_E_PRUNE_ENCR_MISMATCH, + "Conn", "EncrypMismatch"}, + {WLC_E_PRUNE, WL_IW_DONT_CARE, WLC_E_RSN_MISMATCH, + "Conn", "RsnMismatch"}, + {WLC_E_AUTH, WLC_E_STATUS_TIMEOUT, WL_IW_DONT_CARE, + "Conn", "AuthTimeout"}, + {WLC_E_AUTH, WLC_E_STATUS_FAIL, WL_IW_DONT_CARE, + "Conn", "AuthFail"}, + {WLC_E_AUTH, WLC_E_STATUS_NO_ACK, WL_IW_DONT_CARE, + "Conn", "AuthNoAck"}, + {WLC_E_REASSOC, WLC_E_STATUS_FAIL, WL_IW_DONT_CARE, + "Conn", "ReassocFail"}, + {WLC_E_REASSOC, WLC_E_STATUS_TIMEOUT, WL_IW_DONT_CARE, + "Conn", "ReassocTimeout"}, + {WLC_E_REASSOC, WLC_E_STATUS_ABORT, WL_IW_DONT_CARE, + "Conn", "ReassocAbort"}, + {WLC_E_DEAUTH_IND, WL_IW_DONT_CARE, WL_IW_DONT_CARE, + "Conn", "Deauth"}, + {WLC_E_DISASSOC_IND, WL_IW_DONT_CARE, WL_IW_DONT_CARE, + "Conn", "DisassocInd"}, + {WLC_E_DISASSOC, WL_IW_DONT_CARE, WL_IW_DONT_CARE, + "Conn", "Disassoc"} + }; + + const char* name = ""; + const char* cause = NULL; + int i; + + for (i = 0; i < sizeof(event_map)/sizeof(event_map[0]); i++) { + const conn_fail_event_map_t* row = &event_map[i]; + if (row->inEvent == event_type && + (row->inStatus == status || row->inStatus == WL_IW_DONT_CARE) && + (row->inReason == reason || row->inReason == WL_IW_DONT_CARE)) { + name = row->outName; + cause = row->outCause; + break; + } + } + + if (cause) { + memset(stringBuf, 0, buflen); + snprintf(stringBuf, buflen, "%s %s %02d %02d", + name, cause, status, reason); + WL_TRACE(("Connection status: %s\n", stringBuf)); + return TRUE; + } else { + return FALSE; + } +} + +#if (WIRELESS_EXT > 14) + +static bool +wl_iw_check_conn_fail(wl_event_msg_t *e, char* stringBuf, uint buflen) +{ + uint32 event = e->event_type; + uint32 status = e->status; + uint32 reason = e->reason; + + if (wl_iw_conn_status_str(event, status, reason, stringBuf, buflen)) { + return TRUE; + } else + { + return FALSE; + } +} +#endif + +#ifndef IW_CUSTOM_MAX +#define IW_CUSTOM_MAX 256 +#endif + +void +wl_iw_event(struct net_device *dev, wl_event_msg_t *e, void* data) +{ +#if WIRELESS_EXT > 13 + union iwreq_data wrqu; + char extra[IW_CUSTOM_MAX + 1]; + int cmd = 0; + uint32 event_type = e->event_type; + uint16 flags = e->flags; + uint32 datalen = e->datalen; + uint32 status = e->status; + + memset(&wrqu, 0, sizeof(wrqu)); + memset(extra, 0, sizeof(extra)); + + memcpy(wrqu.addr.sa_data, &e->addr, ETHER_ADDR_LEN); + wrqu.addr.sa_family = ARPHRD_ETHER; + + switch (event_type) { + case WLC_E_TXFAIL: + cmd = IWEVTXDROP; + break; +#if WIRELESS_EXT > 14 + case WLC_E_JOIN: + case WLC_E_ASSOC_IND: + case WLC_E_REASSOC_IND: + cmd = IWEVREGISTERED; + break; + case WLC_E_DEAUTH_IND: + case WLC_E_DISASSOC_IND: + cmd = SIOCGIWAP; + wrqu.data.length = strlen(extra); + bzero(wrqu.addr.sa_data, ETHER_ADDR_LEN); + bzero(&extra, ETHER_ADDR_LEN); + break; + + case WLC_E_LINK: + case WLC_E_NDIS_LINK: + cmd = SIOCGIWAP; + wrqu.data.length = strlen(extra); + if (!(flags & WLC_EVENT_MSG_LINK)) { + bzero(wrqu.addr.sa_data, ETHER_ADDR_LEN); + bzero(&extra, ETHER_ADDR_LEN); + } + break; + case WLC_E_ACTION_FRAME: + cmd = IWEVCUSTOM; + if (datalen + 1 <= sizeof(extra)) { + wrqu.data.length = datalen + 1; + extra[0] = WLC_E_ACTION_FRAME; + memcpy(&extra[1], data, datalen); + WL_TRACE(("WLC_E_ACTION_FRAME len %d \n", wrqu.data.length)); + } + break; + + case WLC_E_ACTION_FRAME_COMPLETE: + cmd = IWEVCUSTOM; + if (sizeof(status) + 1 <= sizeof(extra)) { + wrqu.data.length = sizeof(status) + 1; + extra[0] = WLC_E_ACTION_FRAME_COMPLETE; + memcpy(&extra[1], &status, sizeof(status)); + WL_TRACE(("wl_iw_event status %d \n", status)); + } + break; +#endif +#if WIRELESS_EXT > 17 + case WLC_E_MIC_ERROR: { + struct iw_michaelmicfailure *micerrevt = (struct iw_michaelmicfailure *)&extra; + cmd = IWEVMICHAELMICFAILURE; + wrqu.data.length = sizeof(struct iw_michaelmicfailure); + if (flags & WLC_EVENT_MSG_GROUP) + micerrevt->flags |= IW_MICFAILURE_GROUP; + else + micerrevt->flags |= IW_MICFAILURE_PAIRWISE; + memcpy(micerrevt->src_addr.sa_data, &e->addr, ETHER_ADDR_LEN); + micerrevt->src_addr.sa_family = ARPHRD_ETHER; + + break; + } + + case WLC_E_ASSOC_REQ_IE: + cmd = IWEVASSOCREQIE; + wrqu.data.length = datalen; + if (datalen < sizeof(extra)) + memcpy(extra, data, datalen); + break; + + case WLC_E_ASSOC_RESP_IE: + cmd = IWEVASSOCRESPIE; + wrqu.data.length = datalen; + if (datalen < sizeof(extra)) + memcpy(extra, data, datalen); + break; + + case WLC_E_PMKID_CACHE: { + struct iw_pmkid_cand *iwpmkidcand = (struct iw_pmkid_cand *)&extra; + pmkid_cand_list_t *pmkcandlist; + pmkid_cand_t *pmkidcand; + int count; + + if (data == NULL) + break; + + cmd = IWEVPMKIDCAND; + pmkcandlist = data; + count = ntoh32_ua((uint8 *)&pmkcandlist->npmkid_cand); + wrqu.data.length = sizeof(struct iw_pmkid_cand); + pmkidcand = pmkcandlist->pmkid_cand; + while (count) { + bzero(iwpmkidcand, sizeof(struct iw_pmkid_cand)); + if (pmkidcand->preauth) + iwpmkidcand->flags |= IW_PMKID_CAND_PREAUTH; + bcopy(&pmkidcand->BSSID, &iwpmkidcand->bssid.sa_data, + ETHER_ADDR_LEN); + wireless_send_event(dev, cmd, &wrqu, extra); + pmkidcand++; + count--; + } + break; + } +#endif + + case WLC_E_SCAN_COMPLETE: +#if WIRELESS_EXT > 14 + cmd = SIOCGIWSCAN; +#endif + break; + + default: + + break; + } + + if (cmd) { + if (cmd == SIOCGIWSCAN) + wireless_send_event(dev, cmd, &wrqu, NULL); + else + wireless_send_event(dev, cmd, &wrqu, extra); + } + +#if WIRELESS_EXT > 14 + + memset(extra, 0, sizeof(extra)); + if (wl_iw_check_conn_fail(e, extra, sizeof(extra))) { + cmd = IWEVCUSTOM; + wrqu.data.length = strlen(extra); + wireless_send_event(dev, cmd, &wrqu, extra); + } +#endif + +#endif +} + +int wl_iw_get_wireless_stats(struct net_device *dev, struct iw_statistics *wstats) +{ + int res = 0; + wl_cnt_t cnt; + int phy_noise; + int rssi; + scb_val_t scb_val; + + phy_noise = 0; + if ((res = dev_wlc_ioctl(dev, WLC_GET_PHY_NOISE, &phy_noise, sizeof(phy_noise)))) + goto done; + + phy_noise = dtoh32(phy_noise); + WL_TRACE(("wl_iw_get_wireless_stats phy noise=%d\n *****", phy_noise)); + + scb_val.val = 0; + if ((res = dev_wlc_ioctl(dev, WLC_GET_RSSI, &scb_val, sizeof(scb_val_t)))) + goto done; + + rssi = dtoh32(scb_val.val); + WL_TRACE(("wl_iw_get_wireless_stats rssi=%d ****** \n", rssi)); + if (rssi <= WL_IW_RSSI_NO_SIGNAL) + wstats->qual.qual = 0; + else if (rssi <= WL_IW_RSSI_VERY_LOW) + wstats->qual.qual = 1; + else if (rssi <= WL_IW_RSSI_LOW) + wstats->qual.qual = 2; + else if (rssi <= WL_IW_RSSI_GOOD) + wstats->qual.qual = 3; + else if (rssi <= WL_IW_RSSI_VERY_GOOD) + wstats->qual.qual = 4; + else + wstats->qual.qual = 5; + + wstats->qual.level = 0x100 + rssi; + wstats->qual.noise = 0x100 + phy_noise; +#if WIRELESS_EXT > 18 + wstats->qual.updated |= (IW_QUAL_ALL_UPDATED | IW_QUAL_DBM); +#else + wstats->qual.updated |= 7; +#endif + +#if WIRELESS_EXT > 11 + WL_TRACE(("wl_iw_get_wireless_stats counters=%d\n *****", (int)sizeof(wl_cnt_t))); + + memset(&cnt, 0, sizeof(wl_cnt_t)); + res = dev_wlc_bufvar_get(dev, "counters", (char *)&cnt, sizeof(wl_cnt_t)); + if (res) + { + WL_ERROR(("wl_iw_get_wireless_stats counters failed error=%d ****** \n", res)); + goto done; + } + + cnt.version = dtoh16(cnt.version); + if (cnt.version != WL_CNT_T_VERSION) { + WL_TRACE(("\tIncorrect version of counters struct: expected %d; got %d\n", + WL_CNT_T_VERSION, cnt.version)); + goto done; + } + + wstats->discard.nwid = 0; + wstats->discard.code = dtoh32(cnt.rxundec); + wstats->discard.fragment = dtoh32(cnt.rxfragerr); + wstats->discard.retries = dtoh32(cnt.txfail); + wstats->discard.misc = dtoh32(cnt.rxrunt) + dtoh32(cnt.rxgiant); + wstats->miss.beacon = 0; + + WL_TRACE(("wl_iw_get_wireless_stats counters txframe=%d txbyte=%d\n", + dtoh32(cnt.txframe), dtoh32(cnt.txbyte))); + WL_TRACE(("wl_iw_get_wireless_stats counters rxfrmtoolong=%d\n", dtoh32(cnt.rxfrmtoolong))); + WL_TRACE(("wl_iw_get_wireless_stats counters rxbadplcp=%d\n", dtoh32(cnt.rxbadplcp))); + WL_TRACE(("wl_iw_get_wireless_stats counters rxundec=%d\n", dtoh32(cnt.rxundec))); + WL_TRACE(("wl_iw_get_wireless_stats counters rxfragerr=%d\n", dtoh32(cnt.rxfragerr))); + WL_TRACE(("wl_iw_get_wireless_stats counters txfail=%d\n", dtoh32(cnt.txfail))); + WL_TRACE(("wl_iw_get_wireless_stats counters rxrunt=%d\n", dtoh32(cnt.rxrunt))); + WL_TRACE(("wl_iw_get_wireless_stats counters rxgiant=%d\n", dtoh32(cnt.rxgiant))); + +#endif + +done: + return res; +} + +int +wl_iw_attach(struct net_device *dev, void * dhdp) +{ + return 0; +} + +void wl_iw_detach(void) +{ +} + +#endif diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wl_iw.h b/drivers/custom/broadcom-wl/src/wl/sys/wl_iw.h new file mode 100644 index 000000000000..1819e5fcfd41 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wl_iw.h @@ -0,0 +1,151 @@ +/* + * Linux Wireless Extensions support + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wl_iw.h 291086 2011-10-21 01:17:24Z $ + */ + +#ifndef _wl_iw_h_ +#define _wl_iw_h_ + +#include + +#include +#include +#include + +#define WL_SCAN_PARAMS_SSID_MAX 10 +#define GET_SSID "SSID=" +#define GET_CHANNEL "CH=" +#define GET_NPROBE "NPROBE=" +#define GET_ACTIVE_ASSOC_DWELL "ACTIVE=" +#define GET_PASSIVE_ASSOC_DWELL "PASSIVE=" +#define GET_HOME_DWELL "HOME=" +#define GET_SCAN_TYPE "TYPE=" + +#define BAND_GET_CMD "GETBAND" +#define BAND_SET_CMD "SETBAND" +#define DTIM_SKIP_GET_CMD "DTIMSKIPGET" +#define DTIM_SKIP_SET_CMD "DTIMSKIPSET" +#define SETSUSPEND_CMD "SETSUSPENDOPT" +#define PNOSSIDCLR_SET_CMD "PNOSSIDCLR" + +#define PNOSETUP_SET_CMD "PNOSETUP " +#define PNOENABLE_SET_CMD "PNOFORCE" +#define PNODEBUG_SET_CMD "PNODEBUG" +#define TXPOWER_SET_CMD "TXPOWER" + +#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5] +#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x" + +typedef struct wl_iw_extra_params { + int target_channel; +} wl_iw_extra_params_t; + +struct cntry_locales_custom { + char iso_abbrev[WLC_CNTRY_BUF_SZ]; + char custom_locale[WLC_CNTRY_BUF_SZ]; + int32 custom_locale_rev; +}; + +#define WL_IW_RSSI_MINVAL -200 +#define WL_IW_RSSI_NO_SIGNAL -91 +#define WL_IW_RSSI_VERY_LOW -80 +#define WL_IW_RSSI_LOW -70 +#define WL_IW_RSSI_GOOD -68 +#define WL_IW_RSSI_VERY_GOOD -58 +#define WL_IW_RSSI_EXCELLENT -57 +#define WL_IW_RSSI_INVALID 0 +#define MAX_WX_STRING 80 +#undef isprint +#define isprint(c) bcm_isprint(c) +#define WL_IW_SET_ACTIVE_SCAN (SIOCIWFIRSTPRIV+1) +#define WL_IW_GET_RSSI (SIOCIWFIRSTPRIV+3) +#define WL_IW_SET_PASSIVE_SCAN (SIOCIWFIRSTPRIV+5) +#define WL_IW_GET_LINK_SPEED (SIOCIWFIRSTPRIV+7) +#define WL_IW_GET_CURR_MACADDR (SIOCIWFIRSTPRIV+9) +#define WL_IW_SET_STOP (SIOCIWFIRSTPRIV+11) +#define WL_IW_SET_START (SIOCIWFIRSTPRIV+13) + +#define G_SCAN_RESULTS 8*1024 +#define WE_ADD_EVENT_FIX 0x80 +#define G_WLAN_SET_ON 0 +#define G_WLAN_SET_OFF 1 + +typedef struct wl_iw { + char nickname[IW_ESSID_MAX_SIZE]; + + struct iw_statistics wstats; + + int spy_num; + uint32 pwsec; + uint32 gwsec; + bool privacy_invoked; + struct ether_addr spy_addr[IW_MAX_SPY]; + struct iw_quality spy_qual[IW_MAX_SPY]; + void *wlinfo; +} wl_iw_t; + +struct wl_ctrl { + struct timer_list *timer; + struct net_device *dev; + long sysioc_pid; + struct semaphore sysioc_sem; + struct completion sysioc_exited; +}; + +#if WIRELESS_EXT > 12 +#include +extern const struct iw_handler_def wl_iw_handler_def; +#endif + +extern int wl_iw_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +extern void wl_iw_event(struct net_device *dev, wl_event_msg_t *e, void* data); +extern int wl_iw_get_wireless_stats(struct net_device *dev, struct iw_statistics *wstats); +int wl_iw_attach(struct net_device *dev, void * dhdp); +int wl_iw_send_priv_event(struct net_device *dev, char *flag); + +void wl_iw_detach(void); + +#define CSCAN_COMMAND "CSCAN " +#define CSCAN_TLV_PREFIX 'S' +#define CSCAN_TLV_VERSION 1 +#define CSCAN_TLV_SUBVERSION 0 +#define CSCAN_TLV_TYPE_SSID_IE 'S' +#define CSCAN_TLV_TYPE_CHANNEL_IE 'C' +#define CSCAN_TLV_TYPE_NPROBE_IE 'N' +#define CSCAN_TLV_TYPE_ACTIVE_IE 'A' +#define CSCAN_TLV_TYPE_PASSIVE_IE 'P' +#define CSCAN_TLV_TYPE_HOME_IE 'H' +#define CSCAN_TLV_TYPE_STYPE_IE 'T' + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27) +#define IWE_STREAM_ADD_EVENT(info, stream, ends, iwe, extra) \ + iwe_stream_add_event(info, stream, ends, iwe, extra) +#define IWE_STREAM_ADD_VALUE(info, event, value, ends, iwe, event_len) \ + iwe_stream_add_value(info, event, value, ends, iwe, event_len) +#define IWE_STREAM_ADD_POINT(info, stream, ends, iwe, extra) \ + iwe_stream_add_point(info, stream, ends, iwe, extra) +#else +#define IWE_STREAM_ADD_EVENT(info, stream, ends, iwe, extra) \ + iwe_stream_add_event(stream, ends, iwe, extra) +#define IWE_STREAM_ADD_VALUE(info, event, value, ends, iwe, event_len) \ + iwe_stream_add_value(event, value, ends, iwe, event_len) +#define IWE_STREAM_ADD_POINT(info, stream, ends, iwe, extra) \ + iwe_stream_add_point(stream, ends, iwe, extra) +#endif + +#endif diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wl_linux.c b/drivers/custom/broadcom-wl/src/wl/sys/wl_linux.c new file mode 100644 index 000000000000..94d270512433 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wl_linux.c @@ -0,0 +1,3448 @@ +/* + * Linux-specific portion of + * Broadcom 802.11abg Networking Device Driver + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wl_linux.c 580354 2015-08-18 23:42:37Z $ + */ + +#define LINUX_PORT + +#define __UNDEF_NO_VERSION__ + +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define WLC_MAXBSSCFG 1 + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) +#include +#else +#include +#endif +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#include +#else +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 4, 5) +#error "No support for Kernel Rev <= 2.4.5, As the older kernel revs doesn't support Tasklets" +#endif + +#include +#include +#include +#include + +#include +#ifdef USE_IW +struct iw_statistics *wl_get_wireless_stats(struct net_device *dev); +#endif + +#include + +#include + +#if defined(USE_CFG80211) +#include +#endif + +#include + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0)) +#define PDE_DATA pde_data +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) +static void wl_timer(struct timer_list *tl); +#else +static void wl_timer(ulong data); +#endif +static void _wl_timer(wl_timer_t *t); +static struct net_device *wl_alloc_linux_if(wl_if_t *wlif); + +static int wl_monitor_start(struct sk_buff *skb, struct net_device *dev); + +static void wl_start_txqwork(wl_task_t *task); +static void wl_txq_free(wl_info_t *wl); +#define TXQ_LOCK(_wl) spin_lock_bh(&(_wl)->txq_lock) +#define TXQ_UNLOCK(_wl) spin_unlock_bh(&(_wl)->txq_lock) + +static void wl_set_multicast_list_workitem(struct work_struct *work); + +static void wl_timer_task(wl_task_t *task); +static void wl_dpc_rxwork(struct wl_task *task); + +static int wl_reg_proc_entry(wl_info_t *wl); + +static int wl_linux_watchdog(void *ctx); +static +int wl_found = 0; + +typedef struct priv_link { + wl_if_t *wlif; +} priv_link_t; + +#define WL_DEV_IF(dev) ((wl_if_t*)((priv_link_t*)DEV_PRIV(dev))->wlif) + +#ifdef WL_INFO +#undef WL_INFO +#endif +#define WL_INFO(dev) ((wl_info_t*)(WL_DEV_IF(dev)->wl)) + +static int wl_open(struct net_device *dev); +static int wl_close(struct net_device *dev); +static int BCMFASTPATH wl_start(struct sk_buff *skb, struct net_device *dev); +static int wl_start_int(wl_info_t *wl, wl_if_t *wlif, struct sk_buff *skb); + +static struct net_device_stats *wl_get_stats(struct net_device *dev); +static int wl_set_mac_address(struct net_device *dev, void *addr); +static void wl_set_multicast_list(struct net_device *dev); +static void _wl_set_multicast_list(struct net_device *dev); +static int wl_ethtool(wl_info_t *wl, void *uaddr, wl_if_t *wlif); +static void wl_dpc(ulong data); +static void wl_tx_tasklet(ulong data); +static void wl_link_up(wl_info_t *wl, char * ifname); +static void wl_link_down(wl_info_t *wl, char *ifname); +static int wl_schedule_task(wl_info_t *wl, void (*fn)(struct wl_task *), void *context); +#if defined(BCMDBG) +static int wl_dump(wl_info_t *wl, struct bcmstrbuf *b); +#endif +static struct wl_if *wl_alloc_if(wl_info_t *wl, int iftype, uint unit, struct wlc_if* wlc_if); +static void wl_free_if(wl_info_t *wl, wl_if_t *wlif); +static void wl_get_driver_info(struct net_device *dev, struct ethtool_drvinfo *info); + +#if defined(WL_CONFIG_RFKILL) +#include +static int wl_init_rfkill(wl_info_t *wl); +static void wl_uninit_rfkill(wl_info_t *wl); +static int wl_set_radio_block(void *data, bool blocked); +static void wl_report_radio_state(wl_info_t *wl); +#endif + +// Rel. commit "modpost: require a MODULE_DESCRIPTION()" (Jeff Johnson, 11 Mar 2025) +MODULE_DESCRIPTION("Broadcom-wl wireless driver"); +MODULE_LICENSE("MIXED/Proprietary"); + +static struct pci_device_id wl_id_table[] = +{ + { PCI_VENDOR_ID_BROADCOM, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, + PCI_CLASS_NETWORK_OTHER << 8, 0xffff00, 0 }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, wl_id_table); + +static unsigned int online_cpus = 1; + +#ifdef BCMDBG +static int msglevel = 0xdeadbeef; +module_param(msglevel, int, 0); +static int msglevel2 = 0xdeadbeef; +module_param(msglevel2, int, 0); +static int phymsglevel = 0xdeadbeef; +module_param(phymsglevel, int, 0); +#endif + +#ifdef BCMDBG_ASSERT +static int assert_type = 0xdeadbeef; +module_param(assert_type, int, 0); +#endif + +static int passivemode = 0; +module_param(passivemode, int, 0); + +#define WL_TXQ_THRESH 0 +static int wl_txq_thresh = WL_TXQ_THRESH; +module_param(wl_txq_thresh, int, 0); + +static int oneonly = 0; +module_param(oneonly, int, 0); + +static int piomode = 0; +module_param(piomode, int, 0); + +static int instance_base = 0; +module_param(instance_base, int, 0); + +#if defined(BCMDBG) +static struct ether_addr local_ea; +static char *macaddr = NULL; +module_param(macaddr, charp, S_IRUGO); +#endif + +static int nompc = 0; +module_param(nompc, int, 0); + +#ifdef quote_str +#undef quote_str +#endif +#ifdef to_str +#undef to_str +#endif +#define to_str(s) #s +#define quote_str(s) to_str(s) + +#define BRCM_WLAN_IFNAME eth%d + +static char intf_name[IFNAMSIZ] = quote_str(BRCM_WLAN_IFNAME); + +module_param_string(intf_name, intf_name, IFNAMSIZ, 0); + +static const u_int8_t brcm_oui[] = {0x00, 0x10, 0x18}; + +#define WL_RADIOTAP_BRCM2_HT_SNS 0x01 +#define WL_RADIOTAP_BRCM2_HT_MCS 0x00000001 + +#define WL_RADIOTAP_LEGACY_SNS 0x02 +#define WL_RADIOTAP_LEGACY_VHT 0x00000001 + +#define IEEE80211_RADIOTAP_HTMOD_40 0x01 +#define IEEE80211_RADIOTAP_HTMOD_SGI 0x02 +#define IEEE80211_RADIOTAP_HTMOD_GF 0x04 +#define IEEE80211_RADIOTAP_HTMOD_LDPC 0x08 +#define IEEE80211_RADIOTAP_HTMOD_STBC_MASK 0x30 +#define IEEE80211_RADIOTAP_HTMOD_STBC_SHIFT 4 + +#define WL_RADIOTAP_F_NONHT_VHT_DYN_BW 0x01 + +#define WL_RADIOTAP_F_NONHT_VHT_BW 0x02 + +struct wl_radiotap_nonht_vht { + u_int8_t len; + u_int8_t flags; + u_int8_t bw; +} __attribute__ ((packed)); + +typedef struct wl_radiotap_nonht_vht wl_radiotap_nonht_vht_t; + +struct wl_radiotap_legacy { + struct ieee80211_radiotap_header ieee_radiotap; + u_int32_t it_present_ext; + u_int32_t pad1; + uint32 tsft_l; + uint32 tsft_h; + uint8 flags; + uint8 rate; + uint16 channel_freq; + uint16 channel_flags; + uint8 signal; + uint8 noise; + int8 antenna; + uint8 pad2; + u_int8_t vend_oui[3]; + u_int8_t vend_sns; + u_int16_t vend_skip_len; + wl_radiotap_nonht_vht_t nonht_vht; +} __attribute__ ((__packed__)); + +typedef struct wl_radiotap_legacy wl_radiotap_legacy_t; + +#define WL_RADIOTAP_LEGACY_SKIP_LEN htol16(sizeof(struct wl_radiotap_legacy) - \ + offsetof(struct wl_radiotap_legacy, nonht_vht)) + +#define WL_RADIOTAP_NONHT_VHT_LEN (sizeof(wl_radiotap_nonht_vht_t) - 1) + +struct wl_radiotap_ht_brcm_2 { + struct ieee80211_radiotap_header ieee_radiotap; + u_int32_t it_present_ext; + u_int32_t pad1; + uint32 tsft_l; + uint32 tsft_h; + u_int8_t flags; + u_int8_t pad2; + u_int16_t channel_freq; + u_int16_t channel_flags; + u_int8_t signal; + u_int8_t noise; + u_int8_t antenna; + u_int8_t pad3; + u_int8_t vend_oui[3]; + u_int8_t vend_sns; + u_int16_t vend_skip_len; + u_int8_t mcs; + u_int8_t htflags; +} __attribute__ ((packed)); + +typedef struct wl_radiotap_ht_brcm_2 wl_radiotap_ht_brcm_2_t; + +#define WL_RADIOTAP_HT_BRCM2_SKIP_LEN htol16(sizeof(struct wl_radiotap_ht_brcm_2) - \ + offsetof(struct wl_radiotap_ht_brcm_2, mcs)) + +struct wl_radiotap_ht_brcm_3 { + struct ieee80211_radiotap_header ieee_radiotap; + u_int32_t it_present_ext; + u_int32_t pad1; + uint32 tsft_l; + uint32 tsft_h; + u_int8_t flags; + u_int8_t pad2; + u_int16_t channel_freq; + u_int16_t channel_flags; + u_int8_t signal; + u_int8_t noise; + u_int8_t antenna; + u_int8_t mcs_known; + u_int8_t mcs_flags; + u_int8_t mcs_index; + u_int8_t vend_oui[3]; + u_int8_t vend_sns; + u_int16_t vend_skip_len; + wl_radiotap_nonht_vht_t nonht_vht; +} __attribute__ ((packed)); + +typedef struct wl_radiotap_ht_brcm_3 wl_radiotap_ht_brcm_3_t; + +struct wl_radiotap_ht { + struct ieee80211_radiotap_header ieee_radiotap; + uint32 tsft_l; + uint32 tsft_h; + u_int8_t flags; + u_int8_t pad1; + u_int16_t channel_freq; + u_int16_t channel_flags; + u_int8_t signal; + u_int8_t noise; + u_int8_t antenna; + u_int8_t mcs_known; + u_int8_t mcs_flags; + u_int8_t mcs_index; +} __attribute__ ((packed)); + +typedef struct wl_radiotap_ht wl_radiotap_ht_t; + +struct wl_radiotap_vht { + struct ieee80211_radiotap_header ieee_radiotap; + uint32 tsft_l; + uint32 tsft_h; + u_int8_t flags; + u_int8_t pad1; + u_int16_t channel_freq; + u_int16_t channel_flags; + u_int8_t signal; + u_int8_t noise; + u_int8_t antenna; + u_int8_t pad2; + u_int16_t pad3; + uint32 ampdu_ref_num; + u_int16_t ampdu_flags; + u_int8_t ampdu_delim_crc; + u_int8_t ampdu_reserved; + u_int16_t vht_known; + u_int8_t vht_flags; + u_int8_t vht_bw; + u_int8_t vht_mcs_nss[4]; + u_int8_t vht_coding; + u_int8_t vht_group_id; + u_int16_t vht_partial_aid; +} __attribute__ ((packed)); + +typedef struct wl_radiotap_vht wl_radiotap_vht_t; + +#define WL_RADIOTAP_PRESENT_LEGACY \ + ((1 << IEEE80211_RADIOTAP_TSFT) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \ + (1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_VENDOR_NAMESPACE) | \ + (1 << IEEE80211_RADIOTAP_EXT)) + +#define WL_RADIOTAP_PRESENT_HT_BRCM2 \ + ((1 << IEEE80211_RADIOTAP_TSFT) | \ + (1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_VENDOR_NAMESPACE) | \ + (1 << IEEE80211_RADIOTAP_EXT)) + +#define WL_RADIOTAP_PRESENT_HT \ + ((1 << IEEE80211_RADIOTAP_TSFT) | \ + (1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_MCS)) + +#define WL_RADIOTAP_PRESENT_VHT \ + ((1 << IEEE80211_RADIOTAP_TSFT) | \ + (1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_AMPDU) | \ + (1 << IEEE80211_RADIOTAP_VHT)) + +#ifndef ARPHRD_IEEE80211_RADIOTAP +#define ARPHRD_IEEE80211_RADIOTAP 803 +#endif + +#ifndef SRCBASE +#define SRCBASE "." +#endif + +#if WIRELESS_EXT >= 19 || LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +static struct ethtool_ops wl_ethtool_ops = +#else +static const struct ethtool_ops wl_ethtool_ops = +#endif +{ + .get_drvinfo = wl_get_driver_info, +}; +#endif + +#if defined(WL_USE_NETDEV_OPS) + +static const struct net_device_ops wl_netdev_ops = +{ + .ndo_open = wl_open, + .ndo_stop = wl_close, + .ndo_start_xmit = wl_start, + .ndo_get_stats = wl_get_stats, + .ndo_set_mac_address = wl_set_mac_address, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0) + .ndo_set_rx_mode = wl_set_multicast_list, +#else + .ndo_set_multicast_list = wl_set_multicast_list, +#endif + .ndo_do_ioctl = wl_ioctl +}; + +static const struct net_device_ops wl_netdev_monitor_ops = +{ + .ndo_start_xmit = wl_monitor_start, + .ndo_get_stats = wl_get_stats, + .ndo_do_ioctl = wl_ioctl +}; +#endif + +static void +wl_if_setup(struct net_device *dev) +{ +#if defined(WL_USE_NETDEV_OPS) + dev->netdev_ops = &wl_netdev_ops; +#else + dev->open = wl_open; + dev->stop = wl_close; + dev->hard_start_xmit = wl_start; + dev->get_stats = wl_get_stats; + dev->set_mac_address = wl_set_mac_address; + dev->set_multicast_list = wl_set_multicast_list; + dev->do_ioctl = wl_ioctl; +#endif + +#ifdef USE_IW +#if WIRELESS_EXT < 19 + dev->get_wireless_stats = wl_get_wireless_stats; +#endif +#if WIRELESS_EXT > 12 + dev->wireless_handlers = (struct iw_handler_def *) &wl_iw_handler_def; +#endif +#endif + +#if WIRELESS_EXT >= 19 || LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) + dev->ethtool_ops = &wl_ethtool_ops; +#endif +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) +static inline void eth_hw_addr_set(struct net_device *dev, const void *addr) { + memcpy(dev->dev_addr, addr, ETHER_ADDR_LEN); +} +#endif + +static wl_info_t * +wl_attach(uint16 vendor, uint16 device, ulong regs, + uint bustype, void *btparam, uint irq, uchar* bar1_addr, uint32 bar1_size) +{ + struct net_device *dev; + wl_if_t *wlif; + wl_info_t *wl; + osl_t *osh; + int unit, err; +#if defined(USE_CFG80211) + struct device *parentdev; +#endif + + unit = wl_found + instance_base; + err = 0; + + if (unit < 0) { + WL_ERROR(("wl%d: unit number overflow, exiting\n", unit)); + return NULL; + } + + if (oneonly && (unit != instance_base)) { + WL_ERROR(("wl%d: wl_attach: oneonly is set, exiting\n", unit)); + return NULL; + } + + osh = osl_attach(btparam, bustype, TRUE); + ASSERT(osh); + + if ((wl = (wl_info_t*) MALLOC(osh, sizeof(wl_info_t))) == NULL) { + WL_ERROR(("wl%d: malloc wl_info_t, out of memory, malloced %d bytes\n", unit, + MALLOCED(osh))); + osl_detach(osh); + return NULL; + } + bzero(wl, sizeof(wl_info_t)); + + wl->osh = osh; + wl->unit = unit; + atomic_set(&wl->callbacks, 0); + + wl->all_dispatch_mode = (passivemode == 0) ? TRUE : FALSE; + if (WL_ALL_PASSIVE_ENAB(wl)) { + + MY_INIT_WORK(&wl->txq_task.work, (work_func_t)wl_start_txqwork); + wl->txq_task.context = wl; + + MY_INIT_WORK(&wl->multicast_task.work, (work_func_t)wl_set_multicast_list_workitem); + + MY_INIT_WORK(&wl->wl_dpc_task.work, (work_func_t)wl_dpc_rxwork); + wl->wl_dpc_task.context = wl; + } + + wl->txq_dispatched = FALSE; + wl->txq_head = wl->txq_tail = NULL; + wl->txq_cnt = 0; + + wlif = wl_alloc_if(wl, WL_IFTYPE_BSS, unit, NULL); + if (!wlif) { + WL_ERROR(("wl%d: %s: wl_alloc_if failed\n", unit, __FUNCTION__)); + MFREE(osh, wl, sizeof(wl_info_t)); + osl_detach(osh); + return NULL; + } + + if (wl_alloc_linux_if(wlif) == NULL) { + WL_ERROR(("wl%d: %s: wl_alloc_linux_if failed\n", unit, __FUNCTION__)); + MFREE(osh, wl, sizeof(wl_info_t)); + osl_detach(osh); + return NULL; + } + + dev = wlif->dev; + wl->dev = dev; + wl_if_setup(dev); + + dev->base_addr = regs; + + WL_TRACE(("wl%d: Bus: ", unit)); + if (bustype == PCMCIA_BUS) { + + wl->piomode = TRUE; + WL_TRACE(("PCMCIA\n")); + } else if (bustype == PCI_BUS) { + + wl->piomode = piomode; + WL_TRACE(("PCI/%s\n", wl->piomode ? "PIO" : "DMA")); + } + else if (bustype == RPC_BUS) { + + } else { + bustype = PCI_BUS; + WL_TRACE(("force to PCI\n")); + } + wl->bcm_bustype = bustype; + + if ((wl->regsva = ioremap(dev->base_addr, PCI_BAR0_WINSZ)) == NULL) { + WL_ERROR(("wl%d: ioremap() failed\n", unit)); + goto fail; + } + + wl->bar1_addr = bar1_addr; + wl->bar1_size = bar1_size; + + spin_lock_init(&wl->lock); + spin_lock_init(&wl->isr_lock); + + if (WL_ALL_PASSIVE_ENAB(wl)) + sema_init(&wl->sem, 1); + + spin_lock_init(&wl->txq_lock); + + if (!(wl->wlc = wlc_attach((void *) wl, vendor, device, unit, wl->piomode, + osh, wl->regsva, wl->bcm_bustype, btparam, &err))) { + printf("wl driver %s failed with code %d\n", EPI_VERSION_STR, err); + goto fail; + } + wl->pub = wlc_pub(wl->wlc); + + wlif->wlcif = wlc_wlcif_get_by_index(wl->wlc, 0); + + if (nompc) { + if (wlc_iovar_setint(wl->wlc, "mpc", 0)) { + WL_ERROR(("wl%d: Error setting MPC variable to 0\n", unit)); + } + } + + wlc_iovar_setint(wl->wlc, "scan_passive_time", 170); + + wlc_iovar_setint(wl->wlc, "qtxpower", 23 * 4); + +#ifdef BCMDBG + if (macaddr != NULL) { + int dbg_err; + + WL_ERROR(("wl%d: setting MAC ADDRESS %s\n", unit, macaddr)); + bcm_ether_atoe(macaddr, &local_ea); + + dbg_err = wlc_iovar_op(wl->wlc, "cur_etheraddr", NULL, 0, &local_ea, + ETHER_ADDR_LEN, IOV_SET, NULL); + if (dbg_err) + WL_ERROR(("wl%d: Error setting MAC ADDRESS\n", unit)); + } +#endif + eth_hw_addr_set(dev, wl->pub->cur_etheraddr.octet); + + online_cpus = 1; + + WL_ERROR(("wl%d: online cpus %d\n", unit, online_cpus)); + + tasklet_init(&wl->tasklet, wl_dpc, (ulong)wl); + + tasklet_init(&wl->tx_tasklet, wl_tx_tasklet, (ulong)wl); + + { + if (request_irq(irq, wl_isr, IRQF_SHARED, dev->name, wl)) { + WL_ERROR(("wl%d: request_irq() failed\n", unit)); + goto fail; + } + dev->irq = irq; + } + +#if defined(USE_IW) + WL_ERROR(("Using Wireless Extension\n")); +#endif + +#if defined(USE_CFG80211) + parentdev = NULL; + if (wl->bcm_bustype == PCI_BUS) { + parentdev = &((struct pci_dev *)btparam)->dev; + } + if (parentdev) { + if (wl_cfg80211_attach(dev, parentdev, WL_ALL_PASSIVE_ENAB(wl))) { + goto fail; + } + } + else { + WL_ERROR(("unsupported bus type\n")); + goto fail; + } +#else + + if (wl->bcm_bustype == PCI_BUS) { + struct pci_dev *pci_dev = (struct pci_dev *)btparam; + if (pci_dev != NULL) + SET_NETDEV_DEV(dev, &pci_dev->dev); + } +#endif + + if (register_netdev(dev)) { + WL_ERROR(("wl%d: register_netdev() failed\n", unit)); + goto fail; + } + wlif->dev_registed = TRUE; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) +#endif +#ifdef USE_IW + wlif->iw.wlinfo = (void *)wl; +#endif + +#if defined(WL_CONFIG_RFKILL) + if (wl_init_rfkill(wl) < 0) + WL_ERROR(("%s: init_rfkill_failure\n", __FUNCTION__)); +#endif + + if (wlc_iovar_setint(wl->wlc, "leddc", 0xa0000)) { + WL_ERROR(("wl%d: Error setting led duty-cycle\n", unit)); + } + if (wlc_set(wl->wlc, WLC_SET_PM, PM_FAST)) { + WL_ERROR(("wl%d: Error setting PM variable to FAST PS\n", unit)); + } + + if (wlc_iovar_setint(wl->wlc, "vlan_mode", OFF)) { + WL_ERROR(("wl%d: Error setting vlan mode OFF\n", unit)); + } + + if (wlc_set(wl->wlc, WLC_SET_INFRA, 1)) { + WL_ERROR(("wl%d: Error setting infra_mode to infrastructure\n", unit)); + } + + if (wlc_module_register(wl->pub, NULL, "linux", wl, NULL, wl_linux_watchdog, NULL, NULL)) { + WL_ERROR(("wl%d: %s wlc_module_register() failed\n", + wl->pub->unit, __FUNCTION__)); + goto fail; + } + +#ifdef BCMDBG + wlc_dump_register(wl->pub, "wl", (dump_fn_t)wl_dump, (void *)wl); +#endif + + wl_reg_proc_entry(wl); + + printf("%s: Broadcom BCM%04x 802.11 Hybrid Wireless Controller%s %s", + dev->name, device, + WL_ALL_PASSIVE_ENAB(wl) ? ", Passive Mode" : "", EPI_VERSION_STR); + +#ifdef BCMDBG + printf(" (Compiled in " SRCBASE " at " __TIME__ " on " __DATE__ ")"); +#endif + printf("\n"); + + wl_found++; + return wl; + +fail: + wl_free(wl); + return NULL; +} + +static void __devexit wl_remove(struct pci_dev *pdev); + +int __devinit +wl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int rc; + wl_info_t *wl; + uint32 val; + uint32 bar1_size = 0; + void* bar1_addr = NULL; + + WL_TRACE(("%s: bus %d slot %d func %d irq %d\n", __FUNCTION__, + pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), pdev->irq)); + + if ((pdev->vendor != PCI_VENDOR_ID_BROADCOM) || + (((pdev->device & 0xff00) != 0x4300) && + (pdev->device != 0x576) && + ((pdev->device & 0xff00) != 0x4700) && + ((pdev->device < 43000) || (pdev->device > 43999)))) { + WL_TRACE(("%s: unsupported vendor %x device %x\n", __FUNCTION__, + pdev->vendor, pdev->device)); + return (-ENODEV); + } + + rc = pci_enable_device(pdev); + if (rc) { + WL_ERROR(("%s: Cannot enable device %d-%d_%d\n", __FUNCTION__, + pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn))); + return (-ENODEV); + } + pci_set_master(pdev); + + pci_read_config_dword(pdev, 0x40, &val); + if ((val & 0x0000ff00) != 0) + pci_write_config_dword(pdev, 0x40, val & 0xffff00ff); + bar1_size = pci_resource_len(pdev, 2); + bar1_addr = (uchar *)ioremap(pci_resource_start(pdev, 2), + bar1_size); + wl = wl_attach(pdev->vendor, pdev->device, pci_resource_start(pdev, 0), PCI_BUS, pdev, + pdev->irq, bar1_addr, bar1_size); + + if (!wl) + return -ENODEV; + + pci_set_drvdata(pdev, wl); + + return 0; +} + +static int +#if !defined(SIMPLE_DEV_PM_OPS) +wl_suspend(struct pci_dev *pdev, DRV_SUSPEND_STATE_TYPE state) +{ +#else +wl_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); +#endif + wl_info_t *wl = (wl_info_t *) pci_get_drvdata(pdev); + if (!wl) { + WL_ERROR(("wl: wl_suspend: pci_get_drvdata failed\n")); + return -ENODEV; + } + WL_ERROR(("%s: PCI Suspend handler\n", __FUNCTION__)); + + WL_LOCK(wl); + if (WLOFFLD_ENAB(wl->pub) && wlc_iovar_setint(wl->wlc, "wowl_activate", 1) == 0) { + WL_TRACE(("%s: Enabled WOWL OFFLOAD\n", __FUNCTION__)); + } else { + WL_ERROR(("%s: Not WOWL capable\n", __FUNCTION__)); + wl_down(wl); + wl->pub->hw_up = FALSE; + } + WL_UNLOCK(wl); + + if (BUSTYPE(wl->pub->sih->bustype) == PCI_BUS) + si_pci_sleep(wl->pub->sih); + + return 0; +} + +static int +#if !defined(SIMPLE_DEV_PM_OPS) +wl_resume(struct pci_dev *pdev) +{ +#else +wl_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); +#endif + int err = 0; + wl_info_t *wl = (wl_info_t *) pci_get_drvdata(pdev); + if (!wl) { + WL_ERROR(("wl: wl_resume: pci_get_drvdata failed\n")); + return -ENODEV; + } + + WL_ERROR(("%s: PCI Resume handler\n", __FUNCTION__)); + if (WLOFFLD_ENAB(wl->pub)) { + wlc_iovar_setint(wl->wlc, "wowl_activate", 0); + wlc_wowl_wake_reason_process(wl->wlc); + + if (WOWL_ACTIVE(wl->pub)) { + if (BUSTYPE(wl->pub->sih->bustype) == PCI_BUS) { + si_pci_pmeclr(wl->pub->sih); + } + } + } + + WL_LOCK(wl); + err = wl_up(wl); + WL_UNLOCK(wl); + + return (err); +} + +static void __devexit +wl_remove(struct pci_dev *pdev) +{ + wl_info_t *wl = (wl_info_t *) pci_get_drvdata(pdev); + + if (!wl) { + WL_ERROR(("wl: wl_remove: pci_get_drvdata failed\n")); + return; + } + if (!wlc_chipmatch(pdev->vendor, pdev->device)) { + WL_ERROR(("wl: wl_remove: wlc_chipmatch failed\n")); + return; + } + + WL_LOCK(wl); + WL_APSTA_UPDN(("wl%d (%s): wl_remove() -> wl_down()\n", wl->pub->unit, wl->dev->name)); + wl_down(wl); + WL_UNLOCK(wl); + + wl_free(wl); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); +} + +#if defined(SIMPLE_DEV_PM_OPS) +static SIMPLE_DEV_PM_OPS(wl_pm_ops, wl_suspend, wl_resume); +#endif + +static struct pci_driver wl_pci_driver __refdata = { + .name = "wl", + .probe = wl_pci_probe, + .remove = __devexit_p(wl_remove), + .id_table = wl_id_table, +#ifdef SIMPLE_DEV_PM_OPS + .driver.pm = &wl_pm_ops, +#else + .suspend = wl_suspend, + .resume = wl_resume, +#endif +}; + +static int __init +wl_module_init(void) +{ + int error = -ENODEV; + +#ifdef BCMDBG + if (msglevel != 0xdeadbeef) + wl_msg_level = msglevel; + else { + const char *var = getvar(NULL, "wl_msglevel"); + if (var) + wl_msg_level = bcm_strtoul(var, NULL, 0); + } + printf("%s: msglevel set to 0x%x\n", __FUNCTION__, wl_msg_level); + if (msglevel2 != 0xdeadbeef) + wl_msg_level2 = msglevel2; + else { + const char *var = getvar(NULL, "wl_msglevel2"); + if (var) + wl_msg_level2 = bcm_strtoul(var, NULL, 0); + } + printf("%s: msglevel2 set to 0x%x\n", __FUNCTION__, wl_msg_level2); + { + extern uint32 phyhal_msg_level; + + if (phymsglevel != 0xdeadbeef) + phyhal_msg_level = phymsglevel; + else { + const char *var = getvar(NULL, "phy_msglevel"); + if (var) + phyhal_msg_level = bcm_strtoul(var, NULL, 0); + } + printf("%s: phymsglevel set to 0x%x\n", __FUNCTION__, phyhal_msg_level); + } +#endif + + { + const char *var = getvar(NULL, "wl_dispatch_mode"); + if (var) + passivemode = bcm_strtoul(var, NULL, 0); + if (passivemode) + printf("%s: passivemode enabled\n", __FUNCTION__); + } + +#ifdef BCMDBG_ASSERT + + if (assert_type != 0xdeadbeef) + g_assert_type = assert_type; +#endif + + { + char *var = getvar(NULL, "wl_txq_thresh"); + if (var) + wl_txq_thresh = bcm_strtoul(var, NULL, 0); +#ifdef BCMDBG + WL_INFORM(("%s: wl_txq_thresh set to 0x%x\n", + __FUNCTION__, wl_txq_thresh)); +#endif + } + + if (!(error = pci_module_init(&wl_pci_driver))) + return (0); + + return (error); +} + +static void __exit +wl_module_exit(void) +{ + + pci_unregister_driver(&wl_pci_driver); + +} + +module_init(wl_module_init); +module_exit(wl_module_exit); + +void +wl_free(wl_info_t *wl) +{ + wl_timer_t *t, *next; + osl_t *osh; + + WL_TRACE(("wl: wl_free\n")); + { + if (wl->dev && wl->dev->irq) + free_irq(wl->dev->irq, wl); + } + +#if defined(WL_CONFIG_RFKILL) + wl_uninit_rfkill(wl); +#endif + + if (wl->dev) { + wl_free_if(wl, WL_DEV_IF(wl->dev)); + wl->dev = NULL; + } + + tasklet_kill(&wl->tasklet); + + tasklet_kill(&wl->tx_tasklet); + + if (wl->pub) { + wlc_module_unregister(wl->pub, "linux", wl); + } + + if (wl->wlc) { + { + char tmp1[128]; + sprintf(tmp1, "%s%d", HYBRID_PROC, wl->pub->unit); + remove_proc_entry(tmp1, 0); + } + wlc_detach(wl->wlc); + wl->wlc = NULL; + wl->pub = NULL; + } + + while (atomic_read(&wl->callbacks) > 0) + schedule(); + + for (t = wl->timers; t; t = next) { + next = t->next; +#ifdef BCMDBG + if (t->name) + MFREE(wl->osh, t->name, strlen(t->name) + 1); +#endif + MFREE(wl->osh, t, sizeof(wl_timer_t)); + } + + osh = wl->osh; + + if (wl->regsva && BUSTYPE(wl->bcm_bustype) != SDIO_BUS && + BUSTYPE(wl->bcm_bustype) != JTAG_BUS) { + iounmap((void*)wl->regsva); + } + wl->regsva = NULL; + + if (wl->bar1_addr) { + iounmap(wl->bar1_addr); + wl->bar1_addr = NULL; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) +#endif + + wl_txq_free(wl); + + MFREE(osh, wl, sizeof(wl_info_t)); + + if (MALLOCED(osh)) { + printf("Memory leak of bytes %d\n", MALLOCED(osh)); +#ifndef BCMDBG_MEM + ASSERT(0); +#endif + } + +#ifdef BCMDBG_MEM + + MALLOC_DUMP(osh, NULL); +#endif + + osl_detach(osh); +} + +static int +wl_open(struct net_device *dev) +{ + wl_info_t *wl; + int error = 0; + + if (!dev) + return -ENETDOWN; + + wl = WL_INFO(dev); + + WL_TRACE(("wl%d: wl_open\n", wl->pub->unit)); + + WL_LOCK(wl); + WL_APSTA_UPDN(("wl%d: (%s): wl_open() -> wl_up()\n", + wl->pub->unit, wl->dev->name)); + + error = wl_up(wl); + if (!error) { + error = wlc_set(wl->wlc, WLC_SET_PROMISC, (dev->flags & IFF_PROMISC)); + } + WL_UNLOCK(wl); + + if (!error) { + OLD_MOD_INC_USE_COUNT; + } + +#if defined(USE_CFG80211) + if (wl_cfg80211_up(dev)) { + WL_ERROR(("%s: failed to bring up cfg80211\n", __func__)); + return -1; + } +#endif + return (error? -ENODEV : 0); +} + +static int +wl_close(struct net_device *dev) +{ + wl_info_t *wl; + + if (!dev) + return -ENETDOWN; + +#if defined(USE_CFG80211) + wl_cfg80211_down(dev); +#endif + wl = WL_INFO(dev); + + WL_TRACE(("wl%d: wl_close\n", wl->pub->unit)); + + WL_LOCK(wl); + WL_APSTA_UPDN(("wl%d (%s): wl_close() -> wl_down()\n", + wl->pub->unit, wl->dev->name)); + + if (wl->if_list == NULL) { + wl_down(wl); + } + WL_UNLOCK(wl); + + OLD_MOD_DEC_USE_COUNT; + + return (0); +} + +void * BCMFASTPATH +wl_get_ifctx(struct wl_info *wl, int ctx_id, wl_if_t *wlif) +{ + void *ifctx = NULL; + + switch (ctx_id) { + case IFCTX_NETDEV: + ifctx = (void *)((wlif == NULL) ? wl->dev : wlif->dev); + break; + + default: + break; + } + + return ifctx; +} + +static int BCMFASTPATH +wl_start_int(wl_info_t *wl, wl_if_t *wlif, struct sk_buff *skb) +{ + void *pkt; + + WL_TRACE(("wl%d: wl_start: len %d data_len %d summed %d csum: 0x%x\n", + wl->pub->unit, skb->len, skb->data_len, skb->ip_summed, (uint32)skb->csum)); + + WL_LOCK(wl); + + pkt = PKTFRMNATIVE(wl->osh, skb); + ASSERT(pkt != NULL); + + if (WME_ENAB(wl->pub) && (PKTPRIO(pkt) == 0)) + pktsetprio(pkt, FALSE); + + wlc_sendpkt(wl->wlc, pkt, wlif->wlcif); + + WL_UNLOCK(wl); + + return (0); +} + +void +wl_txflowcontrol(wl_info_t *wl, struct wl_if *wlif, bool state, int prio) +{ + struct net_device *dev; + + ASSERT(prio == ALLPRIO); + + if (wlif == NULL) + dev = wl->dev; + else if (!wlif->dev_registed) + return; + else + dev = wlif->dev; + + if (state == ON) + netif_stop_queue(dev); + else + netif_wake_queue(dev); +} + +static int +wl_schedule_task(wl_info_t *wl, void (*fn)(struct wl_task *task), void *context) +{ + wl_task_t *task; + + WL_TRACE(("wl%d: wl_schedule_task\n", wl->pub->unit)); + + if (!(task = MALLOC(wl->osh, sizeof(wl_task_t)))) { + WL_ERROR(("wl%d: wl_schedule_task: out of memory, malloced %d bytes\n", + wl->pub->unit, MALLOCED(wl->osh))); + return -ENOMEM; + } + + MY_INIT_WORK(&task->work, (work_func_t)fn); + task->context = context; + + if (!schedule_work(&task->work)) { + WL_ERROR(("wl%d: schedule_work() failed\n", wl->pub->unit)); + MFREE(wl->osh, task, sizeof(wl_task_t)); + return -ENOMEM; + } + + atomic_inc(&wl->callbacks); + + return 0; +} + +static struct wl_if * +wl_alloc_if(wl_info_t *wl, int iftype, uint subunit, struct wlc_if *wlcif) +{ + wl_if_t *wlif; + wl_if_t *p; + + if (!(wlif = MALLOC(wl->osh, sizeof(wl_if_t)))) { + WL_ERROR(("wl%d: wl_alloc_if: out of memory, malloced %d bytes\n", + (wl->pub)?wl->pub->unit:subunit, MALLOCED(wl->osh))); + return NULL; + } + bzero(wlif, sizeof(wl_if_t)); + wlif->wl = wl; + wlif->wlcif = wlcif; + wlif->subunit = subunit; + wlif->if_type = iftype; + + if (wl->if_list == NULL) + wl->if_list = wlif; + else { + p = wl->if_list; + while (p->next != NULL) + p = p->next; + p->next = wlif; + } + + return wlif; +} + +static void +wl_free_if(wl_info_t *wl, wl_if_t *wlif) +{ + wl_if_t *p; + ASSERT(wlif); + ASSERT(wl); + + WL_TRACE(("%s\n", __FUNCTION__)); + + if (wlif->dev_registed) { + ASSERT(wlif->dev); + unregister_netdev(wlif->dev); + wlif->dev_registed = FALSE; + } + +#if defined(USE_CFG80211) + wl_cfg80211_detach(wlif->dev); +#endif + + p = wl->if_list; + if (p == wlif) + wl->if_list = p->next; + else { + while (p != NULL && p->next != wlif) + p = p->next; + if (p != NULL) + p->next = p->next->next; + } + + if (wlif->dev) { +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) + MFREE(wl->osh, wlif->dev->priv, sizeof(priv_link_t)); + MFREE(wl->osh, wlif->dev, sizeof(struct net_device)); +#else + free_netdev(wlif->dev); + wlif->dev = NULL; +#endif + } + + MFREE(wl->osh, wlif, sizeof(wl_if_t)); +} + +static struct net_device * +wl_alloc_linux_if(wl_if_t *wlif) +{ + wl_info_t *wl = wlif->wl; + struct net_device *dev; + priv_link_t *priv_link; + + WL_TRACE(("%s\n", __FUNCTION__)); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) + dev = MALLOC(wl->osh, sizeof(struct net_device)); + if (!dev) { + WL_ERROR(("wl%d: %s: malloc of net_device failed\n", + (wl->pub)?wl->pub->unit:wlif->subunit, __FUNCTION__)); + return NULL; + } + bzero(dev, sizeof(struct net_device)); + ether_setup(dev); + + strncpy(dev->name, intf_name, IFNAMSIZ-1); + dev->name[IFNAMSIZ-1] = '\0'; + + priv_link = MALLOC(wl->osh, sizeof(priv_link_t)); + if (!priv_link) { + WL_ERROR(("wl%d: %s: malloc of priv_link failed\n", + (wl->pub)?wl->pub->unit:wlif->subunit, __FUNCTION__)); + MFREE(wl->osh, dev, sizeof(struct net_device)); + return NULL; + } + dev->priv = priv_link; +#else + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0)) + dev = alloc_netdev(sizeof(priv_link_t), intf_name, ether_setup); +#else + dev = alloc_netdev(sizeof(priv_link_t), intf_name, NET_NAME_UNKNOWN, ether_setup); +#endif + + if (!dev) { + WL_ERROR(("wl%d: %s: alloc_netdev failed\n", + (wl->pub)?wl->pub->unit:wlif->subunit, __FUNCTION__)); + return NULL; + } + priv_link = netdev_priv(dev); + if (!priv_link) { + WL_ERROR(("wl%d: %s: cannot get netdev_priv\n", + (wl->pub)?wl->pub->unit:wlif->subunit, __FUNCTION__)); + return NULL; + } +#endif + + priv_link->wlif = wlif; + wlif->dev = dev; + + if (wlif->if_type != WL_IFTYPE_MON && wl->dev && netif_queue_stopped(wl->dev)) + netif_stop_queue(dev); + + return dev; +} + +char * +wl_ifname(wl_info_t *wl, wl_if_t *wlif) +{ + if (wlif) { + return wlif->name; + } else { + return wl->dev->name; + } +} + +void +wl_init(wl_info_t *wl) +{ + WL_TRACE(("wl%d: wl_init\n", wl->pub->unit)); + + wl_reset(wl); + + wlc_init(wl->wlc); +} + +uint +wl_reset(wl_info_t *wl) +{ + uint32 macintmask; + + WL_TRACE(("wl%d: wl_reset\n", wl->pub->unit)); + + macintmask = wl_intrsoff(wl); + + wlc_reset(wl->wlc); + + wl_intrsrestore(wl, macintmask); + + wl->resched = 0; + + return (0); +} + +void BCMFASTPATH +wl_intrson(wl_info_t *wl) +{ + unsigned long flags = 0; + + INT_LOCK(wl, flags); + wlc_intrson(wl->wlc); + INT_UNLOCK(wl, flags); +} + +bool +wl_alloc_dma_resources(wl_info_t *wl, uint addrwidth) +{ + return TRUE; +} + +uint32 BCMFASTPATH +wl_intrsoff(wl_info_t *wl) +{ + unsigned long flags = 0; + uint32 status; + + INT_LOCK(wl, flags); + status = wlc_intrsoff(wl->wlc); + INT_UNLOCK(wl, flags); + return status; +} + +void +wl_intrsrestore(wl_info_t *wl, uint32 macintmask) +{ + unsigned long flags = 0; + + INT_LOCK(wl, flags); + wlc_intrsrestore(wl->wlc, macintmask); + INT_UNLOCK(wl, flags); +} + +int +wl_up(wl_info_t *wl) +{ + int error = 0; + wl_if_t *wlif; + + WL_TRACE(("wl%d: wl_up\n", wl->pub->unit)); + + if (wl->pub->up) + return (0); + + error = wlc_up(wl->wlc); + + if (!error) { + for (wlif = wl->if_list; wlif != NULL; wlif = wlif->next) { + wl_txflowcontrol(wl, wlif, OFF, ALLPRIO); + } + } + + return (error); +} + +void +wl_down(wl_info_t *wl) +{ + wl_if_t *wlif; + int monitor = 0; + uint callbacks, ret_val = 0; + + WL_TRACE(("wl%d: wl_down\n", wl->pub->unit)); + + for (wlif = wl->if_list; wlif != NULL; wlif = wlif->next) { + if (wlif->dev) { + netif_down(wlif->dev); + netif_stop_queue(wlif->dev); + } + } + + if (wl->monitor_dev) { + ret_val = wlc_ioctl(wl->wlc, WLC_SET_MONITOR, &monitor, sizeof(int), NULL); + if (ret_val != BCME_OK) { + WL_ERROR(("%s: Disabling MONITOR failed %d\n", __FUNCTION__, ret_val)); + } + } + + if (wl->wlc) + ret_val = wlc_down(wl->wlc); + + callbacks = atomic_read(&wl->callbacks) - ret_val; + BCM_REFERENCE(callbacks); + + WL_UNLOCK(wl); + + if (WL_ALL_PASSIVE_ENAB(wl)) { + int i = 0; + for (i = 0; (atomic_read(&wl->callbacks) > callbacks) && i < 10000; i++) { + schedule(); + __flush_workqueue(system_wq); + } + } + else + { + + SPINWAIT((atomic_read(&wl->callbacks) > callbacks), 100 * 1000); + } + + WL_LOCK(wl); +} + +static int +wl_toe_get(wl_info_t *wl, uint32 *toe_ol) +{ + if (wlc_iovar_getint(wl->wlc, "toe_ol", toe_ol) != 0) + return -EOPNOTSUPP; + + return 0; +} + +static int +wl_toe_set(wl_info_t *wl, uint32 toe_ol) +{ + if (wlc_iovar_setint(wl->wlc, "toe_ol", toe_ol) != 0) + return -EOPNOTSUPP; + + if (wlc_iovar_setint(wl->wlc, "toe", (toe_ol != 0)) != 0) + return -EOPNOTSUPP; + + return 0; +} + +static void +wl_get_driver_info(struct net_device *dev, struct ethtool_drvinfo *info) +{ + wl_info_t *wl = WL_INFO(dev); + +#if WIRELESS_EXT >= 19 || LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) + if (!wl || !wl->pub || !wl->wlc || !wl->dev) + return; +#endif + bzero(info, sizeof(struct ethtool_drvinfo)); + snprintf(info->driver, sizeof(info->driver), "wl%d", wl->pub->unit); + strncpy(info->version, EPI_VERSION_STR, sizeof(info->version)); + info->version[(sizeof(info->version))-1] = '\0'; +} + +static int +wl_ethtool(wl_info_t *wl, void *uaddr, wl_if_t *wlif) +{ + struct ethtool_drvinfo info; + struct ethtool_value edata; + uint32 cmd; + uint32 toe_cmpnt = 0, csum_dir; + int ret; + + if (!wl || !wl->pub || !wl->wlc) + return -ENODEV; + + WL_TRACE(("wl%d: %s\n", wl->pub->unit, __FUNCTION__)); + + if (copy_from_user(&cmd, uaddr, sizeof(uint32))) + return (-EFAULT); + + switch (cmd) { + case ETHTOOL_GDRVINFO: + if (!wl->dev) + return -ENETDOWN; + + wl_get_driver_info(wl->dev, &info); + info.cmd = cmd; + if (copy_to_user(uaddr, &info, sizeof(info))) + return (-EFAULT); + break; + + case ETHTOOL_GRXCSUM: + case ETHTOOL_GTXCSUM: + if ((ret = wl_toe_get(wl, &toe_cmpnt)) < 0) + return ret; + + csum_dir = (cmd == ETHTOOL_GTXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL; + + edata.cmd = cmd; + edata.data = (toe_cmpnt & csum_dir) ? 1 : 0; + + if (copy_to_user(uaddr, &edata, sizeof(edata))) + return (-EFAULT); + break; + + case ETHTOOL_SRXCSUM: + case ETHTOOL_STXCSUM: + if (copy_from_user(&edata, uaddr, sizeof(edata))) + return (-EFAULT); + + if ((ret = wl_toe_get(wl, &toe_cmpnt)) < 0) + return ret; + + csum_dir = (cmd == ETHTOOL_STXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL; + + if (edata.data != 0) + toe_cmpnt |= csum_dir; + else + toe_cmpnt &= ~csum_dir; + + if ((ret = wl_toe_set(wl, toe_cmpnt)) < 0) + return ret; + + if (cmd == ETHTOOL_STXCSUM) { + if (!wl->dev) + return -ENETDOWN; + if (edata.data) + wl->dev->features |= NETIF_F_IP_CSUM; + else + wl->dev->features &= ~NETIF_F_IP_CSUM; + } + + break; + + default: + return (-EOPNOTSUPP); + + } + + return (0); +} + +int +wl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + wl_info_t *wl; + wl_if_t *wlif; + void *buf = NULL; + wl_ioctl_t ioc; + int bcmerror; + + if (!dev) + return -ENETDOWN; + + wl = WL_INFO(dev); + wlif = WL_DEV_IF(dev); + if (wlif == NULL || wl == NULL || wl->dev == NULL) + return -ENETDOWN; + + bcmerror = 0; + + WL_TRACE(("wl%d: wl_ioctl: cmd 0x%x\n", wl->pub->unit, cmd)); + +#ifdef USE_IW + + if ((cmd >= SIOCIWFIRST) && (cmd <= SIOCIWLAST)) { + + return wl_iw_ioctl(dev, ifr, cmd); + } +#endif + + if (cmd == SIOCETHTOOL) + return (wl_ethtool(wl, (void*)ifr->ifr_data, wlif)); + + switch (cmd) { + case SIOCDEVPRIVATE : + break; + default: + bcmerror = BCME_UNSUPPORTED; + goto done2; + } + + if (copy_from_user(&ioc, ifr->ifr_data, sizeof(wl_ioctl_t))) { + bcmerror = BCME_BADADDR; + goto done2; + } + + if (ioc.buf) { + if (!(buf = (void *) MALLOC(wl->osh, MAX(ioc.len, WLC_IOCTL_MAXLEN)))) { + bcmerror = BCME_NORESOURCE; + goto done2; + } + + if (copy_from_user(buf, ioc.buf, ioc.len)) { + bcmerror = BCME_BADADDR; + goto done1; + } + } + + WL_LOCK(wl); + if (!capable(CAP_NET_ADMIN)) { + bcmerror = BCME_EPERM; + } else { + bcmerror = wlc_ioctl(wl->wlc, ioc.cmd, buf, ioc.len, wlif->wlcif); + } + WL_UNLOCK(wl); + +done1: + if (ioc.buf) { + if (copy_to_user(ioc.buf, buf, ioc.len)) + bcmerror = BCME_BADADDR; + MFREE(wl->osh, buf, MAX(ioc.len, WLC_IOCTL_MAXLEN)); + } + +done2: + ASSERT(VALID_BCMERROR(bcmerror)); + if (bcmerror != 0) + wl->pub->bcmerror = bcmerror; + return (OSL_ERROR(bcmerror)); +} + +int +wlc_ioctl_internal(struct net_device *dev, int cmd, void *buf, int len) +{ + wl_info_t *wl; + wl_if_t *wlif; + int bcmerror; + + if (!dev) + return -ENETDOWN; + + wl = WL_INFO(dev); + wlif = WL_DEV_IF(dev); + if (wlif == NULL || wl == NULL || wl->dev == NULL) + return -ENETDOWN; + + bcmerror = 0; + + WL_TRACE(("wl%d: wlc_ioctl_internal: cmd 0x%x\n", wl->pub->unit, cmd)); + + WL_LOCK(wl); + if (!capable(CAP_NET_ADMIN)) { + bcmerror = BCME_EPERM; + } else { + bcmerror = wlc_ioctl(wl->wlc, cmd, buf, len, wlif->wlcif); + } + WL_UNLOCK(wl); + + ASSERT(VALID_BCMERROR(bcmerror)); + if (bcmerror != 0) + wl->pub->bcmerror = bcmerror; + return (OSL_ERROR(bcmerror)); +} + +static struct net_device_stats* +wl_get_stats(struct net_device *dev) +{ + struct net_device_stats *stats_watchdog = NULL; + struct net_device_stats *stats = NULL; + wl_info_t *wl; + wl_if_t *wlif; + + if (!dev) + return NULL; + + if ((wl = WL_INFO(dev)) == NULL) + return NULL; + + if ((wlif = WL_DEV_IF(dev)) == NULL) + return NULL; + + if ((stats = &wlif->stats) == NULL) + return NULL; + + WL_TRACE(("wl%d: wl_get_stats\n", wl->pub->unit)); + + ASSERT(wlif->stats_id < 2); + stats_watchdog = &wlif->stats_watchdog[wlif->stats_id]; + memcpy(stats, stats_watchdog, sizeof(struct net_device_stats)); + return (stats); +} + +#ifdef USE_IW +struct iw_statistics * +wl_get_wireless_stats(struct net_device *dev) +{ + int res = 0; + wl_info_t *wl; + wl_if_t *wlif; + struct iw_statistics *wstats = NULL; + struct iw_statistics *wstats_watchdog = NULL; + int phy_noise, rssi; + + if (!dev) + return NULL; + + if ((wl = WL_INFO(dev)) == NULL) + return NULL; + + if ((wlif = WL_DEV_IF(dev)) == NULL) + return NULL; + + if ((wstats = &wlif->wstats) == NULL) + return NULL; + + WL_TRACE(("wl%d: wl_get_wireless_stats\n", wl->pub->unit)); + + ASSERT(wlif->stats_id < 2); + wstats_watchdog = &wlif->wstats_watchdog[wlif->stats_id]; + + phy_noise = wlif->phy_noise; +#if WIRELESS_EXT > 11 + wstats->discard.nwid = 0; + wstats->discard.code = wstats_watchdog->discard.code; + wstats->discard.fragment = wstats_watchdog->discard.fragment; + wstats->discard.retries = wstats_watchdog->discard.retries; + wstats->discard.misc = wstats_watchdog->discard.misc; + + wstats->miss.beacon = 0; +#endif + + if (AP_ENAB(wl->pub)) + rssi = 0; + else { + scb_val_t scb; + res = wlc_ioctl(wl->wlc, WLC_GET_RSSI, &scb, sizeof(int), wlif->wlcif); + if (res) { + WL_ERROR(("wl%d: %s: WLC_GET_RSSI failed (%d)\n", + wl->pub->unit, __FUNCTION__, res)); + return NULL; + } + rssi = scb.val; + } + + if (rssi <= WLC_RSSI_NO_SIGNAL) + wstats->qual.qual = 0; + else if (rssi <= WLC_RSSI_VERY_LOW) + wstats->qual.qual = 1; + else if (rssi <= WLC_RSSI_LOW) + wstats->qual.qual = 2; + else if (rssi <= WLC_RSSI_GOOD) + wstats->qual.qual = 3; + else if (rssi <= WLC_RSSI_VERY_GOOD) + wstats->qual.qual = 4; + else + wstats->qual.qual = 5; + + wstats->qual.level = 0x100 + rssi; + wstats->qual.noise = 0x100 + phy_noise; +#if WIRELESS_EXT > 18 + wstats->qual.updated |= (IW_QUAL_ALL_UPDATED | IW_QUAL_DBM); +#else + wstats->qual.updated |= 7; +#endif + + return wstats; +} +#endif + +static int +wl_set_mac_address(struct net_device *dev, void *addr) +{ + int err = 0; + wl_info_t *wl; + struct sockaddr *sa = (struct sockaddr *) addr; + + if (!dev) + return -ENETDOWN; + + wl = WL_INFO(dev); + + WL_TRACE(("wl%d: wl_set_mac_address\n", wl->pub->unit)); + + WL_LOCK(wl); + + eth_hw_addr_set(dev, sa->sa_data); + err = wlc_iovar_op(wl->wlc, "cur_etheraddr", NULL, 0, sa->sa_data, ETHER_ADDR_LEN, + IOV_SET, (WL_DEV_IF(dev))->wlcif); + WL_UNLOCK(wl); + if (err) { + WL_ERROR(("wl%d: wl_set_mac_address: error setting MAC addr override\n", + wl->pub->unit)); + } + return err; +} + +static void +wl_set_multicast_list(struct net_device *dev) +{ + if (!WL_ALL_PASSIVE_ENAB((wl_info_t *)WL_INFO(dev))) + _wl_set_multicast_list(dev); + else { + wl_info_t *wl = WL_INFO(dev); + wl->multicast_task.context = dev; + + if (schedule_work(&wl->multicast_task.work)) { + + atomic_inc(&wl->callbacks); + } + } +} + +static void +_wl_set_multicast_list(struct net_device *dev) +{ +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 34) + struct dev_mc_list *mclist; +#else + struct netdev_hw_addr *ha; +#endif + wl_info_t *wl; + int i, buflen; + struct maclist *maclist; + int allmulti; + + if (!dev) + return; + wl = WL_INFO(dev); + + WL_TRACE(("wl%d: wl_set_multicast_list\n", wl->pub->unit)); + + if (wl->pub->up) { + allmulti = (dev->flags & IFF_ALLMULTI)? TRUE: FALSE; + + buflen = sizeof(struct maclist) + (MAXMULTILIST * ETHER_ADDR_LEN); + + if ((maclist = MALLOC(wl->pub->osh, buflen)) == NULL) { + return; + } + + i = 0; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 34) + for (mclist = dev->mc_list; mclist && (i < dev->mc_count); mclist = mclist->next) { + if (i >= MAXMULTILIST) { + allmulti = TRUE; + i = 0; + break; + } + bcopy(mclist->dmi_addr, &maclist->ea[i++], ETHER_ADDR_LEN); + } +#else + netdev_for_each_mc_addr(ha, dev) { + if (i >= MAXMULTILIST) { + allmulti = TRUE; + i = 0; + break; + } + bcopy(ha->addr, &maclist->ea[i++], ETHER_ADDR_LEN); + } +#endif + maclist->count = i; + + WL_LOCK(wl); + + wlc_iovar_op(wl->wlc, "allmulti", NULL, 0, &allmulti, sizeof(allmulti), IOV_SET, + (WL_DEV_IF(dev))->wlcif); + wlc_set(wl->wlc, WLC_SET_PROMISC, (dev->flags & IFF_PROMISC)); + + wlc_iovar_op(wl->wlc, "mcast_list", NULL, 0, maclist, buflen, IOV_SET, + (WL_DEV_IF(dev))->wlcif); + + WL_UNLOCK(wl); + MFREE(wl->pub->osh, maclist, buflen); + } + +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) +irqreturn_t BCMFASTPATH +wl_isr(int irq, void *dev_id) +#else +irqreturn_t BCMFASTPATH +wl_isr(int irq, void *dev_id, struct pt_regs *ptregs) +#endif +{ + wl_info_t *wl; + bool ours, wantdpc; + unsigned long flags; + + wl = (wl_info_t*) dev_id; + + WL_ISRLOCK(wl, flags); + + if ((ours = wlc_isr(wl->wlc, &wantdpc))) { + + if (wantdpc) { + + ASSERT(wl->resched == FALSE); + if (WL_ALL_PASSIVE_ENAB(wl)) { + if (schedule_work(&wl->wl_dpc_task.work)) + atomic_inc(&wl->callbacks); + else + ASSERT(0); + } else + tasklet_schedule(&wl->tasklet); + } + } + + WL_ISRUNLOCK(wl, flags); + + return IRQ_RETVAL(ours); +} + +static void BCMFASTPATH +wl_dpc(ulong data) +{ + wl_info_t *wl; + + wl = (wl_info_t *)data; + + WL_LOCK(wl); + + if (wl->pub->up) { + wlc_dpc_info_t dpci = {0}; + + if (wl->resched) { + unsigned long flags = 0; + INT_LOCK(wl, flags); + wlc_intrsupd(wl->wlc); + INT_UNLOCK(wl, flags); + } + + wl->resched = wlc_dpc(wl->wlc, TRUE, &dpci); + + wl->processed = dpci.processed; + } + + if (!wl->pub->up) { + + if ((WL_ALL_PASSIVE_ENAB(wl))) { + atomic_dec(&wl->callbacks); + } + goto done; + } + + if (wl->resched) { + if (!(WL_ALL_PASSIVE_ENAB(wl))) + tasklet_schedule(&wl->tasklet); + else + if (!schedule_work(&wl->wl_dpc_task.work)) { + + ASSERT(0); + } + } + else { + + if (WL_ALL_PASSIVE_ENAB(wl)) + atomic_dec(&wl->callbacks); + wl_intrson(wl); + } + +done: + WL_UNLOCK(wl); + return; +} + +static void BCMFASTPATH +wl_dpc_rxwork(struct wl_task *task) +{ + wl_info_t *wl = (wl_info_t *)task->context; + WL_TRACE(("wl%d: %s\n", wl->pub->unit, __FUNCTION__)); + + wl_dpc((unsigned long)wl); + return; +} + +void BCMFASTPATH +wl_sendup(wl_info_t *wl, wl_if_t *wlif, void *p, int numpkt) +{ + struct sk_buff *skb; + bool brcm_specialpkt; + + WL_TRACE(("wl%d: wl_sendup: %d bytes\n", wl->pub->unit, PKTLEN(wl->osh, p))); + + brcm_specialpkt = + (ntoh16_ua(PKTDATA(wl->pub->osh, p) + ETHER_TYPE_OFFSET) == ETHER_TYPE_BRCM); + + if (!brcm_specialpkt) { + + } + + if (wlif) { + + if (!wlif->dev || !netif_device_present(wlif->dev)) { + WL_ERROR(("wl%d: wl_sendup: interface not ready\n", wl->pub->unit)); + PKTFREE(wl->osh, p, FALSE); + return; + } + + skb = PKTTONATIVE(wl->osh, p); + skb->dev = wlif->dev; + } else { + + skb = PKTTONATIVE(wl->osh, p); + skb->dev = wl->dev; + + } + + skb->protocol = eth_type_trans(skb, skb->dev); + + if (!brcm_specialpkt && !ISALIGNED(skb->data, 4)) { + WL_ERROR(("Unaligned assert. skb %p. skb->data %p.\n", skb, skb->data)); + if (wlif) { + WL_ERROR(("wl_sendup: dev name is %s (wlif) \n", wlif->dev->name)); + WL_ERROR(("wl_sendup: hard header len %d (wlif) \n", + wlif->dev->hard_header_len)); + } + WL_ERROR(("wl_sendup: dev name is %s (wl) \n", wl->dev->name)); + WL_ERROR(("wl_sendup: hard header len %d (wl) \n", wl->dev->hard_header_len)); + ASSERT(ISALIGNED(skb->data, 4)); + } + + WL_APSTA_RX(("wl%d: wl_sendup(): pkt %p summed %d on interface %p (%s)\n", + wl->pub->unit, p, skb->ip_summed, wlif, skb->dev->name)); + + netif_rx(skb); + +} + +int +wl_osl_pcie_rc(struct wl_info *wl, uint op, int param) +{ + return 0; +} + +void +wl_dump_ver(wl_info_t *wl, struct bcmstrbuf *b) +{ + bcm_bprintf(b, "wl%d: version %s\n", wl->pub->unit, EPI_VERSION_STR); +} + +#if defined(BCMDBG) +static int +wl_dump(wl_info_t *wl, struct bcmstrbuf *b) +{ + wl_if_t *p; + int i; + + wl_dump_ver(wl, b); + + bcm_bprintf(b, "name %s dev %p tbusy %d callbacks %d malloced %d\n", + wl->dev->name, wl->dev, (uint)netif_queue_stopped(wl->dev), + atomic_read(&wl->callbacks), MALLOCED(wl->osh)); + + p = wl->if_list; + if (p) + p = p->next; + for (i = 0; p != NULL; p = p->next, i++) { + if ((i % 4) == 0) { + if (i != 0) + bcm_bprintf(b, "\n"); + bcm_bprintf(b, "Interfaces:"); + } + bcm_bprintf(b, " name %s dev %p", p->dev->name, p->dev); + } + if (i) + bcm_bprintf(b, "\n"); + + return 0; +} +#endif + +static void +wl_link_up(wl_info_t *wl, char *ifname) +{ + WL_ERROR(("wl%d: link up (%s)\n", wl->pub->unit, ifname)); +} + +static void +wl_link_down(wl_info_t *wl, char *ifname) +{ + WL_ERROR(("wl%d: link down (%s)\n", wl->pub->unit, ifname)); +} + +void +wl_event(wl_info_t *wl, char *ifname, wlc_event_t *e) +{ +#ifdef USE_IW + wl_iw_event(wl->dev, &(e->event), e->data); +#endif + +#if defined(USE_CFG80211) + wl_cfg80211_event(wl->dev, &(e->event), e->data); +#endif + switch (e->event.event_type) { + case WLC_E_LINK: + case WLC_E_NDIS_LINK: + if (e->event.flags&WLC_EVENT_MSG_LINK) + wl_link_up(wl, ifname); + else + wl_link_down(wl, ifname); + break; +#if defined(WL_CONFIG_RFKILL) + case WLC_E_RADIO: { + mbool i; + if (wlc_get(wl->wlc, WLC_GET_RADIO, &i) < 0) + WL_ERROR(("%s: WLC_GET_RADIO failed\n", __FUNCTION__)); + if (wl->last_phyind == (mbool)(i & WL_RADIO_HW_DISABLE)) + break; + + wl->last_phyind = (mbool)(i & WL_RADIO_HW_DISABLE); + + WL_ERROR(("wl%d: Radio hardware state changed to %d\n", wl->pub->unit, i)); + wl_report_radio_state(wl); + break; + } +#else + case WLC_E_RADIO: + break; +#endif + } +} + +void +wl_event_sync(wl_info_t *wl, char *ifname, wlc_event_t *e) +{ +} + +static void BCMFASTPATH +wl_sched_tx_tasklet(void *t) +{ + wl_info_t *wl = (wl_info_t *)t; + tasklet_schedule(&wl->tx_tasklet); +} + +#define WL_CONFIG_SMP() FALSE + +static int BCMFASTPATH +wl_start(struct sk_buff *skb, struct net_device *dev) +{ + wl_if_t *wlif; + wl_info_t *wl; + + if (!dev) + return -ENETDOWN; + + wlif = WL_DEV_IF(dev); + wl = WL_INFO(dev); + + skb->prev = NULL; + if (WL_ALL_PASSIVE_ENAB(wl) || (WL_RTR() && WL_CONFIG_SMP())) { + + TXQ_LOCK(wl); + + if ((wl_txq_thresh > 0) && (wl->txq_cnt >= wl_txq_thresh)) { + PKTFRMNATIVE(wl->osh, skb); + PKTCFREE(wl->osh, skb, TRUE); + TXQ_UNLOCK(wl); + return 0; + } + + if (wl->txq_head == NULL) + wl->txq_head = skb; + else + wl->txq_tail->prev = skb; + wl->txq_tail = skb; + wl->txq_cnt++; + + if (!wl->txq_dispatched) { + int32 err = 0; + + if (!WL_ALL_PASSIVE_ENAB(wl)) + wl_sched_tx_tasklet(wl); + else + err = (int32)(schedule_work(&wl->txq_task.work) == 0); + + if (!err) { + atomic_inc(&wl->callbacks); + wl->txq_dispatched = TRUE; + } else { + WL_ERROR(("wl%d: wl_start/schedule_work failed\n", + wl->pub->unit)); + } + } + + TXQ_UNLOCK(wl); + } else + return wl_start_int(wl, wlif, skb); + + return (0); +} + +static void BCMFASTPATH +wl_start_txqwork(wl_task_t *task) +{ + wl_info_t *wl = (wl_info_t *)task->context; + struct sk_buff *skb; + + WL_TRACE(("wl%d: %s txq_cnt %d\n", wl->pub->unit, __FUNCTION__, wl->txq_cnt)); + +#ifdef BCMDBG + if (wl->txq_cnt >= 500) + WL_ERROR(("wl%d: WARNING dispatching over 500 packets in txqwork(%d)\n", + wl->pub->unit, wl->txq_cnt)); +#endif + + TXQ_LOCK(wl); + while (wl->txq_head) { + skb = wl->txq_head; + wl->txq_head = skb->prev; + skb->prev = NULL; + if (wl->txq_head == NULL) + wl->txq_tail = NULL; + wl->txq_cnt--; + TXQ_UNLOCK(wl); + + wl_start_int(wl, WL_DEV_IF(skb->dev), skb); + + TXQ_LOCK(wl); + } + + wl->txq_dispatched = FALSE; + atomic_dec(&wl->callbacks); + TXQ_UNLOCK(wl); + + return; +} + +static void BCMFASTPATH +wl_tx_tasklet(ulong data) +{ + wl_task_t task; + task.context = (void *)data; + wl_start_txqwork(&task); +} + +static void +wl_txq_free(wl_info_t *wl) +{ + struct sk_buff *skb; + + if (wl->txq_head == NULL) { + ASSERT(wl->txq_tail == NULL); + return; + } + + while (wl->txq_head) { + skb = wl->txq_head; + wl->txq_head = skb->prev; + wl->txq_cnt--; + PKTFRMNATIVE(wl->osh, skb); + PKTCFREE(wl->osh, skb, TRUE); + } + + wl->txq_tail = NULL; +} + +static void +wl_set_multicast_list_workitem(struct work_struct *work) +{ + wl_task_t *task = (wl_task_t *)work; + struct net_device *dev = (struct net_device*)task->context; + wl_info_t *wl; + + wl = WL_INFO(dev); + + atomic_dec(&wl->callbacks); + + _wl_set_multicast_list(dev); +} + +static void +wl_timer_task(wl_task_t *task) +{ + wl_timer_t *t = (wl_timer_t *)task->context; + + _wl_timer(t); + MFREE(t->wl->osh, task, sizeof(wl_task_t)); + + atomic_dec(&t->wl->callbacks); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) +static void +wl_timer(struct timer_list *tl) +{ + wl_timer_t *t = (wl_timer_t *)tl; +#else +static void +wl_timer(ulong data) +{ + wl_timer_t *t = (wl_timer_t *)data; +#endif + + if (!WL_ALL_PASSIVE_ENAB(t->wl)) + _wl_timer(t); + else + wl_schedule_task(t->wl, wl_timer_task, t); +} + +static void +_wl_timer(wl_timer_t *t) +{ + wl_info_t *wl = t->wl; + + WL_LOCK(wl); + + if (t->set && (!timer_pending(&t->timer))) { + if (t->periodic) { + t->timer.expires = jiffies + t->ms*HZ/1000; + atomic_inc(&wl->callbacks); + add_timer(&t->timer); + t->set = TRUE; + } else + t->set = FALSE; + + t->fn(t->arg); +#ifdef BCMDBG + wlc_update_perf_stats(wl->wlc, WLC_PERF_STATS_TMR_DPC); + t->ticks++; +#endif + + } + + atomic_dec(&wl->callbacks); + + WL_UNLOCK(wl); +} + +wl_timer_t * +wl_init_timer(wl_info_t *wl, void (*fn)(void *arg), void *arg, const char *tname) +{ + wl_timer_t *t; + + t = (wl_timer_t*)MALLOC(wl->osh, sizeof(wl_timer_t)); + + if (t == NULL) { + WL_ERROR(("wl%d: wl_init_timer: out of memory, malloced %d bytes\n", + wl->unit, MALLOCED(wl->osh))); + return 0; + } + + bzero(t, sizeof(wl_timer_t)); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) + timer_setup(&t->timer, wl_timer, 0); +#else + init_timer(&t->timer); + t->timer.data = (ulong) t; + t->timer.function = wl_timer; +#endif + t->wl = wl; + t->fn = fn; + t->arg = arg; + t->next = wl->timers; + wl->timers = t; + +#ifdef BCMDBG + if ((t->name = MALLOC(wl->osh, strlen(tname) + 1))) + strcpy(t->name, tname); +#endif + + return t; +} + +void +wl_add_timer(wl_info_t *wl, wl_timer_t *t, uint ms, int periodic) +{ +#ifdef BCMDBG + if (t->set) { + WL_ERROR(("%s: Already set. Name: %s, per %d\n", + __FUNCTION__, t->name, periodic)); + } +#endif + + t->ms = ms; + t->periodic = (bool) periodic; + + if (t->set) + return; + + t->set = TRUE; + t->timer.expires = jiffies + ms*HZ/1000; + + atomic_inc(&wl->callbacks); + add_timer(&t->timer); +} + +bool +wl_del_timer(wl_info_t *wl, wl_timer_t *t) +{ + ASSERT(t); + if (t->set) { + t->set = FALSE; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0) + // Rel. commit "treewide: Switch/rename to timer_delete[_sync]()" (Thomas Gleixner, 5 Apr 2025) + if (!timer_delete(&t->timer)) { +#else + if (!del_timer(&t->timer)) { +#endif +#ifdef BCMDBG + WL_INFORM(("wl%d: Failed to delete timer %s\n", wl->unit, t->name)); +#endif + return TRUE; + } + atomic_dec(&wl->callbacks); + } + + return TRUE; +} + +void +wl_free_timer(wl_info_t *wl, wl_timer_t *t) +{ + wl_timer_t *tmp; + + wl_del_timer(wl, t); + + if (wl->timers == t) { + wl->timers = wl->timers->next; +#ifdef BCMDBG + if (t->name) + MFREE(wl->osh, t->name, strlen(t->name) + 1); +#endif + MFREE(wl->osh, t, sizeof(wl_timer_t)); + return; + + } + + tmp = wl->timers; + while (tmp) { + if (tmp->next == t) { + tmp->next = t->next; +#ifdef BCMDBG + if (t->name) + MFREE(wl->osh, t->name, strlen(t->name) + 1); +#endif + MFREE(wl->osh, t, sizeof(wl_timer_t)); + return; + } + tmp = tmp->next; + } + +} + +void +wl_monitor(wl_info_t *wl, wl_rxsts_t *rxsts, void *p) +{ + struct sk_buff *oskb = (struct sk_buff *)p; + struct sk_buff *skb; + uchar *pdata; + uint len; + + len = 0; + skb = NULL; + WL_TRACE(("wl%d: wl_monitor\n", wl->pub->unit)); + + if (!wl->monitor_dev) + return; + + if (wl->monitor_type == 1) { + p80211msg_t *phdr; + + len = sizeof(p80211msg_t) + oskb->len - D11_PHY_HDR_LEN; + if ((skb = dev_alloc_skb(len)) == NULL) { + WL_ERROR(("%s: dev_alloc_skb() failure, mon type 1", __FUNCTION__)); + return; + } + + skb_put(skb, len); + phdr = (p80211msg_t*)skb->data; + + phdr->msgcode = WL_MON_FRAME; + phdr->msglen = sizeof(p80211msg_t); + strcpy(phdr->devname, wl->dev->name); + + phdr->hosttime.did = WL_MON_FRAME_HOSTTIME; + phdr->hosttime.status = P80211ITEM_OK; + phdr->hosttime.len = 4; + phdr->hosttime.data = jiffies; + + phdr->channel.did = WL_MON_FRAME_CHANNEL; + phdr->channel.status = P80211ITEM_NO_VALUE; + phdr->channel.len = 4; + phdr->channel.data = 0; + + phdr->signal.did = WL_MON_FRAME_SIGNAL; + phdr->signal.status = P80211ITEM_OK; + phdr->signal.len = 4; + + phdr->signal.data = rxsts->preamble; + + phdr->noise.did = WL_MON_FRAME_NOISE; + phdr->noise.status = P80211ITEM_NO_VALUE; + phdr->noise.len = 4; + phdr->noise.data = 0; + + phdr->rate.did = WL_MON_FRAME_RATE; + phdr->rate.status = P80211ITEM_OK; + phdr->rate.len = 4; + phdr->rate.data = rxsts->datarate; + + phdr->istx.did = WL_MON_FRAME_ISTX; + phdr->istx.status = P80211ITEM_NO_VALUE; + phdr->istx.len = 4; + phdr->istx.data = 0; + + phdr->mactime.did = WL_MON_FRAME_MACTIME; + phdr->mactime.status = P80211ITEM_OK; + phdr->mactime.len = 4; + phdr->mactime.data = rxsts->mactime; + + phdr->rssi.did = WL_MON_FRAME_RSSI; + phdr->rssi.status = P80211ITEM_OK; + phdr->rssi.len = 4; + phdr->rssi.data = rxsts->signal; + + phdr->sq.did = WL_MON_FRAME_SQ; + phdr->sq.status = P80211ITEM_OK; + phdr->sq.len = 4; + phdr->sq.data = rxsts->sq; + + phdr->frmlen.did = WL_MON_FRAME_FRMLEN; + phdr->frmlen.status = P80211ITEM_OK; + phdr->frmlen.status = P80211ITEM_OK; + phdr->frmlen.len = 4; + phdr->frmlen.data = rxsts->pktlength; + + pdata = skb->data + sizeof(p80211msg_t); + bcopy(oskb->data + D11_PHY_HDR_LEN, pdata, oskb->len - D11_PHY_HDR_LEN); + + } + else if (wl->monitor_type == 2) { + int channel_frequency; + uint16 channel_flags; + uint8 flags; + uint16 rtap_len; + struct dot11_header *mac_header; + uint16 fc; + + if (rxsts->phytype != WL_RXS_PHY_N) + rtap_len = sizeof(wl_radiotap_legacy_t); + else + rtap_len = sizeof(wl_radiotap_ht_brcm_2_t); + + len = rtap_len + (oskb->len - D11_PHY_HDR_LEN); + if ((skb = dev_alloc_skb(len)) == NULL) { + WL_ERROR(("%s: dev_alloc_skb() failure, mon type 2", __FUNCTION__)); + return; + } + + skb_put(skb, len); + + if (CHSPEC_IS2G(rxsts->chanspec)) { + channel_flags = IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_DYN; + channel_frequency = wf_channel2mhz(wf_chspec_ctlchan(rxsts->chanspec), + WF_CHAN_FACTOR_2_4_G); + } else { + channel_flags = IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_OFDM; + channel_frequency = wf_channel2mhz(wf_chspec_ctlchan(rxsts->chanspec), + WF_CHAN_FACTOR_5_G); + } + + mac_header = (struct dot11_header *)(oskb->data + D11_PHY_HDR_LEN); + fc = ltoh16(mac_header->fc); + + flags = IEEE80211_RADIOTAP_F_FCS; + + if (rxsts->preamble == WL_RXS_PREAMBLE_SHORT) + flags |= IEEE80211_RADIOTAP_F_SHORTPRE; + + if (fc & FC_WEP) + flags |= IEEE80211_RADIOTAP_F_WEP; + + if (fc & FC_MOREFRAG) + flags |= IEEE80211_RADIOTAP_F_FRAG; + + if (rxsts->pkterror & WL_RXS_CRC_ERROR) + flags |= IEEE80211_RADIOTAP_F_BADFCS; + + if (rxsts->phytype != WL_RXS_PHY_N) { + wl_radiotap_legacy_t *rtl = (wl_radiotap_legacy_t *)skb->data; + + rtl->ieee_radiotap.it_version = 0; + rtl->ieee_radiotap.it_pad = 0; + rtl->ieee_radiotap.it_len = HTOL16(rtap_len); + rtl->ieee_radiotap.it_present = HTOL32(WL_RADIOTAP_PRESENT_LEGACY); + + rtl->tsft_l = htol32(rxsts->mactime); + rtl->tsft_h = 0; + rtl->flags = flags; + rtl->rate = rxsts->datarate; + rtl->channel_freq = HTOL16(channel_frequency); + rtl->channel_flags = HTOL16(channel_flags); + rtl->signal = (int8)rxsts->signal; + rtl->noise = (int8)rxsts->noise; + rtl->antenna = rxsts->antenna; + + memcpy(rtl->vend_oui, brcm_oui, sizeof(brcm_oui)); + rtl->vend_skip_len = WL_RADIOTAP_LEGACY_SKIP_LEN; + rtl->vend_sns = 0; + + memset(&rtl->nonht_vht, 0, sizeof(rtl->nonht_vht)); + rtl->nonht_vht.len = WL_RADIOTAP_NONHT_VHT_LEN; + } else { + wl_radiotap_ht_brcm_2_t *rtht = (wl_radiotap_ht_brcm_2_t *)skb->data; + + rtht->ieee_radiotap.it_version = 0; + rtht->ieee_radiotap.it_pad = 0; + rtht->ieee_radiotap.it_len = HTOL16(rtap_len); + rtht->ieee_radiotap.it_present = HTOL32(WL_RADIOTAP_PRESENT_HT_BRCM2); + rtht->it_present_ext = HTOL32(WL_RADIOTAP_BRCM2_HT_MCS); + rtht->pad1 = 0; + + rtht->tsft_l = htol32(rxsts->mactime); + rtht->tsft_h = 0; + rtht->flags = flags; + rtht->pad2 = 0; + rtht->channel_freq = HTOL16(channel_frequency); + rtht->channel_flags = HTOL16(channel_flags); + rtht->signal = (int8)rxsts->signal; + rtht->noise = (int8)rxsts->noise; + rtht->antenna = rxsts->antenna; + rtht->pad3 = 0; + + memcpy(rtht->vend_oui, brcm_oui, sizeof(brcm_oui)); + rtht->vend_sns = WL_RADIOTAP_BRCM2_HT_SNS; + rtht->vend_skip_len = WL_RADIOTAP_HT_BRCM2_SKIP_LEN; + rtht->mcs = rxsts->mcs; + rtht->htflags = 0; + if (rxsts->htflags & WL_RXS_HTF_40) + rtht->htflags |= IEEE80211_RADIOTAP_HTMOD_40; + if (rxsts->htflags & WL_RXS_HTF_SGI) + rtht->htflags |= IEEE80211_RADIOTAP_HTMOD_SGI; + if (rxsts->preamble & WL_RXS_PREAMBLE_HT_GF) + rtht->htflags |= IEEE80211_RADIOTAP_HTMOD_GF; + if (rxsts->htflags & WL_RXS_HTF_LDPC) + rtht->htflags |= IEEE80211_RADIOTAP_HTMOD_LDPC; + rtht->htflags |= + (rxsts->htflags & WL_RXS_HTF_STBC_MASK) << + IEEE80211_RADIOTAP_HTMOD_STBC_SHIFT; + } + + pdata = skb->data + rtap_len; + bcopy(oskb->data + D11_PHY_HDR_LEN, pdata, oskb->len - D11_PHY_HDR_LEN); + } + else if (wl->monitor_type == 3) { + int channel_frequency; + uint16 channel_flags; + uint8 flags; + uint16 rtap_len; + struct dot11_header * mac_header; + uint16 fc; + + if (rxsts->phytype == WL_RXS_PHY_N) { + if (rxsts->encoding == WL_RXS_ENCODING_HT) + rtap_len = sizeof(wl_radiotap_ht_t); + else if (rxsts->encoding == WL_RXS_ENCODING_VHT) + rtap_len = sizeof(wl_radiotap_vht_t); + else + rtap_len = sizeof(wl_radiotap_legacy_t); + } else { + rtap_len = sizeof(wl_radiotap_legacy_t); + } + + len = rtap_len + (oskb->len - D11_PHY_HDR_LEN); + + if (oskb->next) { + struct sk_buff *amsdu_p = oskb->next; + uint amsdu_len = 0; + while (amsdu_p) { + amsdu_len += amsdu_p->len; + amsdu_p = amsdu_p->next; + } + len += amsdu_len; + } + + if ((skb = dev_alloc_skb(len)) == NULL) { + WL_ERROR(("%s: dev_alloc_skb() failure, mon type 3", __FUNCTION__)); + return; + } + + skb_put(skb, len); + + if (CHSPEC_IS2G(rxsts->chanspec)) { + channel_flags = IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_DYN; + channel_frequency = wf_channel2mhz(wf_chspec_ctlchan(rxsts->chanspec), + WF_CHAN_FACTOR_2_4_G); + } else { + channel_flags = IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_OFDM; + channel_frequency = wf_channel2mhz(wf_chspec_ctlchan(rxsts->chanspec), + WF_CHAN_FACTOR_5_G); + } + + mac_header = (struct dot11_header *)(oskb->data + D11_PHY_HDR_LEN); + fc = ltoh16(mac_header->fc); + + flags = IEEE80211_RADIOTAP_F_FCS; + + if (rxsts->preamble == WL_RXS_PREAMBLE_SHORT) + flags |= IEEE80211_RADIOTAP_F_SHORTPRE; + + if (fc & FC_WEP) + flags |= IEEE80211_RADIOTAP_F_WEP; + + if (fc & FC_MOREFRAG) + flags |= IEEE80211_RADIOTAP_F_FRAG; + + if (rxsts->pkterror & WL_RXS_CRC_ERROR) + flags |= IEEE80211_RADIOTAP_F_BADFCS; + + if ((rxsts->phytype != WL_RXS_PHY_N) || + ((rxsts->encoding != WL_RXS_ENCODING_HT) && + (rxsts->encoding != WL_RXS_ENCODING_VHT))) { + wl_radiotap_legacy_t *rtl = (wl_radiotap_legacy_t *)skb->data; + + rtl->ieee_radiotap.it_version = 0; + rtl->ieee_radiotap.it_pad = 0; + rtl->ieee_radiotap.it_len = HTOL16(rtap_len); + rtl->ieee_radiotap.it_present = HTOL32(WL_RADIOTAP_PRESENT_LEGACY); + + rtl->it_present_ext = HTOL32(WL_RADIOTAP_LEGACY_VHT); + rtl->tsft_l = htol32(rxsts->mactime); + rtl->tsft_h = 0; + rtl->flags = flags; + rtl->rate = rxsts->datarate; + rtl->channel_freq = HTOL16(channel_frequency); + rtl->channel_flags = HTOL16(channel_flags); + rtl->signal = (int8)rxsts->signal; + rtl->noise = (int8)rxsts->noise; + rtl->antenna = rxsts->antenna; + + memcpy(rtl->vend_oui, brcm_oui, sizeof(brcm_oui)); + rtl->vend_skip_len = WL_RADIOTAP_LEGACY_SKIP_LEN; + rtl->vend_sns = 0; + + memset(&rtl->nonht_vht, 0, sizeof(rtl->nonht_vht)); + rtl->nonht_vht.len = WL_RADIOTAP_NONHT_VHT_LEN; + if (((fc & FC_KIND_MASK) == FC_RTS) || + ((fc & FC_KIND_MASK) == FC_CTS)) { + rtl->nonht_vht.flags |= WL_RADIOTAP_F_NONHT_VHT_BW; + rtl->nonht_vht.bw = rxsts->bw_nonht; + rtl->vend_sns = WL_RADIOTAP_LEGACY_SNS; + + } + if ((fc & FC_KIND_MASK) == FC_RTS) { + if (rxsts->vhtflags & WL_RXS_VHTF_DYN_BW_NONHT) + rtl->nonht_vht.flags + |= WL_RADIOTAP_F_NONHT_VHT_DYN_BW; + } + } + else if (rxsts->encoding == WL_RXS_ENCODING_VHT) { + wl_radiotap_vht_t *rtvht = (wl_radiotap_vht_t *)skb->data; + + rtvht->ieee_radiotap.it_version = 0; + rtvht->ieee_radiotap.it_pad = 0; + rtvht->ieee_radiotap.it_len = HTOL16(rtap_len); + rtvht->ieee_radiotap.it_present = + HTOL32(WL_RADIOTAP_PRESENT_VHT); + + rtvht->tsft_l = htol32(rxsts->mactime); + rtvht->tsft_h = 0; + rtvht->flags = flags; + rtvht->pad1 = 0; + rtvht->channel_freq = HTOL16(channel_frequency); + rtvht->channel_flags = HTOL16(channel_flags); + rtvht->signal = (int8)rxsts->signal; + rtvht->noise = (int8)rxsts->noise; + rtvht->antenna = rxsts->antenna; + + rtvht->vht_known = (IEEE80211_RADIOTAP_VHT_HAVE_STBC | + IEEE80211_RADIOTAP_VHT_HAVE_TXOP_PS | + IEEE80211_RADIOTAP_VHT_HAVE_GI | + IEEE80211_RADIOTAP_VHT_HAVE_SGI_NSYM_DA | + IEEE80211_RADIOTAP_VHT_HAVE_LDPC_EXTRA | + IEEE80211_RADIOTAP_VHT_HAVE_BF | + IEEE80211_RADIOTAP_VHT_HAVE_BW | + IEEE80211_RADIOTAP_VHT_HAVE_GID | + IEEE80211_RADIOTAP_VHT_HAVE_PAID); + + STATIC_ASSERT(WL_RXS_VHTF_STBC == + IEEE80211_RADIOTAP_VHT_STBC); + STATIC_ASSERT(WL_RXS_VHTF_TXOP_PS == + IEEE80211_RADIOTAP_VHT_TXOP_PS); + STATIC_ASSERT(WL_RXS_VHTF_SGI == + IEEE80211_RADIOTAP_VHT_SGI); + STATIC_ASSERT(WL_RXS_VHTF_SGI_NSYM_DA == + IEEE80211_RADIOTAP_VHT_SGI_NSYM_DA); + STATIC_ASSERT(WL_RXS_VHTF_LDPC_EXTRA == + IEEE80211_RADIOTAP_VHT_LDPC_EXTRA); + STATIC_ASSERT(WL_RXS_VHTF_BF == + IEEE80211_RADIOTAP_VHT_BF); + + rtvht->vht_flags = HTOL16(rxsts->vhtflags); + + STATIC_ASSERT(WL_RXS_VHT_BW_20 == + IEEE80211_RADIOTAP_VHT_BW_20); + STATIC_ASSERT(WL_RXS_VHT_BW_40 == + IEEE80211_RADIOTAP_VHT_BW_40); + STATIC_ASSERT(WL_RXS_VHT_BW_20L == + IEEE80211_RADIOTAP_VHT_BW_20L); + STATIC_ASSERT(WL_RXS_VHT_BW_20U == + IEEE80211_RADIOTAP_VHT_BW_20U); + STATIC_ASSERT(WL_RXS_VHT_BW_80 == + IEEE80211_RADIOTAP_VHT_BW_80); + STATIC_ASSERT(WL_RXS_VHT_BW_40L == + IEEE80211_RADIOTAP_VHT_BW_40L); + STATIC_ASSERT(WL_RXS_VHT_BW_40U == + IEEE80211_RADIOTAP_VHT_BW_40U); + STATIC_ASSERT(WL_RXS_VHT_BW_20LL == + IEEE80211_RADIOTAP_VHT_BW_20LL); + STATIC_ASSERT(WL_RXS_VHT_BW_20LU == + IEEE80211_RADIOTAP_VHT_BW_20LU); + STATIC_ASSERT(WL_RXS_VHT_BW_20UL == + IEEE80211_RADIOTAP_VHT_BW_20UL); + STATIC_ASSERT(WL_RXS_VHT_BW_20UU == + IEEE80211_RADIOTAP_VHT_BW_20UU); + + rtvht->vht_bw = rxsts->bw; + + rtvht->vht_mcs_nss[0] = (rxsts->mcs << 4) | + (rxsts->nss & IEEE80211_RADIOTAP_VHT_NSS); + rtvht->vht_mcs_nss[1] = 0; + rtvht->vht_mcs_nss[2] = 0; + rtvht->vht_mcs_nss[3] = 0; + + STATIC_ASSERT(WL_RXS_VHTF_CODING_LDCP == + IEEE80211_RADIOTAP_VHT_CODING_LDPC); + + rtvht->vht_coding = rxsts->coding; + rtvht->vht_group_id = rxsts->gid; + rtvht->vht_partial_aid = HTOL16(rxsts->aid); + + rtvht->ampdu_flags = 0; + rtvht->ampdu_delim_crc = 0; + + rtvht->ampdu_ref_num = rxsts->ampdu_counter; + + if (!(rxsts->nfrmtype & WL_RXS_NFRM_AMPDU_FIRST) && + !(rxsts->nfrmtype & WL_RXS_NFRM_AMPDU_SUB)) + rtvht->ampdu_flags |= IEEE80211_RADIOTAP_AMPDU_IS_LAST; + + if (rxsts->nfrmtype & WL_RXS_NFRM_AMPDU_NONE) + rtvht->ampdu_flags |= IEEE80211_RADIOTAP_AMPDU_MPDU_ONLY; + } + else if (rxsts->encoding == WL_RXS_ENCODING_HT) { + wl_radiotap_ht_t *rtht = + (wl_radiotap_ht_t *)skb->data; + + rtht->ieee_radiotap.it_version = 0; + rtht->ieee_radiotap.it_pad = 0; + rtht->ieee_radiotap.it_len = HTOL16(rtap_len); + rtht->ieee_radiotap.it_present + = HTOL32(WL_RADIOTAP_PRESENT_HT); + rtht->pad1 = 0; + + rtht->tsft_l = htol32(rxsts->mactime); + rtht->tsft_h = 0; + rtht->flags = flags; + rtht->channel_freq = HTOL16(channel_frequency); + rtht->channel_flags = HTOL16(channel_flags); + rtht->signal = (int8)rxsts->signal; + rtht->noise = (int8)rxsts->noise; + rtht->antenna = rxsts->antenna; + + rtht->mcs_known = (IEEE80211_RADIOTAP_MCS_HAVE_BW | + IEEE80211_RADIOTAP_MCS_HAVE_MCS | + IEEE80211_RADIOTAP_MCS_HAVE_GI | + IEEE80211_RADIOTAP_MCS_HAVE_FEC | + IEEE80211_RADIOTAP_MCS_HAVE_FMT); + + rtht->mcs_flags = 0; + switch (rxsts->htflags & WL_RXS_HTF_BW_MASK) { + case WL_RXS_HTF_20L: + rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_BW_20L; + break; + case WL_RXS_HTF_20U: + rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_BW_20U; + break; + case WL_RXS_HTF_40: + rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_BW_40; + break; + default: + rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_BW_20; + } + + if (rxsts->htflags & WL_RXS_HTF_SGI) { + rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_SGI; + } + if (rxsts->preamble & WL_RXS_PREAMBLE_HT_GF) { + rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_FMT_GF; + } + if (rxsts->htflags & WL_RXS_HTF_LDPC) { + rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_FEC_LDPC; + } + rtht->mcs_index = rxsts->mcs; + } + + pdata = skb->data + rtap_len; + bcopy(oskb->data + D11_PHY_HDR_LEN, pdata, oskb->len - D11_PHY_HDR_LEN); + + if (oskb->next) { + struct sk_buff *amsdu_p = oskb->next; + amsdu_p = oskb->next; + pdata += (oskb->len - D11_PHY_HDR_LEN); + while (amsdu_p) { + bcopy(amsdu_p->data, pdata, amsdu_p->len); + pdata += amsdu_p->len; + amsdu_p = amsdu_p->next; + } + } + } + + if (skb == NULL) return; + + skb->dev = wl->monitor_dev; +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) + skb->dev->last_rx = jiffies; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) + skb_reset_mac_header(skb); +#else + skb->mac.raw = skb->data; +#endif + skb->ip_summed = CHECKSUM_NONE; + skb->pkt_type = PACKET_OTHERHOST; + skb->protocol = htons(ETH_P_80211_RAW); + + netif_rx(skb); +} + +static int +wl_monitor_start(struct sk_buff *skb, struct net_device *dev) +{ + wl_info_t *wl; + + wl = WL_DEV_IF(dev)->wl; + PKTFREE(wl->osh, skb, FALSE); + return 0; +} + +static void +_wl_add_monitor_if(wl_task_t *task) +{ + struct net_device *dev; + wl_if_t *wlif = (wl_if_t *) task->context; + wl_info_t *wl = wlif->wl; + + WL_TRACE(("wl%d: %s\n", wl->pub->unit, __FUNCTION__)); + ASSERT(wl); + ASSERT(!wl->monitor_dev); + + if ((dev = wl_alloc_linux_if(wlif)) == NULL) { + WL_ERROR(("wl%d: %s: wl_alloc_linux_if failed\n", wl->pub->unit, __FUNCTION__)); + goto done; + } + + ASSERT(strlen(wlif->name) > 0); + strncpy(wlif->dev->name, wlif->name, strlen(wlif->name)); + + wl->monitor_dev = dev; + if (wl->monitor_type == 1) + dev->type = ARPHRD_IEEE80211_PRISM; + else + dev->type = ARPHRD_IEEE80211_RADIOTAP; + + eth_hw_addr_set(dev, wl->dev->dev_addr); + +#if defined(WL_USE_NETDEV_OPS) + dev->netdev_ops = &wl_netdev_monitor_ops; +#else + dev->hard_start_xmit = wl_monitor_start; + dev->do_ioctl = wl_ioctl; + dev->get_stats = wl_get_stats; +#endif + + if (register_netdev(dev)) { + WL_ERROR(("wl%d: %s, register_netdev failed for %s\n", + wl->pub->unit, __FUNCTION__, wl->monitor_dev->name)); + wl->monitor_dev = NULL; + goto done; + } + wlif->dev_registed = TRUE; + +done: + MFREE(wl->osh, task, sizeof(wl_task_t)); + atomic_dec(&wl->callbacks); +} + +static void +_wl_del_monitor(wl_task_t *task) +{ + wl_info_t *wl = (wl_info_t *) task->context; + + ASSERT(wl); + ASSERT(wl->monitor_dev); + + WL_TRACE(("wl%d: _wl_del_monitor\n", wl->pub->unit)); + + wl_free_if(wl, WL_DEV_IF(wl->monitor_dev)); + wl->monitor_dev = NULL; + + MFREE(wl->osh, task, sizeof(wl_task_t)); + atomic_dec(&wl->callbacks); +} + +void +wl_set_monitor(wl_info_t *wl, int val) +{ + const char *devname; + wl_if_t *wlif; + + WL_TRACE(("wl%d: wl_set_monitor: val %d\n", wl->pub->unit, val)); + if ((val && wl->monitor_dev) || (!val && !wl->monitor_dev)) { + WL_ERROR(("%s: Mismatched params, return\n", __FUNCTION__)); + return; + } + + if (!val) { + (void) wl_schedule_task(wl, _wl_del_monitor, wl); + return; + } + + if (val >= 1 && val <= 3) { + wl->monitor_type = val; + } else { + WL_ERROR(("monitor type %d not supported\n", val)); + ASSERT(0); + } + + wlif = wl_alloc_if(wl, WL_IFTYPE_MON, wl->pub->unit, NULL); + if (!wlif) { + WL_ERROR(("wl%d: %s: alloc wlif failed\n", wl->pub->unit, __FUNCTION__)); + return; + } + + if (wl->monitor_type == 1) + devname = "prism"; + else + devname = "radiotap"; + sprintf(wlif->name, "%s%d", devname, wl->pub->unit); + + if (wl_schedule_task(wl, _wl_add_monitor_if, wlif)) { + MFREE(wl->osh, wlif, sizeof(wl_if_t)); + return; + } +} + +#if LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 15) +const char * +print_tainted() +{ + return ""; +} +#endif + +struct net_device * +wl_netdev_get(wl_info_t *wl) +{ + return wl->dev; +} + +int +wl_set_pktlen(osl_t *osh, void *p, int len) +{ + PKTSETLEN(osh, p, len); + return len; +} + +void * +wl_get_pktbuffer(osl_t *osh, int len) +{ + return (PKTGET(osh, len, FALSE)); +} + +uint +wl_buf_to_pktcopy(osl_t *osh, void *p, uchar *buf, int len, uint offset) +{ + if (PKTLEN(osh, p) < len + offset) + return 0; + bcopy(buf, (char *)PKTDATA(osh, p) + offset, len); + return len; +} + +#if defined(WL_CONFIG_RFKILL) + +static int +wl_set_radio_block(void *data, bool blocked) +{ + wl_info_t *wl = data; + uint32 radioval; + + WL_TRACE(("%s: kernel set blocked = %d\n", __FUNCTION__, blocked)); + + radioval = WL_RADIO_SW_DISABLE << 16 | blocked; + + WL_LOCK(wl); + + if (wlc_set(wl->wlc, WLC_SET_RADIO, radioval) < 0) { + WL_ERROR(("%s: SET_RADIO failed\n", __FUNCTION__)); + return 1; + } + + WL_UNLOCK(wl); + + return 0; +} + +static const struct rfkill_ops bcmwl_rfkill_ops = { + .set_block = wl_set_radio_block +}; + +static int +wl_init_rfkill(wl_info_t *wl) +{ + int status; + + snprintf(wl->wl_rfkill.rfkill_name, sizeof(wl->wl_rfkill.rfkill_name), + "brcmwl-%d", wl->pub->unit); + + wl->wl_rfkill.rfkill = rfkill_alloc(wl->wl_rfkill.rfkill_name, &wl->dev->dev, + RFKILL_TYPE_WLAN, &bcmwl_rfkill_ops, wl); + + if (!wl->wl_rfkill.rfkill) { + WL_ERROR(("%s: RFKILL: Failed to allocate rfkill\n", __FUNCTION__)); + return -ENOMEM; + } + + if (wlc_get(wl->wlc, WLC_GET_RADIO, &status) < 0) { + WL_ERROR(("%s: WLC_GET_RADIO failed\n", __FUNCTION__)); + return 1; + } + + rfkill_init_sw_state(wl->wl_rfkill.rfkill, status); + + if (rfkill_register(wl->wl_rfkill.rfkill)) { + WL_ERROR(("%s: rfkill_register failed! \n", __FUNCTION__)); + rfkill_destroy(wl->wl_rfkill.rfkill); + return 2; + } + + WL_ERROR(("%s: rfkill registered\n", __FUNCTION__)); + wl->wl_rfkill.registered = TRUE; + return 0; +} + +static void +wl_uninit_rfkill(wl_info_t *wl) +{ + if (wl->wl_rfkill.registered) { + rfkill_unregister(wl->wl_rfkill.rfkill); + rfkill_destroy(wl->wl_rfkill.rfkill); + wl->wl_rfkill.registered = FALSE; + wl->wl_rfkill.rfkill = NULL; + } +} + +static void +wl_report_radio_state(wl_info_t *wl) +{ + WL_TRACE(("%s: report radio state %d\n", __FUNCTION__, wl->last_phyind)); + + rfkill_set_hw_state(wl->wl_rfkill.rfkill, wl->last_phyind != 0); +} + +#endif + +static int +wl_linux_watchdog(void *ctx) +{ + wl_info_t *wl = (wl_info_t *) ctx; + struct net_device_stats *stats = NULL; + uint id; + wl_if_t *wlif; + wlc_if_stats_t wlcif_stats; +#ifdef USE_IW + struct iw_statistics *wstats = NULL; + int phy_noise; +#endif + if (wl == NULL) + return -1; + + if (wl->if_list) { + for (wlif = wl->if_list; wlif != NULL; wlif = wlif->next) { + memset(&wlcif_stats, 0, sizeof(wlc_if_stats_t)); + wlc_wlcif_stats_get(wl->wlc, wlif->wlcif, &wlcif_stats); + + if (wl->pub->up) { + ASSERT(wlif->stats_id < 2); + + id = 1 - wlif->stats_id; + stats = &wlif->stats_watchdog[id]; + if (stats) { + stats->rx_packets = WLCNTVAL(wlcif_stats.rxframe); + stats->tx_packets = WLCNTVAL(wlcif_stats.txframe); + stats->rx_bytes = WLCNTVAL(wlcif_stats.rxbyte); + stats->tx_bytes = WLCNTVAL(wlcif_stats.txbyte); + stats->rx_errors = WLCNTVAL(wlcif_stats.rxerror); + stats->tx_errors = WLCNTVAL(wlcif_stats.txerror); + stats->collisions = 0; + stats->rx_length_errors = 0; + + stats->rx_over_errors = WLCNTVAL(wl->pub->_cnt->rxoflo); + stats->rx_crc_errors = WLCNTVAL(wl->pub->_cnt->rxcrc); + stats->rx_frame_errors = 0; + stats->rx_fifo_errors = WLCNTVAL(wl->pub->_cnt->rxoflo); + stats->rx_missed_errors = 0; + stats->tx_fifo_errors = 0; + } + +#ifdef USE_IW + wstats = &wlif->wstats_watchdog[id]; + if (wstats) { +#if WIRELESS_EXT > 11 + wstats->discard.nwid = 0; + wstats->discard.code = WLCNTVAL(wl->pub->_cnt->rxundec); + wstats->discard.fragment = WLCNTVAL(wlcif_stats.rxfragerr); + wstats->discard.retries = WLCNTVAL(wlcif_stats.txfail); + wstats->discard.misc = WLCNTVAL(wl->pub->_cnt->rxrunt) + + WLCNTVAL(wl->pub->_cnt->rxgiant); + wstats->miss.beacon = 0; +#endif + } +#endif + + wlif->stats_id = id; + } +#ifdef USE_IW + if (!wlc_get(wl->wlc, WLC_GET_PHY_NOISE, &phy_noise)) + wlif->phy_noise = phy_noise; +#endif + + } + } + + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) +static int +wl_proc_read(char *buffer, char **start, off_t offset, int length, int *eof, void *data) +{ + wl_info_t * wl = (wl_info_t *)data; +#else +static ssize_t +wl_proc_read(struct file *filp, char __user *buffer, size_t length, loff_t *offp) +{ + wl_info_t * wl = PDE_DATA(file_inode(filp)); +#endif + int bcmerror, len; + int to_user = 0; + char tmp[8]; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) + if (offset > 0) { + *eof = 1; + return 0; + } +#else + if (*offp > 0) { + return 0; + } +#endif + + WL_LOCK(wl); + bcmerror = wlc_ioctl(wl->wlc, WLC_GET_MONITOR, &to_user, sizeof(int), NULL); + WL_UNLOCK(wl); + + if (bcmerror != BCME_OK) { + WL_ERROR(("%s: GET_MONITOR failed with %d\n", __FUNCTION__, bcmerror)); + return -EIO; + } + + len = snprintf(tmp, ARRAY_SIZE(tmp), "%d\n", to_user); + tmp[ARRAY_SIZE(tmp) - 1] = '\0'; + if ((len < 0) || (len >= ARRAY_SIZE(tmp))) { + WL_ERROR(("%s: tmp array not big enough %d > %zu", __FUNCTION__, len, ARRAY_SIZE(tmp))); + return -ERANGE; + } + if (length < len) { + WL_ERROR(( "%s: user buffer is too small (%d < %d)", __FUNCTION__, (int)length, len)); + return -EMSGSIZE; + } + if (copy_to_user(buffer, tmp, len) != 0) { + WL_ERROR(( "%s: unable to copy data!", __FUNCTION__)); + return -EFAULT; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + *offp += len; +#endif + + return len; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) +static int +wl_proc_write(struct file *filp, const char *buff, unsigned long length, void *data) +{ + wl_info_t * wl = (wl_info_t *)data; +#else +static ssize_t +wl_proc_write(struct file *filp, const char __user *buff, size_t length, loff_t *offp) +{ + wl_info_t * wl = PDE_DATA(file_inode(filp)); +#endif + int from_user = 0; + int bcmerror; + + if (length == 0 || length > 2) { + + WL_ERROR(("%s: Invalid data length\n", __FUNCTION__)); + return -EIO; + } + if (copy_from_user(&from_user, buff, 1)) { + WL_ERROR(("%s: copy from user failed\n", __FUNCTION__)); +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) + return -EIO; +#else + return -EFAULT; +#endif + } + + if (from_user >= 0x30) + from_user -= 0x30; + + WL_LOCK(wl); + bcmerror = wlc_ioctl(wl->wlc, WLC_SET_MONITOR, &from_user, sizeof(int), NULL); + WL_UNLOCK(wl); + + if (bcmerror != BCME_OK) { + WL_ERROR(("%s: SET_MONITOR failed with %d\n", __FUNCTION__, bcmerror)); + return -EIO; + } + return length; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) +static struct proc_ops wl_fops = { + .proc_read = wl_proc_read, + .proc_write = wl_proc_write, +}; +#else +static const struct file_operations wl_fops = { + .owner = THIS_MODULE, + .read = wl_proc_read, + .write = wl_proc_write, +}; +#endif +#endif + +static int +wl_reg_proc_entry(wl_info_t *wl) +{ + char tmp[32]; + sprintf(tmp, "%s%d", HYBRID_PROC, wl->pub->unit); +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) + if ((wl->proc_entry = create_proc_entry(tmp, 0644, NULL)) == NULL) { + WL_ERROR(("%s: create_proc_entry %s failed\n", __FUNCTION__, tmp)); +#else + if ((wl->proc_entry = proc_create_data(tmp, 0644, NULL, &wl_fops, wl)) == NULL) { + WL_ERROR(("%s: proc_create_data %s failed\n", __FUNCTION__, tmp)); +#endif + ASSERT(0); + return -1; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) + wl->proc_entry->read_proc = wl_proc_read; + wl->proc_entry->write_proc = wl_proc_write; + wl->proc_entry->data = wl; +#endif + return 0; +} +uint32 wl_pcie_bar1(struct wl_info *wl, uchar** addr) +{ + *addr = wl->bar1_addr; + return (wl->bar1_size); +} diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wl_linux.h b/drivers/custom/broadcom-wl/src/wl/sys/wl_linux.h new file mode 100644 index 000000000000..c8c1f419196c --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wl_linux.h @@ -0,0 +1,194 @@ +/* + * wl_linux.c exported functions and definitions + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wl_linux.h 369548 2012-11-19 09:01:01Z $ + */ + +#ifndef _wl_linux_h_ +#define _wl_linux_h_ + +#include +#include + +typedef struct wl_timer { + struct timer_list timer; + struct wl_info *wl; + void (*fn)(void *); + void *arg; + uint ms; + bool periodic; + bool set; + struct wl_timer *next; +#ifdef BCMDBG + char* name; + uint32 ticks; +#endif +} wl_timer_t; + +typedef struct wl_task { + struct work_struct work; + void *context; +} wl_task_t; + +#define WL_IFTYPE_BSS 1 +#define WL_IFTYPE_WDS 2 +#define WL_IFTYPE_MON 3 + +struct wl_if { +#ifdef USE_IW + wl_iw_t iw; +#endif + struct wl_if *next; + struct wl_info *wl; + struct net_device *dev; + struct wlc_if *wlcif; + uint subunit; + bool dev_registed; + int if_type; + char name[IFNAMSIZ]; + struct net_device_stats stats; + uint stats_id; + struct net_device_stats stats_watchdog[2]; + +#ifdef USE_IW + struct iw_statistics wstats_watchdog[2]; + struct iw_statistics wstats; + int phy_noise; +#endif +}; + +struct rfkill_stuff { + struct rfkill *rfkill; + char rfkill_name[32]; + char registered; +}; + +struct wl_info { + uint unit; + wlc_pub_t *pub; + void *wlc; + osl_t *osh; + struct net_device *dev; + + struct semaphore sem; + spinlock_t lock; + spinlock_t isr_lock; + + uint bcm_bustype; + bool piomode; + void *regsva; + wl_if_t *if_list; + atomic_t callbacks; + struct wl_timer *timers; + struct tasklet_struct tasklet; + struct tasklet_struct tx_tasklet; + +#if 0 && (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30)) + struct napi_struct napi; +#endif + + struct net_device *monitor_dev; + uint monitor_type; + bool resched; + uint32 pci_psstate[16]; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) +#define NUM_GROUP_KEYS 4 +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) + struct lib80211_crypto_ops *tkipmodops; +#else + struct ieee80211_crypto_ops *tkipmodops; +#endif + struct ieee80211_tkip_data *tkip_ucast_data; + struct ieee80211_tkip_data *tkip_bcast_data[NUM_GROUP_KEYS]; +#endif + + bool txq_dispatched; + spinlock_t txq_lock; + struct sk_buff *txq_head; + struct sk_buff *txq_tail; + int txq_cnt; + + wl_task_t txq_task; + wl_task_t multicast_task; + + wl_task_t wl_dpc_task; + bool all_dispatch_mode; + +#if defined(WL_CONFIG_RFKILL) + struct rfkill_stuff wl_rfkill; + mbool last_phyind; +#endif + + uint processed; + struct proc_dir_entry *proc_entry; + uchar* bar1_addr; + uint32 bar1_size; +}; + +#define HYBRID_PROC "brcm_monitor" + +#if defined(WL_ALL_PASSIVE_ON) +#define WL_ALL_PASSIVE_ENAB(wl) 1 +#else +#define WL_ALL_PASSIVE_ENAB(wl) (!(wl)->all_dispatch_mode) +#endif + +#define WL_LOCK(wl) \ +do { \ + if (WL_ALL_PASSIVE_ENAB(wl)) \ + down(&(wl)->sem); \ + else \ + spin_lock_bh(&(wl)->lock); \ +} while (0) + +#define WL_UNLOCK(wl) \ +do { \ + if (WL_ALL_PASSIVE_ENAB(wl)) \ + up(&(wl)->sem); \ + else \ + spin_unlock_bh(&(wl)->lock); \ +} while (0) + +#define WL_ISRLOCK(wl, flags) do {spin_lock(&(wl)->isr_lock); (void)(flags);} while (0) +#define WL_ISRUNLOCK(wl, flags) do {spin_unlock(&(wl)->isr_lock); (void)(flags);} while (0) + +#define INT_LOCK(wl, flags) spin_lock_irqsave(&(wl)->isr_lock, flags) +#define INT_UNLOCK(wl, flags) spin_unlock_irqrestore(&(wl)->isr_lock, flags) + +typedef struct wl_info wl_info_t; + +#ifndef PCI_D0 +#define PCI_D0 0 +#endif + +#ifndef PCI_D3hot +#define PCI_D3hot 3 +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) +extern irqreturn_t wl_isr(int irq, void *dev_id); +#else +extern irqreturn_t wl_isr(int irq, void *dev_id, struct pt_regs *ptregs); +#endif + +extern int __devinit wl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent); +extern void wl_free(wl_info_t *wl); +extern int wl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); +extern int wlc_ioctl_internal(struct net_device *dev, int cmd, void *buf, int len); +extern struct net_device * wl_netdev_get(wl_info_t *wl); + +#endif diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wlc_ethereal.h b/drivers/custom/broadcom-wl/src/wl/sys/wlc_ethereal.h new file mode 100644 index 000000000000..2f9e1289000a --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wlc_ethereal.h @@ -0,0 +1,129 @@ +/* + * Structures and defines for the prism-style rx header that Ethereal + * understands. + * Broadcom 802.11abg Networking Device Driver + * Derived from http://airsnort.shmoo.com/orinoco-09b-packet-1.diff + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wlc_ethereal.h 328348 2012-04-18 22:57:38Z $ + */ + +#ifndef _WLC_ETHEREAL_H_ +#define _WLC_ETHEREAL_H_ + +#ifndef ETH_P_80211_RAW +#define ETH_P_80211_RAW (ETH_P_ECONET + 1) +#endif + +#ifndef ARPHRD_ETHER +#define ARPHRD_ETHER 1 +#endif + +#ifndef ARPHRD_IEEE80211_PRISM +#define ARPHRD_IEEE80211_PRISM 802 +#endif + +#define DNAMELEN 16 + +#define WL_MON_FRAME 0x0041 +#define WL_MON_FRAME_HOSTTIME 0x1041 +#define WL_MON_FRAME_MACTIME 0x2041 +#define WL_MON_FRAME_CHANNEL 0x3041 +#define WL_MON_FRAME_RSSI 0x4041 +#define WL_MON_FRAME_SQ 0x5041 +#define WL_MON_FRAME_SIGNAL 0x6041 +#define WL_MON_FRAME_NOISE 0x7041 +#define WL_MON_FRAME_RATE 0x8041 +#define WL_MON_FRAME_ISTX 0x9041 +#define WL_MON_FRAME_FRMLEN 0xA041 + +#define P80211ITEM_OK 0 +#define P80211ITEM_NO_VALUE 1 + +typedef struct p80211item +{ + uint32 did; + uint16 status; + uint16 len; + uint32 data; +} p80211item_t; + +typedef struct p80211msg +{ + uint32 msgcode; + uint32 msglen; + uint8 devname[DNAMELEN]; + p80211item_t hosttime; + p80211item_t mactime; + p80211item_t channel; + p80211item_t rssi; + p80211item_t sq; + p80211item_t signal; + p80211item_t noise; + p80211item_t rate; + p80211item_t istx; + p80211item_t frmlen; +} p80211msg_t; + +#define WLANCAP_MAGIC_COOKIE_V1 0x80211001 + +#define WLANCAP_PHY_UNKOWN 0 +#define WLANCAP_PHY_FHSS_97 1 +#define WLANCAP_PHY_DSSS_97 2 +#define WLANCAP_PHY_IR 3 +#define WLANCAP_PHY_DSSS_11B 4 +#define WLANCAP_PHY_PBCC_11B 5 +#define WLANCAP_PHY_OFDM_11G 6 +#define WLANCAP_PHY_PBCC_11G 7 +#define WLANCAP_PHY_OFDM_11A 8 +#define WLANCAP_PHY_OFDM_11N 9 + +#define WLANCAP_ENCODING_UNKNOWN 0 +#define WLANCAP_ENCODING_CCK 1 +#define WLANCAP_ENCODING_PBCC 2 +#define WLANCAP_ENCODING_OFDM 3 + +#define WLANCAP_SSI_TYPE_NONE 0 +#define WLANCAP_SSI_TYPE_NORM 1 +#define WLANCAP_SSI_TYPE_DBM 2 +#define WLANCAP_SSI_TYPE_RAW 3 + +#define WLANCAP_PREAMBLE_UNKNOWN 0 +#define WLANCAP_PREAMBLE_SHORT 1 +#define WLANCAP_PREAMBLE_LONG 2 +#define WLANCAP_PREAMBLE_MIMO_MM 3 +#define WLANCAP_PREAMBLE_MIMO_GF 4 + +typedef struct wlan_header_v1 { + uint32 version; + uint32 length; + uint32 mactime_h; + uint32 mactime_l; + uint32 hosttime_h; + uint32 hosttime_l; + uint32 phytype; + uint32 channel; + uint32 datarate; + uint32 antenna; + uint32 priority; + uint32 ssi_type; + int32 ssi_signal; + int32 ssi_noise; + uint32 preamble; + uint32 encoding; +} wlan_header_v1_t; + +#endif diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wlc_key.h b/drivers/custom/broadcom-wl/src/wl/sys/wlc_key.h new file mode 100644 index 000000000000..dfe99df13e59 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wlc_key.h @@ -0,0 +1,76 @@ +/* + * Key management related declarations + * and exported functions for + * Broadcom 802.11abg Networking Device Driver + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wlc_key.h 458427 2014-02-26 23:12:38Z $ + */ + +#ifndef _wlc_key_h_ +#define _wlc_key_h_ + +#include + +typedef struct tkip_info { + uint16 phase1[TKHASH_P1_KEY_SIZE/sizeof(uint16)]; + uint8 phase2[TKHASH_P2_KEY_SIZE]; + uint32 micl; + uint32 micr; +} tkip_info_t; + +typedef struct wsec_iv { + uint32 hi; + uint16 lo; +} wsec_iv_t; + +#define WLC_NUMRXIVS 4 + +#define TWSIZE 128 + +typedef struct wsec_key { + struct ether_addr ea; + uint8 idx; + uint8 id; + uint8 algo; + uint8 rcmta; + uint16 flags; + uint8 algo_hw; + uint8 aes_mode; + int8 iv_len; + int8 icv_len; + uint32 len; + + uint8 data[DOT11_MAX_KEY_SIZE]; + wsec_iv_t rxiv[WLC_NUMRXIVS]; + wsec_iv_t txiv; + tkip_info_t tkip_tx; + tkip_info_t tkip_rx; + uint32 tkip_rx_iv32; + uint8 tkip_rx_ividx; + uint8 tkip_tx_lefts; + uint8 tkip_tx_left[4]; + uint16 tkip_tx_offset; + uint8 tkip_tx_fmic[8]; + int tkip_tx_fmic_written; + +#if defined(UCODE_SEQ) + wsec_iv_t bk_iv; + tkip_info_t tkip_bk_tx; +#endif +} wsec_key_t; + +#endif diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wlc_pub.h b/drivers/custom/broadcom-wl/src/wl/sys/wlc_pub.h new file mode 100644 index 000000000000..2b5a0291cad3 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wlc_pub.h @@ -0,0 +1,928 @@ +/* + * Common (OS-independent) definitions for + * Broadcom 802.11abg Networking Device Driver + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wlc_pub.h 458427 2014-02-26 23:12:38Z $ + */ + +#ifndef _wlc_pub_h_ +#define _wlc_pub_h_ + +#include +#include +#include +#include "proto/802.11.h" +#include "proto/bcmevent.h" + +#define MAX_TIMERS (34 + WLC_MAXMFPS + WLC_MAXDLS_TIMERS + (2 * WLC_MAXDPT)) + +#define WLC_NUMRATES 16 +#define MAXMULTILIST 32 +#define D11_PHY_HDR_LEN 6 + +#define PHY_TYPE_A 0 +#define PHY_TYPE_G 2 +#define PHY_TYPE_N 4 +#define PHY_TYPE_LP 5 +#define PHY_TYPE_SSN 6 +#define PHY_TYPE_HT 7 +#define PHY_TYPE_LCN 8 +#define PHY_TYPE_LCNXN 9 + +#define WLC_10_MHZ 10 +#define WLC_20_MHZ 20 +#define WLC_40_MHZ 40 +#define WLC_80_MHZ 80 +#define WLC_160_MHZ 160 + +#define CHSPEC_WLC_BW(chanspec)(CHSPEC_IS160(chanspec) ? WLC_160_MHZ : \ + CHSPEC_IS80(chanspec) ? WLC_80_MHZ : \ + CHSPEC_IS40(chanspec) ? WLC_40_MHZ : \ + CHSPEC_IS20(chanspec) ? WLC_20_MHZ : \ + WLC_10_MHZ) + +#define WLC_RSSI_MINVAL -200 +#define WLC_RSSI_NO_SIGNAL -91 +#define WLC_RSSI_VERY_LOW -80 +#define WLC_RSSI_LOW -70 +#define WLC_RSSI_GOOD -68 +#define WLC_RSSI_VERY_GOOD -58 +#define WLC_RSSI_EXCELLENT -57 + +#define PREFSZ 160 +#define WLPREFHDRS(h, sz) OSL_PREF_RANGE_ST((h), (sz)) + +struct wlc_info; +struct wlc_hw_info; +struct wlc_bsscfg; +struct wlc_if; + +typedef struct wlc_tunables { + int ntxd; + int nrxd; + int rxbufsz; + int nrxbufpost; + int maxscb; + int ampdunummpdu2streams; + int ampdunummpdu3streams; + int maxpktcb; + int maxdpt; + int maxucodebss; + int maxucodebss4; + int maxbss; + int datahiwat; + int ampdudatahiwat; + int rxbnd; + int txsbnd; + int pktcbnd; + int dngl_mem_restrict_rxdma; + int rpctxbufpost; + int pkt_maxsegs; + int maxscbcubbies; + int maxbsscfgcubbies; + int max_notif_servers; + int max_notif_clients; + int max_mempools; + int maxtdls; + int amsdu_resize_buflen; + int ampdu_pktq_size; + int ampdu_pktq_fav_size; + int ntxd_large; + int nrxd_large; + int wlfcfifocreditac0; + int wlfcfifocreditac1; + int wlfcfifocreditac2; + int wlfcfifocreditac3; + int wlfcfifocreditbcmc; + int wlfcfifocreditother; + int scan_settle_time; + int wlfc_fifo_cr_pending_thresh_ac_bk; + int wlfc_fifo_cr_pending_thresh_ac_be; + int wlfc_fifo_cr_pending_thresh_ac_vi; + int wlfc_fifo_cr_pending_thresh_ac_vo; + int ampdunummpdu1stream; +} wlc_tunables_t; + +typedef struct wlc_rateset { + uint count; + uint8 rates[WLC_NUMRATES]; + uint8 htphy_membership; + uint8 mcs[MCSSET_LEN]; + uint16 vht_mcsmap; +} wlc_rateset_t; + +typedef void *wlc_pkt_t; + +typedef struct wlc_event { + wl_event_msg_t event; + struct ether_addr *addr; + struct wlc_if *wlcif; + void *data; + struct wlc_event *next; +} wlc_event_t; + +typedef struct wlc_bss_info +{ + struct ether_addr BSSID; + uint16 flags; + uint8 SSID_len; + uint8 SSID[32]; + int16 RSSI; + int16 SNR; + uint16 beacon_period; + uint16 atim_window; + chanspec_t chanspec; + int8 infra; + wlc_rateset_t rateset; + uint8 dtim_period; + int8 phy_noise; + uint16 capability; + struct dot11_bcn_prb *bcn_prb; + uint16 bcn_prb_len; + uint8 wme_qosinfo; + struct rsn_parms wpa; + struct rsn_parms wpa2; + uint16 qbss_load_aac; + + uint8 qbss_load_chan_free; + uint8 mcipher; + uint8 wpacfg; + uint16 mdid; + uint16 flags2; + uint32 vht_capabilities; + uint16 vht_rxmcsmap; + uint16 vht_txmcsmap; +} wlc_bss_info_t; + +#define WLC_BSS_54G 0x0001 +#define WLC_BSS_RSSI_ON_CHANNEL 0x0002 +#define WLC_BSS_WME 0x0004 +#define WLC_BSS_BRCM 0x0008 +#define WLC_BSS_WPA 0x0010 +#define WLC_BSS_HT 0x0020 +#define WLC_BSS_40MHZ 0x0040 +#define WLC_BSS_WPA2 0x0080 +#define WLC_BSS_BEACON 0x0100 +#define WLC_BSS_40INTOL 0x0200 +#define WLC_BSS_SGI_20 0x0400 +#define WLC_BSS_SGI_40 0x0800 +#define WLC_BSS_CACHE 0x2000 +#define WLC_BSS_FBT 0x8000 + +#define WLC_BSS_OVERDS_FBT 0x0001 +#define WLC_BSS_VHT 0x0002 +#define WLC_BSS_80MHZ 0x0004 +#define WLC_BSS_SGI_80 0x0008 + +#define WLC_ENOIOCTL 1 +#define WLC_EINVAL 2 +#define WLC_ETOOSMALL 3 +#define WLC_ETOOBIG 4 +#define WLC_ERANGE 5 +#define WLC_EDOWN 6 +#define WLC_EUP 7 +#define WLC_ENOMEM 8 +#define WLC_EBUSY 9 + +#define IOVF_BSSCFG_STA_ONLY (1<<0) +#define IOVF_BSSCFG_AP_ONLY (1<<1) +#define IOVF_BSS_SET_DOWN (1<<2) + +#define IOVF_MFG (1<<3) +#define IOVF_WHL (1<<4) +#define IOVF_NTRL (1<<5) + +#define IOVF_SET_UP (1<<6) +#define IOVF_SET_DOWN (1<<7) +#define IOVF_SET_CLK (1<<8) +#define IOVF_SET_BAND (1<<9) + +#define IOVF_GET_UP (1<<10) +#define IOVF_GET_DOWN (1<<11) +#define IOVF_GET_CLK (1<<12) +#define IOVF_GET_BAND (1<<13) +#define IOVF_OPEN_ALLOW (1<<14) + +#define IOVF_BMAC_IOVAR (1<<15) + +#define BAR0_INVALID (1 << 0) +#define VENDORID_INVALID (1 << 1) +#define NOCARD_PRESENT (1 << 2) +#define PHY_PLL_ERROR (1 << 3) +#define DEADCHIP_ERROR (1 << 4) +#define MACSPEND_TIMOUT (1 << 5) +#define MACSPEND_WOWL_TIMOUT (1 << 6) +#define DMATX_ERROR (1 << 7) +#define DMARX_ERROR (1 << 8) +#define DESCRIPTOR_ERROR (1 << 9) +#define CARD_NOT_POWERED (1 << 10) + +#define WL_HEALTH_LOG(w, s) do {} while (0) + +typedef int (*watchdog_fn_t)(void *handle); +typedef int (*up_fn_t)(void *handle); +typedef int (*down_fn_t)(void *handle); +typedef int (*dump_fn_t)(void *handle, struct bcmstrbuf *b); + +typedef int (*iovar_fn_t)(void *handle, const bcm_iovar_t *vi, uint32 actionid, + const char *name, void *params, uint plen, void *arg, int alen, + int vsize, struct wlc_if *wlcif); + +#define WLC_IOCF_BSSCFG_STA_ONLY (1<<0) +#define WLC_IOCF_BSSCFG_AP_ONLY (1<<1) + +#define WLC_IOCF_MFG (1<<2) + +#define WLC_IOCF_DRIVER_UP (1<<3) +#define WLC_IOCF_DRIVER_DOWN (1<<4) +#define WLC_IOCF_CORE_CLK (1<<5) +#define WLC_IOCF_FIXED_BAND (1<<6) +#define WLC_IOCF_OPEN_ALLOW (1<<7) + +typedef int (*wlc_ioctl_fn_t)(void *handle, int cmd, void *arg, int len, struct wlc_if *wlcif); + +typedef struct wlc_ioctl_cmd_s { + uint16 cmd; + uint16 flags; + int min_len; +} wlc_ioctl_cmd_t; + +typedef struct wlc_pub { + void *wlc; + struct ether_addr cur_etheraddr; + uint unit; + uint corerev; + osl_t *osh; + si_t *sih_obsolete; + char *vars_obsolete; + uint vars_size_obsolete; + bool up; + bool hw_off; + wlc_tunables_t *tunables; + bool hw_up; + bool _piomode; + uint _nbands; + uint now; + + bool promisc; + bool delayed_down; + bool _ap; + bool _apsta; + bool _assoc_recreate; + int _wme; + uint8 _mbss; + bool associated; + + bool phytest_on; + bool bf_preempt_4306; + + bool _wowl; + bool _wowl_active; + bool _ampdu_tx; + bool _ampdu_rx; + bool _amsdu_tx; + bool _cac; + uint _spect_management; + uint8 _n_enab; + bool _n_reqd; + + uint8 _vht_enab; + + int8 _coex; + bool _priofc; + bool phy_bw40_capable; + bool phy_bw80_capable; + + uint32 wlfeatureflag; + int psq_pkts_total; + + uint16 txmaxpkts; + + uint32 swdecrypt; + + int bcmerror; + + mbool radio_disabled; + mbool last_radio_disabled; + bool radio_active; + uint16 roam_time_thresh; + bool align_wd_tbtt; + uint16 boardrev; + uint8 sromrev; + uint32 boardflags; + uint32 boardflags2; + + wl_cnt_t *_cnt; + wl_wme_cnt_t *_wme_cnt; + + uint8 _ndis_cap; + bool _extsta; + bool _pkt_filter; + bool phy_11ncapable; + bool _fbt; + pktpool_t *pktpool; + uint8 _ampdumac; + bool _wleind; + bool _sup_enab; + uint driverrev; + + bool _11h; + bool _11d; +#ifdef WLCNTRY + bool _autocountry; +#endif + uint32 health; + uint8 d11tpl_phy_hdr_len; + uint wsec_max_rcmta_keys; + uint max_addrma_idx; + uint16 m_seckindxalgo_blk; + uint m_seckindxalgo_blk_sz; + uint16 m_coremask_blk; + uint16 m_coremask_blk_wowl; +#ifdef WL_BEAMFORMING + bool _txbf; +#endif + + bool wet_tunnel; + int _ol; + + uint16 vht_features; + + bool _ampdu_hostreorder; + + int8 _pktc; + bool _tdls_support; + bool _okc; + bool _p2po; + bool _anqpo; + bool _wl_rxearlyrc; + bool _tiny_pktjoin; + si_t *sih; + char *vars; + uint vars_size; + bool _proxd; + + bool _arpoe_support; + bool _11u; + + bool _lpc_algo; + bool _relmcast; + bool _relmcast_support; + + bool _l2_filter; + + uint bcn_tmpl_len; +#ifdef WL_OFFLOADSTATS + uint32 offld_cnt_received[4]; + uint32 offld_cnt_consumed[4]; +#endif +#ifdef WL_INTERRUPTSTATS + uint32 intr_cnt[32]; +#endif + +#ifdef TCPKAOE + bool _icmpoe; + bool _tcp_keepalive; +#endif + bool _olpc; +} wlc_pub_t; + +typedef struct wl_rxsts { + uint pkterror; + uint phytype; + chanspec_t chanspec; + uint16 datarate; + uint8 mcs; + uint8 htflags; + uint antenna; + uint pktlength; + uint32 mactime; + uint sq; + int32 signal; + int32 noise; + uint preamble; + uint encoding; + uint nfrmtype; + struct wl_if *wlif; + uint8 nss; + uint8 coding; + uint16 aid; + uint8 gid; + uint8 bw; + uint16 vhtflags; + uint8 bw_nonht; + uint32 ampdu_counter; +} wl_rxsts_t; + +typedef struct wl_txsts { + uint pkterror; + uint phytype; + chanspec_t chanspec; + uint16 datarate; + uint8 mcs; + uint8 htflags; + uint antenna; + uint pktlength; + uint32 mactime; + uint preamble; + uint encoding; + uint nfrmtype; + uint txflags; + uint retries; + struct wl_if *wlif; +} wl_txsts_t; + +typedef struct wlc_if_stats { + + uint32 txframe; + uint32 txbyte; + uint32 txerror; + uint32 txnobuf; + uint32 txrunt; + uint32 txfail; + + uint32 rxframe; + uint32 rxbyte; + uint32 rxerror; + uint32 rxnobuf; + uint32 rxrunt; + uint32 rxfragerr; + + uint32 txretry; + uint32 txretrie; + uint32 txfrmsnt; + uint32 txmulti; + uint32 txfrag; + + uint32 rxmulti; + +} wlc_if_stats_t; + +#define WL_RXS_CRC_ERROR 0x00000001 +#define WL_RXS_RUNT_ERROR 0x00000002 +#define WL_RXS_ALIGN_ERROR 0x00000004 +#define WL_RXS_OVERSIZE_ERROR 0x00000008 +#define WL_RXS_WEP_ICV_ERROR 0x00000010 +#define WL_RXS_WEP_ENCRYPTED 0x00000020 +#define WL_RXS_PLCP_SHORT 0x00000040 +#define WL_RXS_DECRYPT_ERR 0x00000080 +#define WL_RXS_OTHER_ERR 0x80000000 + +#define WL_RXS_PHY_A 0x00000000 +#define WL_RXS_PHY_B 0x00000001 +#define WL_RXS_PHY_G 0x00000002 +#define WL_RXS_PHY_N 0x00000004 + +#define WL_RXS_ENCODING_UNKNOWN 0x00000000 +#define WL_RXS_ENCODING_DSSS_CCK 0x00000001 +#define WL_RXS_ENCODING_OFDM 0x00000002 +#define WL_RXS_ENCODING_HT 0x00000003 +#define WL_RXS_ENCODING_VHT 0x00000004 + +#define WL_RXS_UNUSED_STUB 0x0 +#define WL_RXS_PREAMBLE_SHORT 0x00000001 +#define WL_RXS_PREAMBLE_LONG 0x00000002 +#define WL_RXS_PREAMBLE_HT_MM 0x00000003 +#define WL_RXS_PREAMBLE_HT_GF 0x00000004 + +#define WL_RXS_HTF_BW_MASK 0x07 +#define WL_RXS_HTF_40 0x01 +#define WL_RXS_HTF_20L 0x02 +#define WL_RXS_HTF_20U 0x04 +#define WL_RXS_HTF_SGI 0x08 +#define WL_RXS_HTF_STBC_MASK 0x30 +#define WL_RXS_HTF_STBC_SHIFT 4 +#define WL_RXS_HTF_LDPC 0x40 + +#define WL_RXS_VHTF_STBC 0x01 +#define WL_RXS_VHTF_TXOP_PS 0x02 +#define WL_RXS_VHTF_SGI 0x04 +#define WL_RXS_VHTF_SGI_NSYM_DA 0x08 +#define WL_RXS_VHTF_LDPC_EXTRA 0x10 +#define WL_RXS_VHTF_BF 0x20 +#define WL_RXS_VHTF_DYN_BW_NONHT 0x40 + +#define WL_RXS_VHTF_CODING_LDCP 0x01 + +#define WL_RXS_VHT_BW_20 0 +#define WL_RXS_VHT_BW_40 1 +#define WL_RXS_VHT_BW_20L 2 +#define WL_RXS_VHT_BW_20U 3 +#define WL_RXS_VHT_BW_80 4 +#define WL_RXS_VHT_BW_40L 5 +#define WL_RXS_VHT_BW_40U 6 +#define WL_RXS_VHT_BW_20LL 7 +#define WL_RXS_VHT_BW_20LU 8 +#define WL_RXS_VHT_BW_20UL 9 +#define WL_RXS_VHT_BW_20UU 10 + +#define WL_RXS_NFRM_AMPDU_FIRST 0x00000001 +#define WL_RXS_NFRM_AMPDU_SUB 0x00000002 +#define WL_RXS_NFRM_AMSDU_FIRST 0x00000004 +#define WL_RXS_NFRM_AMSDU_SUB 0x00000008 +#define WL_RXS_NFRM_AMPDU_NONE 0x00000100 + +#define WL_TXS_TXF_FAIL 0x01 +#define WL_TXS_TXF_CTS 0x02 +#define WL_TXS_TXF_RTSCTS 0x04 + +#define BPRESET_ENAB(pub) (0) + +#define AP_ENAB(pub) (0) + +#define APSTA_ENAB(pub) (0) + +#define PSTA_ENAB(pub) (0) + +#if defined(PKTC_DONGLE) +#define PKTC_ENAB(pub) ((pub)->_pktc) +#else +#define PKTC_ENAB(pub) (0) +#endif + +#if defined(WL_BEAMFORMING) + #if defined(WL_ENAB_RUNTIME_CHECK) || !defined(DONGLEBUILD) + #define TXBF_ENAB(pub) ((pub)->_txbf) + #elif defined(WLTXBF_DISABLED) + #define TXBF_ENAB(pub) (0) + #else + #define TXBF_ENAB(pub) (1) + #endif +#else + #define TXBF_ENAB(pub) (0) +#endif + +#define STA_ONLY(pub) (!AP_ENAB(pub)) +#define AP_ONLY(pub) (AP_ENAB(pub) && !APSTA_ENAB(pub)) + + #define PROP_TXSTATUS_ENAB(pub) 0 + +#define WLOFFLD_CAP(wlc) ((wlc)->ol != NULL) +#define WLOFFLD_ENAB(pub) ((pub)->_ol) +#define WLOFFLD_BCN_ENAB(pub) ((pub)->_ol & OL_BCN_ENAB) +#define WLOFFLD_ARP_ENAB(pub) ((pub)->_ol & OL_ARP_ENAB) +#define WLOFFLD_ND_ENAB(pub) ((pub)->_ol & OL_ND_ENAB) +#define WLOFFLD_ARM_TX(pub) ((pub)->_ol & OL_ARM_TX_ENAB) + +#define WOWL_ENAB(pub) ((pub)->_wowl) +#define WOWL_ACTIVE(pub) ((pub)->_wowl_active) + + #define DPT_ENAB(pub) 0 + + #define TDLS_SUPPORT(pub) (0) + #define TDLS_ENAB(pub) (0) + +#define WLDLS_ENAB(pub) 0 + +#ifdef WL_OKC + #if defined(WL_ENAB_RUNTIME_CHECK) +#define OKC_ENAB(pub) ((pub)->_okc) + #elif defined(WL_OKC_DISABLED) + #define OKC_ENAB(pub) (0) +#else + #define OKC_ENAB(pub) ((pub)->_okc) +#endif +#else + #define OKC_ENAB(pub) (0) +#endif + +#define WLBSSLOAD_ENAB(pub) (0) + + #define MCNX_ENAB(pub) 0 + + #define P2P_ENAB(pub) 0 + + #define MCHAN_ENAB(pub) (0) + #define MCHAN_ACTIVE(pub) (0) + + #define MQUEUE_ENAB(pub) (0) + + #define BTA_ENAB(pub) (0) + +#define PIO_ENAB(pub) 0 + +#define CAC_ENAB(pub) ((pub)->_cac) + +#define COEX_ACTIVE(wlc) 0 +#define COEX_ENAB(pub) 0 + +#define RXIQEST_ENAB(pub) (0) + +#define EDCF_ENAB(pub) (WME_ENAB(pub)) +#define QOS_ENAB(pub) (WME_ENAB(pub) || N_ENAB(pub)) + +#define PRIOFC_ENAB(pub) ((pub)->_priofc) + +#define MONITOR_ENAB(wlc) ((wlc)->monitor != 0) + +#define PROMISC_ENAB(wlc_pub) (wlc_pub)->promisc + +#define WLC_SENDUP_MGMT_ENAB(cfg) 0 + + #define TOE_ENAB(pub) (0) + + #define ARPOE_SUPPORT(pub) (0) + #define ARPOE_ENAB(pub) (0) +#define ICMPOE_ENAB(pub) 0 + + #define NWOE_ENAB(pub) (0) + +#define TRAFFIC_MGMT_ENAB(pub) 0 + + #define L2_FILTER_ENAB(pub) (0) + +#define NET_DETECT_ENAB(pub) 0 + +#ifdef PACKET_FILTER +#define PKT_FILTER_ENAB(pub) ((pub)->_pkt_filter) +#else +#define PKT_FILTER_ENAB(pub) 0 +#endif + +#ifdef P2PO + #if defined(WL_ENAB_RUNTIME_CHECK) || !defined(DONGLEBUILD) + #define P2PO_ENAB(pub) ((pub)->_p2po) + #elif defined(P2PO_DISABLED) + #define P2PO_ENAB(pub) (0) + #else + #define P2PO_ENAB(pub) (1) + #endif +#else + #define P2PO_ENAB(pub) 0 +#endif + +#ifdef ANQPO + #if defined(WL_ENAB_RUNTIME_CHECK) || !defined(DONGLEBUILD) + #define ANQPO_ENAB(pub) ((pub)->_anqpo) + #elif defined(ANQPO_DISABLED) + #define ANQPO_ENAB(pub) (0) + #else + #define ANQPO_ENAB(pub) (1) + #endif +#else + #define ANQPO_ENAB(pub) 0 +#endif + +#define ASSOC_RECREATE_ENAB(pub) 0 + +#define WLFBT_ENAB(pub) (0) + +#if 0 && (NDISVER >= 0x0620) +#define WIN7_AND_UP_OS(pub) ((pub)->_ndis_cap) +#else +#define WIN7_AND_UP_OS(pub) 0 +#endif + + #define NDOE_ENAB(pub) (0) + + #define WLEXTSTA_ENAB(pub) 0 + + #define IBSS_PEER_GROUP_KEY_ENAB(pub) (0) + + #define IBSS_PEER_DISCOVERY_EVENT_ENAB(pub) (0) + + #define IBSS_PEER_MGMT_ENAB(pub) (0) + + #if defined(WL_ENAB_RUNTIME_CHECK) || !defined(DONGLEBUILD) + #define WLEIND_ENAB(pub) ((pub)->_wleind) + #elif defined(WLEIND_DISABLED) + #define WLEIND_ENAB(pub) (0) + #else + #define WLEIND_ENAB(pub) (1) + #endif + + #define CCX_ENAB(pub) 0 + + #define BCMAUTH_PSK_ENAB(pub) 0 + + #if defined(WL_ENAB_RUNTIME_CHECK) || !defined(DONGLEBUILD) + #define SUP_ENAB(pub) ((pub)->_sup_enab) + #elif defined(BCMSUP_PSK_DISABLED) + #define SUP_ENAB(pub) (0) + #else + #define SUP_ENAB(pub) (1) + #endif + +#define WLC_PREC_BMP_ALL MAXBITVAL(WLC_PREC_COUNT) + +#define WLC_PREC_BMP_AC_BE (NBITVAL(WLC_PRIO_TO_PREC(PRIO_8021D_BE)) | \ + NBITVAL(WLC_PRIO_TO_HI_PREC(PRIO_8021D_BE)) | \ + NBITVAL(WLC_PRIO_TO_PREC(PRIO_8021D_EE)) | \ + NBITVAL(WLC_PRIO_TO_HI_PREC(PRIO_8021D_EE))) +#define WLC_PREC_BMP_AC_BK (NBITVAL(WLC_PRIO_TO_PREC(PRIO_8021D_BK)) | \ + NBITVAL(WLC_PRIO_TO_HI_PREC(PRIO_8021D_BK)) | \ + NBITVAL(WLC_PRIO_TO_PREC(PRIO_8021D_NONE)) | \ + NBITVAL(WLC_PRIO_TO_HI_PREC(PRIO_8021D_NONE))) +#define WLC_PREC_BMP_AC_VI (NBITVAL(WLC_PRIO_TO_PREC(PRIO_8021D_CL)) | \ + NBITVAL(WLC_PRIO_TO_HI_PREC(PRIO_8021D_CL)) | \ + NBITVAL(WLC_PRIO_TO_PREC(PRIO_8021D_VI)) | \ + NBITVAL(WLC_PRIO_TO_HI_PREC(PRIO_8021D_VI))) +#define WLC_PREC_BMP_AC_VO (NBITVAL(WLC_PRIO_TO_PREC(PRIO_8021D_VO)) | \ + NBITVAL(WLC_PRIO_TO_HI_PREC(PRIO_8021D_VO)) | \ + NBITVAL(WLC_PRIO_TO_PREC(PRIO_8021D_NC)) | \ + NBITVAL(WLC_PRIO_TO_HI_PREC(PRIO_8021D_NC))) + +#define WME_ENAB(pub) ((pub)->_wme != OFF) +#define WME_AUTO(wlc) ((wlc)->pub->_wme == AUTO) + +#ifdef WLCNTRY +#define WLC_AUTOCOUNTRY_ENAB(wlc) ((wlc)->pub->_autocountry) +#else +#define WLC_AUTOCOUNTRY_ENAB(wlc) FALSE +#endif + +#define WL11D_ENAB(wlc) ((wlc)->pub->_11d) + +#define WL11H_ENAB(wlc) ((wlc)->pub->_11h) + +#define WL11U_ENAB(wlc) FALSE + +#define WLPROBRESP_SW_ENAB(wlc) FALSE + +#define LPC_ENAB(wlc) (FALSE) + +#if defined(WLOLPC) +#define OLPC_ENAB(wlc) ((wlc)->pub->_olpc) +#else +#define OLPC_ENAB(wlc) (FALSE) +#endif + +#ifdef WL_RELMCAST + #if defined(WL_ENAB_RUNTIME_CHECK) + #define RMC_SUPPORT(pub) ((pub)->_relmcast_support) + #define RMC_ENAB(pub) ((pub)->_relmcast) + #elif defined(WL_RELMCAST_DISABLED) + #define RMC_SUPPORT(pub) (0) + #define RMC_ENAB(pub) (0) + #else + #define RMC_SUPPORT(pub) (1) + #define RMC_ENAB(pub) ((pub)->_relmcast) + #endif +#else + #define RMC_SUPPORT(pub) (0) + #define RMC_ENAB(pub) (0) +#endif + +#define WLC_USE_COREFLAGS 0xffffffff + +#define WLC_UPDATE_STATS(wlc) 1 +#define WLCNTINCR(a) ((a)++) +#define WLCNTCONDINCR(c, a) do { if (c) (a)++; } while (0) +#define WLCNTDECR(a) ((a)--) +#define WLCNTADD(a,delta) ((a) += (delta)) +#define WLCNTSET(a,value) ((a) = (value)) +#define WLCNTVAL(a) (a) + +#if !defined(RXCHAIN_PWRSAVE) && !defined(RADIO_PWRSAVE) +#define WLPWRSAVERXFADD(wlc, v) +#define WLPWRSAVERXFINCR(wlc) +#define WLPWRSAVETXFINCR(wlc) +#define WLPWRSAVERXFVAL(wlc) 0 +#define WLPWRSAVETXFVAL(wlc) 0 +#endif + +struct wlc_dpc_info { + uint processed; +}; + +extern void *wlc_attach(void *wl, uint16 vendor, uint16 device, uint unit, bool piomode, + osl_t *osh, void *regsva, uint bustype, void *btparam, uint *perr); +extern uint wlc_detach(struct wlc_info *wlc); +extern int wlc_up(struct wlc_info *wlc); +extern uint wlc_down(struct wlc_info *wlc); + +extern int wlc_set(struct wlc_info *wlc, int cmd, int arg); +extern int wlc_get(struct wlc_info *wlc, int cmd, int *arg); +extern int wlc_iovar_getint(struct wlc_info *wlc, const char *name, int *arg); +extern int wlc_iovar_setint(struct wlc_info *wlc, const char *name, int arg); +extern bool wlc_chipmatch(uint16 vendor, uint16 device); +extern void wlc_init(struct wlc_info *wlc); +extern void wlc_reset(struct wlc_info *wlc); +#ifdef MCAST_REGEN +extern int32 wlc_mcast_reverse_translation(struct ether_header *eh); +#endif + +extern void wlc_intrson(struct wlc_info *wlc); +extern uint32 wlc_intrsoff(struct wlc_info *wlc); +extern void wlc_intrsrestore(struct wlc_info *wlc, uint32 macintmask); +extern bool wlc_intrsupd(struct wlc_info *wlc); +extern bool wlc_isr(struct wlc_info *wlc, bool *wantdpc); +extern bool wlc_dpc(struct wlc_info *wlc, bool bounded, struct wlc_dpc_info *dpc); + +extern bool wlc_sendpkt(struct wlc_info *wlc, void *sdu, struct wlc_if *wlcif); +extern bool wlc_send80211_specified(wlc_info_t *wlc, void *sdu, uint32 rspec, struct wlc_if *wlcif); +extern bool wlc_send80211_raw(struct wlc_info *wlc, wlc_if_t *wlcif, void *p, uint ac); +extern int wlc_iovar_op(struct wlc_info *wlc, const char *name, void *params, int p_len, void *arg, + int len, bool set, struct wlc_if *wlcif); +extern int wlc_ioctl(struct wlc_info *wlc, int cmd, void *arg, int len, struct wlc_if *wlcif); + +extern void wlc_statsupd(struct wlc_info *wlc); + +extern wlc_pub_t *wlc_pub(void *wlc); + +extern void tcm_sem_enter(wlc_info_t *wlc); +extern void tcm_sem_exit(wlc_info_t *wlc); +extern void tcm_sem_cleanup(wlc_info_t *wlc); + +extern int wlc_module_register(wlc_pub_t *pub, const bcm_iovar_t *iovars, + const char *name, void *hdl, iovar_fn_t iovar_fn, + watchdog_fn_t watchdog_fn, up_fn_t up_fn, down_fn_t down_fn); +extern int wlc_module_unregister(wlc_pub_t *pub, const char *name, void *hdl); +extern int wlc_module_add_ioctl_fn(wlc_pub_t *pub, void *hdl, + wlc_ioctl_fn_t ioctl_fn, + int num_cmds, const wlc_ioctl_cmd_t *ioctls); +extern int wlc_module_remove_ioctl_fn(wlc_pub_t *pub, void *hdl); + +#define WLC_RPCTX_PARAMS 32 + +extern void wlc_wlcif_stats_get(wlc_info_t *wlc, wlc_if_t *wlcif, wlc_if_stats_t *wlcif_stats); +extern wlc_if_t *wlc_wlcif_get_by_index(wlc_info_t *wlc, uint idx); + +#if defined(BCMDBG) + +#define WLC_PERF_STATS_ISR 0x01 +#define WLC_PERF_STATS_DPC 0x02 +#define WLC_PERF_STATS_TMR_DPC 0x04 +#define WLC_PERF_STATS_PRB_REQ 0x08 +#define WLC_PERF_STATS_PRB_RESP 0x10 +#define WLC_PERF_STATS_BCN_ISR 0x20 +#define WLC_PERF_STATS_BCNS 0x40 + +void wlc_update_perf_stats(wlc_info_t *wlc, uint32 mask); +void wlc_update_isr_stats(wlc_info_t *wlc, uint32 macintstatus); +#endif + +#define WLC_REPLAY_CNTRS_VALUE WPA_CAP_4_REPLAY_CNTRS + +#if WLC_REPLAY_CNTRS_VALUE == WPA_CAP_16_REPLAY_CNTRS +#define PRIO2IVIDX(prio) (prio) +#elif WLC_REPLAY_CNTRS_VALUE == WPA_CAP_4_REPLAY_CNTRS +#define PRIO2IVIDX(prio) WME_PRIO2AC(prio) +#else +#error "Neither WPA_CAP_4_REPLAY_CNTRS nor WPA_CAP_16_REPLAY_CNTRS is used" +#endif + +#define GPIO_2_PA_CTRL_5G_0 0x4 + +#ifdef WL_INTERRUPTSTATS +typedef enum { + nMI_MACSSPNDD = 0, + nMI_BCNTPL, + nMI_TBTT, + nMI_BCNSUCCESS, + nMI_BCNCANCLD, + nMI_ATIMWINEND, + nMI_PMQ, + nMI_NSPECGEN_0, + nMI_NSPECGEN_1, + nMI_MACTXERR, + nMI_NSPECGEN_3, + nMI_PHYTXERR, + nMI_PME, + nMI_GP0, + nMI_GP1, + nMI_DMAINT, + nMI_TXSTOP, + nMI_CCA, + nMI_BG_NOISE, + nMI_DTIM_TBTT, + nMI_PRQ, + nMI_PWRUP, + nMI_BT_RFACT_STUCK, + nMI_BT_PRED_REQ, + nMI_NOTUSED, + nMI_P2P, + nMI_DMATX, + nMI_TSSI_LIMIT, + nMI_RFDISABLE, + nMI_TFS, + nMI_PHYCHANGED, + nMI_TO +} intr_enum; + +#define WLCINC_INTRCNT(intr) (wlc->pub->intr_cnt[(intr)]++) +#else +#define WLCINC_INTRCNT(intr) +#endif + +#if defined(CONFIG_WL) || defined(CONFIG_WL_MODULE) +#define WL_RTR() TRUE +#else +#define WL_RTR() FALSE +#endif + +#endif diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wlc_types.h b/drivers/custom/broadcom-wl/src/wl/sys/wlc_types.h new file mode 100644 index 000000000000..42fcb377ce61 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wlc_types.h @@ -0,0 +1,137 @@ +/* + * Forward declarations for commonly used wl driver structs + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wlc_types.h 402685 2013-05-16 17:47:20Z $ + */ + +#ifndef _wlc_types_h_ +#define _wlc_types_h_ + +typedef struct wlc_info wlc_info_t; +typedef struct wlc_bsscfg wlc_bsscfg_t; +typedef struct vndr_ie_listel vndr_ie_listel_t; +typedef struct wlc_if wlc_if_t; +typedef struct wl_if wl_if_t; +typedef struct led_info led_info_t; +typedef struct bmac_led bmac_led_t; +typedef struct bmac_led_info bmac_led_info_t; +typedef struct seq_cmds_info wlc_seq_cmds_info_t; +typedef struct wlc_ccx ccx_t; +typedef struct wlc_ccx_rm ccx_rm_t; +typedef struct apps_wlc_psinfo apps_wlc_psinfo_t; +typedef struct scb_module scb_module_t; +typedef struct ba_info ba_info_t; +typedef struct wlc_frminfo wlc_frminfo_t; +typedef struct amsdu_info amsdu_info_t; +typedef struct cram_info cram_info_t; +typedef struct wlc_extlog_info wlc_extlog_info_t; +typedef struct wlc_txq_info wlc_txq_info_t; +typedef struct wlc_hrt_info wlc_hrt_info_t; +typedef struct wlc_hrt_to wlc_hrt_to_t; +typedef struct wlc_cac wlc_cac_t; +typedef struct ampdu_tx_info ampdu_tx_info_t; +typedef struct ampdu_rx_info ampdu_rx_info_t; +typedef struct wlc_ratesel_info wlc_ratesel_info_t; +typedef struct ratesel_info ratesel_info_t; +typedef struct wlc_ap_info wlc_ap_info_t; +typedef struct wlc_scan_info wlc_scan_info_t; +typedef struct dpt_info dpt_info_t; +typedef struct tdls_info tdls_info_t; +typedef struct dls_info dls_info_t; +typedef struct l2_filter_info l2_filter_info_t; +typedef struct wlc_auth_info wlc_auth_info_t; +typedef struct wlc_psta_info wlc_psta_info_t; +typedef struct wlc_psa wlc_psa_t; +typedef struct wowl_info wowl_info_t; +typedef struct wlc_plt_info wlc_plt_pub_t; +typedef struct supplicant supplicant_t; +typedef struct authenticator authenticator_t; +typedef struct antsel_info antsel_info_t; +typedef struct bmac_pmq bmac_pmq_t; +typedef struct wlc_rrm_info wlc_rrm_info_t; +typedef struct rm_info rm_info_t; + +struct d11init; + +#ifndef _hnddma_pub_ +#define _hnddma_pub_ +typedef const struct hnddma_pub hnddma_t; +#endif + +typedef struct wlc_dpc_info wlc_dpc_info_t; + +typedef struct wlc_11h_info wlc_11h_info_t; +typedef struct wlc_tpc_info wlc_tpc_info_t; +typedef struct wlc_csa_info wlc_csa_info_t; +typedef struct wlc_quiet_info wlc_quiet_info_t; +typedef struct cca_info cca_info_t; +typedef struct itfr_info itfr_info_t; + +typedef struct wlc_ol_info_t wlc_ol_info_t; +#ifdef WLOLPC +typedef struct wlc_olpc_eng_info_t wlc_olpc_eng_info_t; +#endif +typedef void(*wlc_stf_txchain_evt_notify)(wlc_info_t *wlc); + +typedef struct wlc_11d_info wlc_11d_info_t; +typedef struct wlc_cntry_info wlc_cntry_info_t; + +typedef struct wlc_dfs_info wlc_dfs_info_t; + +typedef struct bsscfg_module bsscfg_module_t; + +typedef struct wlc_prq_info_s wlc_prq_info_t; + +typedef struct wlc_prot_info wlc_prot_info_t; +typedef struct wlc_prot_g_info wlc_prot_g_info_t; +typedef struct wlc_prot_n_info wlc_prot_n_info_t; + +typedef struct wlc_11u_info wlc_11u_info_t; +typedef struct wlc_probresp_info wlc_probresp_info_t; +typedef struct wlc_wapi_info wlc_wapi_info_t; + +typedef struct wlc_bssload_info wlc_bssload_info_t; + +typedef struct wlc_rfc wlc_rfc_t; +typedef struct wlc_pktc_info wlc_pktc_info_t; + +typedef struct wlc_lpc_info wlc_lpc_info_t; +typedef struct lpc_info lpc_info_t; +typedef struct rate_lcb_info rate_lcb_info_t; + +typedef struct wlc_txbf_info wlc_txbf_info_t; + +typedef struct wlc_bcn_clsg_info wlc_bcn_clsg_info_t; + +typedef struct wlc_nar_info wlc_nar_info_t; + +typedef struct wlc_relmcast_info wlc_relmcast_info_t; + +typedef struct okc_info okc_info_t; + +typedef struct wlc_gas_info wlc_gas_info_t; +typedef struct wlc_p2po_info wlc_p2po_info_t; +typedef struct wlc_disc_info wlc_disc_info_t; +typedef struct wlc_anqpo_info wlc_anqpo_info_t; + +typedef struct wlc_pdsvc_info wlc_pdsvc_info_t; + +typedef struct wlc_hw wlc_hw_t; +typedef struct wlc_hw_info wlc_hw_info_t; +typedef struct wlc_hwband wlc_hwband_t; + +#endif diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wlc_utils.h b/drivers/custom/broadcom-wl/src/wl/sys/wlc_utils.h new file mode 100644 index 000000000000..194ca6929e19 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wlc_utils.h @@ -0,0 +1,51 @@ +/* + * utilities related header file + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wlc_p2p.h 274724 2011-08-01 17:06:47Z $ + */ + +#ifndef _wlc_utils_h_ +#define _wlc_utils_h_ + +#include + +struct rsn_parms { + uint8 flags; + uint8 multicast; + uint8 ucount; + uint8 unicast[4]; + uint8 acount; + uint8 auth[4]; + uint8 PAD[4]; + uint8 cap[4]; +}; + +typedef struct rsn_parms rsn_parms_t; + +extern void wlc_uint64_add(uint32* high, uint32* low, uint32 inc_high, uint32 inc_low); +extern void wlc_uint64_sub(uint32* a_high, uint32* a_low, uint32 b_high, uint32 b_low); +extern bool wlc_uint64_lt(uint32 a_high, uint32 a_low, uint32 b_high, uint32 b_low); +extern uint32 wlc_calc_tbtt_offset(uint32 bi, uint32 tsf_h, uint32 tsf_l); +extern uint32 wlc_calc_next_pos32(uint32 tsf, uint32 cur, uint32 interval, bool wrap); +extern void wlc_tsf64_to_next_tbtt64(uint32 bcn_int, uint32 *tsf_h, uint32 *tsf_l); +extern void wlc_tbtt21_to_tbtt32(uint32 tsf_l, uint32 *tbtt_l); +extern void wlc_tbtt21_to_tbtt64(uint32 tsf_h, uint32 tsf_l, uint32 *tbtt_h, uint32 *tbtt_l); + +extern bool wlc_rsn_ucast_lookup(struct rsn_parms *rsn, uint8 auth); +extern bool wlc_rsn_akm_lookup(struct rsn_parms *rsn, uint8 akm); + +#endif diff --git a/drivers/custom/broadcom-wl/src/wl/sys/wlc_wowl.h b/drivers/custom/broadcom-wl/src/wl/sys/wlc_wowl.h new file mode 100644 index 000000000000..d21eb58382e8 --- /dev/null +++ b/drivers/custom/broadcom-wl/src/wl/sys/wlc_wowl.h @@ -0,0 +1,86 @@ +/* + * Wake-on-Wireless related header file + * + * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: wlc_wowl.h 458427 2014-02-26 23:12:38Z $ +*/ + +#ifndef _wlc_wowl_h_ +#define _wlc_wowl_h_ + +#define WLC_WOWL_OFFLOADS + +extern wowl_info_t *wlc_wowl_attach(wlc_info_t *wlc); +extern void wlc_wowl_detach(wowl_info_t *wowl); +extern bool wlc_wowl_cap(struct wlc_info *wlc); +extern bool wlc_wowl_enable(wowl_info_t *wowl); +extern uint32 wlc_wowl_clear(wowl_info_t *wowl); +void wlc_wowl_wake_reason_process(wlc_info_t *wlc); +#if defined(WLC_WOWL_OFFLOADS) +extern void wlc_wowl_set_wpa_m1(wowl_info_t *wowl); +extern void wlc_wowl_set_eapol_id(wowl_info_t *wowl); +extern int wlc_wowl_set_key_info(wowl_info_t *wowl, uint32 offload_id, void *kek, + int kek_len, void* kck, int kck_len, void *replay_counter, int replay_counter_len); +extern int wlc_wowl_add_offload_ipv4_arp(wowl_info_t *wowl, uint32 offload_id, + uint8 * RemoteIPv4Address, uint8 *HostIPv4Address, uint8 * MacAddress); +extern int wlc_wowl_add_offload_ipv6_ns(wowl_info_t *wowl, uint32 offload_id, + uint8 * RemoteIPv6Address, uint8 *SolicitedNodeIPv6Address, + uint8 * MacAddress, uint8 * TargetIPv6Address1, uint8 * TargetIPv6Address2); + +extern void wlc_wowl_set_keepalive(wowl_info_t *wowl, uint16 period_keepalive); +extern uint8 *wlc_wowl_solicitipv6_addr(uint8 *TargetIPv6Address1, uint8 *solicitaddress); +extern int wlc_wowl_remove_offload(wowl_info_t *wowl, uint32 offload_id, uint32 * type); +extern int wlc_wowl_get_replay_counter(wowl_info_t *wowl, void *replay_counter, int *len); + +extern void wlc_wowl_enable_completed(wowl_info_t *wowl); +extern void wlc_wowl_disable_completed(wowl_info_t *wowl, void *wowl_host_info); +#endif + +#define WOWL_IPV4_ARP_TYPE 0 +#define WOWL_IPV6_NS_TYPE 1 +#define WOWL_DOT11_RSN_REKEY_TYPE 2 +#define WOWL_OFFLOAD_INVALID_TYPE 3 + +#define WOWL_IPV4_ARP_IDX 0 +#define WOWL_IPV6_NS_0_IDX 1 +#define WOWL_IPV6_NS_1_IDX 2 +#define WOWL_DOT11_RSN_REKEY_IDX 3 +#define WOWL_OFFLOAD_INVALID_IDX 4 + +#define MAX_WOWL_OFFLOAD_ROWS 4 +#define MAX_WOWL_IPV6_ARP_PATTERNS 1 +#define MAX_WOWL_IPV6_NS_PATTERNS 2 +#define MAX_WOWL_IPV6_NS_OFFLOADS 1 + +#define WOWL_INT_RESERVED_MASK 0xFF000000 +#define WOWL_INT_DATA_MASK 0x00FFFFFF +#define WOWL_INT_PATTERN_FLAG 0x80000000 +#define WOWL_INT_NS_TA2_FLAG 0x40000000 +#define WOWL_INT_PATTERN_IDX_MASK 0x0F000000 +#define WOWL_INT_PATTERN_IDX_SHIFT 24 + +#define MAXPATTERNS(wlc) \ + (wlc_wowl_cap(wlc) ? \ + (WLOFFLD_CAP(wlc) ? 12 : \ + ((D11REV_GE((wlc)->pub->corerev, 15) && D11REV_LT((wlc)->pub->corerev, 40)) ? 12 : 4)) \ + : 0) + +#define WOWL_OFFLOAD_ENABLED(wlc) \ + ((CHIPID(wlc->pub->sih->chip) == BCM4360_CHIP_ID) || WIN7_AND_UP_OS(wlc->pub)) + +#define WOWL_KEEPALIVE_FIXED_PARAM 11 + +#endif # ---------------------------------------- # Module: cdemu # Version: 1c92067aaad2 # ---------------------------------------- diff --git a/drivers/custom/cdemu/vhba-module/Makefile b/drivers/custom/cdemu/vhba-module/Makefile new file mode 100644 index 000000000000..166e43f2f877 --- /dev/null +++ b/drivers/custom/cdemu/vhba-module/Makefile @@ -0,0 +1,14 @@ +VHBA_VERSION := 20250329 + +KERNELRELEASE ?= $(shell uname -r) +KDIR ?= /lib/modules/$(KERNELRELEASE)/build +PWD ?= $(shell pwd) + +obj-m := vhba.o +ccflags-y := -DVHBA_VERSION=\"$(VHBA_VERSION)\" -Werror + +default: modules +install: modules_install + +modules modules_install clean: + $(MAKE) -C $(KDIR) M=$(PWD) $@ diff --git a/drivers/custom/cdemu/vhba-module/debian/vhba-dkms.udev b/drivers/custom/cdemu/vhba-module/debian/vhba-dkms.udev new file mode 100644 index 000000000000..4be2d714f74b --- /dev/null +++ b/drivers/custom/cdemu/vhba-module/debian/vhba-dkms.udev @@ -0,0 +1 @@ +KERNEL=="vhba_ctl", SUBSYSTEM=="misc", TAG+="uaccess" diff --git a/drivers/custom/cdemu/vhba-module/vhba.c b/drivers/custom/cdemu/vhba-module/vhba.c new file mode 100644 index 000000000000..64b09ece2e5a --- /dev/null +++ b/drivers/custom/cdemu/vhba-module/vhba.c @@ -0,0 +1,1132 @@ +/* + * vhba.c + * + * Copyright (C) 2007-2012 Chia-I Wu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define pr_fmt(fmt) "vhba: " fmt + +#include + +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#ifdef CONFIG_COMPAT +#include +#endif +#include +#include +#include +#include +#include +#include + + +MODULE_AUTHOR("Chia-I Wu"); +MODULE_VERSION(VHBA_VERSION); +MODULE_DESCRIPTION("Virtual SCSI HBA"); +MODULE_LICENSE("GPL"); + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0) +#define sdev_dbg(sdev, fmt, a...) \ + dev_dbg(&(sdev)->sdev_gendev, fmt, ##a) +#define scmd_dbg(scmd, fmt, a...) \ + dev_dbg(&(scmd)->device->sdev_gendev, fmt, ##a) +#endif + +#define VHBA_MAX_SECTORS_PER_IO 256 +#define VHBA_MAX_BUS 16 +#define VHBA_MAX_ID 16 +#define VHBA_MAX_DEVICES (VHBA_MAX_BUS * (VHBA_MAX_ID-1)) +#define VHBA_KBUF_SIZE PAGE_SIZE + +#define DATA_TO_DEVICE(dir) ((dir) == DMA_TO_DEVICE || (dir) == DMA_BIDIRECTIONAL) +#define DATA_FROM_DEVICE(dir) ((dir) == DMA_FROM_DEVICE || (dir) == DMA_BIDIRECTIONAL) + + +static int vhba_can_queue = 32; +module_param_named(can_queue, vhba_can_queue, int, 0); + + +enum vhba_req_state { + VHBA_REQ_FREE, + VHBA_REQ_PENDING, + VHBA_REQ_READING, + VHBA_REQ_SENT, + VHBA_REQ_WRITING, +}; + +struct vhba_command { + struct scsi_cmnd *cmd; + /* metatags are per-host. not to be confused with + queue tags that are usually per-lun */ + unsigned long metatag; + int status; + struct list_head entry; +}; + +struct vhba_device { + unsigned int num; + spinlock_t cmd_lock; + struct list_head cmd_list; + wait_queue_head_t cmd_wq; + atomic_t refcnt; + + unsigned char *kbuf; + size_t kbuf_size; +}; + +struct vhba_host { + struct Scsi_Host *shost; + spinlock_t cmd_lock; + int cmd_next; + struct vhba_command *commands; + spinlock_t dev_lock; + struct vhba_device *devices[VHBA_MAX_DEVICES]; + int num_devices; + DECLARE_BITMAP(chgmap, VHBA_MAX_DEVICES); + int chgtype[VHBA_MAX_DEVICES]; + struct work_struct scan_devices; +}; + +#define MAX_COMMAND_SIZE 16 + +struct vhba_request { + __u32 metatag; + __u32 lun; + __u8 cdb[MAX_COMMAND_SIZE]; + __u8 cdb_len; + __u32 data_len; +}; + +struct vhba_response { + __u32 metatag; + __u32 status; + __u32 data_len; +}; + + + +static struct vhba_command *vhba_alloc_command (void); +static void vhba_free_command (struct vhba_command *vcmd); + +static struct platform_device vhba_platform_device; + + + +/* These functions define a symmetric 1:1 mapping between device numbers and + the bus and id. We have reserved the last id per bus for the host itself. */ +static void devnum_to_bus_and_id(unsigned int devnum, unsigned int *bus, unsigned int *id) +{ + *bus = devnum / (VHBA_MAX_ID-1); + *id = devnum % (VHBA_MAX_ID-1); +} + +static unsigned int bus_and_id_to_devnum(unsigned int bus, unsigned int id) +{ + return (bus * (VHBA_MAX_ID-1)) + id; +} + +static struct vhba_device *vhba_device_alloc (void) +{ + struct vhba_device *vdev; + + vdev = kzalloc(sizeof(struct vhba_device), GFP_KERNEL); + if (!vdev) { + return NULL; + } + + spin_lock_init(&vdev->cmd_lock); + INIT_LIST_HEAD(&vdev->cmd_list); + init_waitqueue_head(&vdev->cmd_wq); + atomic_set(&vdev->refcnt, 1); + + vdev->kbuf = NULL; + vdev->kbuf_size = 0; + + return vdev; +} + +static void vhba_device_put (struct vhba_device *vdev) +{ + if (atomic_dec_and_test(&vdev->refcnt)) { + kfree(vdev); + } +} + +static struct vhba_device *vhba_device_get (struct vhba_device *vdev) +{ + atomic_inc(&vdev->refcnt); + + return vdev; +} + +static int vhba_device_queue (struct vhba_device *vdev, struct scsi_cmnd *cmd) +{ + struct vhba_host *vhost; + struct vhba_command *vcmd; + unsigned long flags; + + vhost = platform_get_drvdata(&vhba_platform_device); + + vcmd = vhba_alloc_command(); + if (!vcmd) { + return SCSI_MLQUEUE_HOST_BUSY; + } + + vcmd->cmd = cmd; + + spin_lock_irqsave(&vdev->cmd_lock, flags); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) + vcmd->metatag = scsi_cmd_to_rq(vcmd->cmd)->tag; +#else + vcmd->metatag = vcmd->cmd->request->tag; +#endif + list_add_tail(&vcmd->entry, &vdev->cmd_list); + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + + wake_up_interruptible(&vdev->cmd_wq); + + return 0; +} + +static int vhba_device_dequeue (struct vhba_device *vdev, struct scsi_cmnd *cmd) +{ + struct vhba_command *vcmd; + int retval; + unsigned long flags; + + spin_lock_irqsave(&vdev->cmd_lock, flags); + list_for_each_entry(vcmd, &vdev->cmd_list, entry) { + if (vcmd->cmd == cmd) { + list_del_init(&vcmd->entry); + break; + } + } + + /* command not found */ + if (&vcmd->entry == &vdev->cmd_list) { + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + return SUCCESS; + } + + while (vcmd->status == VHBA_REQ_READING || vcmd->status == VHBA_REQ_WRITING) { + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + scmd_dbg(cmd, "wait for I/O before aborting\n"); + schedule_timeout(1); + spin_lock_irqsave(&vdev->cmd_lock, flags); + } + + retval = (vcmd->status == VHBA_REQ_SENT) ? FAILED : SUCCESS; + + vhba_free_command(vcmd); + + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + + return retval; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0) +static int vhba_slave_alloc(struct scsi_device *sdev) +{ + struct Scsi_Host *shost = sdev->host; + + sdev_dbg(sdev, "enabling tagging (queue depth: %i).\n", sdev->queue_depth); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0) + if (!shost_use_blk_mq(shost) && shost->bqt) { +#else + if (shost->bqt) { +#endif + blk_queue_init_tags(sdev->request_queue, sdev->queue_depth, shost->bqt); + } + scsi_adjust_queue_depth(sdev, 0, sdev->queue_depth); + + return 0; +} +#endif + +static void vhba_scan_devices_add (struct vhba_host *vhost, int bus, int id) +{ + struct scsi_device *sdev; + + sdev = scsi_device_lookup(vhost->shost, bus, id, 0); + if (!sdev) { + scsi_add_device(vhost->shost, bus, id, 0); + } else { + dev_warn(&vhost->shost->shost_gendev, "tried to add an already-existing device %d:%d:0!\n", bus, id); + scsi_device_put(sdev); + } +} + +static void vhba_scan_devices_remove (struct vhba_host *vhost, int bus, int id) +{ + struct scsi_device *sdev; + + sdev = scsi_device_lookup(vhost->shost, bus, id, 0); + if (sdev) { + scsi_remove_device(sdev); + scsi_device_put(sdev); + } else { + dev_warn(&vhost->shost->shost_gendev, "tried to remove non-existing device %d:%d:0!\n", bus, id); + } +} + +static void vhba_scan_devices (struct work_struct *work) +{ + struct vhba_host *vhost = container_of(work, struct vhba_host, scan_devices); + unsigned long flags; + int change, exists; + unsigned int devnum; + unsigned int bus, id; + + for (;;) { + spin_lock_irqsave(&vhost->dev_lock, flags); + + devnum = find_first_bit(vhost->chgmap, VHBA_MAX_DEVICES); + if (devnum >= VHBA_MAX_DEVICES) { + spin_unlock_irqrestore(&vhost->dev_lock, flags); + break; + } + change = vhost->chgtype[devnum]; + exists = vhost->devices[devnum] != NULL; + + vhost->chgtype[devnum] = 0; + clear_bit(devnum, vhost->chgmap); + + spin_unlock_irqrestore(&vhost->dev_lock, flags); + + devnum_to_bus_and_id(devnum, &bus, &id); + + if (change < 0) { + dev_dbg(&vhost->shost->shost_gendev, "trying to remove target %d:%d:0\n", bus, id); + vhba_scan_devices_remove(vhost, bus, id); + } else if (change > 0) { + dev_dbg(&vhost->shost->shost_gendev, "trying to add target %d:%d:0\n", bus, id); + vhba_scan_devices_add(vhost, bus, id); + } else { + /* quick sequence of add/remove or remove/add; we determine + which one it was by checking if device structure exists */ + if (exists) { + /* remove followed by add: remove and (re)add */ + dev_dbg(&vhost->shost->shost_gendev, "trying to (re)add target %d:%d:0\n", bus, id); + vhba_scan_devices_remove(vhost, bus, id); + vhba_scan_devices_add(vhost, bus, id); + } else { + /* add followed by remove: no-op */ + dev_dbg(&vhost->shost->shost_gendev, "no-op for target %d:%d:0\n", bus, id); + } + } + } +} + +static int vhba_add_device (struct vhba_device *vdev) +{ + struct vhba_host *vhost; + unsigned int devnum; + unsigned long flags; + + vhost = platform_get_drvdata(&vhba_platform_device); + + vhba_device_get(vdev); + + spin_lock_irqsave(&vhost->dev_lock, flags); + if (vhost->num_devices >= VHBA_MAX_DEVICES) { + spin_unlock_irqrestore(&vhost->dev_lock, flags); + vhba_device_put(vdev); + return -EBUSY; + } + + for (devnum = 0; devnum < VHBA_MAX_DEVICES; devnum++) { + if (vhost->devices[devnum] == NULL) { + vdev->num = devnum; + vhost->devices[devnum] = vdev; + vhost->num_devices++; + set_bit(devnum, vhost->chgmap); + vhost->chgtype[devnum]++; + break; + } + } + spin_unlock_irqrestore(&vhost->dev_lock, flags); + + schedule_work(&vhost->scan_devices); + + return 0; +} + +static int vhba_remove_device (struct vhba_device *vdev) +{ + struct vhba_host *vhost; + unsigned long flags; + + vhost = platform_get_drvdata(&vhba_platform_device); + + spin_lock_irqsave(&vhost->dev_lock, flags); + set_bit(vdev->num, vhost->chgmap); + vhost->chgtype[vdev->num]--; + vhost->devices[vdev->num] = NULL; + vhost->num_devices--; + spin_unlock_irqrestore(&vhost->dev_lock, flags); + + vhba_device_put(vdev); + + schedule_work(&vhost->scan_devices); + + return 0; +} + +static struct vhba_device *vhba_lookup_device (int devnum) +{ + struct vhba_host *vhost; + struct vhba_device *vdev = NULL; + unsigned long flags; + + vhost = platform_get_drvdata(&vhba_platform_device); + + if (likely(devnum < VHBA_MAX_DEVICES)) { + spin_lock_irqsave(&vhost->dev_lock, flags); + vdev = vhost->devices[devnum]; + if (vdev) { + vdev = vhba_device_get(vdev); + } + + spin_unlock_irqrestore(&vhost->dev_lock, flags); + } + + return vdev; +} + +static struct vhba_command *vhba_alloc_command (void) +{ + struct vhba_host *vhost; + struct vhba_command *vcmd; + unsigned long flags; + int i; + + vhost = platform_get_drvdata(&vhba_platform_device); + + spin_lock_irqsave(&vhost->cmd_lock, flags); + + vcmd = vhost->commands + vhost->cmd_next++; + if (vcmd->status != VHBA_REQ_FREE) { + for (i = 0; i < vhba_can_queue; i++) { + vcmd = vhost->commands + i; + + if (vcmd->status == VHBA_REQ_FREE) { + vhost->cmd_next = i + 1; + break; + } + } + + if (i == vhba_can_queue) { + vcmd = NULL; + } + } + + if (vcmd) { + vcmd->status = VHBA_REQ_PENDING; + } + + vhost->cmd_next %= vhba_can_queue; + + spin_unlock_irqrestore(&vhost->cmd_lock, flags); + + return vcmd; +} + +static void vhba_free_command (struct vhba_command *vcmd) +{ + struct vhba_host *vhost; + unsigned long flags; + + vhost = platform_get_drvdata(&vhba_platform_device); + + spin_lock_irqsave(&vhost->cmd_lock, flags); + vcmd->status = VHBA_REQ_FREE; + spin_unlock_irqrestore(&vhost->cmd_lock, flags); +} + +static int vhba_queuecommand (struct Scsi_Host *shost, struct scsi_cmnd *cmd) +{ + struct vhba_device *vdev; + int retval; + unsigned int devnum; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) + scmd_dbg(cmd, "queue %p tag %i\n", cmd, scsi_cmd_to_rq(cmd)->tag); +#else + scmd_dbg(cmd, "queue %p tag %i\n", cmd, cmd->request->tag); +#endif + + devnum = bus_and_id_to_devnum(cmd->device->channel, cmd->device->id); + vdev = vhba_lookup_device(devnum); + if (!vdev) { + scmd_dbg(cmd, "no such device\n"); + + cmd->result = DID_NO_CONNECT << 16; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0) + scsi_done(cmd); +#else + cmd->scsi_done(cmd); +#endif + + return 0; + } + + retval = vhba_device_queue(vdev, cmd); + + vhba_device_put(vdev); + + return retval; +} + +static int vhba_abort (struct scsi_cmnd *cmd) +{ + struct vhba_device *vdev; + int retval = SUCCESS; + unsigned int devnum; + + scmd_dbg(cmd, "abort %p\n", cmd); + + devnum = bus_and_id_to_devnum(cmd->device->channel, cmd->device->id); + vdev = vhba_lookup_device(devnum); + if (vdev) { + retval = vhba_device_dequeue(vdev, cmd); + vhba_device_put(vdev); + } else { + cmd->result = DID_NO_CONNECT << 16; + } + + return retval; +} + +static struct scsi_host_template vhba_template = { + .module = THIS_MODULE, + .name = "vhba", + .proc_name = "vhba", + .queuecommand = vhba_queuecommand, + .eh_abort_handler = vhba_abort, + .this_id = -1, + .max_sectors = VHBA_MAX_SECTORS_PER_IO, + .sg_tablesize = 256, +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0) + .slave_alloc = vhba_slave_alloc, +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0) + .tag_alloc_policy_rr = true, +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) + .tag_alloc_policy = BLK_TAG_ALLOC_RR, +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) + .use_blk_tags = 1, +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) + .max_segment_size = VHBA_KBUF_SIZE, +#endif +}; + +static ssize_t do_request (struct vhba_device *vdev, unsigned long metatag, struct scsi_cmnd *cmd, char __user *buf, size_t buf_len) +{ + struct vhba_request vreq; + ssize_t ret; + + scmd_dbg(cmd, "request %lu (%p), cdb 0x%x, bufflen %d, sg count %d\n", + metatag, cmd, cmd->cmnd[0], scsi_bufflen(cmd), scsi_sg_count(cmd)); + + ret = sizeof(vreq); + if (DATA_TO_DEVICE(cmd->sc_data_direction)) { + ret += scsi_bufflen(cmd); + } + + if (ret > buf_len) { + scmd_dbg(cmd, "buffer too small (%zd < %zd) for a request\n", buf_len, ret); + return -EIO; + } + + vreq.metatag = metatag; + vreq.lun = cmd->device->lun; + memcpy(vreq.cdb, cmd->cmnd, MAX_COMMAND_SIZE); + vreq.cdb_len = cmd->cmd_len; + vreq.data_len = scsi_bufflen(cmd); + + if (copy_to_user(buf, &vreq, sizeof(vreq))) { + return -EFAULT; + } + + if (DATA_TO_DEVICE(cmd->sc_data_direction) && vreq.data_len) { + buf += sizeof(vreq); + + if (scsi_sg_count(cmd)) { + unsigned char *kaddr, *uaddr; + struct scatterlist *sglist = scsi_sglist(cmd); + struct scatterlist *sg; + int i; + + uaddr = (unsigned char *) buf; + + for_each_sg(sglist, sg, scsi_sg_count(cmd), i) { + size_t len = sg->length; + + if (len > vdev->kbuf_size) { + scmd_dbg(cmd, "segment size (%zu) exceeds kbuf size (%zu)!", len, vdev->kbuf_size); + len = vdev->kbuf_size; + } + + kaddr = kmap_atomic(sg_page(sg)); + memcpy(vdev->kbuf, kaddr + sg->offset, len); + kunmap_atomic(kaddr); + + if (copy_to_user(uaddr, vdev->kbuf, len)) { + return -EFAULT; + } + uaddr += len; + } + } else { + if (copy_to_user(buf, scsi_sglist(cmd), vreq.data_len)) { + return -EFAULT; + } + } + } + + return ret; +} + +static ssize_t do_response (struct vhba_device *vdev, unsigned long metatag, struct scsi_cmnd *cmd, const char __user *buf, size_t buf_len, struct vhba_response *res) +{ + ssize_t ret = 0; + + scmd_dbg(cmd, "response %lu (%p), status %x, data len %d, sg count %d\n", + metatag, cmd, res->status, res->data_len, scsi_sg_count(cmd)); + + if (res->status) { + if (res->data_len > SCSI_SENSE_BUFFERSIZE) { + scmd_dbg(cmd, "truncate sense (%d < %d)", SCSI_SENSE_BUFFERSIZE, res->data_len); + res->data_len = SCSI_SENSE_BUFFERSIZE; + } + + if (copy_from_user(cmd->sense_buffer, buf, res->data_len)) { + return -EFAULT; + } + + cmd->result = res->status; + + ret += res->data_len; + } else if (DATA_FROM_DEVICE(cmd->sc_data_direction) && scsi_bufflen(cmd)) { + size_t to_read; + + if (res->data_len > scsi_bufflen(cmd)) { + scmd_dbg(cmd, "truncate data (%d < %d)\n", scsi_bufflen(cmd), res->data_len); + res->data_len = scsi_bufflen(cmd); + } + + to_read = res->data_len; + + if (scsi_sg_count(cmd)) { + unsigned char *kaddr, *uaddr; + struct scatterlist *sglist = scsi_sglist(cmd); + struct scatterlist *sg; + int i; + + uaddr = (unsigned char *)buf; + + for_each_sg(sglist, sg, scsi_sg_count(cmd), i) { + size_t len = (sg->length < to_read) ? sg->length : to_read; + + if (len > vdev->kbuf_size) { + scmd_dbg(cmd, "segment size (%zu) exceeds kbuf size (%zu)!", len, vdev->kbuf_size); + len = vdev->kbuf_size; + } + + if (copy_from_user(vdev->kbuf, uaddr, len)) { + return -EFAULT; + } + uaddr += len; + + kaddr = kmap_atomic(sg_page(sg)); + memcpy(kaddr + sg->offset, vdev->kbuf, len); + kunmap_atomic(kaddr); + + to_read -= len; + if (to_read == 0) { + break; + } + } + } else { + if (copy_from_user(scsi_sglist(cmd), buf, res->data_len)) { + return -EFAULT; + } + + to_read -= res->data_len; + } + + scsi_set_resid(cmd, to_read); + + ret += res->data_len - to_read; + } + + return ret; +} + +static struct vhba_command *next_command (struct vhba_device *vdev) +{ + struct vhba_command *vcmd; + + list_for_each_entry(vcmd, &vdev->cmd_list, entry) { + if (vcmd->status == VHBA_REQ_PENDING) { + break; + } + } + + if (&vcmd->entry == &vdev->cmd_list) { + vcmd = NULL; + } + + return vcmd; +} + +static struct vhba_command *match_command (struct vhba_device *vdev, __u32 metatag) +{ + struct vhba_command *vcmd; + + list_for_each_entry(vcmd, &vdev->cmd_list, entry) { + if (vcmd->metatag == metatag) { + break; + } + } + + if (&vcmd->entry == &vdev->cmd_list) { + vcmd = NULL; + } + + return vcmd; +} + +static struct vhba_command *wait_command (struct vhba_device *vdev, unsigned long flags) +{ + struct vhba_command *vcmd; + DEFINE_WAIT(wait); + + while (!(vcmd = next_command(vdev))) { + if (signal_pending(current)) { + break; + } + + prepare_to_wait(&vdev->cmd_wq, &wait, TASK_INTERRUPTIBLE); + + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + + schedule(); + + spin_lock_irqsave(&vdev->cmd_lock, flags); + } + + finish_wait(&vdev->cmd_wq, &wait); + if (vcmd) { + vcmd->status = VHBA_REQ_READING; + } + + return vcmd; +} + +static ssize_t vhba_ctl_read (struct file *file, char __user *buf, size_t buf_len, loff_t *offset) +{ + struct vhba_device *vdev; + struct vhba_command *vcmd; + ssize_t ret; + unsigned long flags; + + vdev = file->private_data; + + /* Get next command */ + if (file->f_flags & O_NONBLOCK) { + /* Non-blocking variant */ + spin_lock_irqsave(&vdev->cmd_lock, flags); + vcmd = next_command(vdev); + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + + if (!vcmd) { + return -EWOULDBLOCK; + } + } else { + /* Blocking variant */ + spin_lock_irqsave(&vdev->cmd_lock, flags); + vcmd = wait_command(vdev, flags); + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + + if (!vcmd) { + return -ERESTARTSYS; + } + } + + ret = do_request(vdev, vcmd->metatag, vcmd->cmd, buf, buf_len); + + spin_lock_irqsave(&vdev->cmd_lock, flags); + if (ret >= 0) { + vcmd->status = VHBA_REQ_SENT; + *offset += ret; + } else { + vcmd->status = VHBA_REQ_PENDING; + } + + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + + return ret; +} + +static ssize_t vhba_ctl_write (struct file *file, const char __user *buf, size_t buf_len, loff_t *offset) +{ + struct vhba_device *vdev; + struct vhba_command *vcmd; + struct vhba_response res; + ssize_t ret; + unsigned long flags; + + if (buf_len < sizeof(res)) { + return -EIO; + } + + if (copy_from_user(&res, buf, sizeof(res))) { + return -EFAULT; + } + + vdev = file->private_data; + + spin_lock_irqsave(&vdev->cmd_lock, flags); + vcmd = match_command(vdev, res.metatag); + if (!vcmd || vcmd->status != VHBA_REQ_SENT) { + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + pr_debug("ctl dev #%u not expecting response\n", vdev->num); + return -EIO; + } + vcmd->status = VHBA_REQ_WRITING; + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + + ret = do_response(vdev, vcmd->metatag, vcmd->cmd, buf + sizeof(res), buf_len - sizeof(res), &res); + + spin_lock_irqsave(&vdev->cmd_lock, flags); + if (ret >= 0) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0) + scsi_done(vcmd->cmd); +#else + vcmd->cmd->scsi_done(vcmd->cmd); +#endif + ret += sizeof(res); + + /* don't compete with vhba_device_dequeue */ + if (!list_empty(&vcmd->entry)) { + list_del_init(&vcmd->entry); + vhba_free_command(vcmd); + } + } else { + vcmd->status = VHBA_REQ_SENT; + } + + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + + return ret; +} + +static long vhba_ctl_ioctl (struct file *file, unsigned int cmd, unsigned long arg) +{ + struct vhba_device *vdev = file->private_data; + struct vhba_host *vhost = platform_get_drvdata(&vhba_platform_device); + + switch (cmd) { + case 0xBEEF001: { + unsigned int ident[4]; /* host, channel, id, lun */ + + ident[0] = vhost->shost->host_no; + devnum_to_bus_and_id(vdev->num, &ident[1], &ident[2]); + ident[3] = 0; /* lun */ + + if (copy_to_user((void *) arg, ident, sizeof(ident))) { + return -EFAULT; + } + + return 0; + } + case 0xBEEF002: { + unsigned int devnum = vdev->num; + + if (copy_to_user((void *) arg, &devnum, sizeof(devnum))) { + return -EFAULT; + } + + return 0; + } + } + + return -ENOTTY; +} + +#ifdef CONFIG_COMPAT +static long vhba_ctl_compat_ioctl (struct file *file, unsigned int cmd, unsigned long arg) +{ + unsigned long compat_arg = (unsigned long)compat_ptr(arg); + return vhba_ctl_ioctl(file, cmd, compat_arg); +} +#endif + +static unsigned int vhba_ctl_poll (struct file *file, poll_table *wait) +{ + struct vhba_device *vdev = file->private_data; + unsigned int mask = 0; + unsigned long flags; + + poll_wait(file, &vdev->cmd_wq, wait); + + spin_lock_irqsave(&vdev->cmd_lock, flags); + if (next_command(vdev)) { + mask |= POLLIN | POLLRDNORM; + } + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + + return mask; +} + +static int vhba_ctl_open (struct inode *inode, struct file *file) +{ + struct vhba_device *vdev; + int retval; + + pr_debug("ctl dev open\n"); + + /* check if vhba is probed */ + if (!platform_get_drvdata(&vhba_platform_device)) { + return -ENODEV; + } + + vdev = vhba_device_alloc(); + if (!vdev) { + return -ENOMEM; + } + + vdev->kbuf_size = VHBA_KBUF_SIZE; + vdev->kbuf = kzalloc(vdev->kbuf_size, GFP_KERNEL); + if (!vdev->kbuf) { + return -ENOMEM; + } + + if (!(retval = vhba_add_device(vdev))) { + file->private_data = vdev; + } + + vhba_device_put(vdev); + + return retval; +} + +static int vhba_ctl_release (struct inode *inode, struct file *file) +{ + struct vhba_device *vdev; + struct vhba_command *vcmd; + unsigned long flags; + + vdev = file->private_data; + + pr_debug("ctl dev release\n"); + + vhba_device_get(vdev); + vhba_remove_device(vdev); + + spin_lock_irqsave(&vdev->cmd_lock, flags); + list_for_each_entry(vcmd, &vdev->cmd_list, entry) { + WARN_ON(vcmd->status == VHBA_REQ_READING || vcmd->status == VHBA_REQ_WRITING); + + scmd_dbg(vcmd->cmd, "device released with command %lu (%p)\n", vcmd->metatag, vcmd->cmd); + vcmd->cmd->result = DID_NO_CONNECT << 16; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0) + scsi_done(vcmd->cmd); +#else + vcmd->cmd->scsi_done(vcmd->cmd); +#endif + vhba_free_command(vcmd); + } + INIT_LIST_HEAD(&vdev->cmd_list); + spin_unlock_irqrestore(&vdev->cmd_lock, flags); + + kfree(vdev->kbuf); + vdev->kbuf = NULL; + + vhba_device_put(vdev); + + return 0; +} + +static struct file_operations vhba_ctl_fops = { + .owner = THIS_MODULE, + .open = vhba_ctl_open, + .release = vhba_ctl_release, + .read = vhba_ctl_read, + .write = vhba_ctl_write, + .poll = vhba_ctl_poll, + .unlocked_ioctl = vhba_ctl_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = vhba_ctl_compat_ioctl, +#endif +}; + +static struct miscdevice vhba_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "vhba_ctl", + .fops = &vhba_ctl_fops, +}; + +static int vhba_probe (struct platform_device *pdev) +{ + struct Scsi_Host *shost; + struct vhba_host *vhost; + int i; + + vhba_can_queue = clamp(vhba_can_queue, 1, 256); + + shost = scsi_host_alloc(&vhba_template, sizeof(struct vhba_host)); + if (!shost) { + return -ENOMEM; + } + + shost->max_channel = VHBA_MAX_BUS-1; + shost->max_id = VHBA_MAX_ID; + /* we don't support lun > 0 */ + shost->max_lun = 1; + shost->max_cmd_len = MAX_COMMAND_SIZE; + shost->can_queue = vhba_can_queue; + shost->cmd_per_lun = vhba_can_queue; + + vhost = (struct vhba_host *)shost->hostdata; + memset(vhost, 0, sizeof(struct vhba_host)); + + vhost->shost = shost; + vhost->num_devices = 0; + spin_lock_init(&vhost->dev_lock); + spin_lock_init(&vhost->cmd_lock); + INIT_WORK(&vhost->scan_devices, vhba_scan_devices); + vhost->cmd_next = 0; + vhost->commands = kzalloc(vhba_can_queue * sizeof(struct vhba_command), GFP_KERNEL); + if (!vhost->commands) { + return -ENOMEM; + } + + for (i = 0; i < vhba_can_queue; i++) { + vhost->commands[i].status = VHBA_REQ_FREE; + } + + platform_set_drvdata(pdev, vhost); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) + i = scsi_init_shared_tag_map(shost, vhba_can_queue); + if (i) return i; +#endif + + if (scsi_add_host(shost, &pdev->dev)) { + scsi_host_put(shost); + return -ENOMEM; + } + + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) +static int vhba_remove (struct platform_device *pdev) +#else +static void vhba_remove (struct platform_device *pdev) +#endif +{ + struct vhba_host *vhost; + struct Scsi_Host *shost; + + vhost = platform_get_drvdata(pdev); + shost = vhost->shost; + + scsi_remove_host(shost); + scsi_host_put(shost); + + kfree(vhost->commands); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) + return 0; +#endif +} + +static void vhba_release (struct device * dev) +{ + return; +} + +static struct platform_device vhba_platform_device = { + .name = "vhba", + .id = -1, + .dev = { + .release = vhba_release, + }, +}; + +static struct platform_driver vhba_platform_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "vhba", + }, + .probe = vhba_probe, + .remove = vhba_remove, +}; + +static int __init vhba_init (void) +{ + int ret; + + ret = platform_device_register(&vhba_platform_device); + if (ret < 0) { + return ret; + } + + ret = platform_driver_register(&vhba_platform_driver); + if (ret < 0) { + platform_device_unregister(&vhba_platform_device); + return ret; + } + + ret = misc_register(&vhba_miscdev); + if (ret < 0) { + platform_driver_unregister(&vhba_platform_driver); + platform_device_unregister(&vhba_platform_device); + return ret; + } + + return 0; +} + +static void __exit vhba_exit(void) +{ + misc_deregister(&vhba_miscdev); + platform_driver_unregister(&vhba_platform_driver); + platform_device_unregister(&vhba_platform_device); +} + +module_init(vhba_init); +module_exit(vhba_exit); + # ---------------------------------------- # Module: evdi # Version: 5d708d117baa # ---------------------------------------- diff --git a/drivers/custom/evdi/module/Kconfig b/drivers/custom/evdi/module/Kconfig new file mode 100644 index 000000000000..80d876599640 --- /dev/null +++ b/drivers/custom/evdi/module/Kconfig @@ -0,0 +1,33 @@ +# +# Copyright (c) 2015 - 2019 DisplayLink (UK) Ltd. +# +# This file is subject to the terms and conditions of the GNU General Public +# License v2. See the file COPYING in the main directory of this archive for +# more details. +# + +config DRM_EVDI + tristate "Extensible Virtual Display Interface" + depends on DRM + depends on USB_SUPPORT + depends on USB_ARCH_HAS_HCD + depends on MODULES + select USB + select DRM_KMS_HELPER + help + This is a KMS interface driver allowing user-space programs to + register a virtual display (that imitates physical monitor) and + retrieve contents (as a frame buffer) that system renders on it. + Say M/Y to add support for these devices via DRM/KMS interfaces. + +config DRM_EVDI_KUNIT_TEST + tristate "KUnit tests for evdi" if !KUNIT_ALL_TESTS + depends on DRM_EVDI && KUNIT + default KUNIT_ALL_TESTS + help + KUnit tests for evdi DRM module. + This option is useful for kernel developers. + Please remember to provide tests for code you add. + + If in doubt, say "N". + diff --git a/drivers/custom/evdi/module/Makefile b/drivers/custom/evdi/module/Makefile new file mode 100644 index 000000000000..a6bdf8eb6b74 --- /dev/null +++ b/drivers/custom/evdi/module/Makefile @@ -0,0 +1,105 @@ +# +# Copyright (c) 2015 - 2020 DisplayLink (UK) Ltd. +# +# This file is subject to the terms and conditions of the GNU General Public +# License v2. See the file COPYING in the main directory of this archive for +# more details. +# + +include /etc/os-release + +ifneq (,$(findstring rhel,$(ID_LIKE))) +ELFLAG := -DEL$(shell echo $(VERSION_ID) | cut -d. -f1) +endif + +Raspbian := $(shell grep -Eic 'raspb(erry|ian)' /proc/cpuinfo /etc/os-release 2>/dev/null ) +ifeq (,$(findstring 0, $(Raspbian))) +RPIFLAG := -DRPI +endif + +ifneq ($(DKMS_BUILD),) + +# DKMS + +KERN_DIR := /lib/modules/$(KERNELRELEASE)/build + +ccflags-y := -Iinclude/uapi/drm -Iinclude/drm $(ELFLAG) $(RPIFLAG) +evdi-y := evdi_platform_drv.o evdi_platform_dev.o evdi_sysfs.o evdi_modeset.o evdi_connector.o evdi_encoder.o evdi_drm_drv.o evdi_fb.o evdi_gem.o evdi_painter.o evdi_params.o evdi_cursor.o evdi_debug.o evdi_i2c.o +evdi-$(CONFIG_COMPAT) += evdi_ioc32.o +obj-m := evdi.o + +KBUILD_VERBOSE ?= 1 + +all: + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) M=$(CURDIR) SUBDIRS=$(CURDIR) SRCROOT=$(CURDIR) CONFIG_MODULE_SIG= -C $(KERN_DIR) modules + +clean: + @echo $(KERN_DIR) + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) M=$(CURDIR) SUBDIRS=$(CURDIR) SRCROOT=$(CURDIR) -C $(KERN_DIR) clean + +else + +# Not DKMS + +ifneq ($(KERNELRELEASE),) + +# inside kbuild +# Note: this can be removed once it is in kernel tree and Kconfig is properly used +ccflags-y := -isystem include/uapi/drm $(CFLAGS) $(ELFLAG) $(RPIFLAG) +evdi-y := evdi_platform_drv.o evdi_platform_dev.o evdi_sysfs.o evdi_modeset.o evdi_connector.o evdi_encoder.o evdi_drm_drv.o evdi_fb.o evdi_gem.o evdi_painter.o evdi_params.o evdi_cursor.o evdi_debug.o evdi_i2c.o +evdi-$(CONFIG_COMPAT) += evdi_ioc32.o +CONFIG_DRM_EVDI ?= m +obj-$(CONFIG_DRM_EVDI) := evdi.o +obj-y += tests/ + +else + +# kbuild against specified or current kernel +CP ?= cp +DKMS ?= dkms +RM ?= rm + +MODVER=1.14.11 + +ifeq ($(KVER),) + KVER := $(shell uname -r) +endif + +ifneq ($(RUN_DEPMOD),) + DEPMOD := /sbin/depmod -a +else + DEPMOD := true +endif + +ifeq ($(KDIR),) + KDIR := /lib/modules/$(KVER)/build +endif + +MOD_KERNEL_PATH := /kernel/drivers/gpu/drm/evdi + +default: module + +module: + $(MAKE) -C $(KDIR) M=$$PWD + +clean: + $(RM) -rf *.o *.a *.ko .tmp* .*.*.cmd Module.symvers evdi.mod.c modules.order + +install: + $(MAKE) -C $(KDIR) M=$$PWD INSTALL_MOD_PATH=$(DESTDIR) INSTALL_MOD_DIR=$(MOD_KERNEL_PATH) modules_install + $(DEPMOD) + +uninstall: + $(RM) -rf $(DESTDIR)/lib/modules/$(KVER)/$(MOD_KERNEL_PATH) + $(DEPMOD) + +install_dkms: + $(DKMS) install . + +uninstall_dkms: + $(DKMS) remove evdi/$(MODVER) --all + $(RM) -rf /usr/src/evdi-$(MODVER) + +endif # ifneq ($(KERNELRELEASE),) + +endif # ifneq ($(DKMS_BUILD),) diff --git a/drivers/custom/evdi/module/evdi_connector.c b/drivers/custom/evdi/module/evdi_connector.c new file mode 100644 index 000000000000..77e524bc7cb8 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_connector.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + * Copyright (c) 2015 - 2020 DisplayLink (UK) Ltd. + * + * Based on parts on udlfb.c: + * Copyright (C) 2009 its respective authors + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include +#include +#include +#include +#include +#include "evdi_drm_drv.h" + +#if KERNEL_VERSION(5, 1, 0) <= LINUX_VERSION_CODE || defined(EL8) +#include +#endif + +/* + * dummy connector to just get EDID, + * all EVDI appear to have a DVI-D + */ + +static int evdi_get_modes(struct drm_connector *connector) +{ + struct evdi_device *evdi = connector->dev->dev_private; + struct edid *edid = NULL; + int ret = 0; + + edid = (struct edid *)evdi_painter_get_edid_copy(evdi); + + if (!edid) { +#if KERNEL_VERSION(4, 19, 0) <= LINUX_VERSION_CODE || defined(EL8) + drm_connector_update_edid_property(connector, NULL); +#else + drm_mode_connector_update_edid_property(connector, NULL); +#endif + return 0; + } + +#if KERNEL_VERSION(4, 19, 0) <= LINUX_VERSION_CODE || defined(EL8) + ret = drm_connector_update_edid_property(connector, edid); +#else + ret = drm_mode_connector_update_edid_property(connector, edid); +#endif + + if (ret) { + EVDI_ERROR("Failed to set edid property! error: %d\n", ret); + goto err; + } + + ret = drm_add_edid_modes(connector, edid); + EVDI_INFO("(card%d) Edid property set\n", evdi->dev_index); +err: + kfree(edid); + return ret; +} + +static bool is_lowest_frequency_mode_of_given_resolution( + struct drm_connector *connector, const struct drm_display_mode *mode) +{ + struct drm_display_mode *modeptr; + + list_for_each_entry(modeptr, &(connector->modes), head) { + if (modeptr->hdisplay == mode->hdisplay && + modeptr->vdisplay == mode->vdisplay && + drm_mode_vrefresh(modeptr) < drm_mode_vrefresh(mode)) { + return false; + } + } + return true; +} + +static enum drm_mode_status evdi_mode_valid(struct drm_connector *connector, +#if KERNEL_VERSION(6, 15, 0) <= LINUX_VERSION_CODE || defined(EL9) || defined(EL10) + const struct drm_display_mode *mode) +#else + struct drm_display_mode *mode) +#endif +{ + struct evdi_device *evdi = connector->dev->dev_private; + uint32_t area_limit = mode->hdisplay * mode->vdisplay; + uint32_t mode_limit = area_limit * drm_mode_vrefresh(mode); + + if (evdi->pixel_per_second_limit == 0) + return MODE_OK; + + if (area_limit > evdi->pixel_area_limit) { + EVDI_WARN( + "(card%d) Mode %dx%d@%d rejected. Reason: mode area too big\n", + evdi->dev_index, + mode->hdisplay, + mode->vdisplay, + drm_mode_vrefresh(mode)); + return MODE_BAD; + } + + if (mode_limit <= evdi->pixel_per_second_limit) + return MODE_OK; + + if (is_lowest_frequency_mode_of_given_resolution(connector, mode)) { + EVDI_WARN( + "(card%d) Mode exceeds maximal frame rate for the device. Mode %dx%d@%d may have a limited output frame rate", + evdi->dev_index, + mode->hdisplay, + mode->vdisplay, + drm_mode_vrefresh(mode)); + return MODE_OK; + } + + EVDI_WARN( + "(card%d) Mode %dx%d@%d rejected. Reason: mode pixel clock too high\n", + evdi->dev_index, + mode->hdisplay, + mode->vdisplay, + drm_mode_vrefresh(mode)); + + return MODE_BAD; +} + +static enum drm_connector_status +evdi_detect(struct drm_connector *connector, __always_unused bool force) +{ + struct evdi_device *evdi = connector->dev->dev_private; + + EVDI_CHECKPT(); + if (evdi_painter_is_connected(evdi->painter)) { + EVDI_INFO("(card%d) Connector state: connected\n", + evdi->dev_index); + return connector_status_connected; + } + EVDI_VERBOSE("(card%d) Connector state: disconnected\n", + evdi->dev_index); + return connector_status_disconnected; +} + +static void evdi_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); + kfree(connector); +} + +static struct drm_encoder *evdi_best_encoder(struct drm_connector *connector) +{ +#if KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE || defined(EL8) + struct drm_encoder *encoder; + + drm_connector_for_each_possible_encoder(connector, encoder) { + return encoder; + } + + return NULL; +#else + return drm_encoder_find(connector->dev, + NULL, + connector->encoder_ids[0]); +#endif +} + +static struct drm_connector_helper_funcs evdi_connector_helper_funcs = { + .get_modes = evdi_get_modes, + .mode_valid = evdi_mode_valid, + .best_encoder = evdi_best_encoder, +}; + +static const struct drm_connector_funcs evdi_connector_funcs = { + .detect = evdi_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = evdi_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state +}; + +int evdi_connector_init(struct drm_device *dev, struct drm_encoder *encoder) +{ + struct drm_connector *connector; + struct evdi_device *evdi = dev->dev_private; + + connector = kzalloc(sizeof(struct drm_connector), GFP_KERNEL); + if (!connector) + return -ENOMEM; + + /* TODO: Initialize connector with actual connector type */ + drm_connector_init(dev, connector, &evdi_connector_funcs, + DRM_MODE_CONNECTOR_DVII); + drm_connector_helper_add(connector, &evdi_connector_helper_funcs); + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_register(connector); + + evdi->conn = connector; + +#if KERNEL_VERSION(4, 19, 0) <= LINUX_VERSION_CODE || defined(EL8) + drm_connector_attach_encoder(connector, encoder); +#else + drm_mode_connector_attach_encoder(connector, encoder); +#endif + return 0; +} diff --git a/drivers/custom/evdi/module/evdi_cursor.c b/drivers/custom/evdi/module/evdi_cursor.c new file mode 100644 index 000000000000..3450f2051554 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_cursor.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * evdi_cursor.c + * + * Copyright (c) 2016 The Chromium OS Authors + * Copyright (c) 2016 - 2020 DisplayLink (UK) Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#if KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else +#include +#endif +#include + +#include "evdi_cursor.h" +#include "evdi_drm_drv.h" + +/* + * EVDI drm cursor private structure. + */ +struct evdi_cursor { + bool enabled; + int32_t x; + int32_t y; + uint32_t width; + uint32_t height; + int32_t hot_x; + int32_t hot_y; + uint32_t pixel_format; + uint32_t stride; + struct evdi_gem_object *obj; + struct mutex lock; +}; + +static void evdi_cursor_set_gem(struct evdi_cursor *cursor, + struct evdi_gem_object *obj) +{ + if (obj) + drm_gem_object_get(&obj->base); + if (cursor->obj) +#if KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE || defined(EL8) + drm_gem_object_put(&cursor->obj->base); +#else + drm_gem_object_put_unlocked(&cursor->obj->base); +#endif + + cursor->obj = obj; +} + +struct evdi_gem_object *evdi_cursor_gem(struct evdi_cursor *cursor) +{ + return cursor->obj; +} + +int evdi_cursor_init(struct evdi_cursor **cursor) +{ + if (WARN_ON(*cursor)) + return -EINVAL; + + *cursor = kzalloc(sizeof(struct evdi_cursor), GFP_KERNEL); + if (*cursor) { + mutex_init(&(*cursor)->lock); + return 0; + } else { + return -ENOMEM; + } +} + +void evdi_cursor_lock(struct evdi_cursor *cursor) +{ + mutex_lock(&cursor->lock); +} + +void evdi_cursor_unlock(struct evdi_cursor *cursor) +{ + mutex_unlock(&cursor->lock); +} + +void evdi_cursor_free(struct evdi_cursor *cursor) +{ + if (WARN_ON(!cursor)) + return; + evdi_cursor_set_gem(cursor, NULL); + kfree(cursor); +} + +bool evdi_cursor_enabled(struct evdi_cursor *cursor) +{ + return cursor->enabled; +} + +void evdi_cursor_enable(struct evdi_cursor *cursor, bool enable) +{ + evdi_cursor_lock(cursor); + cursor->enabled = enable; + if (!enable) + evdi_cursor_set_gem(cursor, NULL); + evdi_cursor_unlock(cursor); +} + +void evdi_cursor_set(struct evdi_cursor *cursor, + struct evdi_gem_object *obj, + uint32_t width, uint32_t height, + int32_t hot_x, int32_t hot_y, + uint32_t pixel_format, uint32_t stride) +{ + int err = 0; + + evdi_cursor_lock(cursor); + if (obj && !obj->vmapping) + err = evdi_gem_vmap(obj); + + if (err != 0) { + EVDI_ERROR("Failed to map cursor.\n"); + obj = NULL; + } + + cursor->enabled = obj != NULL; + cursor->width = width; + cursor->height = height; + cursor->hot_x = hot_x; + cursor->hot_y = hot_y; + cursor->pixel_format = pixel_format; + cursor->stride = stride; + evdi_cursor_set_gem(cursor, obj); + + evdi_cursor_unlock(cursor); +} + +void evdi_cursor_move(struct evdi_cursor *cursor, int32_t x, int32_t y) +{ + evdi_cursor_lock(cursor); + cursor->x = x; + cursor->y = y; + evdi_cursor_unlock(cursor); +} + +static inline uint32_t blend_component(uint32_t pixel, + uint32_t blend, + uint32_t alpha) +{ + uint32_t pre_blend = (pixel * (255 - alpha) + blend * alpha); + + return (pre_blend + ((pre_blend + 1) << 8)) >> 16; +} + +static inline uint32_t blend_alpha(const uint32_t pixel_val32, + uint32_t blend_val32) +{ + uint32_t alpha = (blend_val32 >> 24); + + return blend_component(pixel_val32 & 0xff, + blend_val32 & 0xff, alpha) | + blend_component((pixel_val32 & 0xff00) >> 8, + (blend_val32 & 0xff00) >> 8, alpha) << 8 | + blend_component((pixel_val32 & 0xff0000) >> 16, + (blend_val32 & 0xff0000) >> 16, alpha) << 16; +} + +static int evdi_cursor_compose_pixel(char __user *buffer, + int const cursor_value, + int const fb_value, + int cmd_offset) +{ + int const composed_value = blend_alpha(fb_value, cursor_value); + + return copy_to_user(buffer + cmd_offset, &composed_value, 4); +} + +int evdi_cursor_compose_and_copy(struct evdi_cursor *cursor, + struct evdi_framebuffer *efb, + char __user *buffer, + int buf_byte_stride) +{ + int x, y; + struct drm_framebuffer *fb = &efb->base; + const int h_cursor_w = cursor->width >> 1; + const int h_cursor_h = cursor->height >> 1; + uint32_t *cursor_buffer = NULL; + uint32_t bytespp = 0; + + if (!cursor->enabled) + return 0; + + if (!cursor->obj) + return -EINVAL; + + if (!cursor->obj->vmapping) + return -EINVAL; + + bytespp = evdi_fb_get_bpp(cursor->pixel_format); + bytespp = DIV_ROUND_UP(bytespp, 8); + if (bytespp != 4) { + EVDI_ERROR("Unsupported cursor format bpp=%u\n", bytespp); + return -EINVAL; + } + + if (cursor->width * cursor->height * bytespp > + cursor->obj->base.size){ + EVDI_ERROR("Wrong cursor size\n"); + return -EINVAL; + } + + cursor_buffer = (uint32_t *)cursor->obj->vmapping; + + for (y = -h_cursor_h; y < h_cursor_h; ++y) { + for (x = -h_cursor_w; x < h_cursor_w; ++x) { + uint32_t curs_val; + int *fbsrc; + int fb_value; + int cmd_offset; + int cursor_pix; + int const mouse_pix_x = cursor->x + x + h_cursor_w; + int const mouse_pix_y = cursor->y + y + h_cursor_h; + bool const is_pix_sane = + mouse_pix_x >= 0 && + mouse_pix_y >= 0 && + mouse_pix_x < (int)fb->width && + mouse_pix_y < (int)fb->height; + + if (!is_pix_sane) + continue; + + cursor_pix = h_cursor_w+x + + (h_cursor_h+y)*cursor->width; + curs_val = le32_to_cpu(cursor_buffer[cursor_pix]); + fbsrc = (int *)(efb->obj->vmapping + fb->offsets[0]); + fb_value = *(fbsrc + ((fb->pitches[0]>>2) * + mouse_pix_y + mouse_pix_x)); + cmd_offset = (buf_byte_stride * mouse_pix_y) + + (mouse_pix_x * bytespp); + if (evdi_cursor_compose_pixel(buffer, + curs_val, + fb_value, + cmd_offset)) { + EVDI_ERROR("Failed to compose cursor pixel\n"); + return -EFAULT; + } + } + } + + return 0; +} + +void evdi_cursor_position(struct evdi_cursor *cursor, int32_t *x, int32_t *y) +{ + *x = cursor->x; + *y = cursor->y; +} + +void evdi_cursor_hotpoint(struct evdi_cursor *cursor, + int32_t *hot_x, int32_t *hot_y) +{ + *hot_x = cursor->hot_x; + *hot_y = cursor->hot_y; +} + +void evdi_cursor_size(struct evdi_cursor *cursor, + uint32_t *width, uint32_t *height) +{ + *width = cursor->width; + *height = cursor->height; +} + +void evdi_cursor_format(struct evdi_cursor *cursor, uint32_t *format) +{ + *format = cursor->pixel_format; +} + +void evdi_cursor_stride(struct evdi_cursor *cursor, uint32_t *stride) +{ + *stride = cursor->stride; +} + diff --git a/drivers/custom/evdi/module/evdi_cursor.h b/drivers/custom/evdi/module/evdi_cursor.h new file mode 100644 index 000000000000..13d8a8ab3687 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_cursor.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * evdi_cursor.h + * + * Copyright (c) 2016 The Chromium OS Authors + * Copyright (c) 2016 - 2020 DisplayLink (UK) Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _EVDI_CURSOR_H_ +#define _EVDI_CURSOR_H_ + +#include +#include +#if KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else +#include +#endif +#include + +struct evdi_cursor; +struct evdi_framebuffer; +struct evdi_gem_object; + +int evdi_cursor_init(struct evdi_cursor **cursor); +void evdi_cursor_free(struct evdi_cursor *cursor); +void evdi_cursor_lock(struct evdi_cursor *cursor); +void evdi_cursor_unlock(struct evdi_cursor *cursor); +bool evdi_cursor_enabled(struct evdi_cursor *cursor); +void evdi_cursor_enable(struct evdi_cursor *cursor, bool enabled); +void evdi_cursor_set(struct evdi_cursor *cursor, + struct evdi_gem_object *obj, + uint32_t width, uint32_t height, + int32_t hot_x, int32_t hot_y, + uint32_t pixel_format, uint32_t stride); + +void evdi_cursor_move(struct evdi_cursor *cursor, int32_t x, int32_t y); +void evdi_cursor_position(struct evdi_cursor *cursor, int32_t *x, int32_t *y); +void evdi_cursor_hotpoint(struct evdi_cursor *cursor, + int32_t *hot_x, int32_t *hot_y); +void evdi_cursor_size(struct evdi_cursor *cursor, + uint32_t *width, uint32_t *height); +void evdi_cursor_format(struct evdi_cursor *cursor, uint32_t *format); +void evdi_cursor_stride(struct evdi_cursor *cursor, uint32_t *stride); +struct evdi_gem_object *evdi_cursor_gem(struct evdi_cursor *cursor); + +int evdi_cursor_compose_and_copy(struct evdi_cursor *cursor, + struct evdi_framebuffer *efb, + char __user *buffer, + int buf_byte_stride); +#endif diff --git a/drivers/custom/evdi/module/evdi_debug.c b/drivers/custom/evdi/module/evdi_debug.c new file mode 100644 index 000000000000..0f941d35bebf --- /dev/null +++ b/drivers/custom/evdi/module/evdi_debug.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015 - 2019 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include +#include + +#include "evdi_debug.h" + +void evdi_log_process(char *buf, size_t size) +{ + int task_pid = (int)task_pid_nr(current); + char task_comm[TASK_COMM_LEN] = { 0 }; + + get_task_comm(task_comm, current); + + if (current->group_leader) { + char process_comm[TASK_COMM_LEN] = { 0 }; + + get_task_comm(process_comm, current->group_leader); + snprintf(buf, size, "Task %d (%s) of process %d (%s)", + task_pid, + task_comm, + (int)task_pid_nr(current->group_leader), + process_comm); + } else { + snprintf(buf, size, "Task %d (%s)", + task_pid, + task_comm); + } +} diff --git a/drivers/custom/evdi/module/evdi_debug.h b/drivers/custom/evdi/module/evdi_debug.h new file mode 100644 index 000000000000..14000811e03b --- /dev/null +++ b/drivers/custom/evdi/module/evdi_debug.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * Copyright (c) 2015 - 2019 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#ifndef EVDI_DEBUG_H +#define EVDI_DEBUG_H + +#include "evdi_params.h" + +#define EVDI_LOGLEVEL_FATAL 1 +#define EVDI_LOGLEVEL_ERROR 2 +#define EVDI_LOGLEVEL_WARN 3 +#define EVDI_LOGLEVEL_INFO 4 +#define EVDI_LOGLEVEL_DEBUG 5 +#define EVDI_LOGLEVEL_VERBOSE 6 + +#define EVDI_PRINTK(KERN_LEVEL, LEVEL, FORMAT_STR, ...) do { \ + if (evdi_loglevel >= LEVEL) {\ + printk(KERN_LEVEL "evdi: " FORMAT_STR, ##__VA_ARGS__); \ + } \ +} while (0) + +#define EVDI_FATAL(FORMAT_STR, ...) \ + EVDI_PRINTK(KERN_CRIT, EVDI_LOGLEVEL_FATAL,\ + "[F] %s:%d " FORMAT_STR, __func__, __LINE__, ##__VA_ARGS__) + +#define EVDI_ERROR(FORMAT_STR, ...) \ + EVDI_PRINTK(KERN_ERR, EVDI_LOGLEVEL_ERROR,\ + "[E] %s:%d " FORMAT_STR, __func__, __LINE__, ##__VA_ARGS__) + +#define EVDI_WARN(FORMAT_STR, ...) \ + EVDI_PRINTK(KERN_WARNING, EVDI_LOGLEVEL_WARN,\ + "[W] %s:%d " FORMAT_STR, __func__, __LINE__, ##__VA_ARGS__) + +#define EVDI_INFO(FORMAT_STR, ...) \ + EVDI_PRINTK(KERN_DEFAULT, EVDI_LOGLEVEL_INFO,\ + "[I] " FORMAT_STR, ##__VA_ARGS__) + +#define EVDI_DEBUG(FORMAT_STR, ...) \ + EVDI_PRINTK(KERN_DEFAULT, EVDI_LOGLEVEL_DEBUG,\ + "[D] %s:%d " FORMAT_STR, __func__, __LINE__, ##__VA_ARGS__) + +#define EVDI_VERBOSE(FORMAT_STR, ...) \ + EVDI_PRINTK(KERN_DEFAULT, EVDI_LOGLEVEL_VERBOSE,\ + "[V] %s:%d " FORMAT_STR, __func__, __LINE__, ##__VA_ARGS__) + +#define EVDI_CHECKPT() EVDI_VERBOSE("\n") +#define EVDI_ENTER() EVDI_VERBOSE("enter\n") +#define EVDI_EXIT() EVDI_VERBOSE("exit\n") + +void evdi_log_process(char *buf, size_t size); + +#endif /* EVDI_DEBUG_H */ diff --git a/drivers/custom/evdi/module/evdi_drm.h b/drivers/custom/evdi/module/evdi_drm.h new file mode 100644 index 000000000000..501269379627 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_drm.h @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note + * + * Copyright (c) 2015 - 2020 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#ifndef __UAPI_EVDI_DRM_H__ +#define __UAPI_EVDI_DRM_H__ + +#ifdef __KERNEL__ +#include +#include +#else +#include +#endif + +/* Output events sent from driver to evdi lib */ +#define DRM_EVDI_EVENT_UPDATE_READY 0x80000000 +#define DRM_EVDI_EVENT_DPMS 0x80000001 +#define DRM_EVDI_EVENT_MODE_CHANGED 0x80000002 +#define DRM_EVDI_EVENT_CRTC_STATE 0x80000003 +#define DRM_EVDI_EVENT_CURSOR_SET 0x80000004 +#define DRM_EVDI_EVENT_CURSOR_MOVE 0x80000005 +#define DRM_EVDI_EVENT_DDCCI_DATA 0x80000006 + +struct drm_evdi_event_update_ready { + struct drm_event base; +}; + +struct drm_evdi_event_dpms { + struct drm_event base; + int32_t mode; +}; + +struct drm_evdi_event_mode_changed { + struct drm_event base; + int32_t hdisplay; + int32_t vdisplay; + int32_t vrefresh; + int32_t bits_per_pixel; + uint32_t pixel_format; +}; + +struct drm_evdi_event_crtc_state { + struct drm_event base; + int32_t state; +}; + +struct drm_evdi_connect { + int32_t connected; + int32_t dev_index; + const unsigned char * __user edid; + uint32_t edid_length; + uint32_t pixel_area_limit; + uint32_t pixel_per_second_limit; +}; + +struct drm_evdi_request_update { + int32_t reserved; +}; + +enum drm_evdi_grabpix_mode { + EVDI_GRABPIX_MODE_RECTS = 0, + EVDI_GRABPIX_MODE_DIRTY = 1, +}; + +struct drm_evdi_grabpix { + enum drm_evdi_grabpix_mode mode; + int32_t buf_width; + int32_t buf_height; + int32_t buf_byte_stride; + unsigned char __user *buffer; + int32_t num_rects; + struct drm_clip_rect __user *rects; +}; + +struct drm_evdi_event_cursor_set { + struct drm_event base; + int32_t hot_x; + int32_t hot_y; + uint32_t width; + uint32_t height; + uint8_t enabled; + uint32_t buffer_handle; + uint32_t buffer_length; + uint32_t pixel_format; + uint32_t stride; +}; + +struct drm_evdi_event_cursor_move { + struct drm_event base; + int32_t x; + int32_t y; +}; + +struct drm_evdi_ddcci_response { + const unsigned char * __user buffer; + uint32_t buffer_length; + uint8_t result; +}; + +struct drm_evdi_enable_cursor_events { + struct drm_event base; + uint8_t enable; +}; + +#define DDCCI_BUFFER_SIZE 64 + +struct drm_evdi_event_ddcci_data { + struct drm_event base; + unsigned char buffer[DDCCI_BUFFER_SIZE]; + uint32_t buffer_length; + uint16_t flags; + uint16_t address; +}; + +/* Input ioctls from evdi lib to driver */ +#define DRM_EVDI_CONNECT 0x00 +#define DRM_EVDI_REQUEST_UPDATE 0x01 +#define DRM_EVDI_GRABPIX 0x02 +#define DRM_EVDI_DDCCI_RESPONSE 0x03 +#define DRM_EVDI_ENABLE_CURSOR_EVENTS 0x04 +/* LAST_IOCTL 0x5F -- 96 driver specific ioctls to use */ + +#define DRM_IOCTL_EVDI_CONNECT DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_EVDI_CONNECT, struct drm_evdi_connect) +#define DRM_IOCTL_EVDI_REQUEST_UPDATE DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_EVDI_REQUEST_UPDATE, struct drm_evdi_request_update) +#define DRM_IOCTL_EVDI_GRABPIX DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_EVDI_GRABPIX, struct drm_evdi_grabpix) +#define DRM_IOCTL_EVDI_DDCCI_RESPONSE DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_EVDI_DDCCI_RESPONSE, struct drm_evdi_ddcci_response) +#define DRM_IOCTL_EVDI_ENABLE_CURSOR_EVENTS DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_EVDI_ENABLE_CURSOR_EVENTS, struct drm_evdi_enable_cursor_events) + +#endif /* __EVDI_UAPI_DRM_H__ */ diff --git a/drivers/custom/evdi/module/evdi_drm_drv.c b/drivers/custom/evdi/module/evdi_drm_drv.c new file mode 100644 index 000000000000..1a424967f46a --- /dev/null +++ b/drivers/custom/evdi/module/evdi_drm_drv.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + * Copyright (c) 2015 - 2020 DisplayLink (UK) Ltd. + * + * Based on parts on udlfb.c: + * Copyright (C) 2009 its respective authors + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include +#if KERNEL_VERSION(5, 16, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) +#include +#include +#include +#include +#elif KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE +#else +#include +#endif +#if KERNEL_VERSION(5, 1, 0) <= LINUX_VERSION_CODE || defined(EL8) +#include +#endif +#if KERNEL_VERSION(5, 8, 0) <= LINUX_VERSION_CODE || defined(EL8) +#include +#endif +#include +#include "evdi_drm_drv.h" +#include "evdi_platform_drv.h" +#include "evdi_cursor.h" +#include "evdi_debug.h" +#include "evdi_drm.h" + +#if KERNEL_VERSION(6, 8, 0) <= LINUX_VERSION_CODE || defined(EL9) +#define EVDI_DRM_UNLOCKED 0 +#else +#define EVDI_DRM_UNLOCKED DRM_UNLOCKED +#endif + +static struct drm_driver driver; + +struct drm_ioctl_desc evdi_painter_ioctls[] = { + DRM_IOCTL_DEF_DRV(EVDI_CONNECT, evdi_painter_connect_ioctl, EVDI_DRM_UNLOCKED), + DRM_IOCTL_DEF_DRV(EVDI_REQUEST_UPDATE, evdi_painter_request_update_ioctl, EVDI_DRM_UNLOCKED), + DRM_IOCTL_DEF_DRV(EVDI_GRABPIX, evdi_painter_grabpix_ioctl, EVDI_DRM_UNLOCKED), + DRM_IOCTL_DEF_DRV(EVDI_DDCCI_RESPONSE, evdi_painter_ddcci_response_ioctl, EVDI_DRM_UNLOCKED), + DRM_IOCTL_DEF_DRV(EVDI_ENABLE_CURSOR_EVENTS, evdi_painter_enable_cursor_events_ioctl, EVDI_DRM_UNLOCKED), +}; + +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else +static const struct vm_operations_struct evdi_gem_vm_ops = { + .fault = evdi_gem_fault, + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; +#endif + +static const struct file_operations evdi_driver_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .mmap = evdi_drm_gem_mmap, + .poll = drm_poll, + .read = drm_read, + .unlocked_ioctl = drm_ioctl, + .release = drm_release, + +#ifdef CONFIG_COMPAT + .compat_ioctl = evdi_compat_ioctl, +#endif + + .llseek = noop_llseek, + +#if defined(FOP_UNSIGNED_OFFSET) + .fop_flags = FOP_UNSIGNED_OFFSET, +#endif +}; + +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else +static int evdi_enable_vblank(__always_unused struct drm_device *dev, + __always_unused unsigned int pipe) +{ + return 1; +} + +static void evdi_disable_vblank(__always_unused struct drm_device *dev, + __always_unused unsigned int pipe) +{ +} +#endif + +static struct drm_driver driver = { +#if KERNEL_VERSION(5, 4, 0) <= LINUX_VERSION_CODE || defined(EL8) + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, +#else + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME + | DRIVER_ATOMIC, +#endif + + .open = evdi_driver_open, + .postclose = evdi_driver_postclose, + +#if KERNEL_VERSION(6, 15, 0) <= LINUX_VERSION_CODE +#ifdef CONFIG_FB + .fbdev_probe = evdifb_create, +#endif +#endif + + /* gem hooks */ +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8) +#elif KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE + .gem_free_object_unlocked = evdi_gem_free_object, +#else + .gem_free_object = evdi_gem_free_object, +#endif + +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else + .gem_vm_ops = &evdi_gem_vm_ops, +#endif + + .dumb_create = evdi_dumb_create, + .dumb_map_offset = evdi_gem_mmap, +#if KERNEL_VERSION(5, 12, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else + .dumb_destroy = drm_gem_dumb_destroy, +#endif + + .ioctls = evdi_painter_ioctls, + .num_ioctls = ARRAY_SIZE(evdi_painter_ioctls), + + .fops = &evdi_driver_fops, + + .gem_prime_import = drm_gem_prime_import, +#if KERNEL_VERSION(6, 6, 0) <= LINUX_VERSION_CODE || defined(EL9) +#else + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, +#endif +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else + .preclose = evdi_driver_preclose, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_get_sg_table = evdi_prime_get_sg_table, + .enable_vblank = evdi_enable_vblank, + .disable_vblank = evdi_disable_vblank, +#endif + .gem_prime_import_sg_table = evdi_prime_import_sg_table, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, +#if KERNEL_VERSION(6, 14, 0) <= LINUX_VERSION_CODE || defined(EL9) || defined(EL10) +#else + .date = DRIVER_DATE, +#endif + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCH, +}; + +static void evdi_drm_device_release_cb(__always_unused struct drm_device *dev, + __always_unused void *ptr) +{ + struct evdi_device *evdi = dev->dev_private; + + evdi_cursor_free(evdi->cursor); + evdi_painter_cleanup(evdi->painter); + kfree(evdi); + dev->dev_private = NULL; + EVDI_INFO("Evdi drm_device removed.\n"); + + EVDI_TEST_HOOK(evdi_testhook_drm_device_destroyed()); +} + +static int evdi_drm_device_init(struct drm_device *dev) +{ + struct evdi_device *evdi; + int ret; + + EVDI_CHECKPT(); + evdi = kzalloc(sizeof(struct evdi_device), GFP_KERNEL); + if (!evdi) + return -ENOMEM; + + evdi->ddev = dev; + evdi->dev_index = dev->primary->index; + evdi->cursor_events_enabled = false; + dev->dev_private = evdi; + ret = evdi_painter_init(evdi); + if (ret) + goto err_free; + ret = evdi_cursor_init(&evdi->cursor); + if (ret) + goto err_free; + + evdi_modeset_init(dev); +#ifdef CONFIG_FB + ret = evdi_fbdev_init(dev); + if (ret) + goto err_init; +#endif /* CONFIG_FB */ + + ret = drm_vblank_init(dev, 1); + if (ret) + goto err_init; + drm_kms_helper_poll_init(dev); + +#if KERNEL_VERSION(5, 8, 0) <= LINUX_VERSION_CODE || defined(EL8) + ret = drmm_add_action_or_reset(dev, evdi_drm_device_release_cb, NULL); + if (ret) + goto err_init; +#endif + + return 0; + +err_init: +#ifdef CONFIG_FB + evdi_fbdev_cleanup(dev); +#endif /* CONFIG_FB */ +err_free: + EVDI_ERROR("Failed to setup drm device %d\n", ret); + evdi_cursor_free(evdi->cursor); + kfree(evdi->painter); + kfree(evdi); + dev->dev_private = NULL; + return ret; +} + +int evdi_driver_open(struct drm_device *dev, __always_unused struct drm_file *file) +{ + char buf[100]; + + evdi_log_process(buf, sizeof(buf)); + EVDI_INFO("(card%d) Opened by %s\n", dev->primary->index, buf); + return 0; +} + +static void evdi_driver_close(struct drm_device *drm_dev, struct drm_file *file) +{ + struct evdi_device *evdi = drm_dev->dev_private; + + EVDI_CHECKPT(); + if (evdi) + evdi_painter_close(evdi, file); +} + +void evdi_driver_preclose(struct drm_device *drm_dev, struct drm_file *file) +{ + evdi_driver_close(drm_dev, file); +} + +void evdi_driver_postclose(struct drm_device *dev, struct drm_file *file) +{ + char buf[100]; + + evdi_log_process(buf, sizeof(buf)); + evdi_driver_close(dev, file); + EVDI_INFO("(card%d) Closed by %s\n", dev->primary->index, buf); +} + +struct drm_device *evdi_drm_device_create(struct device *parent) +{ + struct drm_device *dev = NULL; + int ret; + + dev = drm_dev_alloc(&driver, parent); + if (IS_ERR(dev)) + return dev; + + ret = evdi_drm_device_init(dev); + if (ret) + goto err_free; + + ret = drm_dev_register(dev, 0); + if (ret) + goto err_free; + + return dev; + +err_free: + drm_dev_put(dev); + return ERR_PTR(ret); +} + +static void evdi_drm_device_deinit(struct drm_device *dev) +{ + drm_kms_helper_poll_fini(dev); +#ifdef CONFIG_FB + evdi_fbdev_unplug(dev); + evdi_fbdev_cleanup(dev); +#endif /* CONFIG_FB */ + evdi_modeset_cleanup(dev); + drm_atomic_helper_shutdown(dev); +} + +int evdi_drm_device_remove(struct drm_device *dev) +{ + drm_dev_unplug(dev); + evdi_drm_device_deinit(dev); +#if KERNEL_VERSION(5, 8, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else + evdi_drm_device_release_cb(dev, NULL); +#endif + drm_dev_put(dev); + return 0; +} + diff --git a/drivers/custom/evdi/module/evdi_drm_drv.h b/drivers/custom/evdi/module/evdi_drm_drv.h new file mode 100644 index 000000000000..748046c7f38c --- /dev/null +++ b/drivers/custom/evdi/module/evdi_drm_drv.h @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * Copyright (C) 2012 Red Hat + * Copyright (c) 2015 - 2020 DisplayLink (UK) Ltd. + * + * Based on parts on udlfb.c: + * Copyright (C) 2009 its respective authors + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#ifndef EVDI_DRV_H +#define EVDI_DRV_H + +#include +#include +#include +#include +#include +#if KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE || defined(EL8) +#include +#include +#include +#include +#else +#include +#endif +#if KERNEL_VERSION(5, 15, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include + +#include "evdi_debug.h" +#include "tests/evdi_test.h" + +struct evdi_fbdev; +struct evdi_painter; + +struct evdi_device { + struct drm_device *ddev; + struct drm_connector *conn; + struct evdi_cursor *cursor; + bool cursor_events_enabled; + + uint32_t pixel_area_limit; + uint32_t pixel_per_second_limit; + + struct evdi_fbdev *fbdev; + struct evdi_painter *painter; + struct i2c_adapter *i2c_adapter; + + int dev_index; +}; + +struct evdi_gem_object { + struct drm_gem_object base; + struct page **pages; + unsigned int pages_pin_count; + struct mutex pages_lock; + void *vmapping; +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8) + bool vmap_is_iomem; +#endif + struct sg_table *sg; + bool allow_sw_cursor_rect_updates; +}; + +#define to_evdi_bo(x) container_of(x, struct evdi_gem_object, base) + +struct evdi_framebuffer { + struct drm_framebuffer base; + struct evdi_gem_object *obj; + bool active; +}; + +#define to_evdi_fb(x) container_of(x, struct evdi_framebuffer, base) + +/* modeset */ +void evdi_modeset_init(struct drm_device *dev); +void evdi_modeset_cleanup(struct drm_device *dev); +int evdi_connector_init(struct drm_device *dev, struct drm_encoder *encoder); + +struct drm_encoder *evdi_encoder_init(struct drm_device *dev); + +int evdi_driver_open(struct drm_device *drm_dev, struct drm_file *file); +void evdi_driver_preclose(struct drm_device *dev, struct drm_file *file_priv); +void evdi_driver_postclose(struct drm_device *dev, struct drm_file *file_priv); + +#ifdef CONFIG_COMPAT +long evdi_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); +#endif + + +#ifdef CONFIG_FB +int evdi_fbdev_init(struct drm_device *dev); +void evdi_fbdev_cleanup(struct drm_device *dev); +void evdi_fbdev_unplug(struct drm_device *dev); +int evdifb_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes); +#endif /* CONFIG_FB */ +struct drm_framebuffer *evdi_fb_user_fb_create( + struct drm_device *dev, + struct drm_file *file, +#if KERNEL_VERSION(6, 17, 0) <= LINUX_VERSION_CODE + const struct drm_format_info *info, +#endif + const struct drm_mode_fb_cmd2 *mode_cmd); + +int evdi_dumb_create(struct drm_file *file_priv, + struct drm_device *dev, struct drm_mode_create_dumb *args); +int evdi_gem_mmap(struct drm_file *file_priv, + struct drm_device *dev, uint32_t handle, uint64_t *offset); + +void evdi_gem_free_object(struct drm_gem_object *gem_obj); +struct evdi_gem_object *evdi_gem_alloc_object(struct drm_device *dev, + size_t size); +uint32_t evdi_gem_object_handle_lookup(struct drm_file *filp, + struct drm_gem_object *obj); + +struct sg_table *evdi_prime_get_sg_table(struct drm_gem_object *obj); +struct drm_gem_object * +evdi_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sg); + +int evdi_gem_vmap(struct evdi_gem_object *obj); +void evdi_gem_vunmap(struct evdi_gem_object *obj); +int evdi_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma); + +#if KERNEL_VERSION(4, 17, 0) <= LINUX_VERSION_CODE +vm_fault_t evdi_gem_fault(struct vm_fault *vmf); +#else +int evdi_gem_fault(struct vm_fault *vmf); +#endif + +bool evdi_painter_is_connected(struct evdi_painter *painter); +void evdi_painter_close(struct evdi_device *evdi, struct drm_file *file); +u8 *evdi_painter_get_edid_copy(struct evdi_device *evdi); +int evdi_painter_get_num_dirts(struct evdi_painter *painter); +void evdi_painter_mark_dirty(struct evdi_device *evdi, + const struct drm_clip_rect *rect); +void evdi_painter_set_vblank(struct evdi_painter *painter, + struct drm_crtc *crtc, + struct drm_pending_vblank_event *vblank); +void evdi_painter_send_update_ready_if_needed(struct evdi_painter *painter); +void evdi_painter_dpms_notify(struct evdi_painter *painter, int mode); +void evdi_painter_mode_changed_notify(struct evdi_device *evdi, + struct drm_display_mode *mode); +unsigned int evdi_painter_poll(struct file *filp, + struct poll_table_struct *wait); + +int evdi_painter_status_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file); +int evdi_painter_connect_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file); +int evdi_painter_grabpix_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file); +int evdi_painter_request_update_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file); +int evdi_painter_ddcci_response_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file); +int evdi_painter_enable_cursor_events_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file); + +int evdi_painter_init(struct evdi_device *evdi); +void evdi_painter_cleanup(struct evdi_painter *painter); +void evdi_painter_set_scanout_buffer(struct evdi_painter *painter, + struct evdi_framebuffer *buffer); + +struct drm_clip_rect evdi_framebuffer_sanitize_rect( + const struct evdi_framebuffer *fb, + const struct drm_clip_rect *rect); + +struct drm_device *evdi_drm_device_create(struct device *parent); +int evdi_drm_device_remove(struct drm_device *dev); + +void evdi_painter_send_cursor_set(struct evdi_painter *painter, + struct evdi_cursor *cursor); +void evdi_painter_send_cursor_move(struct evdi_painter *painter, + struct evdi_cursor *cursor); +bool evdi_painter_needs_full_modeset(struct evdi_painter *painter); +void evdi_painter_force_full_modeset(struct evdi_painter *painter); +struct drm_clip_rect evdi_painter_framebuffer_size(struct evdi_painter *painter); +bool evdi_painter_i2c_data_notify(struct evdi_painter *painter, struct i2c_msg *msg); + +int evdi_fb_get_bpp(uint32_t format); +#endif diff --git a/drivers/custom/evdi/module/evdi_encoder.c b/drivers/custom/evdi/module/evdi_encoder.c new file mode 100644 index 000000000000..47fd19f29025 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_encoder.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + * Copyright (c) 2015 - 2020 DisplayLink (UK) Ltd. + * + * Based on parts on udlfb.c: + * Copyright (C) 2009 its respective authors + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include +#if KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else +#include +#endif +#include +#include +#include +#include +#include "evdi_drm_drv.h" + +/* dummy encoder */ +static void evdi_enc_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); +} + +static void evdi_encoder_enable(__always_unused struct drm_encoder *encoder) +{ +} + +static void evdi_encoder_disable(__always_unused struct drm_encoder *encoder) +{ +} + +static const struct drm_encoder_helper_funcs evdi_enc_helper_funcs = { + .enable = evdi_encoder_enable, + .disable = evdi_encoder_disable +}; + +static const struct drm_encoder_funcs evdi_enc_funcs = { + .destroy = evdi_enc_destroy, +}; + +struct drm_encoder *evdi_encoder_init(struct drm_device *dev) +{ + struct drm_encoder *encoder; + int ret = 0; + + encoder = kzalloc(sizeof(struct drm_encoder), GFP_KERNEL); + if (!encoder) + goto err; + + ret = drm_encoder_init(dev, encoder, &evdi_enc_funcs, + DRM_MODE_ENCODER_TMDS, "%s", dev_name(dev->dev)); + if (ret) { + EVDI_ERROR("Failed to initialize encoder: %d\n", ret); + goto err_encoder; + } + + drm_encoder_helper_add(encoder, &evdi_enc_helper_funcs); + + encoder->possible_crtcs = 1; + return encoder; + +err_encoder: + kfree(encoder); +err: + return NULL; +} diff --git a/drivers/custom/evdi/module/evdi_fb.c b/drivers/custom/evdi/module/evdi_fb.c new file mode 100644 index 000000000000..13c70fb6fe56 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_fb.c @@ -0,0 +1,663 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + * Copyright (c) 2015 - 2020 DisplayLink (UK) Ltd. + * + * Based on parts on udlfb.c: + * Copyright (C) 2009 its respective authors + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include +#ifdef CONFIG_FB +#include +#endif /* CONFIG_FB */ +#include +#include +#if KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else +#include +#endif +#include +#include +#include +#include +#if KERNEL_VERSION(5, 0, 0) <= LINUX_VERSION_CODE || defined(EL8) +#include +#endif +#include "evdi_drm_drv.h" + + +struct evdi_fbdev { + struct drm_fb_helper helper; + struct evdi_framebuffer efb; + struct list_head fbdev_list; + const struct fb_ops *fb_ops; + int fb_count; +}; + +struct drm_clip_rect evdi_framebuffer_sanitize_rect( + const struct evdi_framebuffer *fb, + const struct drm_clip_rect *dirty_rect) +{ + struct drm_clip_rect rect = *dirty_rect; + + if (rect.x1 > rect.x2) { + unsigned short tmp = rect.x2; + + EVDI_WARN("Wrong clip rect: x1 > x2\n"); + rect.x2 = rect.x1; + rect.x1 = tmp; + } + + if (rect.y1 > rect.y2) { + unsigned short tmp = rect.y2; + + EVDI_WARN("Wrong clip rect: y1 > y2\n"); + rect.y2 = rect.y1; + rect.y1 = tmp; + } + + + if (rect.x1 > fb->base.width) { + EVDI_DEBUG("Wrong clip rect: x1 > fb.width\n"); + rect.x1 = fb->base.width; + } + + if (rect.y1 > fb->base.height) { + EVDI_DEBUG("Wrong clip rect: y1 > fb.height\n"); + rect.y1 = fb->base.height; + } + + if (rect.x2 > fb->base.width) { + EVDI_DEBUG("Wrong clip rect: x2 > fb.width\n"); + rect.x2 = fb->base.width; + } + + if (rect.y2 > fb->base.height) { + EVDI_DEBUG("Wrong clip rect: y2 > fb.height\n"); + rect.y2 = fb->base.height; + } + + return rect; +} + +#ifdef CONFIG_FB +static int evdi_handle_damage(struct evdi_framebuffer *fb, + int x, int y, int width, int height) +{ + const struct drm_clip_rect dirty_rect = { x, y, x + width, y + height }; + const struct drm_clip_rect rect = + evdi_framebuffer_sanitize_rect(fb, &dirty_rect); + struct drm_device *dev = fb->base.dev; + struct evdi_device *evdi = dev->dev_private; + + EVDI_CHECKPT(); + + if (!fb->active) + return 0; + evdi_painter_set_scanout_buffer(evdi->painter, fb); + evdi_painter_mark_dirty(evdi, &rect); + + return 0; +} + +static int evdi_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + unsigned long page, pos; + + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) + return -EINVAL; + + if (offset > info->fix.smem_len || + size > info->fix.smem_len - offset) + return -EINVAL; + + pos = (unsigned long)info->fix.smem_start + offset; + + pr_notice("mmap() framebuffer addr:%lu size:%lu\n", pos, size); + + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return 0; +} + +static void evdi_fb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct evdi_fbdev *efbdev = info->par; + + EVDI_CHECKPT(); + sys_fillrect(info, rect); + evdi_handle_damage(&efbdev->efb, rect->dx, rect->dy, rect->width, + rect->height); +} + +static void evdi_fb_copyarea(struct fb_info *info, + const struct fb_copyarea *region) +{ + struct evdi_fbdev *efbdev = info->par; + + EVDI_CHECKPT(); + sys_copyarea(info, region); + evdi_handle_damage(&efbdev->efb, region->dx, region->dy, region->width, + region->height); +} + +static void evdi_fb_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct evdi_fbdev *efbdev = info->par; + + EVDI_CHECKPT(); + sys_imageblit(info, image); + evdi_handle_damage(&efbdev->efb, image->dx, image->dy, image->width, + image->height); +} + +/* + * It's common for several clients to have framebuffer open simultaneously. + * e.g. both fbcon and X. Makes things interesting. + * Assumes caller is holding info->lock (for open and release at least) + */ +static int evdi_fb_open(struct fb_info *info, int user) +{ + struct evdi_fbdev *efbdev = info->par; + + efbdev->fb_count++; + pr_notice("open /dev/fb%d user=%d fb_info=%p count=%d\n", + info->node, user, info, efbdev->fb_count); + + return 0; +} + +/* + * Assumes caller is holding info->lock mutex (for open and release at least) + */ +static int evdi_fb_release(struct fb_info *info, int user) +{ + struct evdi_fbdev *efbdev = info->par; + + efbdev->fb_count--; + + pr_warn("released /dev/fb%d user=%d count=%d\n", + info->node, user, efbdev->fb_count); + + return 0; +} +static const struct fb_ops evdifb_ops = { + .owner = THIS_MODULE, + .fb_check_var = drm_fb_helper_check_var, + .fb_set_par = drm_fb_helper_set_par, + .fb_fillrect = evdi_fb_fillrect, + .fb_copyarea = evdi_fb_copyarea, + .fb_imageblit = evdi_fb_imageblit, + .fb_pan_display = drm_fb_helper_pan_display, + .fb_blank = drm_fb_helper_blank, + .fb_setcmap = drm_fb_helper_setcmap, + .fb_debug_enter = drm_fb_helper_debug_enter, + .fb_debug_leave = drm_fb_helper_debug_leave, + .fb_mmap = evdi_fb_mmap, + .fb_open = evdi_fb_open, + .fb_release = evdi_fb_release, +}; +#endif /* CONFIG_FB */ + +#if KERNEL_VERSION(5, 0, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else +/* + * Function taken from + * https://lore.kernel.org/dri-devel/20180905233901.2321-5-drawat@vmware.com/ + */ +static int evdi_user_framebuffer_dirty( + struct drm_framebuffer *fb, + __maybe_unused struct drm_file *file_priv, + __always_unused unsigned int flags, + __always_unused unsigned int color, + __always_unused struct drm_clip_rect *clips, + __always_unused unsigned int num_clips) +{ + struct evdi_framebuffer *efb = to_evdi_fb(fb); + struct drm_device *dev = efb->base.dev; + struct evdi_device *evdi = dev->dev_private; + + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_state *state; + struct drm_plane *plane; + int ret = 0; + unsigned int i; + + EVDI_CHECKPT(); + + drm_modeset_acquire_init(&ctx, + /* + * When called from ioctl, we are interruptable, + * but not when called internally (ie. defio worker) + */ + file_priv ? DRM_MODESET_ACQUIRE_INTERRUPTIBLE : 0); + + state = drm_atomic_state_alloc(fb->dev); + if (!state) { + ret = -ENOMEM; + goto out; + } + state->acquire_ctx = &ctx; + + for (i = 0; i < num_clips; ++i) + evdi_painter_mark_dirty(evdi, &clips[i]); + +retry: + + drm_for_each_plane(plane, fb->dev) { + struct drm_plane_state *plane_state; + + if (plane->state->fb != fb) + continue; + + /* + * Even if it says 'get state' this function will create and + * initialize state if it does not exists. We use this property + * to force create state. + */ + plane_state = drm_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_state)) { + ret = PTR_ERR(plane_state); + goto out; + } + } + + ret = drm_atomic_commit(state); + +out: + if (ret == -EDEADLK) { + drm_atomic_state_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry; + } + + if (state) + drm_atomic_state_put(state); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + return ret; +} +#endif + +static int evdi_user_framebuffer_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned int *handle) +{ + struct evdi_framebuffer *efb = to_evdi_fb(fb); + + return drm_gem_handle_create(file_priv, &efb->obj->base, handle); +} + +static void evdi_user_framebuffer_destroy(struct drm_framebuffer *fb) +{ + struct evdi_framebuffer *efb = to_evdi_fb(fb); + + EVDI_CHECKPT(); + if (efb->obj) +#if KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE || defined(EL8) + drm_gem_object_put(&efb->obj->base); +#else + drm_gem_object_put_unlocked(&efb->obj->base); +#endif + drm_framebuffer_cleanup(fb); + kfree(efb); +} + +static const struct drm_framebuffer_funcs evdifb_funcs = { + .create_handle = evdi_user_framebuffer_create_handle, + .destroy = evdi_user_framebuffer_destroy, +#if KERNEL_VERSION(5, 0, 0) <= LINUX_VERSION_CODE || defined(EL8) + .dirty = drm_atomic_helper_dirtyfb, +#else + .dirty = evdi_user_framebuffer_dirty, +#endif +}; + +static int +evdi_framebuffer_init(struct drm_device *dev, + struct evdi_framebuffer *efb, +#if KERNEL_VERSION(6, 17, 0) <= LINUX_VERSION_CODE + const struct drm_format_info *info, +#endif + const struct drm_mode_fb_cmd2 *mode_cmd, + struct evdi_gem_object *obj) +{ + efb->obj = obj; +#if KERNEL_VERSION(6, 17, 0) <= LINUX_VERSION_CODE + if (info == NULL) + info = drm_get_format_info(dev, mode_cmd->pixel_format, + mode_cmd->modifier[0]); +#endif + drm_helper_mode_fill_fb_struct(dev, &efb->base, +#if KERNEL_VERSION(6, 17, 0) <= LINUX_VERSION_CODE + info, +#endif + mode_cmd); + return drm_framebuffer_init(dev, &efb->base, &evdifb_funcs); +} + +#ifdef CONFIG_FB +int evdifb_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct evdi_fbdev *efbdev = (struct evdi_fbdev *)helper; + struct drm_device *dev = efbdev->helper.dev; + struct fb_info *info; + struct device *device = dev->dev; + struct drm_framebuffer *fb; + struct drm_mode_fb_cmd2 mode_cmd; + struct evdi_gem_object *obj; + uint32_t size; + int ret = 0; + + if (sizes->surface_bpp == 24) { + sizes->surface_bpp = 32; + } else if (sizes->surface_bpp != 32) { + EVDI_ERROR("Not supported pixel format (bpp=%d)\n", + sizes->surface_bpp); + return -EINVAL; + } + + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + mode_cmd.pitches[0] = mode_cmd.width * ((sizes->surface_bpp + 7) / 8); + + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + size = mode_cmd.pitches[0] * mode_cmd.height; + size = ALIGN(size, PAGE_SIZE); + + obj = evdi_gem_alloc_object(dev, size); + if (!obj) + goto out; + + ret = evdi_gem_vmap(obj); + if (ret) { + DRM_ERROR("failed to vmap fb\n"); + goto out_gfree; + } + + info = framebuffer_alloc(0, device); + if (!info) { + ret = -ENOMEM; + goto out_gfree; + } + info->par = efbdev; + + ret = evdi_framebuffer_init(dev, &efbdev->efb, +#if KERNEL_VERSION(6, 17, 0) <= LINUX_VERSION_CODE + NULL, +#endif + &mode_cmd, obj); + if (ret) + goto out_gfree; + + fb = &efbdev->efb.base; + + efbdev->helper.fb = fb; +#if KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) + efbdev->helper.info = info; +#else + efbdev->helper.fbdev = info; +#endif + + + strscpy(info->fix.id, "evdidrmfb", sizeof(info->fix.id)); + + info->screen_base = efbdev->efb.obj->vmapping; + info->fix.smem_len = size; + info->fix.smem_start = (unsigned long)efbdev->efb.obj->vmapping; + +#if KERNEL_VERSION(6, 4, 0) <= LINUX_VERSION_CODE || defined(EL9) +#elif KERNEL_VERSION(4, 20, 0) <= LINUX_VERSION_CODE || defined(EL8) + info->flags = FBINFO_DEFAULT; +#else + info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT; +#endif + + efbdev->fb_ops = &evdifb_ops; + info->fbops = efbdev->fb_ops; + +#if KERNEL_VERSION(5, 2, 0) <= LINUX_VERSION_CODE || defined(EL8) + drm_fb_helper_fill_info(info, &efbdev->helper, sizes); +#else + drm_fb_helper_fill_fix(info, fb->pitches[0], fb->format->depth); + drm_fb_helper_fill_var(info, &efbdev->helper, sizes->fb_width, + sizes->fb_height); +#endif + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) { + ret = -ENOMEM; + goto out_gfree; + } + + DRM_DEBUG_KMS("allocated %dx%d vmal %p\n", + fb->width, fb->height, efbdev->efb.obj->vmapping); + + return ret; + out_gfree: +#if KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE || defined(EL8) + drm_gem_object_put(&efbdev->efb.obj->base); +#else + drm_gem_object_put_unlocked(&efbdev->efb.obj->base); +#endif + out: + return ret; +} + +#if KERNEL_VERSION(6, 15, 0) <= LINUX_VERSION_CODE || defined(EL9) || defined(EL10) +#else +static struct drm_fb_helper_funcs evdi_fb_helper_funcs = { + .fb_probe = evdifb_create, +}; +#endif + +static void evdi_fbdev_destroy(__always_unused struct drm_device *dev, + struct evdi_fbdev *efbdev) +{ + struct fb_info *info; + +#if KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) + if (efbdev->helper.info) { + info = efbdev->helper.info; +#else + if (efbdev->helper.fbdev) { + info = efbdev->helper.fbdev; +#endif + unregister_framebuffer(info); + if (info->cmap.len) + fb_dealloc_cmap(&info->cmap); + + framebuffer_release(info); + } + drm_fb_helper_fini(&efbdev->helper); + if (efbdev->efb.obj) { + drm_framebuffer_unregister_private(&efbdev->efb.base); + drm_framebuffer_cleanup(&efbdev->efb.base); +#if KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE || defined(EL8) + drm_gem_object_put(&efbdev->efb.obj->base); +#else + drm_gem_object_put_unlocked(&efbdev->efb.obj->base); +#endif + } +} + +int evdi_fbdev_init(struct drm_device *dev) +{ + struct evdi_device *evdi; + struct evdi_fbdev *efbdev; + int ret; + + evdi = dev->dev_private; + efbdev = kzalloc(sizeof(struct evdi_fbdev), GFP_KERNEL); + if (!efbdev) + return -ENOMEM; + + evdi->fbdev = efbdev; +#if KERNEL_VERSION(6, 15, 0) <= LINUX_VERSION_CODE || defined(EL9) || defined(EL10) + drm_fb_helper_prepare(dev, &efbdev->helper, 32, NULL); +#elif KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) + drm_fb_helper_prepare(dev, &efbdev->helper, 32, &evdi_fb_helper_funcs); +#else + drm_fb_helper_prepare(dev, &efbdev->helper, &evdi_fb_helper_funcs); +#endif + +#if KERNEL_VERSION(5, 7, 0) <= LINUX_VERSION_CODE || defined(EL8) + ret = drm_fb_helper_init(dev, &efbdev->helper); +#else + ret = drm_fb_helper_init(dev, &efbdev->helper, 1); +#endif + if (ret) { + kfree(efbdev); + return ret; + } + +#if KERNEL_VERSION(5, 7, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else + drm_fb_helper_single_add_all_connectors(&efbdev->helper); +#endif + +#if KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) + ret = drm_fb_helper_initial_config(&efbdev->helper); +#else + ret = drm_fb_helper_initial_config(&efbdev->helper, 32); +#endif + + if (ret) { + drm_fb_helper_fini(&efbdev->helper); + kfree(efbdev); + } + return ret; +} + +void evdi_fbdev_cleanup(struct drm_device *dev) +{ + struct evdi_device *evdi = dev->dev_private; + + if (!evdi->fbdev) + return; + + evdi_fbdev_destroy(dev, evdi->fbdev); + kfree(evdi->fbdev); + evdi->fbdev = NULL; +} + +void evdi_fbdev_unplug(struct drm_device *dev) +{ + struct evdi_device *evdi = dev->dev_private; + struct evdi_fbdev *efbdev; + + if (!evdi->fbdev) + return; + + efbdev = evdi->fbdev; +#if KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) + if (efbdev->helper.info) { + struct fb_info *info; + + info = efbdev->helper.info; +#else + if (efbdev->helper.fbdev) { + struct fb_info *info; + + info = efbdev->helper.fbdev; +#endif +#if KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE || defined(EL8) + unregister_framebuffer(info); +#else + unlink_framebuffer(info); +#endif + } +} +#endif /* CONFIG_FB */ + +int evdi_fb_get_bpp(uint32_t format) +{ + const struct drm_format_info *info = drm_format_info(format); + + if (!info) + return 0; + return info->cpp[0] * 8; +} + +struct drm_framebuffer *evdi_fb_user_fb_create( + struct drm_device *dev, + struct drm_file *file, +#if KERNEL_VERSION(6, 17, 0) <= LINUX_VERSION_CODE + const struct drm_format_info *info, +#endif + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_gem_object *obj; + struct evdi_framebuffer *efb; + int ret; + uint32_t size; + int bpp = evdi_fb_get_bpp(mode_cmd->pixel_format); + + if (bpp != 32) { + EVDI_ERROR("Unsupported bpp (%d)\n", bpp); + return ERR_PTR(-EINVAL); + } + + obj = drm_gem_object_lookup(file, mode_cmd->handles[0]); + if (obj == NULL) + return ERR_PTR(-ENOENT); + + size = mode_cmd->offsets[0] + mode_cmd->pitches[0] * mode_cmd->height; + size = ALIGN(size, PAGE_SIZE); + + if (size > obj->size) { + DRM_ERROR("object size not sufficient for fb %d %zu %u %d %d\n", + size, obj->size, mode_cmd->offsets[0], + mode_cmd->pitches[0], mode_cmd->height); + goto err_no_mem; + } + + efb = kzalloc(sizeof(*efb), GFP_KERNEL); + if (efb == NULL) + goto err_no_mem; + efb->base.obj[0] = obj; + + ret = evdi_framebuffer_init(dev, efb, +#if KERNEL_VERSION(6, 17, 0) <= LINUX_VERSION_CODE + info, +#endif + mode_cmd, to_evdi_bo(obj)); + if (ret) + goto err_inval; + return &efb->base; + + err_no_mem: + drm_gem_object_put(obj); + return ERR_PTR(-ENOMEM); + err_inval: + kfree(efb); + drm_gem_object_put(obj); + return ERR_PTR(-EINVAL); +} diff --git a/drivers/custom/evdi/module/evdi_gem.c b/drivers/custom/evdi/module/evdi_gem.c new file mode 100644 index 000000000000..ee1dbcff6229 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_gem.c @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + * Copyright (c) 2015 - 2020 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include +#include +#if KERNEL_VERSION(5, 18, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) +#elif KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE +#include +#endif +#if KERNEL_VERSION(5, 16, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) +#include +#include +#elif KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE +#else +#include +#endif +#include "evdi_drm_drv.h" +#include "evdi_params.h" +#include +#include +#include +#include + + +#if KERNEL_VERSION(6, 13, 0) <= LINUX_VERSION_CODE || defined(EL10) +MODULE_IMPORT_NS("DMA_BUF"); +#elif KERNEL_VERSION(5, 16, 0) <= LINUX_VERSION_CODE || defined(EL9) +MODULE_IMPORT_NS(DMA_BUF); +#endif + +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8) +static int evdi_prime_pin(struct drm_gem_object *obj); +static void evdi_prime_unpin(struct drm_gem_object *obj); + +static const struct vm_operations_struct evdi_gem_vm_ops = { + .fault = evdi_gem_fault, + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +static struct drm_gem_object_funcs gem_obj_funcs = { + .free = evdi_gem_free_object, + .pin = evdi_prime_pin, + .unpin = evdi_prime_unpin, + .vm_ops = &evdi_gem_vm_ops, + .export = drm_gem_prime_export, + .get_sg_table = evdi_prime_get_sg_table, +}; +#endif + +static bool evdi_was_called_by_mutter(void) +{ + char task_comm[TASK_COMM_LEN] = { 0 }; + + get_task_comm(task_comm, current); + + return strcmp(task_comm, "gnome-shell") == 0; +} + +static bool evdi_drm_gem_object_use_import_attach(struct drm_gem_object *obj) +{ + if (!obj || !obj->import_attach || !obj->import_attach->dmabuf->owner) + return false; + + return strcmp(obj->import_attach->dmabuf->owner->name, "amdgpu") != 0; +} + +uint32_t evdi_gem_object_handle_lookup(struct drm_file *filp, + struct drm_gem_object *obj) +{ + uint32_t it_handle = 0; + struct drm_gem_object *it_obj = NULL; + + spin_lock(&filp->table_lock); + idr_for_each_entry(&filp->object_idr, it_obj, it_handle) { + if (it_obj == obj) + break; + } + spin_unlock(&filp->table_lock); + + if (!it_obj) + it_handle = 0; + + return it_handle; +} + +struct evdi_gem_object *evdi_gem_alloc_object(struct drm_device *dev, + size_t size) +{ + struct evdi_gem_object *obj; + + obj = kzalloc(sizeof(*obj), GFP_KERNEL); + if (obj == NULL) + return NULL; + + if (drm_gem_object_init(dev, &obj->base, size) != 0) { + kfree(obj); + return NULL; + } + + +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8) + obj->base.funcs = &gem_obj_funcs; +#endif + + obj->allow_sw_cursor_rect_updates = false; + + mutex_init(&obj->pages_lock); + + return obj; +} + +static int +evdi_gem_create(struct drm_file *file, + struct drm_device *dev, uint64_t size, uint32_t *handle_p) +{ + struct evdi_gem_object *obj; + int ret; + u32 handle; + + size = roundup(size, PAGE_SIZE); + + obj = evdi_gem_alloc_object(dev, size); + if (obj == NULL) + return -ENOMEM; + + obj->allow_sw_cursor_rect_updates = evdi_was_called_by_mutter(); + ret = drm_gem_handle_create(file, &obj->base, &handle); + if (ret) { + drm_gem_object_release(&obj->base); + kfree(obj); + return ret; + } +#if KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE || defined(EL8) + drm_gem_object_put(&obj->base); +#else + drm_gem_object_put_unlocked(&obj->base); +#endif + *handle_p = handle; + return 0; +} + +static int evdi_align_pitch(int width, int cpp) +{ + int aligned = width; + int pitch_mask = 0; + + switch (cpp) { + case 1: + pitch_mask = 255; + break; + case 2: + pitch_mask = 127; + break; + case 3: + case 4: + pitch_mask = 63; + break; + } + + aligned += pitch_mask; + aligned &= ~pitch_mask; + return aligned * cpp; +} + +int evdi_dumb_create(struct drm_file *file, + struct drm_device *dev, struct drm_mode_create_dumb *args) +{ + args->pitch = evdi_align_pitch(args->width, DIV_ROUND_UP(args->bpp, 8)); + + args->size = args->pitch * args->height; + return evdi_gem_create(file, dev, args->size, &args->handle); +} + +int evdi_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int ret; + + ret = drm_gem_mmap(filp, vma); + if (ret) + return ret; + +/* Some VMA modifier function patches present in 6.3 were reverted in EL8 kernels */ +#if KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE || defined(EL9) + vm_flags_mod(vma, VM_MIXEDMAP, VM_PFNMAP); +#else + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_flags |= VM_MIXEDMAP; +#endif + + return ret; +} + +#if KERNEL_VERSION(4, 17, 0) <= LINUX_VERSION_CODE +vm_fault_t evdi_gem_fault(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; +#else +int evdi_gem_fault(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; +#endif + struct evdi_gem_object *obj = to_evdi_bo(vma->vm_private_data); + struct page *page; + pgoff_t page_offset; + loff_t num_pages = obj->base.size >> PAGE_SHIFT; + int ret = 0; + + page_offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT; + + if (!obj->pages || page_offset >= (unsigned long)num_pages) + return VM_FAULT_SIGBUS; + + page = obj->pages[page_offset]; + ret = vm_insert_page(vma, vmf->address, page); + switch (ret) { + case -EAGAIN: + case 0: + case -ERESTARTSYS: + case -EBUSY: + return VM_FAULT_NOPAGE; + case -ENOMEM: + return VM_FAULT_OOM; + default: + return VM_FAULT_SIGBUS; + } + return VM_FAULT_SIGBUS; +} + +static int evdi_gem_get_pages(struct evdi_gem_object *obj, + __always_unused gfp_t gfpmask) +{ + struct page **pages; + + if (obj->pages) + return 0; + + pages = drm_gem_get_pages(&obj->base); + + if (IS_ERR(pages)) + return PTR_ERR(pages); + + obj->pages = pages; + +#if defined(CONFIG_X86) + drm_clflush_pages(obj->pages, DIV_ROUND_UP(obj->base.size, PAGE_SIZE)); +#endif + + return 0; +} + +static void evdi_gem_put_pages(struct evdi_gem_object *obj) +{ + if (obj->base.import_attach) { + kvfree(obj->pages); + obj->pages = NULL; + return; + } + + drm_gem_put_pages(&obj->base, obj->pages, false, false); + obj->pages = NULL; +} + +static int evdi_pin_pages(struct evdi_gem_object *obj) +{ + int ret = 0; + + mutex_lock(&obj->pages_lock); + if (obj->pages_pin_count++ == 0) { + ret = evdi_gem_get_pages(obj, GFP_KERNEL); + if (ret) + obj->pages_pin_count--; + } + mutex_unlock(&obj->pages_lock); + return ret; +} + +static void evdi_unpin_pages(struct evdi_gem_object *obj) +{ + mutex_lock(&obj->pages_lock); + if (--obj->pages_pin_count == 0) + evdi_gem_put_pages(obj); + mutex_unlock(&obj->pages_lock); +} + +int evdi_gem_vmap(struct evdi_gem_object *obj) +{ + int page_count = DIV_ROUND_UP(obj->base.size, PAGE_SIZE); + int ret; + + if (evdi_drm_gem_object_use_import_attach(&obj->base)) { +#if KERNEL_VERSION(5, 18, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) + struct iosys_map map = IOSYS_MAP_INIT_VADDR(NULL); +#elif KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE + struct dma_buf_map map = DMA_BUF_MAP_INIT_VADDR(NULL); +#endif + +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8) +# if KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) + ret = dma_buf_vmap_unlocked(obj->base.import_attach->dmabuf, &map); +# else + ret = dma_buf_vmap(obj->base.import_attach->dmabuf, &map); +# endif + if (ret) + return -ENOMEM; + obj->vmapping = map.vaddr; + obj->vmap_is_iomem = map.is_iomem; +#else + obj->vmapping = dma_buf_vmap(obj->base.import_attach->dmabuf); + if (!obj->vmapping) + return -ENOMEM; +#endif + return 0; + } + + ret = evdi_pin_pages(obj); + if (ret) + return ret; + + obj->vmapping = vmap(obj->pages, page_count, 0, PAGE_KERNEL); + if (!obj->vmapping) + return -ENOMEM; + return 0; +} + +void evdi_gem_vunmap(struct evdi_gem_object *obj) +{ + if (evdi_drm_gem_object_use_import_attach(&obj->base)) { +#if KERNEL_VERSION(5, 18, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) + struct iosys_map map = IOSYS_MAP_INIT_VADDR(NULL); + + if (obj->vmap_is_iomem) + iosys_map_set_vaddr_iomem(&map, obj->vmapping); + else + iosys_map_set_vaddr(&map, obj->vmapping); + +# if KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) + dma_buf_vunmap_unlocked(obj->base.import_attach->dmabuf, &map); +# else + dma_buf_vunmap(obj->base.import_attach->dmabuf, &map); +# endif + +#elif KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE + struct dma_buf_map map; + + if (obj->vmap_is_iomem) + dma_buf_map_set_vaddr_iomem(&map, obj->vmapping); + else + dma_buf_map_set_vaddr(&map, obj->vmapping); + + dma_buf_vunmap(obj->base.import_attach->dmabuf, &map); +#else + dma_buf_vunmap(obj->base.import_attach->dmabuf, obj->vmapping); +#endif + obj->vmapping = NULL; + return; + } + + if (obj->vmapping) { + vunmap(obj->vmapping); + obj->vmapping = NULL; + } + + evdi_unpin_pages(obj); +} + +void evdi_gem_free_object(struct drm_gem_object *gem_obj) +{ + struct evdi_gem_object *obj = to_evdi_bo(gem_obj); + + if (obj->vmapping) + evdi_gem_vunmap(obj); + + if (gem_obj->import_attach) + drm_prime_gem_destroy(gem_obj, obj->sg); + + if (obj->pages) + evdi_gem_put_pages(obj); + + if (gem_obj->dev->vma_offset_manager) + drm_gem_free_mmap_offset(gem_obj); + mutex_destroy(&obj->pages_lock); + drm_gem_object_release(&obj->base); + kfree(obj); +} + +/* + * the dumb interface doesn't work with the GEM straight MMAP + * interface, it expects to do MMAP on the drm fd, like normal + */ +int evdi_gem_mmap(struct drm_file *file, + struct drm_device *dev, uint32_t handle, uint64_t *offset) +{ + struct evdi_gem_object *gobj; + struct drm_gem_object *obj; + int ret = 0; + + obj = drm_gem_object_lookup(file, handle); + if (obj == NULL) { + return -ENOENT; + } + gobj = to_evdi_bo(obj); + + ret = evdi_pin_pages(gobj); + if (ret) + goto out; + + /* Don't allow imported objects to be mapped */ + if (obj->import_attach) { + EVDI_WARN("Don't allow imported objects to be mapped: owner: %s\n", obj->import_attach->dmabuf->owner->name); + ret = -EINVAL; + goto out; + } + + ret = drm_gem_create_mmap_offset(obj); + if (ret) + goto out; + + *offset = drm_vma_node_offset_addr(&gobj->base.vma_node); + + out: + drm_gem_object_put(&gobj->base); + return ret; +} + +struct drm_gem_object * +evdi_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sg) +{ + struct evdi_gem_object *obj; + int npages; + bool called_by_mutter; + + called_by_mutter = evdi_was_called_by_mutter(); + + obj = evdi_gem_alloc_object(dev, attach->dmabuf->size); + if (IS_ERR(obj)) + return ERR_CAST(obj); + + npages = DIV_ROUND_UP(attach->dmabuf->size, PAGE_SIZE); + DRM_DEBUG_PRIME("Importing %d pages\n", npages); + obj->pages = kvmalloc_array(npages, sizeof(struct page *), GFP_KERNEL); + if (!obj->pages) { + evdi_gem_free_object(&obj->base); + return ERR_PTR(-ENOMEM); + } + +#if KERNEL_VERSION(5, 12, 0) <= LINUX_VERSION_CODE || defined(EL8) + drm_prime_sg_to_page_array(sg, obj->pages, npages); +#else + drm_prime_sg_to_page_addr_arrays(sg, obj->pages, NULL, npages); +#endif + obj->sg = sg; + obj->allow_sw_cursor_rect_updates = called_by_mutter; + return &obj->base; +} + +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8) +static int evdi_prime_pin(struct drm_gem_object *obj) +{ + struct evdi_gem_object *bo = to_evdi_bo(obj); + + return evdi_pin_pages(bo); +} + +static void evdi_prime_unpin(struct drm_gem_object *obj) +{ + struct evdi_gem_object *bo = to_evdi_bo(obj); + + evdi_unpin_pages(bo); +} +#endif + +struct sg_table *evdi_prime_get_sg_table(struct drm_gem_object *obj) +{ + struct evdi_gem_object *bo = to_evdi_bo(obj); + +#if KERNEL_VERSION(5, 10, 0) <= LINUX_VERSION_CODE || defined(EL8) + return drm_prime_pages_to_sg(obj->dev, bo->pages, bo->base.size >> PAGE_SHIFT); +#else + return drm_prime_pages_to_sg(bo->pages, bo->base.size >> PAGE_SHIFT); +#endif +} + diff --git a/drivers/custom/evdi/module/evdi_i2c.c b/drivers/custom/evdi/module/evdi_i2c.c new file mode 100644 index 000000000000..1616c212cb2e --- /dev/null +++ b/drivers/custom/evdi/module/evdi_i2c.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include "evdi_i2c.h" +#include "evdi_debug.h" +#include "evdi_drm_drv.h" + +static int dli2c_access_master(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + int i = 0, result = 0; + struct evdi_device *evdi = adapter->algo_data; + struct evdi_painter *painter = evdi->painter; + + for (i = 0; i < num; i++) { + if (evdi_painter_i2c_data_notify(painter, &msgs[i])) + result++; + } + + return result; +} + +static u32 dli2c_func(__always_unused struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm dli2c_algorithm = { + .master_xfer = dli2c_access_master, + .functionality = dli2c_func, +}; + +int evdi_i2c_add(struct i2c_adapter *adapter, struct device *parent, + void *ddev) +{ + adapter->owner = THIS_MODULE; +#if KERNEL_VERSION(6, 8, 0) <= LINUX_VERSION_CODE || defined(EL9) +#else + adapter->class = I2C_CLASS_DDC; +#endif + adapter->algo = &dli2c_algorithm; + strscpy(adapter->name, "DisplayLink I2C Adapter", sizeof(adapter->name)); + adapter->dev.parent = parent; + adapter->algo_data = ddev; + + return i2c_add_adapter(adapter); +} + +void evdi_i2c_remove(struct i2c_adapter *adapter) +{ + i2c_del_adapter(adapter); +} diff --git a/drivers/custom/evdi/module/evdi_i2c.h b/drivers/custom/evdi/module/evdi_i2c.h new file mode 100644 index 000000000000..e8718c3f5292 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_i2c.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * Copyright (c) 2020 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#ifndef EVDI_I2C_H +#define EVDI_I2C_H + +#include +#include + +int evdi_i2c_add(struct i2c_adapter *adapter, + struct device *parent, + void *ddev); +void evdi_i2c_remove(struct i2c_adapter *adapter); + +#endif /* EVDI_I2C_H */ diff --git a/drivers/custom/evdi/module/evdi_ioc32.c b/drivers/custom/evdi/module/evdi_ioc32.c new file mode 100644 index 000000000000..896b9d40c46e --- /dev/null +++ b/drivers/custom/evdi/module/evdi_ioc32.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * evdi_ioc32.c + * + * Copyright (c) 2016 The Chromium OS Authors + * Copyright (c) 2017 - 2020 DisplayLink (UK) Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include +#if KERNEL_VERSION(5, 16, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) +#include +#elif KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE +#else +#include +#endif +#include +#include "evdi_drm.h" + +#include "evdi_drm_drv.h" + +struct drm_evdi_connect32 { + int32_t connected; + int32_t dev_index; + uint32_t edid_ptr32; + uint32_t edid_length; + uint32_t pixel_area_limit; + uint32_t pixel_per_second_limit; +}; + +struct drm_evdi_grabpix32 { + uint32_t mode; + int32_t buf_width; + int32_t buf_height; + int32_t buf_byte_stride; + uint32_t buffer_ptr32; + int32_t num_rects; + uint32_t rects_ptr32; +}; + +static int compat_evdi_connect(struct file *file, + unsigned int __always_unused cmd, + unsigned long arg) +{ + struct drm_evdi_connect32 req32; + struct drm_evdi_connect krequest; + + if (copy_from_user(&req32, (void __user *)arg, sizeof(req32))) + return -EFAULT; + + krequest.connected = req32.connected; + krequest.dev_index = req32.dev_index; + krequest.edid = compat_ptr(req32.edid_ptr32); + krequest.edid_length = req32.edid_length; + krequest.pixel_area_limit = req32.pixel_area_limit; + krequest.pixel_per_second_limit = req32.pixel_per_second_limit; + + return drm_ioctl_kernel(file, evdi_painter_connect_ioctl, &krequest, 0); +} + +static int compat_evdi_grabpix(struct file *file, + unsigned int __always_unused cmd, + unsigned long arg) +{ + struct drm_evdi_grabpix32 req32; + struct drm_evdi_grabpix krequest; + int ret; + + if (copy_from_user(&req32, (void __user *)arg, sizeof(req32))) + return -EFAULT; + + krequest.mode = req32.mode; + krequest.buf_width = req32.buf_width; + krequest.buf_height = req32.buf_height; + krequest.buf_byte_stride = req32.buf_byte_stride; + krequest.buffer = compat_ptr(req32.buffer_ptr32); + krequest.num_rects = req32.num_rects; + krequest.rects = compat_ptr(req32.rects_ptr32); + + ret = drm_ioctl_kernel(file, evdi_painter_grabpix_ioctl, &krequest, 0); + if (ret) + return ret; + + req32.num_rects = krequest.num_rects; + if (copy_to_user((void __user *)arg, &req32, sizeof(req32))) + return -EFAULT; + return 0; +} + +static drm_ioctl_compat_t *evdi_compat_ioctls[] = { + [DRM_EVDI_CONNECT] = compat_evdi_connect, + [DRM_EVDI_GRABPIX] = compat_evdi_grabpix, +}; + +/* + * Called whenever a 32-bit process running under a 64-bit kernel + * performs an ioctl on /dev/dri/card. + * + * \param filp file pointer. + * \param cmd command. + * \param arg user argument. + * \return zero on success or negative number on failure. + */ +long evdi_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + unsigned int nr = DRM_IOCTL_NR(cmd); + drm_ioctl_compat_t *fn = NULL; + int ret; + + if (nr < DRM_COMMAND_BASE || nr >= DRM_COMMAND_END) + return drm_compat_ioctl(filp, cmd, arg); + + if (nr < DRM_COMMAND_BASE + ARRAY_SIZE(evdi_compat_ioctls)) + fn = evdi_compat_ioctls[nr - DRM_COMMAND_BASE]; + + if (fn != NULL) + ret = (*fn) (filp, cmd, arg); + else + ret = drm_ioctl(filp, cmd, arg); + + return ret; +} diff --git a/drivers/custom/evdi/module/evdi_modeset.c b/drivers/custom/evdi/module/evdi_modeset.c new file mode 100644 index 000000000000..6674e4b30aa3 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_modeset.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + * Copyright (c) 2015 - 2020 DisplayLink (UK) Ltd. + * + * Based on parts on udlfb.c: + * Copyright (C) 2009 its respective authors + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include +#if KERNEL_VERSION(5, 16, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) +#include +#include +#elif KERNEL_VERSION(5, 0, 0) <= LINUX_VERSION_CODE +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include "evdi_drm.h" +#include "evdi_drm_drv.h" +#include "evdi_cursor.h" +#include "evdi_params.h" +#if KERNEL_VERSION(5, 13, 0) <= LINUX_VERSION_CODE || defined(EL8) +#include +#else +#include +#endif + +static void evdi_crtc_dpms(__always_unused struct drm_crtc *crtc, + __always_unused int mode) +{ + EVDI_CHECKPT(); +} + +static void evdi_crtc_disable(__always_unused struct drm_crtc *crtc) +{ + EVDI_CHECKPT(); + drm_crtc_vblank_off(crtc); +} + +static void evdi_crtc_destroy(struct drm_crtc *crtc) +{ + EVDI_CHECKPT(); + drm_crtc_cleanup(crtc); + kfree(crtc); +} + +static void evdi_crtc_commit(__always_unused struct drm_crtc *crtc) +{ + EVDI_CHECKPT(); +} + +static void evdi_crtc_set_nofb(__always_unused struct drm_crtc *crtc) +{ +} + +static void evdi_crtc_atomic_flush( + struct drm_crtc *crtc +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(RPI) || defined(EL8) + , struct drm_atomic_state *state +#else + , __always_unused struct drm_crtc_state *old_state +#endif + ) +{ +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(RPI) || defined(EL8) + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); +#else + struct drm_crtc_state *crtc_state = crtc->state; +#endif + struct evdi_device *evdi = crtc->dev->dev_private; + bool notify_mode_changed = crtc_state->active && + (crtc_state->mode_changed || evdi_painter_needs_full_modeset(evdi->painter)); + bool notify_dpms = crtc_state->active_changed || evdi_painter_needs_full_modeset(evdi->painter); + + if (notify_mode_changed) + evdi_painter_mode_changed_notify(evdi, &crtc_state->adjusted_mode); + + if (notify_dpms) + evdi_painter_dpms_notify(evdi->painter, + crtc_state->active ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF); + + evdi_painter_set_vblank(evdi->painter, crtc, crtc_state->event); + evdi_painter_send_update_ready_if_needed(evdi->painter); + crtc_state->event = NULL; +} + +#if KERNEL_VERSION(5, 10, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else +static void evdi_mark_full_screen_dirty(struct evdi_device *evdi) +{ + const struct drm_clip_rect rect = + evdi_painter_framebuffer_size(evdi->painter); + + evdi_painter_mark_dirty(evdi, &rect); + evdi_painter_send_update_ready_if_needed(evdi->painter); +} + +static int evdi_crtc_cursor_set(struct drm_crtc *crtc, + struct drm_file *file, + uint32_t handle, + uint32_t width, + uint32_t height, + int32_t hot_x, + int32_t hot_y) +{ + struct drm_device *dev = crtc->dev; + struct evdi_device *evdi = dev->dev_private; + struct drm_gem_object *obj = NULL; + struct evdi_gem_object *eobj = NULL; + /* + * evdi_crtc_cursor_set is callback function using + * deprecated cursor entry point. + * There is no info about underlaying pixel format. + * Hence we are assuming that it is in ARGB 32bpp format. + * This format it the only one supported in cursor composition + * function. + * This format is also enforced during framebuffer creation. + * + * Proper format will be available when driver start support + * universal planes for cursor. + */ + uint32_t format = DRM_FORMAT_ARGB8888; + uint32_t stride = 4 * width; + + EVDI_CHECKPT(); + if (handle) { + obj = drm_gem_object_lookup(file, handle); + if (obj) + eobj = to_evdi_bo(obj); + else + EVDI_ERROR("Failed to lookup gem object.\n"); + } + + evdi_cursor_set(evdi->cursor, + eobj, width, height, hot_x, hot_y, + format, stride); + #if KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE || defined(EL8) + drm_gem_object_put(obj); + #else + drm_gem_object_put_unlocked(obj); + #endif + + /* + * For now we don't care whether the application wanted the mouse set, + * or not. + */ + if (evdi->cursor_events_enabled) + evdi_painter_send_cursor_set(evdi->painter, evdi->cursor); + else + evdi_mark_full_screen_dirty(evdi); + return 0; +} + +static int evdi_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) +{ + struct drm_device *dev = crtc->dev; + struct evdi_device *evdi = dev->dev_private; + + EVDI_CHECKPT(); + evdi_cursor_move(evdi->cursor, x, y); + + if (evdi->cursor_events_enabled) + evdi_painter_send_cursor_move(evdi->painter, evdi->cursor); + else + evdi_mark_full_screen_dirty(evdi); + + return 0; +} +#endif + +static struct drm_crtc_helper_funcs evdi_helper_funcs = { + .mode_set_nofb = evdi_crtc_set_nofb, + .atomic_flush = evdi_crtc_atomic_flush, + + .dpms = evdi_crtc_dpms, + .commit = evdi_crtc_commit, + .disable = evdi_crtc_disable +}; + +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(RPI) || defined(EL8) +static int evdi_enable_vblank(__always_unused struct drm_crtc *crtc) +{ + return 1; +} + +static void evdi_disable_vblank(__always_unused struct drm_crtc *crtc) +{ +} +#endif + +static const struct drm_crtc_funcs evdi_crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = evdi_crtc_destroy, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + +#if KERNEL_VERSION(5, 10, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else + .cursor_set2 = evdi_crtc_cursor_set, + .cursor_move = evdi_crtc_cursor_move, +#endif +#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(RPI) || defined(EL8) + .enable_vblank = evdi_enable_vblank, + .disable_vblank = evdi_disable_vblank, +#endif +}; + +static void evdi_plane_atomic_update(struct drm_plane *plane, +#if KERNEL_VERSION(5, 13, 0) <= LINUX_VERSION_CODE || defined(EL8) + struct drm_atomic_state *atom_state +#else + struct drm_plane_state *old_state +#endif + ) +{ +#if KERNEL_VERSION(5, 13, 0) <= LINUX_VERSION_CODE || defined(EL8) + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(atom_state, plane); +#else +#endif + struct drm_plane_state *state; + struct evdi_device *evdi; + struct evdi_painter *painter; + struct drm_crtc *crtc; + +#if KERNEL_VERSION(5, 0, 0) <= LINUX_VERSION_CODE || defined(EL8) + struct drm_atomic_helper_damage_iter iter; + struct drm_rect rect; + struct drm_clip_rect clip_rect; +#endif + + if (!plane || !plane->state) { + EVDI_WARN("Plane state is null\n"); + return; + } + + if (!plane->dev || !plane->dev->dev_private) { + EVDI_WARN("Plane device is null\n"); + return; + } + + state = plane->state; + evdi = plane->dev->dev_private; + painter = evdi->painter; + crtc = state->crtc; + + if (!old_state->crtc && state->crtc) + evdi_painter_dpms_notify(evdi->painter, DRM_MODE_DPMS_ON); + else if (old_state->crtc && !state->crtc) + evdi_painter_dpms_notify(evdi->painter, DRM_MODE_DPMS_OFF); + + if (state->fb) { + struct drm_framebuffer *fb = state->fb; + struct drm_framebuffer *old_fb = old_state->fb; + struct evdi_framebuffer *efb = to_evdi_fb(fb); + + const struct drm_clip_rect fullscreen_rect = { + 0, 0, fb->width, fb->height + }; + + if (!old_fb && crtc) + evdi_painter_force_full_modeset(painter); + + if (old_fb && + fb->format && old_fb->format && + fb->format->format != old_fb->format->format) + evdi_painter_force_full_modeset(painter); + + if (fb != old_fb || + evdi_painter_needs_full_modeset(painter)) { + + evdi_painter_set_scanout_buffer(painter, efb); + +#if KERNEL_VERSION(5, 0, 0) <= LINUX_VERSION_CODE || defined(EL8) + state->visible = true; + state->src.x1 = 0; + state->src.y1 = 0; + state->src.x2 = fb->width << 16; + state->src.y2 = fb->height << 16; + + drm_atomic_helper_damage_iter_init(&iter, old_state, state); + while (drm_atomic_helper_damage_iter_next(&iter, &rect)) { + clip_rect.x1 = rect.x1; + clip_rect.y1 = rect.y1; + clip_rect.x2 = rect.x2; + clip_rect.y2 = rect.y2; + evdi_painter_mark_dirty(evdi, &clip_rect); + } +#endif + + }; + + if (evdi_painter_get_num_dirts(painter) == 0) + evdi_painter_mark_dirty(evdi, &fullscreen_rect); + } +} + +static void evdi_cursor_atomic_get_rect(struct drm_clip_rect *rect, + struct drm_plane_state *state) +{ + rect->x1 = (state->crtc_x < 0) ? 0 : state->crtc_x; + rect->y1 = (state->crtc_y < 0) ? 0 : state->crtc_y; + rect->x2 = state->crtc_x + state->crtc_w; + rect->y2 = state->crtc_y + state->crtc_h; +} + +static void evdi_cursor_atomic_update(struct drm_plane *plane, +#if KERNEL_VERSION(5, 13, 0) <= LINUX_VERSION_CODE || defined(EL8) + struct drm_atomic_state *atom_state +#else + struct drm_plane_state *old_state +#endif + ) +{ +#if KERNEL_VERSION(5, 13, 0) <= LINUX_VERSION_CODE || defined(EL8) + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(atom_state, plane); + +#else +#endif + if (plane && plane->state && plane->dev && plane->dev->dev_private) { + struct drm_plane_state *state = plane->state; + struct evdi_device *evdi = plane->dev->dev_private; + struct drm_framebuffer *fb = state->fb; + struct evdi_framebuffer *efb = to_evdi_fb(fb); + + struct drm_clip_rect old_rect; + struct drm_clip_rect rect; + bool cursor_changed = false; + bool cursor_position_changed = false; + int32_t cursor_position_x = 0; + int32_t cursor_position_y = 0; + + evdi_cursor_position(evdi->cursor, &cursor_position_x, + &cursor_position_y); + evdi_cursor_move(evdi->cursor, state->crtc_x, state->crtc_y); + cursor_position_changed = cursor_position_x != state->crtc_x || + cursor_position_y != state->crtc_y; + + if (fb != old_state->fb) { + if (fb != NULL) { + uint32_t stride = 4 * fb->width; + + evdi_cursor_set(evdi->cursor, + efb->obj, + fb->width, + fb->height, + 0, + 0, + fb->format->format, + stride); + } + + evdi_cursor_enable(evdi->cursor, fb != NULL); + cursor_changed = true; + } + + if (!evdi->cursor_events_enabled) { + if (fb != NULL) { + if (efb->obj->allow_sw_cursor_rect_updates) { + evdi_cursor_atomic_get_rect(&old_rect, old_state); + evdi_cursor_atomic_get_rect(&rect, state); + + evdi_painter_mark_dirty(evdi, &old_rect); + } else { + rect = evdi_painter_framebuffer_size(evdi->painter); + } + evdi_painter_mark_dirty(evdi, &rect); + } + return; + } + + if (cursor_changed) + evdi_painter_send_cursor_set(evdi->painter, + evdi->cursor); + if (cursor_position_changed) + evdi_painter_send_cursor_move(evdi->painter, + evdi->cursor); + } +} + +static const struct drm_plane_helper_funcs evdi_plane_helper_funcs = { + .atomic_update = evdi_plane_atomic_update, +#if KERNEL_VERSION(5, 13, 0) <= LINUX_VERSION_CODE || defined(EL8) + .prepare_fb = drm_gem_plane_helper_prepare_fb +#else + .prepare_fb = drm_gem_fb_prepare_fb +#endif +}; + +static const struct drm_plane_helper_funcs evdi_cursor_helper_funcs = { + .atomic_update = evdi_cursor_atomic_update, +#if KERNEL_VERSION(5, 13, 0) <= LINUX_VERSION_CODE || defined(EL8) + .prepare_fb = drm_gem_plane_helper_prepare_fb +#else + .prepare_fb = drm_gem_fb_prepare_fb +#endif +}; + +static const struct drm_plane_funcs evdi_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static const uint32_t formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, +}; + +static struct drm_plane *evdi_create_plane( + struct drm_device *dev, + enum drm_plane_type type, + const struct drm_plane_helper_funcs *helper_funcs) +{ + struct drm_plane *plane; + int ret; + char *plane_type = (type == DRM_PLANE_TYPE_CURSOR) ? "cursor" : "primary"; + + plane = kzalloc(sizeof(*plane), GFP_KERNEL); + if (plane == NULL) { + EVDI_ERROR("Failed to allocate %s plane\n", plane_type); + return NULL; + } + plane->format_default = true; + + ret = drm_universal_plane_init(dev, + plane, + 0xFF, + &evdi_plane_funcs, + formats, + ARRAY_SIZE(formats), + NULL, + type, + NULL + ); + + if (ret) { + EVDI_ERROR("Failed to initialize %s plane\n", plane_type); + kfree(plane); + return NULL; + } + + drm_plane_helper_add(plane, helper_funcs); + + return plane; +} + +static int evdi_crtc_init(struct drm_device *dev) +{ + struct drm_crtc *crtc = NULL; + struct drm_plane *primary_plane = NULL; + struct drm_plane *cursor_plane = NULL; + int status = 0; + + EVDI_CHECKPT(); + crtc = kzalloc(sizeof(struct drm_crtc), GFP_KERNEL); + if (crtc == NULL) + return -ENOMEM; + + primary_plane = evdi_create_plane(dev, DRM_PLANE_TYPE_PRIMARY, + &evdi_plane_helper_funcs); + +#if KERNEL_VERSION(5, 10, 0) <= LINUX_VERSION_CODE || defined(EL8) + cursor_plane = evdi_create_plane(dev, DRM_PLANE_TYPE_CURSOR, + &evdi_cursor_helper_funcs); +#endif + +#if KERNEL_VERSION(5, 0, 0) <= LINUX_VERSION_CODE || defined(EL8) + drm_plane_enable_fb_damage_clips(primary_plane); +#endif + + status = drm_crtc_init_with_planes(dev, crtc, + primary_plane, cursor_plane, + &evdi_crtc_funcs, + NULL + ); + + EVDI_DEBUG("drm_crtc_init: %d p%p\n", status, primary_plane); + drm_crtc_helper_add(crtc, &evdi_helper_funcs); + + return 0; +} + +static const struct drm_mode_config_funcs evdi_mode_funcs = { + .fb_create = evdi_fb_user_fb_create, +#if KERNEL_VERSION(6, 11, 0) < LINUX_VERSION_CODE || defined(EL9) +#else + .output_poll_changed = NULL, +#endif + .atomic_commit = drm_atomic_helper_commit, + .atomic_check = drm_atomic_helper_check +}; + +void evdi_modeset_init(struct drm_device *dev) +{ + struct drm_encoder *encoder; + + EVDI_CHECKPT(); + + drm_mode_config_init(dev); + + dev->mode_config.min_width = 64; + dev->mode_config.min_height = 64; + + dev->mode_config.max_width = 7680; + dev->mode_config.max_height = 4320; + + dev->mode_config.prefer_shadow = 0; + dev->mode_config.preferred_depth = 24; + + dev->mode_config.funcs = &evdi_mode_funcs; + + evdi_crtc_init(dev); + + encoder = evdi_encoder_init(dev); + + evdi_connector_init(dev, encoder); + + drm_mode_config_reset(dev); +} + +void evdi_modeset_cleanup(__maybe_unused struct drm_device *dev) +{ +#if KERNEL_VERSION(5, 8, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else + drm_mode_config_cleanup(dev); +#endif +} diff --git a/drivers/custom/evdi/module/evdi_painter.c b/drivers/custom/evdi/module/evdi_painter.c new file mode 100644 index 000000000000..c5d12512901a --- /dev/null +++ b/drivers/custom/evdi/module/evdi_painter.c @@ -0,0 +1,1419 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013 - 2020 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include "linux/thread_info.h" +#include "linux/mm.h" +#include +#if KERNEL_VERSION(5, 16, 0) <= LINUX_VERSION_CODE || defined(EL8) || defined(EL9) +#include +#include +#include +#elif KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE +#else +#include +#endif +#include +#include "evdi_drm.h" +#include "evdi_drm_drv.h" +#include "evdi_cursor.h" +#include "evdi_params.h" +#include "evdi_i2c.h" +#include +#include +#include +#include + +#include +#include +#if KERNEL_VERSION(5, 4, 0) <= LINUX_VERSION_CODE || defined(EL8) +#include +#endif + +/* Import of DMA_BUF namespace was reverted in EL8 */ +#if KERNEL_VERSION(6, 13, 0) <= LINUX_VERSION_CODE || defined(EL10) +MODULE_IMPORT_NS("DMA_BUF"); +#elif KERNEL_VERSION(5, 16, 0) <= LINUX_VERSION_CODE || defined(EL9) +MODULE_IMPORT_NS(DMA_BUF); +#endif + +#if KERNEL_VERSION(5, 1, 0) <= LINUX_VERSION_CODE || defined(EL8) +#include +#endif + +struct evdi_event_cursor_set_pending { + struct drm_pending_event base; + struct drm_evdi_event_cursor_set cursor_set; +}; + +struct evdi_event_cursor_move_pending { + struct drm_pending_event base; + struct drm_evdi_event_cursor_move cursor_move; +}; + +struct evdi_event_update_ready_pending { + struct drm_pending_event base; + struct drm_evdi_event_update_ready update_ready; +}; + +struct evdi_event_dpms_pending { + struct drm_pending_event base; + struct drm_evdi_event_dpms dpms; +}; + +struct evdi_event_mode_changed_pending { + struct drm_pending_event base; + struct drm_evdi_event_mode_changed mode_changed; +}; + +struct evdi_event_crtc_state_pending { + struct drm_pending_event base; + struct drm_evdi_event_crtc_state crtc_state; +}; + +struct evdi_event_ddcci_data_pending { + struct drm_pending_event base; + struct drm_evdi_event_ddcci_data ddcci_data; +}; + +#define MAX_DIRTS 16 +#define EDID_EXT_BLOCK_SIZE 128 +#define MAX_EDID_SIZE (255 * EDID_EXT_BLOCK_SIZE + sizeof(struct edid)) +#define I2C_ADDRESS_DDCCI 0x37 +#define DDCCI_TIMEOUT_MS 50 + +struct evdi_painter { + bool is_connected; + struct edid *edid; + unsigned int edid_length; + + struct mutex lock; + struct drm_clip_rect dirty_rects[MAX_DIRTS]; + int num_dirts; + struct evdi_framebuffer *scanout_fb; + + struct drm_file *drm_filp; + struct drm_device *drm_device; + + bool was_update_requested; + bool needs_full_modeset; + struct drm_crtc *crtc; + struct drm_pending_vblank_event *vblank; + + struct list_head pending_events; + struct delayed_work send_events_work; + + struct completion ddcci_response_received; + char *ddcci_buffer; + unsigned int ddcci_buffer_length; + struct notifier_block vt_notifier; + int fg_console; +}; + +static void expand_rect(struct drm_clip_rect *a, const struct drm_clip_rect *b) +{ + a->x1 = min(a->x1, b->x1); + a->y1 = min(a->y1, b->y1); + a->x2 = max(a->x2, b->x2); + a->y2 = max(a->y2, b->y2); +} + +static int rect_area(const struct drm_clip_rect *r) +{ + return (r->x2 - r->x1) * (r->y2 - r->y1); +} + +static void merge_dirty_rects(struct drm_clip_rect *rects, int *count) +{ + int a, b; + + for (a = 0; a < *count - 1; ++a) { + for (b = a + 1; b < *count;) { + /* collapse to bounding rect if it is fewer pixels */ + const int area_a = rect_area(&rects[a]); + const int area_b = rect_area(&rects[b]); + struct drm_clip_rect bounding_rect = rects[a]; + + expand_rect(&bounding_rect, &rects[b]); + + if (rect_area(&bounding_rect) <= area_a + area_b) { + rects[a] = bounding_rect; + rects[b] = rects[*count - 1]; + /* repass */ + b = a + 1; + --*count; + } else { + ++b; + } + } + } +} + +static void collapse_dirty_rects(struct drm_clip_rect *rects, int *count) +{ + int i; + + EVDI_VERBOSE("Not enough space for rects. They will be collapsed"); + + for (i = 1; i < *count; ++i) + expand_rect(&rects[0], &rects[i]); + + *count = 1; +} + +static int copy_primary_pixels(struct evdi_framebuffer *efb, + char __user *buffer, + int buf_byte_stride, + int num_rects, struct drm_clip_rect *rects, + int const max_x, + int const max_y) +{ + struct drm_framebuffer *fb = &efb->base; + struct drm_clip_rect *r; + + EVDI_CHECKPT(); + + for (r = rects; r != rects + num_rects; ++r) { + const int byte_offset = r->x1 * 4; + const int byte_span = (r->x2 - r->x1) * 4; + const int src_offset = fb->offsets[0] + + fb->pitches[0] * r->y1 + byte_offset; + const char *src = (char *)efb->obj->vmapping + src_offset; + const int dst_offset = buf_byte_stride * r->y1 + byte_offset; + char __user *dst = buffer + dst_offset; + int y = r->y2 - r->y1; + + /* rect size may correspond to previous resolution */ + if (max_x < r->x2 || max_y < r->y2) { + EVDI_WARN("Rect size beyond expected dimensions\n"); + return -EFAULT; + } + + EVDI_VERBOSE("copy rect %d,%d-%d,%d\n", r->x1, r->y1, r->x2, + r->y2); + + for (; y > 0; --y) { + if (copy_to_user(dst, src, byte_span)) + return -EFAULT; + + src += fb->pitches[0]; + dst += buf_byte_stride; + } + } + + return 0; +} + +static void copy_cursor_pixels(struct evdi_framebuffer *efb, + char __user *buffer, + int buf_byte_stride, + struct evdi_cursor *cursor) +{ + evdi_cursor_lock(cursor); + if (evdi_cursor_compose_and_copy(cursor, + efb, + buffer, + buf_byte_stride)) + EVDI_ERROR("Failed to blend cursor\n"); + + evdi_cursor_unlock(cursor); +} + +#define painter_lock(painter) \ + do { \ + EVDI_VERBOSE("Painter lock\n"); \ + mutex_lock(&painter->lock); \ + } while (0) + +#define painter_unlock(painter) \ + do { \ + EVDI_VERBOSE("Painter unlock\n"); \ + mutex_unlock(&painter->lock); \ + } while (0) + +bool evdi_painter_is_connected(struct evdi_painter *painter) +{ + return painter ? painter->is_connected : false; +} + +u8 *evdi_painter_get_edid_copy(struct evdi_device *evdi) +{ + u8 *block = NULL; + + EVDI_CHECKPT(); + + painter_lock(evdi->painter); + if (evdi_painter_is_connected(evdi->painter) && + evdi->painter->edid && + evdi->painter->edid_length) { + block = kmalloc(evdi->painter->edid_length, GFP_KERNEL); + if (block) { + memcpy(block, + evdi->painter->edid, + evdi->painter->edid_length); + } + } + painter_unlock(evdi->painter); + return block; +} + +static bool is_evdi_event_squashable(struct drm_pending_event *event) +{ + return event->event->type == DRM_EVDI_EVENT_CURSOR_SET || + event->event->type == DRM_EVDI_EVENT_CURSOR_MOVE; +} + +static void evdi_painter_add_event_to_pending_list( + struct evdi_painter *painter, + struct drm_pending_event *event) +{ + unsigned long flags; + struct drm_pending_event *last_event = NULL; + struct list_head *list = NULL; + + spin_lock_irqsave(&painter->drm_device->event_lock, flags); + + list = &painter->pending_events; + if (!list_empty(list)) { + last_event = + list_last_entry(list, struct drm_pending_event, link); + } + + if (last_event && + event->event->type == last_event->event->type && + is_evdi_event_squashable(event)) { + list_replace(&last_event->link, &event->link); + kfree(last_event); + } else + list_add_tail(&event->link, list); + + spin_unlock_irqrestore(&painter->drm_device->event_lock, flags); +} + +static bool evdi_painter_flush_pending_events(struct evdi_painter *painter) +{ + unsigned long flags; + struct drm_pending_event *event_to_be_sent = NULL; + struct list_head *list = NULL; + bool has_space = false; + bool flushed_all = false; + + spin_lock_irqsave(&painter->drm_device->event_lock, flags); + + list = &painter->pending_events; + while ((event_to_be_sent = list_first_entry_or_null( + list, struct drm_pending_event, link))) { + has_space = drm_event_reserve_init_locked(painter->drm_device, + painter->drm_filp, event_to_be_sent, + event_to_be_sent->event) == 0; + if (has_space) { + list_del_init(&event_to_be_sent->link); + drm_send_event_locked(painter->drm_device, + event_to_be_sent); + } else + break; + } + + flushed_all = list_empty(&painter->pending_events); + spin_unlock_irqrestore(&painter->drm_device->event_lock, flags); + + return flushed_all; +} + +static void evdi_painter_send_event(struct evdi_painter *painter, + struct drm_pending_event *event) +{ + if (!event) { + EVDI_ERROR("Null drm event!\n"); + return; + } + + if (!painter->drm_filp) { + EVDI_VERBOSE("Painter is not connected!"); + drm_event_cancel_free(painter->drm_device, event); + return; + } + + if (!painter->drm_device) { + EVDI_WARN("Painter is not connected to drm device!\n"); + drm_event_cancel_free(painter->drm_device, event); + return; + } + + if (!painter->is_connected) { + EVDI_WARN("Painter is not connected!\n"); + drm_event_cancel_free(painter->drm_device, event); + return; + } + + evdi_painter_add_event_to_pending_list(painter, event); + if (delayed_work_pending(&painter->send_events_work)) + return; + + if (evdi_painter_flush_pending_events(painter)) + return; + + schedule_delayed_work(&painter->send_events_work, msecs_to_jiffies(5)); +} + +static struct drm_pending_event *create_update_ready_event(void) +{ + struct evdi_event_update_ready_pending *event; + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (!event) { + EVDI_ERROR("Failed to create update ready event\n"); + return NULL; + } + + event->update_ready.base.type = DRM_EVDI_EVENT_UPDATE_READY; + event->update_ready.base.length = sizeof(event->update_ready); + event->base.event = &event->update_ready.base; + return &event->base; +} + +static void evdi_painter_send_update_ready(struct evdi_painter *painter) +{ + struct drm_pending_event *event = create_update_ready_event(); + + evdi_painter_send_event(painter, event); +} + +static uint32_t evdi_painter_get_gem_handle(struct evdi_painter *painter, + struct evdi_gem_object *obj) +{ + uint32_t handle = 0; + + if (!obj) + return 0; + + handle = evdi_gem_object_handle_lookup(painter->drm_filp, &obj->base); + + if (handle) + return handle; + + if (drm_gem_handle_create(painter->drm_filp, + &obj->base, &handle)) { + EVDI_ERROR("Failed to create gem handle for %p\n", + painter->drm_filp); + } + + return handle; +} + +static struct drm_pending_event *create_cursor_set_event( + struct evdi_painter *painter, + struct evdi_cursor *cursor) +{ + struct evdi_event_cursor_set_pending *event; + struct evdi_gem_object *eobj = NULL; + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (!event) { + EVDI_ERROR("Failed to create cursor set event\n"); + return NULL; + } + + event->cursor_set.base.type = DRM_EVDI_EVENT_CURSOR_SET; + event->cursor_set.base.length = sizeof(event->cursor_set); + + evdi_cursor_lock(cursor); + event->cursor_set.enabled = evdi_cursor_enabled(cursor); + evdi_cursor_hotpoint(cursor, &event->cursor_set.hot_x, + &event->cursor_set.hot_y); + evdi_cursor_size(cursor, + &event->cursor_set.width, + &event->cursor_set.height); + evdi_cursor_format(cursor, &event->cursor_set.pixel_format); + evdi_cursor_stride(cursor, &event->cursor_set.stride); + eobj = evdi_cursor_gem(cursor); + event->cursor_set.buffer_handle = + evdi_painter_get_gem_handle(painter, eobj); + if (eobj) + event->cursor_set.buffer_length = eobj->base.size; + if (!event->cursor_set.buffer_handle) { + event->cursor_set.enabled = false; + event->cursor_set.buffer_length = 0; + } + evdi_cursor_unlock(cursor); + + event->base.event = &event->cursor_set.base; + return &event->base; +} + +void evdi_painter_send_cursor_set(struct evdi_painter *painter, + struct evdi_cursor *cursor) +{ + struct drm_pending_event *event = + create_cursor_set_event(painter, cursor); + + evdi_painter_send_event(painter, event); +} + +static struct drm_pending_event *create_cursor_move_event( + struct evdi_cursor *cursor) +{ + struct evdi_event_cursor_move_pending *event; + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (!event) { + EVDI_ERROR("Failed to create cursor move event\n"); + return NULL; + } + + event->cursor_move.base.type = DRM_EVDI_EVENT_CURSOR_MOVE; + event->cursor_move.base.length = sizeof(event->cursor_move); + + evdi_cursor_lock(cursor); + evdi_cursor_position( + cursor, + &event->cursor_move.x, + &event->cursor_move.y); + evdi_cursor_unlock(cursor); + + event->base.event = &event->cursor_move.base; + return &event->base; +} + +void evdi_painter_send_cursor_move(struct evdi_painter *painter, + struct evdi_cursor *cursor) +{ + struct drm_pending_event *event = create_cursor_move_event(cursor); + + evdi_painter_send_event(painter, event); +} + +static struct drm_pending_event *create_dpms_event(int mode) +{ + struct evdi_event_dpms_pending *event; + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (!event) { + EVDI_ERROR("Failed to create dpms event\n"); + return NULL; + } + + event->dpms.base.type = DRM_EVDI_EVENT_DPMS; + event->dpms.base.length = sizeof(event->dpms); + event->dpms.mode = mode; + event->base.event = &event->dpms.base; + return &event->base; +} + +static void evdi_painter_send_dpms(struct evdi_painter *painter, int mode) +{ + struct drm_pending_event *event = create_dpms_event(mode); + + EVDI_TEST_HOOK(evdi_testhook_painter_send_dpms(mode)); + evdi_painter_send_event(painter, event); +} + +static struct drm_pending_event *create_mode_changed_event( + struct drm_display_mode *current_mode, + int32_t bits_per_pixel, + uint32_t pixel_format) +{ + struct evdi_event_mode_changed_pending *event; + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (!event) { + EVDI_ERROR("Failed to create mode changed event\n"); + return NULL; + } + + event->mode_changed.base.type = DRM_EVDI_EVENT_MODE_CHANGED; + event->mode_changed.base.length = sizeof(event->mode_changed); + + event->mode_changed.hdisplay = current_mode->hdisplay; + event->mode_changed.vdisplay = current_mode->vdisplay; + event->mode_changed.vrefresh = drm_mode_vrefresh(current_mode); + event->mode_changed.bits_per_pixel = bits_per_pixel; + event->mode_changed.pixel_format = pixel_format; + + event->base.event = &event->mode_changed.base; + return &event->base; +} + +static void evdi_painter_send_mode_changed( + struct evdi_painter *painter, + struct drm_display_mode *current_mode, + int32_t bits_per_pixel, + uint32_t pixel_format) +{ + struct drm_pending_event *event = create_mode_changed_event( + current_mode, bits_per_pixel, pixel_format); + + evdi_painter_send_event(painter, event); +} + +int evdi_painter_get_num_dirts(struct evdi_painter *painter) +{ + int num_dirts; + + if (painter == NULL) { + EVDI_WARN("Painter is not connected!\n"); + return 0; + } + + painter_lock(painter); + + num_dirts = painter->num_dirts; + + painter_unlock(painter); + + return num_dirts; +} + +struct drm_clip_rect evdi_painter_framebuffer_size( + struct evdi_painter *painter) +{ + struct drm_clip_rect rect = {0, 0, 0, 0}; + struct evdi_framebuffer *efb = NULL; + + if (painter == NULL) { + EVDI_WARN("Painter is not connected!\n"); + return rect; + } + + painter_lock(painter); + efb = painter->scanout_fb; + if (!efb) { + if (painter->is_connected) + EVDI_WARN("Scanout buffer not set.\n"); + goto unlock; + } + rect.x1 = 0; + rect.y1 = 0; + rect.x2 = efb->base.width; + rect.y2 = efb->base.height; +unlock: + painter_unlock(painter); + return rect; +} + +void evdi_painter_mark_dirty(struct evdi_device *evdi, + const struct drm_clip_rect *dirty_rect) +{ + struct drm_clip_rect rect; + struct evdi_framebuffer *efb = NULL; + struct evdi_painter *painter = evdi->painter; + + if (painter == NULL) { + EVDI_WARN("Painter is not connected!\n"); + return; + } + + painter_lock(painter); + efb = painter->scanout_fb; + if (!efb) { + if (painter->is_connected) + EVDI_WARN("(card%d) Skip clip rect. Scanout buffer not set.\n", + evdi->dev_index); + goto unlock; + } + + rect = evdi_framebuffer_sanitize_rect(efb, dirty_rect); + + EVDI_VERBOSE("(card%d) %d,%d-%d,%d\n", evdi->dev_index, rect.x1, + rect.y1, rect.x2, rect.y2); + + if (painter->num_dirts == MAX_DIRTS) + merge_dirty_rects(&painter->dirty_rects[0], + &painter->num_dirts); + + if (painter->num_dirts == MAX_DIRTS) + collapse_dirty_rects(&painter->dirty_rects[0], + &painter->num_dirts); + + memcpy(&painter->dirty_rects[painter->num_dirts], &rect, sizeof(rect)); + painter->num_dirts++; + +unlock: + painter_unlock(painter); +} + +static void evdi_send_vblank(struct drm_crtc *crtc, + struct drm_pending_vblank_event *vblank) +{ + if (crtc && vblank) { + unsigned long flags = 0; + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + drm_crtc_send_vblank_event(crtc, vblank); + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + } +} + +static void evdi_painter_send_vblank(struct evdi_painter *painter) +{ + EVDI_CHECKPT(); + + evdi_send_vblank(painter->crtc, painter->vblank); + + painter->crtc = NULL; + painter->vblank = NULL; +} + +void evdi_painter_set_vblank( + struct evdi_painter *painter, + struct drm_crtc *crtc, + struct drm_pending_vblank_event *vblank) +{ + EVDI_CHECKPT(); + + if (painter) { + painter_lock(painter); + + evdi_painter_send_vblank(painter); + + if (painter->num_dirts > 0 && painter->is_connected) { + painter->crtc = crtc; + painter->vblank = vblank; + } else { + evdi_send_vblank(crtc, vblank); + } + + painter_unlock(painter); + } else { + evdi_send_vblank(crtc, vblank); + } +} + +void evdi_painter_send_update_ready_if_needed(struct evdi_painter *painter) +{ + EVDI_CHECKPT(); + if (painter) { + painter_lock(painter); +#if KERNEL_VERSION(5, 10, 0) <= LINUX_VERSION_CODE || defined(EL8) + if (painter->was_update_requested && painter->num_dirts) { +#else + if (painter->was_update_requested) { +#endif + evdi_painter_send_update_ready(painter); + painter->was_update_requested = false; + } + + painter_unlock(painter); + } else { + EVDI_WARN("Painter does not exist!\n"); + } +} + +static const char * const dpms_str[] = { "on", "standby", "suspend", "off" }; + +void evdi_painter_dpms_notify(struct evdi_painter *painter, int mode) +{ + const char *mode_str; + + if (!painter) { + EVDI_WARN("Painter does not exist!\n"); + return; + } + + if (!painter->is_connected) + return; + + switch (mode) { + case DRM_MODE_DPMS_ON: + painter->fg_console = fg_console; +#if KERNEL_VERSION(5, 4, 0) <= LINUX_VERSION_CODE || defined(EL8) + fallthrough; +#else +#endif + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + mode_str = dpms_str[mode]; + break; + default: + mode_str = "unknown"; + }; + EVDI_INFO("(card%d) Notifying display power state: %s\n", + painter->drm_device->primary->index, mode_str); + evdi_painter_send_dpms(painter, mode); +} + +static void evdi_log_pixel_format(uint32_t pixel_format, + char *buf, size_t size) +{ +#if KERNEL_VERSION(5, 14, 0) <= LINUX_VERSION_CODE || defined(EL8) + snprintf(buf, size, "pixel format %p4cc", &pixel_format); +#else + struct drm_format_name_buf format_name; + + drm_get_format_name(pixel_format, &format_name); + snprintf(buf, size, "pixel format %s", format_name.str); +#endif +} + +void evdi_painter_mode_changed_notify(struct evdi_device *evdi, + struct drm_display_mode *new_mode) +{ + struct evdi_painter *painter = evdi->painter; + struct drm_framebuffer *fb; + int bits_per_pixel; + uint32_t pixel_format; + char buf[100]; + + if (painter == NULL) + return; + + painter_lock(painter); + fb = &painter->scanout_fb->base; + if (fb == NULL) { + painter_unlock(painter); + return; + } + + bits_per_pixel = fb->format->cpp[0] * 8; + pixel_format = fb->format->format; + painter_unlock(painter); + + evdi_log_pixel_format(pixel_format, buf, sizeof(buf)); + EVDI_INFO("(card%d) Notifying mode changed: %dx%d@%d; bpp %d; %s\n", + evdi->dev_index, new_mode->hdisplay, new_mode->vdisplay, + drm_mode_vrefresh(new_mode), bits_per_pixel, buf); + + evdi_painter_send_mode_changed(painter, + new_mode, + bits_per_pixel, + pixel_format); + painter->needs_full_modeset = false; +} + +static void evdi_painter_events_cleanup(struct evdi_painter *painter) +{ + struct drm_pending_event *event, *temp; + unsigned long flags; + + spin_lock_irqsave(&painter->drm_device->event_lock, flags); + list_for_each_entry_safe(event, temp, &painter->pending_events, link) { + list_del(&event->link); + kfree(event); + } + spin_unlock_irqrestore(&painter->drm_device->event_lock, flags); + + cancel_delayed_work_sync(&painter->send_events_work); +} + +static void evdi_add_i2c_adapter(struct evdi_device *evdi) +{ + struct drm_device *ddev = evdi->ddev; + struct platform_device *platdev = to_platform_device(ddev->dev); + int result = 0; + + evdi->i2c_adapter = kzalloc(sizeof(*evdi->i2c_adapter), GFP_KERNEL); + + if (!evdi->i2c_adapter) { + EVDI_ERROR("(card%d) Failed to allocate for i2c adapter\n", + evdi->dev_index); + return; + } + + result = evdi_i2c_add(evdi->i2c_adapter, &platdev->dev, ddev->dev_private); + + if (result) { + kfree(evdi->i2c_adapter); + evdi->i2c_adapter = NULL; + EVDI_ERROR("(card%d) Failed to add i2c adapter, error %d\n", + evdi->dev_index, result); + return; + } + + EVDI_INFO("(card%d) Added i2c adapter bus number %d\n", + evdi->dev_index, evdi->i2c_adapter->nr); + + result = sysfs_create_link(&evdi->conn->kdev->kobj, + &evdi->i2c_adapter->dev.kobj, "ddc"); + + if (result) { + EVDI_ERROR("(card%d) Failed to create sysfs link, error %d\n", + evdi->dev_index, result); + return; + } +} + +static void evdi_remove_i2c_adapter(struct evdi_device *evdi) +{ + if (evdi->i2c_adapter) { + EVDI_INFO("(card%d) Removing i2c adapter bus number %d\n", + evdi->dev_index, evdi->i2c_adapter->nr); + + sysfs_remove_link(&evdi->conn->kdev->kobj, "ddc"); + + evdi_i2c_remove(evdi->i2c_adapter); + + kfree(evdi->i2c_adapter); + evdi->i2c_adapter = NULL; + } +} + +static int +evdi_painter_connect(struct evdi_device *evdi, + void const __user *edid_data, unsigned int edid_length, + uint32_t pixel_area_limit, + uint32_t pixel_per_second_limit, + struct drm_file *file, __always_unused int dev_index) +{ + struct evdi_painter *painter = evdi->painter; + struct edid *new_edid = NULL; + char buf[100]; + + evdi_log_process(buf, sizeof(buf)); + + if (edid_length < sizeof(struct edid)) { + EVDI_ERROR("Edid length too small\n"); + return -EINVAL; + } + + if (edid_length > MAX_EDID_SIZE) { + EVDI_ERROR("Edid length too large\n"); + return -EINVAL; + } + + new_edid = kzalloc(edid_length, GFP_KERNEL); + if (!new_edid) + return -ENOMEM; + + if (copy_from_user(new_edid, edid_data, edid_length)) { + EVDI_ERROR("(card%d) Failed to read edid\n", evdi->dev_index); + kfree(new_edid); + return -EFAULT; + } + + if (painter->drm_filp) + EVDI_WARN("(card%d) Double connect - replacing %p with %p\n", + evdi->dev_index, painter->drm_filp, file); + + painter_lock(painter); + + evdi->pixel_area_limit = pixel_area_limit; + evdi->pixel_per_second_limit = pixel_per_second_limit; + painter->drm_filp = file; + kfree(painter->edid); + painter->edid_length = edid_length; + painter->edid = new_edid; + painter->is_connected = true; + painter->needs_full_modeset = true; + + if (!evdi->i2c_adapter) + evdi_add_i2c_adapter(evdi); + + painter_unlock(painter); + + EVDI_INFO("(card%d) Connected with %s\n", evdi->dev_index, buf); + + drm_helper_hpd_irq_event(evdi->ddev); + + return 0; +} + +static int evdi_painter_disconnect(struct evdi_device *evdi, + struct drm_file *file) +{ + struct evdi_painter *painter = evdi->painter; + char buf[100]; + + EVDI_CHECKPT(); + + painter_lock(painter); + + if (file != painter->drm_filp) { + painter_unlock(painter); + return -EFAULT; + } + + if (painter->scanout_fb) { + drm_framebuffer_put(&painter->scanout_fb->base); + painter->scanout_fb = NULL; + } + + painter->is_connected = false; + + evdi_log_process(buf, sizeof(buf)); + EVDI_INFO("(card%d) Disconnected from %s\n", evdi->dev_index, buf); + evdi_painter_events_cleanup(painter); + + evdi_painter_send_vblank(painter); + + evdi_cursor_enable(evdi->cursor, false); + + kfree(painter->ddcci_buffer); + painter->ddcci_buffer = NULL; + painter->ddcci_buffer_length = 0; + + evdi_remove_i2c_adapter(evdi); + + painter->drm_filp = NULL; + + painter->was_update_requested = false; + evdi->cursor_events_enabled = false; + + painter_unlock(painter); + + // Signal anything waiting for ddc/ci response with NULL buffer + complete(&painter->ddcci_response_received); + + drm_helper_hpd_irq_event(evdi->ddev); + return 0; +} + +void evdi_painter_close(struct evdi_device *evdi, struct drm_file *file) +{ + EVDI_CHECKPT(); + + if (evdi->painter && file == evdi->painter->drm_filp) + evdi_painter_disconnect(evdi, file); +} + +int evdi_painter_connect_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + struct evdi_device *evdi = drm_dev->dev_private; + struct evdi_painter *painter = evdi->painter; + struct drm_evdi_connect *cmd = data; + int ret; + + EVDI_CHECKPT(); + if (painter) { + if (cmd->connected) + ret = evdi_painter_connect(evdi, + cmd->edid, + cmd->edid_length, + cmd->pixel_area_limit, + cmd->pixel_per_second_limit, + file, + cmd->dev_index); + else + ret = evdi_painter_disconnect(evdi, file); + + if (ret) { + EVDI_WARN("(card%d)(pid=%d) disconnect failed\n", + evdi->dev_index, (int)task_pid_nr(current)); + } + return ret; + } + EVDI_WARN("(card%d) Painter does not exist!\n", evdi->dev_index); + return -ENODEV; +} + +int evdi_painter_grabpix_ioctl(struct drm_device *drm_dev, void *data, + __always_unused struct drm_file *file) +{ + struct evdi_device *evdi = drm_dev->dev_private; + struct evdi_painter *painter = evdi->painter; + struct drm_evdi_grabpix *cmd = data; + struct evdi_framebuffer *efb = NULL; + struct drm_clip_rect dirty_rects[MAX_DIRTS]; + struct drm_crtc *crtc = NULL; + struct drm_pending_vblank_event *vblank = NULL; + int err; + int ret; + struct dma_buf_attachment *import_attach; + + EVDI_CHECKPT(); + + if (cmd->mode != EVDI_GRABPIX_MODE_DIRTY) { + EVDI_ERROR("Unknown command mode\n"); + return -EINVAL; + } + + if (cmd->num_rects < 1) { + EVDI_ERROR("No space for clip rects\n"); + return -EINVAL; + } + + if (!painter) + return -ENODEV; + + painter_lock(painter); + + if (painter->was_update_requested) { + EVDI_WARN("(card%d) Update ready not sent,", + evdi->dev_index); + EVDI_WARN(" but pixels are grabbed.\n"); + } + + if (painter->num_dirts < 0) { + err = -EAGAIN; + goto err_painter; + } + + merge_dirty_rects(&painter->dirty_rects[0], + &painter->num_dirts); + if (painter->num_dirts > cmd->num_rects) + collapse_dirty_rects(&painter->dirty_rects[0], + &painter->num_dirts); + + cmd->num_rects = painter->num_dirts; + memcpy(dirty_rects, painter->dirty_rects, + painter->num_dirts * sizeof(painter->dirty_rects[0])); + + efb = painter->scanout_fb; + + if (!efb) { + EVDI_ERROR("Scanout buffer not set\n"); + err = -EAGAIN; + goto err_painter; + } + + painter->num_dirts = 0; + + drm_framebuffer_get(&efb->base); + + crtc = painter->crtc; + painter->crtc = NULL; + + vblank = painter->vblank; + painter->vblank = NULL; + + + painter_unlock(painter); + + if (!efb->obj->vmapping) { + if (evdi_gem_vmap(efb->obj) == -ENOMEM) { + EVDI_ERROR("Failed to map scanout buffer\n"); + err = -EFAULT; + goto err_fb; + } + if (!efb->obj->vmapping) { + EVDI_ERROR("Inexistent vmapping\n"); + err = -EFAULT; + goto err_fb; + } + } + + if ((unsigned int)cmd->buf_width != efb->base.width || + (unsigned int)cmd->buf_height != efb->base.height) { + EVDI_DEBUG("Invalid buffer dimension\n"); + err = -EINVAL; + goto err_fb; + } + + if (copy_to_user(cmd->rects, dirty_rects, + cmd->num_rects * sizeof(cmd->rects[0]))) { + err = -EFAULT; + goto err_fb; + } + + import_attach = efb->obj->base.import_attach; + if (import_attach) { + ret = dma_buf_begin_cpu_access(import_attach->dmabuf, + DMA_FROM_DEVICE); + if (ret) { + err = -EFAULT; + goto err_fb; + } + } + + err = copy_primary_pixels(efb, + cmd->buffer, + cmd->buf_byte_stride, + cmd->num_rects, + dirty_rects, + cmd->buf_width, + cmd->buf_height); + if (err == 0 && !evdi->cursor_events_enabled) + copy_cursor_pixels(efb, + cmd->buffer, + cmd->buf_byte_stride, + evdi->cursor); + + if (import_attach) + dma_buf_end_cpu_access(import_attach->dmabuf, + DMA_FROM_DEVICE); + +err_fb: + evdi_send_vblank(crtc, vblank); + + drm_framebuffer_put(&efb->base); + + return err; + +err_painter: + painter_unlock(painter); + return err; +} + +int evdi_painter_request_update_ioctl(struct drm_device *drm_dev, + __always_unused void *data, + __always_unused struct drm_file *file) +{ + struct evdi_device *evdi = drm_dev->dev_private; + struct evdi_painter *painter = evdi->painter; + int result = 0; + + if (painter) { + painter_lock(painter); + + if (painter->was_update_requested) { + EVDI_WARN + ("(card%d) Update was already requested - ignoring\n", + evdi->dev_index); + } else { + if (painter->num_dirts > 0) + result = 1; + else + painter->was_update_requested = true; + } + + painter_unlock(painter); + + return result; + } else { + return -ENODEV; + } +} + +static void evdi_send_events_work(struct work_struct *work) +{ + struct evdi_painter *painter = + container_of(work, struct evdi_painter, send_events_work.work); + + if (evdi_painter_flush_pending_events(painter)) + return; + + schedule_delayed_work(&painter->send_events_work, msecs_to_jiffies(5)); +} + +#define vt_notifier_block_to_evdi_painter(x) container_of(x, struct evdi_painter, vt_notifier) +static int evdi_painter_vt_notifier_call(struct notifier_block *blk, + __always_unused unsigned long code, __always_unused void *_param) +{ + struct evdi_painter *painter = vt_notifier_block_to_evdi_painter(blk); + + if (painter->is_connected && fg_console != painter->fg_console && !painter->needs_full_modeset) { + EVDI_INFO("(card%d) VT switch detected\n", painter->drm_device->primary->index); + evdi_painter_dpms_notify(painter, DRM_MODE_DPMS_OFF); + evdi_painter_force_full_modeset(painter); + } + + return NOTIFY_OK; +} + + +static void evdi_painter_register_to_vt(struct evdi_painter *painter) +{ + painter->vt_notifier.notifier_call = evdi_painter_vt_notifier_call; + register_vt_notifier(&painter->vt_notifier); + + EVDI_TEST_HOOK(evdi_testhook_painter_vt_register(&painter->vt_notifier)); +} + +static void evdi_painter_unregister_from_vt(struct evdi_painter *painter) +{ + unregister_vt_notifier(&painter->vt_notifier); + painter->vt_notifier.notifier_call = NULL; + + EVDI_TEST_HOOK(evdi_testhook_painter_vt_register(&painter->vt_notifier)); +} + +int evdi_painter_init(struct evdi_device *dev) +{ + EVDI_CHECKPT(); + dev->painter = kzalloc(sizeof(*dev->painter), GFP_KERNEL); + if (dev->painter) { + mutex_init(&dev->painter->lock); + dev->painter->edid = NULL; + dev->painter->edid_length = 0; + dev->painter->needs_full_modeset = true; + dev->painter->crtc = NULL; + dev->painter->vblank = NULL; + dev->painter->drm_device = dev->ddev; + evdi_painter_register_to_vt(dev->painter); + + INIT_LIST_HEAD(&dev->painter->pending_events); + INIT_DELAYED_WORK(&dev->painter->send_events_work, + evdi_send_events_work); + init_completion(&dev->painter->ddcci_response_received); + return 0; + } + return -ENOMEM; +} + +void evdi_painter_cleanup(struct evdi_painter *painter) +{ + EVDI_CHECKPT(); + if (!painter) { + EVDI_WARN("Painter does not exist\n"); + return; + } + + painter_lock(painter); + evdi_painter_unregister_from_vt(painter); + kfree(painter->edid); + painter->edid_length = 0; + painter->edid = NULL; + if (painter->scanout_fb) + drm_framebuffer_put(&painter->scanout_fb->base); + painter->scanout_fb = NULL; + + evdi_painter_send_vblank(painter); + + evdi_painter_events_cleanup(painter); + + painter->drm_device = NULL; + painter_unlock(painter); + kfree(painter); +} + +void evdi_painter_set_scanout_buffer(struct evdi_painter *painter, + struct evdi_framebuffer *newfb) +{ + struct evdi_framebuffer *oldfb = NULL; + + if (newfb) + drm_framebuffer_get(&newfb->base); + + painter_lock(painter); + + oldfb = painter->scanout_fb; + painter->scanout_fb = newfb; + + painter_unlock(painter); + + if (oldfb) + drm_framebuffer_put(&oldfb->base); +} + +bool evdi_painter_needs_full_modeset(struct evdi_painter *painter) +{ + return painter ? painter->needs_full_modeset : false; +} + + +void evdi_painter_force_full_modeset(struct evdi_painter *painter) +{ + if (painter) + painter->needs_full_modeset = true; +} + +static struct drm_pending_event *create_ddcci_data_event(struct i2c_msg *msg) +{ + struct evdi_event_ddcci_data_pending *event; + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (!event || !msg) { + EVDI_ERROR("Failed to create ddcci data event\n"); + return NULL; + } + + event->ddcci_data.base.type = DRM_EVDI_EVENT_DDCCI_DATA; + event->ddcci_data.base.length = sizeof(event->ddcci_data); + // Truncate buffers to a maximum of 64 bytes + event->ddcci_data.buffer_length = min_t(__u16, msg->len, + sizeof(event->ddcci_data.buffer)); + memcpy(event->ddcci_data.buffer, msg->buf, + event->ddcci_data.buffer_length); + event->ddcci_data.flags = msg->flags; + event->ddcci_data.address = msg->addr; + + event->base.event = &event->ddcci_data.base; + return &event->base; +} + +static void evdi_painter_ddcci_data(struct evdi_painter *painter, struct i2c_msg *msg) +{ + struct drm_pending_event *event = create_ddcci_data_event(msg); + + reinit_completion(&painter->ddcci_response_received); + evdi_painter_send_event(painter, event); + + if (wait_for_completion_interruptible_timeout( + &painter->ddcci_response_received, + msecs_to_jiffies(DDCCI_TIMEOUT_MS)) > 0) { + + // Match expected buffer length including any truncation + const uint32_t expected_response_length = min_t(__u16, msg->len, + DDCCI_BUFFER_SIZE); + + painter_lock(painter); + + if (expected_response_length != painter->ddcci_buffer_length) + EVDI_WARN("DDCCI buffer length mismatch\n"); + else if (painter->ddcci_buffer) + memcpy(msg->buf, painter->ddcci_buffer, + painter->ddcci_buffer_length); + else + EVDI_WARN("Ignoring NULL DDCCI buffer\n"); + + painter_unlock(painter); + } else { + EVDI_WARN("DDCCI response timeout\n"); + } +} + +bool evdi_painter_i2c_data_notify(struct evdi_painter *painter, struct i2c_msg *msg) +{ + if (!evdi_painter_is_connected(painter)) { + EVDI_WARN("Painter not connected\n"); + return false; + } + + if (!msg) { + EVDI_WARN("Ignored NULL ddc/ci message\n"); + return false; + } + + if (msg->addr != I2C_ADDRESS_DDCCI) { + EVDI_WARN("Ignored ddc/ci data for address 0x%x\n", msg->addr); + return false; + } + + evdi_painter_ddcci_data(painter, msg); + return true; +} + +int evdi_painter_ddcci_response_ioctl(struct drm_device *drm_dev, void *data, + __always_unused struct drm_file *file) +{ + struct evdi_device *evdi = drm_dev->dev_private; + struct evdi_painter *painter = evdi->painter; + struct drm_evdi_ddcci_response *cmd = data; + int result = 0; + + painter_lock(painter); + + // Truncate any read to 64 bytes + painter->ddcci_buffer_length = min_t(uint32_t, cmd->buffer_length, + DDCCI_BUFFER_SIZE); + + kfree(painter->ddcci_buffer); + painter->ddcci_buffer = kzalloc(painter->ddcci_buffer_length, GFP_KERNEL); + if (!painter->ddcci_buffer) { + EVDI_ERROR("DDC buffer allocation failed\n"); + result = -ENOMEM; + goto unlock; + } + + if (copy_from_user(painter->ddcci_buffer, cmd->buffer, + painter->ddcci_buffer_length)) { + EVDI_ERROR("Failed to read ddcci_buffer\n"); + kfree(painter->ddcci_buffer); + painter->ddcci_buffer = NULL; + result = -EFAULT; + goto unlock; + } + + complete(&painter->ddcci_response_received); + +unlock: + painter_unlock(painter); + return result; +} + +int evdi_painter_enable_cursor_events_ioctl(struct drm_device *drm_dev, void *data, + __always_unused struct drm_file *file) +{ + struct evdi_device *evdi = drm_dev->dev_private; + struct drm_evdi_enable_cursor_events *cmd = data; + + evdi->cursor_events_enabled = cmd->enable; + + return 0; +} diff --git a/drivers/custom/evdi/module/evdi_params.c b/drivers/custom/evdi/module/evdi_params.c new file mode 100644 index 000000000000..6ef92862b470 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_params.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 - 2020 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include +#include + +#include "evdi_params.h" +#include "evdi_debug.h" + +unsigned int evdi_loglevel __read_mostly = EVDI_LOGLEVEL_INFO; +unsigned short int evdi_initial_device_count __read_mostly; + +module_param_named(initial_loglevel, evdi_loglevel, int, 0400); +MODULE_PARM_DESC(initial_loglevel, "Initial log level"); + +module_param_named(initial_device_count, + evdi_initial_device_count, ushort, 0644); +MODULE_PARM_DESC(initial_device_count, "Initial DRM device count (default: 0)"); + diff --git a/drivers/custom/evdi/module/evdi_params.h b/drivers/custom/evdi/module/evdi_params.h new file mode 100644 index 000000000000..5d67c6b712ab --- /dev/null +++ b/drivers/custom/evdi/module/evdi_params.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * Copyright (c) 2015 - 2020 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#ifndef EVDI_PARAMS_H +#define EVDI_PARAMS_H + +extern unsigned int evdi_loglevel; +extern unsigned short int evdi_initial_device_count; + +#endif /* EVDI_PARAMS_H */ diff --git a/drivers/custom/evdi/module/evdi_platform_dev.c b/drivers/custom/evdi/module/evdi_platform_dev.c new file mode 100644 index 000000000000..b47f2d6fd2b7 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_platform_dev.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 DisplayLink (UK) Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "evdi_platform_dev.h" +#include +#include +#include +#include + +#include "evdi_platform_drv.h" +#include "evdi_debug.h" +#include "evdi_drm_drv.h" + +struct evdi_platform_device_data { + struct drm_device *drm_dev; + struct device *parent; + bool symlinked; +}; + +struct platform_device *evdi_platform_dev_create(struct platform_device_info *info) +{ + struct platform_device *platform_dev = NULL; + + platform_dev = platform_device_register_full(info); + if (dma_set_mask(&platform_dev->dev, DMA_BIT_MASK(64))) { + EVDI_WARN("Unable to change dma mask to 64 bit. "); + EVDI_WARN("Sticking with 32 bit\n"); + } + + EVDI_INFO("Evdi platform_device create\n"); + + return platform_dev; +} + +void evdi_platform_dev_destroy(struct platform_device *dev) +{ + platform_device_unregister(dev); + EVDI_INFO("Evdi platform_device destroy\n"); +} + +int evdi_platform_device_probe(struct platform_device *pdev) +{ + struct drm_device *dev; + struct evdi_platform_device_data *data; + + EVDI_CHECKPT(); + data = kzalloc(sizeof(struct evdi_platform_device_data), GFP_KERNEL); + if (!data) + return -ENOMEM; +#if KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE || defined(EL8) +#else + #if IS_ENABLED(CONFIG_IOMMU_API) && defined(CONFIG_INTEL_IOMMU) + /* Intel-IOMMU workaround: platform-bus unsupported, force ID-mapping */ + #define INTEL_IOMMU_DUMMY_DOMAIN ((void *)-1) + pdev->dev.archdata.iommu = INTEL_IOMMU_DUMMY_DOMAIN; + #endif +#endif + + dev = evdi_drm_device_create(&pdev->dev); + if (IS_ERR_OR_NULL(dev)) + goto err_free; + + data->drm_dev = dev; + data->symlinked = false; + platform_set_drvdata(pdev, data); + return PTR_ERR_OR_ZERO(dev); + +err_free: + kfree(data); + return PTR_ERR_OR_ZERO(dev); +} + +/* EL9 kernel removed the callback that was returning void */ +#if KERNEL_VERSION(6, 11, 0) <= LINUX_VERSION_CODE +void evdi_platform_device_remove(struct platform_device *pdev) +#else +int evdi_platform_device_remove(struct platform_device *pdev) +#endif +{ + struct evdi_platform_device_data *data = platform_get_drvdata(pdev); + + EVDI_CHECKPT(); + + evdi_drm_device_remove(data->drm_dev); + kfree(data); +#if KERNEL_VERSION(6, 11, 0) <= LINUX_VERSION_CODE +#else + return 0; +#endif +} + +bool evdi_platform_device_is_free(struct platform_device *pdev) +{ + struct evdi_platform_device_data *data = platform_get_drvdata(pdev); + struct evdi_device *evdi = data->drm_dev->dev_private; + + if (evdi && !evdi_painter_is_connected(evdi->painter) && + !data->symlinked) + return true; + return false; +} + +void evdi_platform_device_link(struct platform_device *pdev, + struct device *parent) +{ + struct evdi_platform_device_data *data = NULL; + int ret = 0; + + if (!parent || !pdev) + return; + + data = platform_get_drvdata(pdev); + if (!evdi_platform_device_is_free(pdev)) { + EVDI_FATAL("Device is already attached can't symlink again\n"); + return; + } + + ret = sysfs_create_link(&pdev->dev.kobj, &parent->kobj, "device"); + if (ret) { + EVDI_FATAL("Failed to create sysfs link from evdi to parent device\n"); + } else { + data->symlinked = true; + data->parent = parent; + } +} + +void evdi_platform_device_unlink_if_linked_with(struct platform_device *pdev, + struct device *parent) +{ + struct evdi_platform_device_data *data = platform_get_drvdata(pdev); + + if (parent && data->parent == parent) { + sysfs_remove_link(&pdev->dev.kobj, "device"); + data->symlinked = false; + data->parent = NULL; + EVDI_INFO("Detached from parent device\n"); + } +} diff --git a/drivers/custom/evdi/module/evdi_platform_dev.h b/drivers/custom/evdi/module/evdi_platform_dev.h new file mode 100644 index 000000000000..5dddcbb72e62 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_platform_dev.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * evdi_platform_dev.h + * + * Copyright (c) 2020 DisplayLink (UK) Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _EVDI_PLATFORM_DEV_H_ +#define _EVDI_PLATFORM_DEV_H_ + +#include +#include + +struct platform_device_info; +struct platform_device; +struct drm_driver; +struct device; + +struct platform_device *evdi_platform_dev_create(struct platform_device_info *info); +void evdi_platform_dev_destroy(struct platform_device *dev); + +int evdi_platform_device_probe(struct platform_device *pdev); +/* EL9 kernel removed the callback that was returning void */ +#if KERNEL_VERSION(6, 11, 0) <= LINUX_VERSION_CODE +void evdi_platform_device_remove(struct platform_device *pdev); +#else +int evdi_platform_device_remove(struct platform_device *pdev); +#endif +bool evdi_platform_device_is_free(struct platform_device *pdev); +void evdi_platform_device_link(struct platform_device *pdev, + struct device *parent); +void evdi_platform_device_unlink_if_linked_with(struct platform_device *pdev, + struct device *parent); + +#endif + diff --git a/drivers/custom/evdi/module/evdi_platform_drv.c b/drivers/custom/evdi/module/evdi_platform_drv.c new file mode 100644 index 000000000000..b83f12fe44e1 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_platform_drv.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + * Copyright (c) 2015 - 2020 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include +#include +#include +#include +#ifdef CONFIG_USB_SUPPORT +#include +#endif + +#include "evdi_params.h" +#include "evdi_debug.h" +#include "evdi_platform_drv.h" +#include "evdi_platform_dev.h" +#include "evdi_sysfs.h" + +MODULE_AUTHOR("DisplayLink (UK) Ltd."); +MODULE_DESCRIPTION("Extensible Virtual Display Interface"); +MODULE_LICENSE("GPL"); + +#define EVDI_DEVICE_COUNT_MAX 16 + +static struct evdi_platform_drv_context { + struct device *root_dev; + unsigned int dev_count; + struct platform_device *devices[EVDI_DEVICE_COUNT_MAX]; +#ifdef CONFIG_USB_SUPPORT + struct notifier_block usb_notifier; +#endif + struct mutex lock; +} g_ctx; + +#define evdi_platform_drv_context_lock(ctx) \ + mutex_lock(&ctx->lock) + +#define evdi_platform_drv_context_unlock(ctx) \ + mutex_unlock(&ctx->lock) + +#ifdef CONFIG_USB_SUPPORT +static int evdi_platform_drv_usb(__always_unused struct notifier_block *nb, + unsigned long action, + void *data) +{ + struct usb_device *usb_dev = (struct usb_device *)(data); + struct platform_device *pdev; + int i = 0; + + if (!usb_dev) + return 0; + if (action != BUS_NOTIFY_DEL_DEVICE) + return 0; + + for (i = 0; i < EVDI_DEVICE_COUNT_MAX; ++i) { + pdev = g_ctx.devices[i]; + if (!pdev) + continue; + evdi_platform_device_unlink_if_linked_with(pdev, &usb_dev->dev); + if (pdev->dev.parent == &usb_dev->dev) { + EVDI_INFO("Parent USB removed. Removing evdi.%d\n", i); + evdi_platform_dev_destroy(pdev); + evdi_platform_drv_context_lock((&g_ctx)); + g_ctx.dev_count--; + g_ctx.devices[i] = NULL; + evdi_platform_drv_context_unlock((&g_ctx)); + } + } + return 0; +} +#endif + +static int evdi_platform_drv_get_free_idx(struct evdi_platform_drv_context *ctx) +{ + int i; + + for (i = 0; i < EVDI_DEVICE_COUNT_MAX; ++i) { + if (ctx->devices[i] == NULL) + return i; + } + return -ENOMEM; +} + +static struct platform_device *evdi_platform_drv_get_free_device(struct evdi_platform_drv_context *ctx) +{ + int i; + struct platform_device *pdev = NULL; + + for (i = 0; i < EVDI_DEVICE_COUNT_MAX; ++i) { + pdev = ctx->devices[i]; + if (pdev && evdi_platform_device_is_free(pdev)) + return pdev; + } + return NULL; +} + + +static struct platform_device *evdi_platform_drv_create_new_device(struct evdi_platform_drv_context *ctx) +{ + struct platform_device *pdev = NULL; + struct platform_device_info pdevinfo = { + .parent = NULL, + .name = DRIVER_NAME, + .id = evdi_platform_drv_get_free_idx(ctx), + .res = NULL, + .num_res = 0, + .data = NULL, + .size_data = 0, + .dma_mask = DMA_BIT_MASK(32), + }; + + if (pdevinfo.id < 0 || ctx->dev_count >= EVDI_DEVICE_COUNT_MAX) { + EVDI_ERROR("Evdi device add failed. Too many devices.\n"); + return ERR_PTR(-EINVAL); + } + + pdev = evdi_platform_dev_create(&pdevinfo); + ctx->devices[pdevinfo.id] = pdev; + ctx->dev_count++; + + return pdev; +} + +int evdi_platform_device_add(struct device *device, struct device *parent) +{ + struct evdi_platform_drv_context *ctx = + (struct evdi_platform_drv_context *)dev_get_drvdata(device); + struct platform_device *pdev = NULL; + + evdi_platform_drv_context_lock(ctx); + if (parent) + pdev = evdi_platform_drv_get_free_device(ctx); + + if (IS_ERR_OR_NULL(pdev)) + pdev = evdi_platform_drv_create_new_device(ctx); + evdi_platform_drv_context_unlock(ctx); + + if (IS_ERR_OR_NULL(pdev)) + return -EINVAL; + + evdi_platform_device_link(pdev, parent); + return 0; +} + +int evdi_platform_add_devices(struct device *device, unsigned int val) +{ + unsigned int dev_count = evdi_platform_device_count(device); + + if (val == 0) { + EVDI_WARN("Adding 0 devices has no effect\n"); + return 0; + } + if (val > EVDI_DEVICE_COUNT_MAX - dev_count) { + EVDI_ERROR("Evdi device add failed. Too many devices.\n"); + return -EINVAL; + } + + EVDI_INFO("Increasing device count to %u\n", dev_count + val); + while (val-- && evdi_platform_device_add(device, NULL) == 0) + ; + return 0; +} + +void evdi_platform_remove_all_devices(struct device *device) +{ + int i; + struct evdi_platform_drv_context *ctx = + (struct evdi_platform_drv_context *)dev_get_drvdata(device); + + evdi_platform_drv_context_lock(ctx); + for (i = 0; i < EVDI_DEVICE_COUNT_MAX; ++i) { + if (ctx->devices[i]) { + EVDI_INFO("Removing evdi %d\n", i); + evdi_platform_dev_destroy(ctx->devices[i]); + g_ctx.dev_count--; + g_ctx.devices[i] = NULL; + } + } + ctx->dev_count = 0; + evdi_platform_drv_context_unlock(ctx); +} + +unsigned int evdi_platform_device_count(struct device *device) +{ + unsigned int count = 0; + struct evdi_platform_drv_context *ctx = NULL; + + ctx = (struct evdi_platform_drv_context *)dev_get_drvdata(device); + evdi_platform_drv_context_lock(ctx); + count = ctx->dev_count; + evdi_platform_drv_context_unlock(ctx); + + return count; + +} + +static struct platform_driver evdi_platform_driver = { + .probe = evdi_platform_device_probe, + .remove = evdi_platform_device_remove, + .driver = { + .name = DRIVER_NAME, + .mod_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + } +}; + +static int __init evdi_init(void) +{ + int ret; + + EVDI_INFO("Initialising logging on level %u\n", evdi_loglevel); + EVDI_INFO("Atomic driver: yes\n"); + + memset(&g_ctx, 0, sizeof(g_ctx)); + g_ctx.root_dev = root_device_register(DRIVER_NAME); +#ifdef CONFIG_USB_SUPPORT + g_ctx.usb_notifier.notifier_call = evdi_platform_drv_usb; +#endif + mutex_init(&g_ctx.lock); + dev_set_drvdata(g_ctx.root_dev, &g_ctx); + +#ifdef CONFIG_USB_SUPPORT + usb_register_notify(&g_ctx.usb_notifier); +#endif + evdi_sysfs_init(g_ctx.root_dev); + ret = platform_driver_register(&evdi_platform_driver); + if (ret) + return ret; + + if (evdi_initial_device_count) + return evdi_platform_add_devices( + g_ctx.root_dev, evdi_initial_device_count); + + return 0; +} + +static void __exit evdi_exit(void) +{ + EVDI_CHECKPT(); + evdi_platform_remove_all_devices(g_ctx.root_dev); + platform_driver_unregister(&evdi_platform_driver); + + if (!PTR_ERR_OR_ZERO(g_ctx.root_dev)) { + evdi_sysfs_exit(g_ctx.root_dev); +#ifdef CONFIG_USB_SUPPORT + usb_unregister_notify(&g_ctx.usb_notifier); +#endif + dev_set_drvdata(g_ctx.root_dev, NULL); + root_device_unregister(g_ctx.root_dev); + } + EVDI_INFO("Exit %s driver\n", DRIVER_NAME); +} + +module_init(evdi_init); +module_exit(evdi_exit); diff --git a/drivers/custom/evdi/module/evdi_platform_drv.h b/drivers/custom/evdi/module/evdi_platform_drv.h new file mode 100644 index 000000000000..49171600a481 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_platform_drv.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * evdi_platform_drv.h + * + * Copyright (c) 2020 DisplayLink (UK) Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _EVDI_PLATFORM_DRV_H_ +#define _EVDI_PLATFORM_DRV_H_ + +#include + +struct device; +struct platform_device_info; + +#define DRIVER_NAME "evdi" +#define DRIVER_DESC "Extensible Virtual Display Interface" +#if KERNEL_VERSION(6, 14, 0) <= LINUX_VERSION_CODE +#else +#define DRIVER_DATE "20250630" +#endif + +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 14 +#define DRIVER_PATCH 11 + +void evdi_platform_remove_all_devices(struct device *device); +unsigned int evdi_platform_device_count(struct device *device); +int evdi_platform_add_devices(struct device *device, unsigned int val); +int evdi_platform_device_add(struct device *device, struct device *parent); + +#endif + diff --git a/drivers/custom/evdi/module/evdi_sysfs.c b/drivers/custom/evdi/module/evdi_sysfs.c new file mode 100644 index 000000000000..a643434dbb4d --- /dev/null +++ b/drivers/custom/evdi/module/evdi_sysfs.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * evdi_sysfs.c + * + * Copyright (c) 2020 DisplayLink (UK) Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include +#include +#include + +#include "evdi_sysfs.h" +#include "evdi_params.h" +#include "evdi_debug.h" +#include "evdi_platform_drv.h" + +#define MAX_EVDI_USB_ADDR 10 + +static ssize_t version_show(__always_unused struct device *dev, + __always_unused struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u.%u.%u\n", DRIVER_MAJOR, + DRIVER_MINOR, DRIVER_PATCH); +} + +static ssize_t count_show(__always_unused struct device *dev, + __always_unused struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", evdi_platform_device_count(dev)); +} + +struct evdi_usb_addr { + int addr[MAX_EVDI_USB_ADDR]; + int len; + struct usb_device *usb; +}; + +#ifdef CONFIG_USB_SUPPORT +static int evdi_platform_device_attach(struct device *device, + struct evdi_usb_addr *parent_addr); + +static ssize_t add_device_with_usb_path(struct device *dev, + const char *buf, size_t count) +{ + char *usb_path = kstrdup(buf, GFP_KERNEL); + char *temp_path = usb_path; + char *bus_token; + char *usb_token; + char *usb_token_copy = NULL; + char *token; + char *bus; + char *port; + struct evdi_usb_addr usb_addr; + + if (!usb_path) + return -ENOMEM; + + memset(&usb_addr, 0, sizeof(usb_addr)); + temp_path = strnstr(temp_path, "usb:", count); + if (!temp_path) + goto err_parse_usb_path; + + + temp_path = strim(temp_path); + + bus_token = strsep(&temp_path, ":"); + if (!bus_token) + goto err_parse_usb_path; + + usb_token = strsep(&temp_path, ":"); + if (!usb_token) + goto err_parse_usb_path; + + /* Separate trailing ':*' from usb_token */ + strsep(&temp_path, ":"); + + token = usb_token_copy = kstrdup(usb_token, GFP_KERNEL); + bus = strsep(&token, "-"); + if (!bus) + goto err_parse_usb_path; + if (kstrtouint(bus, 10, &usb_addr.addr[usb_addr.len++])) + goto err_parse_usb_path; + + do { + port = strsep(&token, "."); + if (!port) + goto err_parse_usb_path; + if (kstrtouint(port, 10, &usb_addr.addr[usb_addr.len++])) + goto err_parse_usb_path; + } while (token && port && usb_addr.len < MAX_EVDI_USB_ADDR); + + if (evdi_platform_device_attach(dev, &usb_addr) != 0) { + EVDI_ERROR("Unable to attach to: %s\n", buf); + kfree(usb_path); + kfree(usb_token_copy); + return -EINVAL; + } + + EVDI_INFO("Attaching to %s:%s\n", bus_token, usb_token); + kfree(usb_path); + kfree(usb_token_copy); + return count; + +err_parse_usb_path: + EVDI_ERROR("Unable to parse usb path: %s", buf); + kfree(usb_path); + kfree(usb_token_copy); + return -EINVAL; +} + +static int find_usb_device_at_path(struct usb_device *usb, void *data) +{ + struct evdi_usb_addr *find_path = (struct evdi_usb_addr *)(data); + struct usb_device *pdev = usb; + int port = 0; + int i; + + i = find_path->len - 1; + while (pdev != NULL && i >= 0 && i < MAX_EVDI_USB_ADDR) { + port = pdev->portnum; + if (port == 0) + port = pdev->bus->busnum; + + if (port != find_path->addr[i]) + return 0; + + if (pdev->parent == NULL && i == 0) { + find_path->usb = usb; + return 1; + } + pdev = pdev->parent; + i--; + } + + return 0; +} + +static int evdi_platform_device_attach(struct device *device, + struct evdi_usb_addr *parent_addr) +{ + struct device *parent = NULL; + + if (!parent_addr) + return -EINVAL; + + if (!usb_for_each_dev(parent_addr, find_usb_device_at_path) || + !parent_addr->usb) + return -EINVAL; + + parent = &parent_addr->usb->dev; + return evdi_platform_device_add(device, parent); +} + +#else /* !CONFIG_USB_SUPPORT */ + +static ssize_t add_device_with_usb_path(struct device *dev, + const char *buf, size_t count) +{ + return -EINVAL; +} + +#endif /* CONFIG_USB_SUPPORT */ + +static ssize_t add_store(struct device *dev, + __always_unused struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int val; + int ret; + + if (strnstr(buf, "usb:", count)) + return add_device_with_usb_path(dev, buf, count); + + if (kstrtouint(buf, 10, &val)) { + EVDI_ERROR("Invalid device count \"%s\"\n", buf); + return -EINVAL; + } + + ret = evdi_platform_add_devices(dev, val); + if (ret) + return ret; + + return count; +} + +static ssize_t remove_all_store(struct device *dev, + __always_unused struct device_attribute *attr, + __always_unused const char *buf, + size_t count) +{ + evdi_platform_remove_all_devices(dev); + return count; +} + +static ssize_t loglevel_show(__always_unused struct device *dev, + __always_unused struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", evdi_loglevel); +} + +static ssize_t loglevel_store(__always_unused struct device *dev, + __always_unused struct device_attribute *attr, + const char *buf, + size_t count) +{ + unsigned int val; + + if (kstrtouint(buf, 10, &val)) { + EVDI_ERROR("Unable to parse %u\n", val); + return -EINVAL; + } + if (val > EVDI_LOGLEVEL_VERBOSE) { + EVDI_ERROR("Invalid loglevel %u\n", val); + return -EINVAL; + } + + EVDI_INFO("Setting loglevel to %u\n", val); + evdi_loglevel = val; + return count; +} + +static struct device_attribute evdi_device_attributes[] = { + __ATTR_RO(count), + __ATTR_RO(version), + __ATTR_RW(loglevel), + __ATTR_WO(add), + __ATTR_WO(remove_all) +}; + +void evdi_sysfs_init(struct device *root) +{ + unsigned int i; + + if (!PTR_ERR_OR_ZERO(root)) + for (i = 0; i < ARRAY_SIZE(evdi_device_attributes); i++) + device_create_file(root, &evdi_device_attributes[i]); +} + +void evdi_sysfs_exit(struct device *root) +{ + unsigned int i; + + if (PTR_ERR_OR_ZERO(root)) { + EVDI_ERROR("root device is null"); + return; + } + for (i = 0; i < ARRAY_SIZE(evdi_device_attributes); i++) + device_remove_file(root, &evdi_device_attributes[i]); +} + diff --git a/drivers/custom/evdi/module/evdi_sysfs.h b/drivers/custom/evdi/module/evdi_sysfs.h new file mode 100644 index 000000000000..9207bf77e3a0 --- /dev/null +++ b/drivers/custom/evdi/module/evdi_sysfs.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * evdi_sysfs.h + * + * Copyright (c) 2020 DisplayLink (UK) Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _EVDI_SYSFS_H_ +#define _EVDI_SYSFS_H_ + +struct device; + +void evdi_sysfs_init(struct device *root); +void evdi_sysfs_exit(struct device *root); + +#endif diff --git a/drivers/custom/evdi/module/tests/Makefile b/drivers/custom/evdi/module/tests/Makefile new file mode 100644 index 000000000000..5714ba7ac2c1 --- /dev/null +++ b/drivers/custom/evdi/module/tests/Makefile @@ -0,0 +1,5 @@ + +ccflags-$(CONFIG_DRM_EVDI_KUNIT_TEST) += -I$(srctree)/drivers/gpu/drm/evdi + +obj-$(CONFIG_DRM_EVDI_KUNIT_TEST) += evdi_test.o test_evdi_vt_switch.o evdi_fake_user_client.o evdi_fake_compositor.o + diff --git a/drivers/custom/evdi/module/tests/evdi_fake_compositor.c b/drivers/custom/evdi/module/tests/evdi_fake_compositor.c new file mode 100644 index 000000000000..afa14735cf7f --- /dev/null +++ b/drivers/custom/evdi/module/tests/evdi_fake_compositor.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + + +#include +#include +#include + +#include +#include + +#include + +#include "evdi_drm_drv.h" +#include "evdi_drm.h" +#include "tests/evdi_test.h" +#include "tests/evdi_fake_compositor.h" + + +void evdi_fake_compositor_create(struct kunit *test) +{ + struct kunit_resource *resource; + struct evdi_fake_compositor_data *compositor_data; + + resource = kunit_find_named_resource(test, "fake_wayland"); + if (resource) { + KUNIT_FAIL(test, "fake_wayland data already exists"); + return; + } + + resource = kunit_kzalloc(test, sizeof(struct kunit_resource), GFP_KERNEL); + compositor_data = kunit_kzalloc(test, sizeof(struct evdi_fake_compositor_data), GFP_KERNEL); + kunit_add_named_resource(test, NULL, NULL, resource, "fake_wayland", compositor_data); + + static const struct drm_display_mode default_mode = { + DRM_SIMPLE_MODE(640, 480, 64, 48) + }; + struct evdi_framebuffer efb = { + .base = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { 4*640, 0, 0 }, + }, + .obj = NULL, + .active = true + }; + + compositor_data->efb = kunit_kzalloc(test, sizeof(struct evdi_framebuffer), GFP_KERNEL); + memcpy(compositor_data->efb, &efb, sizeof(struct evdi_framebuffer)); + memcpy(&compositor_data->mode, &default_mode, sizeof(default_mode)); +} + +void evdi_fake_compositor_connect(struct kunit *test, struct drm_device *device) +{ + struct kunit_resource *resource = kunit_find_named_resource(test, "fake_wayland"); + struct evdi_fake_compositor_data *compositor_data = resource->data; + struct evdi_device *evdi = (struct evdi_device *)device->dev_private; + + evdi_painter_set_scanout_buffer(evdi->painter, compositor_data->efb); + evdi_painter_mode_changed_notify(evdi, &compositor_data->mode); + evdi_painter_dpms_notify(evdi->painter, DRM_MODE_DPMS_ON); +} + +void evdi_fake_compositor_disconnect(__maybe_unused struct kunit *test, __maybe_unused struct drm_device *device) +{ +} + diff --git a/drivers/custom/evdi/module/tests/evdi_fake_compositor.h b/drivers/custom/evdi/module/tests/evdi_fake_compositor.h new file mode 100644 index 000000000000..efe8354175a3 --- /dev/null +++ b/drivers/custom/evdi/module/tests/evdi_fake_compositor.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (c) 2024 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#ifndef EVDI_FAKE_COMPOSITOR_H +#define EVDI_FAKE_COMPOSITOR_H + +#ifdef CONFIG_DRM_EVDI_KUNIT_TEST + + +#include +#include +#include + + + +/* kunit tests helpers faking userspace compositor leveraging evdi to add virtual display. + * e.g. Xorg, gnome-wayland, kwin + */ +struct evdi_fake_compositor_data { + struct evdi_framebuffer *efb; + struct drm_display_mode mode; +}; + +void evdi_fake_compositor_create(struct kunit *test); +void evdi_fake_compositor_connect(struct kunit *test, struct drm_device *device); +void evdi_fake_compositor_disconnect(struct kunit *test, struct drm_device *device); + + +#endif // CONFIG_DRM_EVDI_KUNIT_TEST +#endif // EVDI_FAKE_COMPOSITOR_H + diff --git a/drivers/custom/evdi/module/tests/evdi_fake_user_client.c b/drivers/custom/evdi/module/tests/evdi_fake_user_client.c new file mode 100644 index 000000000000..76e4c128830f --- /dev/null +++ b/drivers/custom/evdi/module/tests/evdi_fake_user_client.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + + +#include +#include +#include + +#include +#include + +#include + +#include "evdi_drm_drv.h" +#include "evdi_drm.h" +#include "tests/evdi_test.h" +#include "tests/evdi_fake_user_client.h" + +/* copied from drm/tests/drm_kunit_edid.h */ +static const unsigned char test_edid_dvi_1080p[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x2a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x01, 0x03, 0x81, 0xa0, 0x5a, 0x78, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38, + 0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0x40, 0x84, 0x63, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x44, + 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, + 0x46, 0x1e, 0x46, 0x0f, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab +}; + +void evdi_fake_user_client_create(struct kunit *test) +{ + struct kunit_resource *resource; + struct evdi_fake_user_data *user_data; + + resource = kunit_find_named_resource(test, "fake_evdi_user"); + if (resource) { + KUNIT_FAIL(test, "fake_evdi_user data already exists"); + return; + } + + resource = kunit_kzalloc(test, sizeof(struct kunit_resource), GFP_KERNEL); + user_data = kunit_kzalloc(test, sizeof(struct evdi_fake_user_data), GFP_KERNEL); + kunit_add_named_resource(test, NULL, NULL, resource, "fake_evdi_user", user_data); +} + +void evdi_fake_user_client_connect(struct kunit *test, struct drm_device *device) +{ + struct kunit_resource *resource = kunit_find_named_resource(test, "fake_evdi_user"); + struct evdi_fake_user_data *user_data = resource->data; + + void __user *user_edid = evdi_kunit_alloc_usermem(test, sizeof(test_edid_dvi_1080p)); + struct drm_evdi_connect connect_data = { + .connected = true, + .dev_index = device->primary->index, + .edid = user_edid, + .edid_length = sizeof(test_edid_dvi_1080p), + .pixel_area_limit = 1920 * 1080, + .pixel_per_second_limit = 1920 * 1080 * 60, + }; + + if (copy_to_user((unsigned char * __user)connect_data.edid, test_edid_dvi_1080p, sizeof(test_edid_dvi_1080p))) + KUNIT_FAIL(test, "Failed to copy edid to userspace memory"); + user_data->file = mock_drm_getfile(device->primary, O_RDWR); + + evdi_painter_connect_ioctl(device, &connect_data, user_data->file->private_data); +} + +void evdi_fake_user_client_disconnect(struct kunit *test, struct drm_device *device) +{ + struct kunit_resource *resource = kunit_find_named_resource(test, "fake_evdi_user"); + struct evdi_fake_user_data *user_data = resource->data; + + if (user_data->file) { + fput(user_data->file); + user_data->file = NULL; + flush_delayed_fput(); + } +} + diff --git a/drivers/custom/evdi/module/tests/evdi_fake_user_client.h b/drivers/custom/evdi/module/tests/evdi_fake_user_client.h new file mode 100644 index 000000000000..f8db226c2bcf --- /dev/null +++ b/drivers/custom/evdi/module/tests/evdi_fake_user_client.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (c) 2024 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#ifndef EVDI_FAKE_USER_CLIENT_H +#define EVDI_FAKE_USER_CLIENT_H + +#ifdef CONFIG_DRM_EVDI_KUNIT_TEST + + +#include +#include +#include + +#include +#include +#include + +/* kunit tests helpers faking evdi userspace client, usually displaylink-driver daemon. */ + +struct evdi_fake_user_data { + struct file *file; +}; +void __user *evdi_kunit_alloc_usermem(struct kunit *test, unsigned int size); + +void evdi_fake_user_client_create(struct kunit *test); +void evdi_fake_user_client_connect(struct kunit *test, struct drm_device *device); +void evdi_fake_user_client_disconnect(struct kunit *test, struct drm_device *device); + +#endif // CONFIG_DRM_EVDI_KUNIT_TEST +#endif // EVDI_FAKE_USER_CLIENT_H + diff --git a/drivers/custom/evdi/module/tests/evdi_test.c b/drivers/custom/evdi/module/tests/evdi_test.c new file mode 100644 index 000000000000..4fbe566d223a --- /dev/null +++ b/drivers/custom/evdi/module/tests/evdi_test.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "evdi_drm_drv.h" + +void __user *evdi_kunit_alloc_usermem(struct kunit *test, unsigned int size) +{ + void *kmem = kunit_kzalloc(test, size, GFP_KERNEL); + unsigned long user_addr; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, kmem); + + user_addr = kunit_vm_mmap(test, NULL, 0, size, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, 0); + KUNIT_ASSERT_NE_MSG(test, user_addr, 0, + "Could not create userspace mm"); + KUNIT_ASSERT_LT_MSG(test, user_addr, (unsigned long)TASK_SIZE, + "Failed to allocate user memory"); + + return (void __user *)user_addr; +} + +void evdi_testhook_painter_vt_register(struct notifier_block *vt_notifier) +{ + struct kunit *test = kunit_get_current_test(); + struct evdi_test_data *base = (struct evdi_test_data *)test->priv; + + if (base && base->hooks.painter_vt_register) + base->hooks.painter_vt_register(vt_notifier); +} + +void evdi_testhook_painter_send_dpms(int mode) +{ + struct kunit *test = kunit_get_current_test(); + struct evdi_test_data *base = (struct evdi_test_data *)test->priv; + + if (base && base->hooks.painter_send_dpms) + base->hooks.painter_send_dpms(mode); +} + +void evdi_testhook_drm_device_destroyed(void) +{ + struct kunit *test = kunit_get_current_test(); + struct evdi_test_data *base = (struct evdi_test_data *)test->priv; + + if (base && base->hooks.drm_device_destroyed) + base->hooks.drm_device_destroyed(); +} + +static void testhook_drm_device_destroyed(void) +{ + struct kunit *test = kunit_get_current_test(); + struct evdi_test_data *base = (struct evdi_test_data *)test->priv; + + complete(base->dev_destroyed); +} + +void evdi_test_data_init(struct kunit *test, struct evdi_test_data *data) +{ + data->parent = kunit_device_register(test, "/dev/card1"); + data->dev_destroyed = kunit_kzalloc(test, sizeof(struct completion), GFP_KERNEL); + init_completion(data->dev_destroyed); + + data->hooks.drm_device_destroyed = testhook_drm_device_destroyed; + test->priv = (void *)data; +} + +void evdi_test_data_exit(struct kunit *test, struct evdi_test_data *data) +{ + if (!wait_for_completion_timeout(data->dev_destroyed, msecs_to_jiffies(1000))) + KUNIT_FAIL(test, "Failed to wait for drm_device removal\n"); + + kunit_device_unregister(test, data->parent); +} + +static void test_evdi_create_drm_device(struct kunit *test) +{ + struct evdi_test_data *data = kunit_kzalloc(test, sizeof(struct evdi_test_data), GFP_KERNEL); + struct drm_device *dev; + + evdi_test_data_init(test, data); + + dev = evdi_drm_device_create(data->parent); + + KUNIT_EXPECT_NOT_NULL(test, dev); + + evdi_drm_device_remove(dev); + evdi_test_data_exit(test, data); + kunit_kfree(test, test->priv); +} + +static struct kunit_case evdi_test_cases[] = { + KUNIT_CASE(test_evdi_create_drm_device), + {} +}; + +static struct kunit_suite evdi_test_suite = { + .name = "drm_evdi_tests", + .test_cases = evdi_test_cases, +}; + +kunit_test_suite(evdi_test_suite); + +MODULE_LICENSE("GPL"); diff --git a/drivers/custom/evdi/module/tests/evdi_test.h b/drivers/custom/evdi/module/tests/evdi_test.h new file mode 100644 index 000000000000..e33ecaffd854 --- /dev/null +++ b/drivers/custom/evdi/module/tests/evdi_test.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (c) 2024 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + + +#ifndef EVDI_TEST_H +#define EVDI_TEST_H + +#ifdef CONFIG_DRM_EVDI_KUNIT_TEST +#define EVDI_TEST_HOOK(foo) foo +#else +#include +#define EVDI_TEST_HOOK(foo) no_printk(__stringify(foo)) +#endif + +#ifdef CONFIG_DRM_EVDI_KUNIT_TEST + +#include + +/* evdi hooks for kunit tests */ +void evdi_testhook_painter_vt_register(struct notifier_block *vt_notifier); +void evdi_testhook_painter_send_dpms(int mode); +void evdi_testhook_drm_device_destroyed(void); + +struct evdi_test_hooks { + void (*painter_vt_register)(struct notifier_block *vt_notifier); + void (*painter_send_dpms)(int mode); + void (*drm_device_destroyed)(void); +}; + +/* evdi kunit base type for test private data */ +struct evdi_test_data { + struct device *parent; + struct drm_device *dev; + struct completion *dev_destroyed; + struct evdi_test_hooks hooks; +}; + +void evdi_test_data_init(struct kunit *test, struct evdi_test_data *data); +void evdi_test_data_exit(struct kunit *test, struct evdi_test_data *data); + +/* evdi test utils */ +void __user *evdi_kunit_alloc_usermem(struct kunit *test, unsigned int size); + +#endif // CONFIG_DRM_EVDI_KUNIT_TEST +#endif // EVDI_TEST_H + diff --git a/drivers/custom/evdi/module/tests/test_evdi_vt_switch.c b/drivers/custom/evdi/module/tests/test_evdi_vt_switch.c new file mode 100644 index 000000000000..675325d1380e --- /dev/null +++ b/drivers/custom/evdi/module/tests/test_evdi_vt_switch.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 DisplayLink (UK) Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + */ + + +#include +#include +#include +#include +#include "evdi_drm_drv.h" +#include "tests/evdi_test.h" +#include "tests/evdi_fake_user_client.h" +#include "tests/evdi_fake_compositor.h" + + +struct evdi_vt_test { + struct evdi_test_data base; + struct notifier_block *vt_notifier; + int dpms_mode; +}; +#define to_evdi_vt_test(x) container_of(x, struct evdi_vt_test, base) + +static void testhook_painter_vt_register(struct notifier_block *vt_notifier) +{ + struct kunit *test = kunit_get_current_test(); + struct evdi_vt_test *data = to_evdi_vt_test(test->priv); + + data->vt_notifier = vt_notifier; +} + +static void testhook_painter_send_dpms(int mode) +{ + struct kunit *test = kunit_get_current_test(); + struct evdi_test_data *base = (struct evdi_test_data *)test->priv; + struct evdi_vt_test *data = to_evdi_vt_test(base); + + data->dpms_mode = mode; +} + +static int suite_test_vt_init(struct kunit *test) +{ + struct evdi_vt_test *data = kunit_kzalloc(test, sizeof(struct evdi_vt_test), GFP_KERNEL); + + evdi_test_data_init(test, &data->base); + data->base.hooks.painter_vt_register = testhook_painter_vt_register; + data->base.hooks.painter_send_dpms = testhook_painter_send_dpms; + data->base.dev = evdi_drm_device_create(data->base.parent); + + return 0; +} + +static void suite_test_vt_exit(struct kunit *test) +{ + struct evdi_vt_test *data = to_evdi_vt_test(test->priv); + + if (data->base.dev) { + evdi_drm_device_remove(data->base.dev); + data->base.dev = NULL; + } + + evdi_test_data_exit(test, &data->base); + kunit_kfree(test, test->priv); +} + +static int suite_test_vt_and_user_connected_init(struct kunit *test) +{ + int result = suite_test_vt_init(test); + struct evdi_vt_test *data = (struct evdi_vt_test *)test->priv; + + KUNIT_EXPECT_EQ(test, result, 0); + evdi_fake_compositor_create(test); + evdi_fake_user_client_create(test); + + KUNIT_EXPECT_NE(test, data->dpms_mode, DRM_MODE_DPMS_OFF); + evdi_fake_user_client_connect(test, data->base.dev); + evdi_fake_compositor_connect(test, data->base.dev); + + return result; +} + +static void suite_test_vt_and_user_connected_exit(struct kunit *test) +{ + struct evdi_vt_test *data = to_evdi_vt_test(test->priv); + + evdi_fake_compositor_disconnect(test, data->base.dev); + evdi_fake_user_client_disconnect(test, data->base.dev); + + return suite_test_vt_exit(test); +} + +static void test_evdi_painter_registers_for_vt(struct kunit *test) +{ + struct evdi_vt_test *data = to_evdi_vt_test(test->priv); + + KUNIT_EXPECT_NOT_NULL(test, data->vt_notifier->notifier_call); +} + +static void test_evdi_painter_unregisters_for_vt_on_removal(struct kunit *test) +{ + struct evdi_vt_test *data = to_evdi_vt_test(test->priv); + + evdi_drm_device_remove(data->base.dev); + data->base.dev = NULL; + + KUNIT_EXPECT_NULL(test, data->vt_notifier->notifier_call); +} + +static void test_evdi_painter_when_not_connected_does_not_send_dpms_off_event_on_fg_console_change(struct kunit *test) +{ + struct evdi_vt_test *data = to_evdi_vt_test(test->priv); + + KUNIT_EXPECT_NE(test, data->dpms_mode, DRM_MODE_DPMS_OFF); + + data->vt_notifier->notifier_call(data->vt_notifier, 0, NULL); + + KUNIT_EXPECT_NE(test, data->dpms_mode, DRM_MODE_DPMS_OFF); +} + +static void test_evdi_painter_when_connected_does_not_send_dpms_off_event_when_fg_console_has_not_changed(struct kunit *test) +{ + struct evdi_vt_test *data = to_evdi_vt_test(test->priv); + struct evdi_device *evdi = (struct evdi_device *)data->base.dev->dev_private; + + data->vt_notifier->notifier_call(data->vt_notifier, 0, NULL); + + KUNIT_EXPECT_EQ(test, data->dpms_mode, DRM_MODE_DPMS_ON); + KUNIT_EXPECT_FALSE(test, evdi_painter_needs_full_modeset(evdi->painter)); +} + +static void test_evdi_painter_when_connected_sends_dpms_off_event_on_fg_console_change(struct kunit *test) +{ + struct evdi_vt_test *data = to_evdi_vt_test(test->priv); + struct evdi_device *evdi = (struct evdi_device *)data->base.dev->dev_private; + + fg_console = fg_console + 1; + + data->vt_notifier->notifier_call(data->vt_notifier, 0, NULL); + + KUNIT_EXPECT_EQ(test, data->dpms_mode, DRM_MODE_DPMS_OFF); + KUNIT_EXPECT_TRUE(test, evdi_painter_needs_full_modeset(evdi->painter)); +} + +static struct kunit_case evdi_test_cases[] = { + KUNIT_CASE(test_evdi_painter_registers_for_vt), + KUNIT_CASE(test_evdi_painter_unregisters_for_vt_on_removal), + KUNIT_CASE(test_evdi_painter_when_not_connected_does_not_send_dpms_off_event_on_fg_console_change), + {} +}; + +static struct kunit_case evdi_test_cases_with_user_connected[] = { + KUNIT_CASE(test_evdi_painter_when_connected_does_not_send_dpms_off_event_when_fg_console_has_not_changed), + KUNIT_CASE(test_evdi_painter_when_connected_sends_dpms_off_event_on_fg_console_change), + {} +}; + +static struct kunit_suite evdi_test_suite = { + .name = "drm_evdi_vt_tests", + .test_cases = evdi_test_cases, + .init = suite_test_vt_init, + .exit = suite_test_vt_exit, +}; + +static struct kunit_suite evdi_test_suite_with_connected_user = { + .name = "drm_evdi_vt_tests_with_connected_user", + .test_cases = evdi_test_cases_with_user_connected, + .init = suite_test_vt_and_user_connected_init, + .exit = suite_test_vt_and_user_connected_exit, +}; + +kunit_test_suite(evdi_test_suite_with_connected_user); +kunit_test_suite(evdi_test_suite); # ---------------------------------------- # Module: facetimehd # Version: 1bd87461d3d1 # ---------------------------------------- diff --git a/drivers/custom/facetimehd/Makefile b/drivers/custom/facetimehd/Makefile new file mode 100644 index 000000000000..0ddf6667c7e8 --- /dev/null +++ b/drivers/custom/facetimehd/Makefile @@ -0,0 +1,18 @@ +facetimehd-objs := fthd_ddr.o fthd_hw.o fthd_drv.o fthd_ringbuf.o fthd_isp.o fthd_v4l2.o fthd_buffer.o fthd_debugfs.o +obj-m := facetimehd.o + +KVERSION := $(KERNELRELEASE) +ifeq ($(origin KERNELRELEASE), undefined) +KVERSION := $(shell uname -r) +endif +KDIR := /lib/modules/$(KVERSION)/build +PWD := $(shell pwd) + +all: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean + +install: + $(MAKE) -C $(KDIR) M=$(PWD) modules_install diff --git a/drivers/custom/facetimehd/fthd_buffer.c b/drivers/custom/facetimehd/fthd_buffer.c new file mode 100644 index 000000000000..fae443c1f380 --- /dev/null +++ b/drivers/custom/facetimehd/fthd_buffer.c @@ -0,0 +1,125 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2015 Sven Schnelle + * + */ + +#include +#include +#include +#include "fthd_drv.h" +#include "fthd_isp.h" +#include "fthd_hw.h" +#include "fthd_buffer.h" + +#define GET_IOMMU_PAGES(_x) (((_x) + 4095)/4096) + +struct buf_ctx { + struct fthd_plane plane[4]; + struct isp_mem_obj *isphdr; +}; + +static int iommu_allocator_init(struct fthd_private *dev_priv) +{ + dev_priv->iommu = kzalloc(sizeof(struct resource), GFP_KERNEL); + if (!dev_priv->iommu) + return -ENOMEM; + + dev_priv->iommu->start = 0; + dev_priv->iommu->end = 4095; + return 0; +} + +struct iommu_obj *iommu_allocate_sgtable(struct fthd_private *dev_priv, struct sg_table *sgtable) +{ + struct iommu_obj *obj; + struct resource *root = dev_priv->iommu; + struct scatterlist *sg; + int ret, i, pos; + int total_len = 0, dma_length; + dma_addr_t dma_addr; + + for(i = 0; i < sgtable->nents; i++) + total_len += sg_dma_len(sgtable->sgl + i); + + if (!total_len) + return NULL; + + total_len += 4095; + total_len /= 4096; + + obj = kzalloc(sizeof(struct iommu_obj), GFP_KERNEL); + if (!obj) + return NULL; + + obj->base.name = "S2 IOMMU"; + ret = allocate_resource(root, &obj->base, total_len, root->start, root->end, + 1, NULL, NULL); + if (ret) { + dev_err(&dev_priv->pdev->dev, + "Failed to allocate resource (size: %d, start: %Ld, end: %Ld)\n", + total_len, root->start, root->end); + kfree(obj); + obj = NULL; + return NULL; + } + + obj->offset = obj->base.start - root->start; + obj->size = total_len; + + pos = 0x9000 + obj->offset * 4; + for(i = 0; i < sgtable->nents; i++) { + sg = sgtable->sgl + i; + WARN_ON(sg->offset); + dma_addr = sg_dma_address(sg); + WARN_ON(dma_addr & 0xfff); + dma_addr >>= 12; + + for(dma_length = 0; dma_length < sg_dma_len(sg); dma_length += 0x1000) { + // pr_debug("IOMMU %08x -> %08llx (dma length %d)\n", pos, dma_addr, dma_length); + FTHD_S2_REG_WRITE(dma_addr++, pos); + pos += 4; + } + } + + pr_debug("allocated %d pages @ %p / offset %d\n", obj->size, obj, obj->offset); + return obj; +} + +void iommu_free(struct fthd_private *dev_priv, struct iommu_obj *obj) +{ + int i; + pr_debug("freeing %p\n", obj); + + if (!obj) + return; + + for (i = obj->offset; i < obj->offset + obj->size; i++) + FTHD_S2_REG_WRITE(0, 0x9000 + i * 4); + + release_resource(&obj->base); + kfree(obj); + obj = NULL; +} + +static void iommu_allocator_destroy(struct fthd_private *dev_priv) +{ + kfree(dev_priv->iommu); +} + +int fthd_buffer_init(struct fthd_private *dev_priv) +{ + int i; + for(i = 0; i < 0x1000; i++) + FTHD_S2_REG_WRITE(0, 0x9000 + i * 4); + + return iommu_allocator_init(dev_priv); +} + +void fthd_buffer_exit(struct fthd_private *dev_priv) +{ + iommu_allocator_destroy(dev_priv); +} diff --git a/drivers/custom/facetimehd/fthd_buffer.h b/drivers/custom/facetimehd/fthd_buffer.h new file mode 100644 index 000000000000..2e76ded90215 --- /dev/null +++ b/drivers/custom/facetimehd/fthd_buffer.h @@ -0,0 +1,73 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2015 Sven Schnelle + * + */ +#ifndef FTHD_BUFFER_H +#define FTHD_BUFFER_H + +#include +#include "fthd_buffer.h" + +enum fthd_buffer_state { + BUF_FREE, + BUF_ALLOC, + BUF_DRV_QUEUED, + BUF_HW_QUEUED, +}; + +struct dma_descriptor { + u32 addr0; + u32 addr1; + u32 addr2; + u32 field_c; + u32 field_10; + u32 field_14; + u32 count; + u32 pool; + u64 tag; +} __attribute__((packed)); + +struct dma_descriptor_list { + u32 field0; + u32 count; + struct dma_descriptor desc[4]; + char unknown[216]; +} __attribute__((packed)); + +struct iommu_obj { + struct resource base; + int size; + int offset; +}; + +struct fthd_plane { + u8 *virt; + u64 phys; + dma_addr_t dma; + int len; + struct iommu_obj *iommu; +}; + +struct h2t_buf_ctx { + enum fthd_buffer_state state; + struct vb2_buffer *vb; + struct iommu_obj *plane[4]; + struct isp_mem_obj *dma_desc_obj; + struct dma_descriptor_list dma_desc_list; + /* waitqueue for signaling buffer completion */ + wait_queue_head_t wq; + int done; +}; + +extern int setup_buffers(struct fthd_private *dev_priv); +extern int fthd_buffer_init(struct fthd_private *dev_priv); +extern void fthd_buffer_exit(struct fthd_private *dev_priv); +extern void fthd_buffer_return_handler(struct fthd_private *dev_priv, u32 offset, int size); +extern void fthd_buffer_queued_handler(struct fthd_private *dev_priv, u32 offset); +extern struct iommu_obj *iommu_allocate_sgtable(struct fthd_private *dev_priv, struct sg_table *); +extern void iommu_free(struct fthd_private *dev_priv, struct iommu_obj *obj); +#endif diff --git a/drivers/custom/facetimehd/fthd_ddr.c b/drivers/custom/facetimehd/fthd_ddr.c new file mode 100644 index 000000000000..df21537680db --- /dev/null +++ b/drivers/custom/facetimehd/fthd_ddr.c @@ -0,0 +1,675 @@ +/* + * FacetimeHD camera driver + * + * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation. + * + */ + +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) +#include +#else +#include +#endif + +#include "fthd_drv.h" +#include "fthd_hw.h" +#include "fthd_ddr.h" + +int fthd_ddr_verify_mem(struct fthd_private *dev_priv, u32 base, int count) +{ + u32 i, val, val_read; + int failed_bits = 0; + struct rnd_state state; + + prandom_seed_state(&state, 0x12345678); + + for (i = 0; i < count; i++) { + val = prandom_u32_state(&state); + FTHD_S2_MEM_WRITE(val, i * 4 + MEM_VERIFY_BASE); + } + + prandom_seed_state(&state, 0x12345678); + + for (i = 0; i < count; i++) { + val = prandom_u32_state(&state); + val_read = FTHD_S2_MEM_READ(i * 4 + MEM_VERIFY_BASE); + + failed_bits |= val ^ val_read; + } + + return ((failed_bits & 0xffff) | ((failed_bits >> 16) & 0xffff)); +} + +static int fthd_ddr_calibrate_rd_data_dly_fifo(struct fthd_private *dev_priv) +{ + u32 fifo_status[2]; + u32 rden_bytes[3]; + u32 rden_wl_setting, rden_bl_setting, setting, tmp; + u32 byte0_pass, byte1_pass; + int passed; + + rden_bytes[0] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE); + rden_bytes[1] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE0); + rden_bytes[2] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE1); + + FTHD_S2_REG_WRITE(0x30000, S2_DDR40_RDEN_BYTE); + FTHD_S2_REG_WRITE(0x30100, S2_DDR40_RDEN_BYTE0); + FTHD_S2_REG_WRITE(0x30100, S2_DDR40_RDEN_BYTE1); + + passed = 0; + setting = 1; + byte0_pass = 0; + byte1_pass = 0; + rden_wl_setting = 0; + rden_bl_setting = 0; + + while (passed == 0) { + FTHD_S2_REG_WRITE(setting & 0x7, S2_DDR40_WL_RD_DATA_DLY); + + fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM); + + tmp = FTHD_S2_REG_READ(S2_DDR40_WL_READ_FIFO_STATUS); + fifo_status[0] = tmp & 0xf; + fifo_status[1] = (tmp & 0xf0) >> 4; + + FTHD_S2_REG_WRITE(1, S2_DDR40_WL_READ_FIFO_CLEAR); + + if (fifo_status[0] == 0) { + if (byte0_pass == 0) + byte0_pass = setting; + } + + if (fifo_status[1] == 0) { + if (byte1_pass == 0) + byte1_pass = setting; + } + + if (rden_wl_setting < 57) + rden_wl_setting += 7; + else + rden_bl_setting += 7; + + if (rden_bl_setting > 63) { + setting++; + rden_wl_setting = 0; + rden_bl_setting = 0; + } + + if (byte0_pass != 0 && byte1_pass != 0) + passed = 1; + + /* Seems we default to setting=7 if no pass is found */ + if (setting > 7) { + passed = 1; + setting = 7; + + if (byte0_pass == 0) + byte0_pass = setting; + + if (byte1_pass == 0) + byte1_pass = setting; + } + + /* Write new setting */ + FTHD_S2_REG_WRITE(rden_wl_setting | 0x30000, + S2_DDR40_RDEN_BYTE); + FTHD_S2_REG_WRITE(rden_bl_setting | 0x30100, + S2_DDR40_RDEN_BYTE0); + FTHD_S2_REG_WRITE(rden_bl_setting | 0x30100, + S2_DDR40_RDEN_BYTE1); + } + + setting = byte0_pass; + + if (byte1_pass > setting) + setting = byte1_pass; + + setting++; + + if (setting > 7) + setting = 7; + + /* Restore settings */ + FTHD_S2_REG_WRITE(rden_bytes[0], S2_DDR40_RDEN_BYTE); + FTHD_S2_REG_WRITE(rden_bytes[1], S2_DDR40_RDEN_BYTE0); + FTHD_S2_REG_WRITE(rden_bytes[2], S2_DDR40_RDEN_BYTE1); + + if (setting < 7) + setting++; + + FTHD_S2_REG_WRITE(setting, S2_DDR40_WL_RD_DATA_DLY); + dev_info(&dev_priv->pdev->dev, "RD_DATA_DLY: 0x%x\n", setting); + + return 0; +} + +static int fthd_ddr_calibrate_one_re_fifo(struct fthd_private *dev_priv, + u32 *rden_byte, u32 *rden_byte0, u32 *rden_byte1) +{ + u32 fifo_status[2]; + u32 wl_start; + u32 bl_pass[2] = {0, 0}; + u32 bl_start[2] = {0, 0}; + u32 word_setting, byte_setting, passed, delta; + u32 tmp; + + delta = ((FTHD_S2_REG_READ(S2_DDR40_PHY_VDL_STATUS) & 0xffc) >> 2) / 4; + + /* Start with word and byte setting at 0 */ + FTHD_S2_REG_WRITE(0x30000, S2_DDR40_RDEN_BYTE); + FTHD_S2_REG_WRITE(0x30100, S2_DDR40_RDEN_BYTE0); + FTHD_S2_REG_WRITE(0x30100, S2_DDR40_RDEN_BYTE0); + + fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM); + + FTHD_S2_REG_WRITE(1, S2_DDR40_WL_READ_FIFO_CLEAR); + + word_setting = 0; + byte_setting = 0; + passed = 0; + + while (passed == 0) { + fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM); + + fifo_status[0] = + FTHD_S2_REG_READ(S2_DDR40_WL_READ_FIFO_STATUS) & 0xf; + fifo_status[1] = + (FTHD_S2_REG_READ(S2_DDR40_WL_READ_FIFO_STATUS) & + 0xf0) >> 4; + + FTHD_S2_REG_WRITE(1, S2_DDR40_WL_READ_FIFO_CLEAR); + + if (fifo_status[0] == 0) { + if (bl_pass[0] == 0) + bl_pass[0] = 1; + else + passed = 1; + } + + if (fifo_status[1] == 0) { + if (bl_pass[1] == 0) + bl_pass[1] = 1; + else + passed = 1; + } + + /* Still not passed */ + if (passed == 0) { + if (word_setting < 63) { + word_setting++; + FTHD_S2_REG_WRITE(0x30000 | (word_setting & 0x3f), + S2_DDR40_RDEN_BYTE); + } else { + byte_setting++; + FTHD_S2_REG_WRITE(0x30100 | (byte_setting & 0x3f), + S2_DDR40_RDEN_BYTE0); + FTHD_S2_REG_WRITE(0x30100 | (byte_setting & 0x3f), + S2_DDR40_RDEN_BYTE1); + + if (word_setting > 64) { + dev_err(&dev_priv->pdev->dev, + "RDEN byte timeout\n"); + return -EIO; + } + } + } + } + + wl_start = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE) & 0x3f; + + if (bl_pass[0] == 1) { + bl_start[0] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE0) & 0x3f; + passed = 0; + + while (passed == 0) { + byte_setting++; + if (byte_setting > 64) { + dev_err(&dev_priv->pdev->dev, + "RDEN BYTE1 timeout\n"); + return -EIO; + } + + FTHD_S2_REG_WRITE(0x30100 | (byte_setting & 0x3f), + S2_DDR40_RDEN_BYTE1); + + fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM); + + fifo_status[0] = FTHD_S2_REG_READ( + S2_DDR40_WL_READ_FIFO_STATUS) & 0xf; + fifo_status[1] = + (FTHD_S2_REG_READ(S2_DDR40_WL_READ_FIFO_STATUS) & + 0xf0) >> 4; + FTHD_S2_REG_WRITE(1, S2_DDR40_WL_READ_FIFO_CLEAR); + + if (fifo_status[1] == 0) + passed = 1; + } + + bl_start[1] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE1) & 0x3f; + } + if (bl_pass[1] == 1) { + bl_start[1] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE1) & 0x3f; + passed = 0; + + while (passed == 0) { + byte_setting++; + if (byte_setting > 64) { + dev_err(&dev_priv->pdev->dev, + "RDEN BYTE0 timeout\n"); + return -EIO; + } + + FTHD_S2_REG_WRITE(0x30100 | (byte_setting & 0x3f), + S2_DDR40_RDEN_BYTE0); + + fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM); + + fifo_status[0] = FTHD_S2_REG_READ( + S2_DDR40_WL_READ_FIFO_STATUS) & 0xf; + fifo_status[1] = FTHD_S2_REG_READ( + S2_DDR40_WL_READ_FIFO_STATUS) & 0xf0; + FTHD_S2_REG_WRITE(1, S2_DDR40_WL_READ_FIFO_CLEAR); + + if (fifo_status[0] == 0) + passed = 1; + } + + bl_start[0] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE0) & 0x3f; + } + + *rden_byte = wl_start + delta; + + if (*rden_byte > 63) { + tmp = *rden_byte - 63; + *rden_byte = 63; + *rden_byte0 = bl_start[0] + tmp; + *rden_byte1 = bl_start[1] + tmp; + } else { + *rden_byte0 = bl_start[0]; + *rden_byte1 = bl_start[1]; + } + + if (*rden_byte0 > 63) { + *rden_byte0 = 63; + } + if (*rden_byte1 > 63) { + *rden_byte1 = 63; + } + + return 0; +} + +static int fthd_ddr_calibrate_re_byte_fifo(struct fthd_private *dev_priv) +{ + u32 rden_byte = 0; + u32 rden_byte0 = 0; + u32 rden_byte1 = 0; + int ret; + + ret = fthd_ddr_calibrate_one_re_fifo(dev_priv, &rden_byte, &rden_byte0, &rden_byte1); + if (ret) + return ret; + + rden_byte = (rden_byte & 0x3f) | 0x30000; + FTHD_S2_REG_WRITE(rden_byte, S2_DDR40_RDEN_BYTE); + + rden_byte0 = (rden_byte0 & 0x3f) | 0x30100; + FTHD_S2_REG_WRITE(rden_byte0, S2_DDR40_RDEN_BYTE0); + + rden_byte1 = (rden_byte1 & 0x3f) | 0x30100; + FTHD_S2_REG_WRITE(rden_byte1, S2_DDR40_RDEN_BYTE1); + + dev_info(&dev_priv->pdev->dev, + "RE BYTE FIFO success: b0 = 0x%x, b1 = 0x%x, b = 0x%x\n", + rden_byte0, rden_byte1, rden_byte); + + return 0; +} + +/* Set default/generic read data strobe */ +static int fthd_ddr_generic_shmoo_rd_dqs(struct fthd_private *dev_priv, + u32 *fail_bits) +{ + u32 retries, setting, tmp, offset; + u32 bytes[S2_DDR40_NUM_BYTE_LANES]; + int i, j, ret, fail; + + /* Save the current byte lanes */ + for (i = 0; i < S2_DDR40_NUM_BYTE_LANES; i++) { + tmp = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE0 + + (i * S2_DDR40_BYTE_LANE_SIZE)); + bytes[i] = tmp & 0x3f; + } + + /* Clear all byte lanes */ + for (i = 0; i < S2_DDR40_NUM_BYTE_LANES; i++) { + for (j = 0; j < 8; j++) { + offset = S2_DDR40_2A38 + (i * 0xa0) + (j * 8); + + FTHD_S2_REG_WRITE(0x30000, offset - 4); + FTHD_S2_REG_WRITE(0x30000, offset); + } + } + + setting = (FTHD_S2_REG_READ(S2_DDR40_PHY_DQ_CALIB_STATUS) >> 20) & 0x3f; + + retries = 1000; + fail = 0; + + while (retries-- > 0 && !fail) { + ret = fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM); + fail_bits[0] = ret; + + if (ret == 0xffff) { + fail = 1; + break; + } + + setting++; + tmp = (setting & 0x3f) | 0x30100; + + /* Byte 0 */ + FTHD_S2_REG_WRITE(tmp, S2_DDR40_2A08); + FTHD_S2_REG_WRITE(tmp, S2_DDR40_2A0C); + + /* Byte 1 */ + FTHD_S2_REG_WRITE(tmp, S2_DDR40_2AA8); + FTHD_S2_REG_WRITE(tmp, S2_DDR40_2AAC); + + if (setting > 62) + fail = 1; + + offset = S2_DDR40_RDEN_BYTE0; + + /* Write byte lane settings */ + for (i = 0; i < 2; i++) { + bytes[i]++; + + if (bytes[i] > 62) + fail = 1; + + FTHD_S2_REG_WRITE((bytes[i] & 0x3f) | 0x30100, offset); + + offset += 0xa0; + } + } + + if (retries == 0) { + dev_err(&dev_priv->pdev->dev, "Generic shmoo RD DQS timeout\n"); + ret = -EIO; + } + + if (fail) + dev_info(&dev_priv->pdev->dev, "Generic RD DQS failed\n"); + else + dev_info(&dev_priv->pdev->dev, "Generic RD DQS succeeded\n"); + + /* It always fails, so just pass success */ + return 0; +} + +static int fthd_ddr_calibrate_rd_dqs(struct fthd_private *dev_priv, + u32 *fails, u32 *settings) +{ + s32 pass_len[16]; + u32 pass_start[16]; // u32 var_b0[16]; + u32 pass_end[16]; // u32 var_f0[16]; + int fail_sum, i, j, bit; + s32 setting; + printk(KERN_CONT "\n"); + + for (bit = 0; bit < 16; bit++) { + pass_start[bit] = 64; + pass_end[bit] = 64; + + printk(KERN_CONT "%.2d: ", bit); + + /* Start looking for start of pass */ + for (i = 0; i < 63; i++) { + fail_sum = 0; + + /* We check ahead the 6 next fail bits */ + for (j = 0; (j < 6) && ((i + j) < 64); j++) + fail_sum += fails[i + j] & (1 << bit); + + if (fail_sum) { + printk(KERN_CONT "."); + } else { + printk(KERN_CONT "O"); + + pass_start[bit] = i; + break; + } + } + + /* Start looking for end of pass */ + for (; i < 63; i++) { + if (fails[i] & (1 << bit)) { + if (pass_end[bit] == 64) + pass_end[bit] = i; + + printk(KERN_CONT "."); + } else { + printk(KERN_CONT "O"); + } + } + + /* Calculate pass length */ + pass_len[bit] = pass_end[bit] - pass_start[bit]; + + /* Calculate new setting */ + setting = (pass_len[bit] / 2) + pass_start[bit]; + if (setting < 0) + setting = 0; + else if (setting > 63) + setting = 63; + settings[bit] = setting; + + printk(KERN_CONT " : start=%d end=%d len=%d new=%d\n", pass_start[bit], + pass_end[bit], pass_len[bit], settings[bit]); + } + + // Some global stuff that I need to figure out + + return 0; +} + +static int fthd_ddr_wr_dqs_setting(struct fthd_private *dev_priv, int set_bits, + u32 *fail_bits, u32 *settings) +{ + u32 bl, setting, byte, bit, offset, tmp, start, inc, reg; + int i; + + for (setting = 0; setting < 64; setting++) { + for (byte = 0; byte < 2; byte++) { + for (bit = 0; bit < 8; bit++) { + offset = S2_DDR40_2A38 + (byte * 0xa0) + + (bit * 8); + tmp = setting | 0x30000; + + if (set_bits & 1) + FTHD_S2_REG_WRITE(tmp, offset - 4); + + if (set_bits & 2) + FTHD_S2_REG_WRITE(tmp, offset); + } + } + fail_bits[setting] = fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM); + } + + if (set_bits == 3) { + start = 0; + inc = 1; + } else if (set_bits == 2) { + start = 0; + inc = 2; + } else { + start = 1; + inc = 2; + } + + fthd_ddr_calibrate_rd_dqs(dev_priv, fail_bits, settings); + + offset = 0; + + for (bl = 0; bl < 2; bl++) { + reg = S2_DDR40_2A34 + bl * 0xa0; + + for (i = start; i < 16; i += inc) { + + if (settings[offset] == 0 || settings[offset] >= 63) { + dev_err(&dev_priv->pdev->dev, + "Bad VDL. Step %d = 0x%x\n", + offset, settings[offset]); + return -EINVAL; + } + + tmp = (settings[offset] & 0x3f) | 0x30000; + FTHD_S2_REG_WRITE(tmp, reg); + if (set_bits == 3) { + if (i & 1) + offset++; + } else { + offset++; + } + + reg += inc; + } + } + + return 0; +} + +static int fthd_ddr_calibrate_create_result(struct fthd_private *dev_priv) +{ + return 0; +} + +static int fthd_ddr_generic_shmoo_calibrate_rd_dqs( + struct fthd_private *dev_priv) +{ + u32 settings[64]; /* Don't know the real size yet */ + u32 fails[64]; /* Number of fails on a setting */ + int ret; + + ret = fthd_ddr_generic_shmoo_rd_dqs(dev_priv, fails); + if (ret) + return ret; + + ret = fthd_ddr_wr_dqs_setting(dev_priv, 3, fails, settings); + if (ret) + return ret; + + ret = fthd_ddr_wr_dqs_setting(dev_priv, 1, fails, settings); + if (ret) + return ret; + + + ret = fthd_ddr_wr_dqs_setting(dev_priv, 2, fails, settings); + if (ret) + return ret; + + /* NOP for now */ + ret = fthd_ddr_calibrate_create_result(dev_priv); + if (ret) + return ret; + + return 0; +} + +static int fthd_ddr_calibrate_wr_dq(struct fthd_private *dev_priv, u32 *fails, + u32 *settings) +{ + return 0; +} + +static int fthd_ddr_generic_shmoo_calibrate_wr_dq(struct fthd_private *dev_priv) +{ + u32 fails[64]; + u32 settings[64]; /* Size is actually 16? */ + u32 setting, offset; + int bit, bl, ret = 0; + + /* shmoo_wr_dq */ + for (setting = 0; setting < 64; setting++) { + for (bl = 0; bl < 2; bl++) { + offset = S2_DDR40_2A10 + (bl * S2_DDR40_BYTE_LANE_SIZE); + for (bit = 0; bit < 8; bit++) { + FTHD_S2_REG_WRITE(setting | 0x30000, + offset + (bit * 4)); + } + } + + fails[setting] = fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM); + } + + fails[63] = 0xffff; /* Last setting is always a fail */ + + fthd_ddr_calibrate_wr_dq(dev_priv, fails, settings); + + return ret; +} + +static int fthd_ddr_generic_shmoo_calibrate_wr_dm(struct fthd_private *dev_priv) +{ + return 0; +} + +static int fthd_ddr_generic_shmoo_calibrate_addr(struct fthd_private *dev_priv) +{ + return 0; +} + +int fthd_ddr_calibrate(struct fthd_private *dev_priv) +{ + u32 reg; + int ret; + + FTHD_S2_REG_WRITE(0, S2_DDR40_PHY_VDL_CTL); + FTHD_S2_REG_WRITE(0x200, S2_DDR40_PHY_VDL_CTL); + + while (1) { + reg = FTHD_S2_REG_READ(S2_DDR40_PHY_VDL_STATUS); + if (reg & 0x1) + break; + } + + ret = fthd_ddr_calibrate_rd_data_dly_fifo(dev_priv); + if (ret) + return ret; + + ret = fthd_ddr_calibrate_re_byte_fifo(dev_priv); + if (ret) + return ret; + + ret = fthd_ddr_generic_shmoo_calibrate_rd_dqs(dev_priv); + if (ret) + return ret; + + ret = fthd_ddr_generic_shmoo_calibrate_wr_dq(dev_priv); + if (ret) + return ret; + + ret = fthd_ddr_generic_shmoo_calibrate_wr_dm(dev_priv); + if (ret) + return ret; + + ret = fthd_ddr_generic_shmoo_calibrate_addr(dev_priv); + if (ret) + return ret; + + return 0; +} diff --git a/drivers/custom/facetimehd/fthd_ddr.h b/drivers/custom/facetimehd/fthd_ddr.h new file mode 100644 index 000000000000..162e7c343ab7 --- /dev/null +++ b/drivers/custom/facetimehd/fthd_ddr.h @@ -0,0 +1,30 @@ +/* + * Broadcom PCIe 1570 webcam driver + * + * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation. + * + */ + +#ifndef _FTHD_DDR_H +#define _FTHD_DDR_H + +#define MEM_VERIFY_BASE 0x0 /* 0x1000000 */ +#define MEM_VERIFY_NUM 128 +#define MEM_VERIFY_NUM_FULL (1 * 1024 * 1024) + +int fthd_ddr_calibrate(struct fthd_private *dev_priv); +int fthd_ddr_verify_mem(struct fthd_private *dev_priv, u32 base, int count); + +#endif diff --git a/drivers/custom/facetimehd/fthd_debugfs.c b/drivers/custom/facetimehd/fthd_debugfs.c new file mode 100644 index 000000000000..50ab5a75f14c --- /dev/null +++ b/drivers/custom/facetimehd/fthd_debugfs.c @@ -0,0 +1,198 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2015 Sven Schnelle + * + */ + +#include +#include +#include +#include "fthd_drv.h" +#include "fthd_debugfs.h" +#include "fthd_isp.h" +#include "fthd_ringbuf.h" +#include "fthd_hw.h" + +static ssize_t fthd_store_debug(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) + +{ + struct fthd_isp_debug_cmd cmd; + struct fthd_private *dev_priv = file->private_data; + int ret, opcode; + char buf[64]; + int len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + memset(&cmd, 0, sizeof(cmd)); + + if (!strcmp(buf, "ps")) + opcode = CISP_CMD_DEBUG_PS; + else if (!strcmp(buf, "banner")) + opcode = CISP_CMD_DEBUG_BANNER; + else if (!strcmp(buf, "get_root")) + opcode = CISP_CMD_DEBUG_GET_ROOT_HANDLE; + else if (!strcmp(buf, "heap")) + opcode = CISP_CMD_DEBUG_HEAP_STATISTICS; + else if (!strcmp(buf, "irq")) + opcode = CISP_CMD_DEBUG_IRQ_STATISTICS; + else if (!strcmp(buf, "semaphore")) + opcode = CISP_CMD_DEBUG_SHOW_SEMAPHORE_STATUS; + else if (!strcmp(buf, "wiring")) + opcode = CISP_CMD_DEBUG_SHOW_WIRING_OPERATIONS; + else if (sscanf(buf, "get_object_by_name %s", (char *)&cmd.arg) == 1) + opcode = CISP_CMD_DEBUG_GET_OBJECT_BY_NAME; + else if (sscanf(buf, "dump_object %x", &cmd.arg[0]) == 1) + opcode = CISP_CMD_DEBUG_DUMP_OBJECT; + else if (!strcmp(buf, "dump_objects")) + opcode = CISP_CMD_DEBUG_DUMP_ALL_OBJECTS; + else if (!strcmp(buf, "show_objects")) + opcode = CISP_CMD_DEBUG_SHOW_OBJECT_GRAPH; + else if (sscanf(buf, "get_debug_level %i", &cmd.arg[0]) == 1) + opcode = CISP_CMD_DEBUG_GET_DEBUG_LEVEL; + else if (sscanf(buf, "set_debug_level %x %i", &cmd.arg[0], &cmd.arg[1]) == 2) + opcode = CISP_CMD_DEBUG_SET_DEBUG_LEVEL; + else if (sscanf(buf, "set_debug_level_rec %x %i", &cmd.arg[0], &cmd.arg[1]) == 2) + opcode = CISP_CMD_DEBUG_SET_DEBUG_LEVEL_RECURSIVE; + else if (!strcmp(buf, "get_fsm_count")) + opcode = CISP_CMD_DEBUG_GET_FSM_COUNT; + else if (sscanf(buf, "get_fsm_by_name %s", (char *)&cmd.arg[0]) == 1) + opcode = CISP_CMD_DEBUG_GET_FSM_BY_NAME; + else if (sscanf(buf, "get_fsm_by_index %i", &cmd.arg[0]) == 1) + opcode = CISP_CMD_DEBUG_GET_FSM_BY_INDEX; + else if (sscanf(buf, "get_fsm_debug_level %x", &cmd.arg[0]) == 1) + opcode = CISP_CMD_DEBUG_GET_FSM_DEBUG_LEVEL; + else if (sscanf(buf, "set_fsm_debug_level %x", &cmd.arg[0]) == 2) + opcode = CISP_CMD_DEBUG_SET_FSM_DEBUG_LEVEL; + + else if (sscanf(buf, "%i %i\n", &opcode, &cmd.arg[0]) != 2) + return -EINVAL; + cmd.show_errors = 1; + + ret = fthd_isp_debug_cmd(dev_priv, opcode, &cmd, sizeof(cmd), NULL); + if (ret) + return ret; + + return count; +} + + +static int seq_channel_read(struct seq_file *seq, struct fthd_private *dev_priv, + struct fw_channel *chan) +{ + int i; + char pos; + u32 entry; + + spin_lock_irq(&chan->lock); + for( i = 0; i < chan->size; i++) { + if (chan->ringbuf.idx == i) + pos = '*'; + else + pos = ' '; + entry = get_entry_addr(dev_priv, chan, i); + seq_printf(seq, "%c%3.3d: ADDRESS %08x REQUEST_SIZE %08x RESPONSE_SIZE %08x\n", + pos, i, + FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS), + FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE), + FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE)); + } + spin_unlock_irq(&chan->lock); + return 0; +} + +static int seq_channel_terminal_read(struct seq_file *seq, void *data) + +{ + struct fthd_private *dev_priv = dev_get_drvdata(seq->private); + return seq_channel_read(seq, dev_priv, dev_priv->channel_terminal); +} + +static int seq_channel_sharedmalloc_read(struct seq_file *seq, void *data) + +{ + struct fthd_private *dev_priv = dev_get_drvdata(seq->private); + return seq_channel_read(seq, dev_priv, dev_priv->channel_shared_malloc); +} + +static int seq_channel_io_read(struct seq_file *seq, void *data) + +{ + struct fthd_private *dev_priv = dev_get_drvdata(seq->private); + return seq_channel_read(seq, dev_priv, dev_priv->channel_io); +} + +static int seq_channel_io_t2h_read(struct seq_file *seq, void *data) + +{ + struct fthd_private *dev_priv = dev_get_drvdata(seq->private); + return seq_channel_read(seq, dev_priv, dev_priv->channel_io_t2h); +} + +static int seq_channel_buf_h2t_read(struct seq_file *seq, void *data) + +{ + struct fthd_private *dev_priv = dev_get_drvdata(seq->private); + return seq_channel_read(seq, dev_priv, dev_priv->channel_buf_h2t); +} + +static int seq_channel_buf_t2h_read(struct seq_file *seq, void *data) + +{ + struct fthd_private *dev_priv = dev_get_drvdata(seq->private); + return seq_channel_read(seq, dev_priv, dev_priv->channel_buf_t2h); +} + +static int seq_channel_debug_read(struct seq_file *seq, void *data) + +{ + struct fthd_private *dev_priv = dev_get_drvdata(seq->private); + return seq_channel_read(seq, dev_priv, dev_priv->channel_debug); +} + +static const struct file_operations fops_debug = { + .read = NULL, + .write = fthd_store_debug, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +int fthd_debugfs_init(struct fthd_private *dev_priv) +{ + struct dentry *d, *top; + + top = debugfs_create_dir("facetimehd", NULL); + if (IS_ERR(top)) + return PTR_ERR(top); + + d = debugfs_create_dir(dev_name(&dev_priv->pdev->dev), top); + if (IS_ERR(d)) { + debugfs_remove_recursive(top); + return PTR_ERR(d); + } + + debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_terminal", d, seq_channel_terminal_read); + debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_sharedmalloc", d, seq_channel_sharedmalloc_read); + debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_io", d, seq_channel_io_read); + debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_io_t2h", d, seq_channel_io_t2h_read); + debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_buf_h2t", d, seq_channel_buf_h2t_read); + debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_buf_t2h", d, seq_channel_buf_t2h_read); + debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_debug", d, seq_channel_debug_read); + debugfs_create_file("debug", S_IRUSR | S_IWUSR, d, dev_priv, &fops_debug); + dev_priv->debugfs = top; + return 0; +} + +void fthd_debugfs_exit(struct fthd_private *dev_priv) +{ + debugfs_remove_recursive(dev_priv->debugfs); +} diff --git a/drivers/custom/facetimehd/fthd_debugfs.h b/drivers/custom/facetimehd/fthd_debugfs.h new file mode 100644 index 000000000000..d3f8b74239e4 --- /dev/null +++ b/drivers/custom/facetimehd/fthd_debugfs.h @@ -0,0 +1,17 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2015 Sven Schnelle + * + */ + +#ifndef _FTHD_SYSFS_H +#define _FTHD_SYSFS_H + +struct fthd_private; + +int fthd_debugfs_init(struct fthd_private *priv); +void fthd_debugfs_exit(struct fthd_private *priv); +#endif diff --git a/drivers/custom/facetimehd/fthd_drv.c b/drivers/custom/facetimehd/fthd_drv.c new file mode 100644 index 000000000000..25ae22c1fac5 --- /dev/null +++ b/drivers/custom/facetimehd/fthd_drv.c @@ -0,0 +1,559 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com) + * + */ + +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include "fthd_drv.h" +#include "fthd_hw.h" +#include "fthd_isp.h" +#include "fthd_ringbuf.h" +#include "fthd_buffer.h" +#include "fthd_v4l2.h" +#include "fthd_debugfs.h" + +static int fthd_pci_reserve_mem(struct fthd_private *dev_priv) +{ + unsigned long start; + unsigned long len; + int ret; + + /* Reserve resources */ + ret = pci_request_region(dev_priv->pdev, FTHD_PCI_S2_IO, "S2 IO"); + if (ret) { + dev_err(&dev_priv->pdev->dev, "Failed to request S2 IO\n"); + return ret; + } + + ret = pci_request_region(dev_priv->pdev, FTHD_PCI_ISP_IO, "ISP IO"); + if (ret) { + dev_err(&dev_priv->pdev->dev, "Failed to request ISP IO\n"); + pci_release_region(dev_priv->pdev, FTHD_PCI_S2_IO); + return ret; + } + + ret = pci_request_region(dev_priv->pdev, FTHD_PCI_S2_MEM, "S2 MEM"); + if (ret) { + pci_release_region(dev_priv->pdev, FTHD_PCI_ISP_IO); + pci_release_region(dev_priv->pdev, FTHD_PCI_S2_IO); + return ret; + } + + /* S2 IO */ + start = pci_resource_start(dev_priv->pdev, FTHD_PCI_S2_IO); + len = pci_resource_len(dev_priv->pdev, FTHD_PCI_S2_IO); + dev_priv->s2_io = ioremap(start, len); + dev_priv->s2_io_len = len; + + /* S2 MEM */ + start = pci_resource_start(dev_priv->pdev, FTHD_PCI_S2_MEM); + len = pci_resource_len(dev_priv->pdev, FTHD_PCI_S2_MEM); + dev_priv->s2_mem = ioremap(start, len); + dev_priv->s2_mem_len = len; + + /* ISP IO */ + start = pci_resource_start(dev_priv->pdev, FTHD_PCI_ISP_IO); + len = pci_resource_len(dev_priv->pdev, FTHD_PCI_ISP_IO); + dev_priv->isp_io = ioremap(start, len); + dev_priv->isp_io_len = len; + + pr_debug("Allocated S2 regs (BAR %d). %u bytes at 0x%p\n", + FTHD_PCI_S2_IO, dev_priv->s2_io_len, dev_priv->s2_io); + + pr_debug("Allocated S2 mem (BAR %d). %u bytes at 0x%p\n", + FTHD_PCI_S2_MEM, dev_priv->s2_mem_len, dev_priv->s2_mem); + + pr_debug("Allocated ISP regs (BAR %d). %u bytes at 0x%p\n", + FTHD_PCI_ISP_IO, dev_priv->isp_io_len, dev_priv->isp_io); + + return 0; +} + +static void sharedmalloc_handler(struct fthd_private *dev_priv, + struct fw_channel *chan, + u32 entry) +{ + u32 request_size, response_size, address; + struct isp_mem_obj *obj; + int ret; + + request_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE); + response_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE); + address = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS) & ~ 3; + + if (address) { + pr_debug("Firmware wants to free memory at %08x\n", address); + FTHD_S2_MEMCPY_FROMIO(&obj, address - 64, sizeof(obj)); + isp_mem_destroy(obj); + + ret = fthd_channel_ringbuf_send(dev_priv, chan, 0, 0, 0, NULL); + if (ret) + pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret); + } else { + if (!request_size) + return; + obj = isp_mem_create(dev_priv, FTHD_MEM_SHAREDMALLOC, request_size + 64); + if (!obj) + return; + + pr_debug("Firmware allocated %d bytes at %08lx (tag %c%c%c%c)\n", request_size, obj->offset, + response_size >> 24,response_size >> 16, + response_size >> 8, response_size); + FTHD_S2_MEMCPY_TOIO(obj->offset, &obj, sizeof(obj)); + ret = fthd_channel_ringbuf_send(dev_priv, chan, obj->offset + 64, 0, 0, NULL); + if (ret) + pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret); + + } + +} + + +static void terminal_handler(struct fthd_private *dev_priv, + struct fw_channel *chan, + u32 entry) +{ + u32 request_size, response_size, address; + char buf[512]; + + request_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE); + response_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE); + address = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS) & ~ 3; + + if (!address || !request_size) + return; + + if (request_size > 512) + request_size = 512; + FTHD_S2_MEMCPY_FROMIO(buf, address, request_size); + pr_info("FWMSG: %.*s", request_size, buf); +} + +static void buf_t2h_handler(struct fthd_private *dev_priv, + struct fw_channel *chan, + u32 entry) +{ + u32 request_size, response_size, address; + int ret; + request_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE); + response_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE); + address = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS); + + if (address & 1) + return; + + + fthd_buffer_return_handler(dev_priv, address & ~3, request_size); + ret = fthd_channel_ringbuf_send(dev_priv, chan, (response_size & 0x10000000) ? address : 0, + 0, 0x80000000, NULL); + if (ret) + pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret); + +} + +static void io_t2h_handler(struct fthd_private *dev_priv, + struct fw_channel *chan, + u32 entry) +{ + int ret = fthd_channel_ringbuf_send(dev_priv, chan, 0, 0, 0, NULL); + if (ret) + pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret); + +} + +static void fthd_handle_irq(struct fthd_private *dev_priv, struct fw_channel *chan) +{ + u32 entry; + int ret; + + if (chan == dev_priv->channel_io) { + pr_debug("IO channel ready\n"); + wake_up_interruptible(&chan->wq); + return; + } + + if (chan == dev_priv->channel_buf_h2t) { + pr_debug("H2T channel ready\n"); + wake_up_interruptible(&chan->wq); + return; + } + + if (chan == dev_priv->channel_debug) { + pr_debug("DEBUG channel ready\n"); + wake_up_interruptible(&chan->wq); + return; + } + + while((entry = fthd_channel_ringbuf_receive(dev_priv, chan)) != (u32)-1) { + pr_debug("channel %s: message available, address %08x\n", chan->name, FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS)); + if (chan == dev_priv->channel_shared_malloc) { + sharedmalloc_handler(dev_priv, chan, entry); + } else if (chan == dev_priv->channel_terminal) { + terminal_handler(dev_priv, chan, entry); + ret = fthd_channel_ringbuf_send(dev_priv, chan, 0, 0, 0, NULL); + if (ret) + pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret); + } else if (chan == dev_priv->channel_buf_t2h) { + buf_t2h_handler(dev_priv, chan, entry); + } else if (chan == dev_priv->channel_io_t2h) { + io_t2h_handler(dev_priv, chan, entry); + } + } +} + +static void fthd_irq_uninstall(struct fthd_private *dev_priv) +{ + free_irq(dev_priv->pdev->irq, dev_priv); +} + +static void fthd_irq_work(struct work_struct *work) +{ + struct fthd_private *dev_priv = container_of(work, struct fthd_private, irq_work); + struct fw_channel *chan; + + u32 pending; + int i = 0; + + while(i++ < 500) { + spin_lock_irq(&dev_priv->io_lock); + pending = FTHD_ISP_REG_READ(ISP_IRQ_STATUS); + spin_unlock_irq(&dev_priv->io_lock); + + if (!(pending & 0xf0)) + break; + + pci_write_config_dword(dev_priv->pdev, 0x94, 0); + spin_lock_irq(&dev_priv->io_lock); + FTHD_ISP_REG_WRITE(pending, ISP_IRQ_CLEAR); + spin_unlock_irq(&dev_priv->io_lock); + pci_write_config_dword(dev_priv->pdev, 0x90, 0x200); + + for(i = 0; i < dev_priv->num_channels; i++) { + chan = dev_priv->channels[i]; + + + BUG_ON(chan->source > 3); + if (!((0x10 << chan->source) & pending)) + continue; + fthd_handle_irq(dev_priv, chan); + } + } + + if (i >= 500) { + dev_err(&dev_priv->pdev->dev, "irq stuck, disabling\n"); + fthd_irq_uninstall(dev_priv); + } + pci_write_config_dword(dev_priv->pdev, 0x94, 0x200); +} + +static irqreturn_t fthd_irq_handler(int irq, void *arg) +{ + struct fthd_private *dev_priv = arg; + u32 pending; + unsigned long flags; + + spin_lock_irqsave(&dev_priv->io_lock, flags); + pending = FTHD_ISP_REG_READ(ISP_IRQ_STATUS); + spin_unlock_irqrestore(&dev_priv->io_lock, flags); + + if (!(pending & 0xf0)) + return IRQ_NONE; + + schedule_work(&dev_priv->irq_work); + + return IRQ_HANDLED; +} + +static int fthd_irq_install(struct fthd_private *dev_priv) +{ + int ret; + + ret = request_irq(dev_priv->pdev->irq, fthd_irq_handler, IRQF_SHARED, + KBUILD_MODNAME, (void *)dev_priv); + + if (ret) + dev_err(&dev_priv->pdev->dev, "Failed to request IRQ\n"); + + return ret; +} + +static int fthd_pci_set_dma_mask(struct fthd_private *dev_priv, + unsigned int mask) +{ + int ret; + + ret = dma_set_mask_and_coherent(&dev_priv->pdev->dev, DMA_BIT_MASK(mask)); + if (ret) { + dev_err(&dev_priv->pdev->dev, "Failed to set %u pci dma mask\n", + mask); + return ret; + } + + dev_priv->dma_mask = mask; + + return 0; +} + +static void fthd_stop_firmware(struct fthd_private *dev_priv) +{ + fthd_isp_cmd_stop(dev_priv); + isp_powerdown(dev_priv); +} + +static void fthd_pci_remove(struct pci_dev *pdev) +{ + struct fthd_private *dev_priv; + + dev_priv = pci_get_drvdata(pdev); + if (!dev_priv) + goto out; + + fthd_debugfs_exit(dev_priv); + + fthd_v4l2_unregister(dev_priv); + + fthd_stop_firmware(dev_priv); + + fthd_irq_uninstall(dev_priv); + + cancel_work_sync(&dev_priv->irq_work); + + isp_uninit(dev_priv); + + fthd_hw_deinit(dev_priv); + + fthd_buffer_exit(dev_priv); + + pci_disable_msi(pdev); + + if (dev_priv->s2_io) + iounmap(dev_priv->s2_io); + if (dev_priv->s2_mem) + iounmap(dev_priv->s2_mem); + if (dev_priv->isp_io) + iounmap(dev_priv->isp_io); + + pci_release_region(pdev, FTHD_PCI_S2_IO); + pci_release_region(pdev, FTHD_PCI_S2_MEM); + pci_release_region(pdev, FTHD_PCI_ISP_IO); +out: + pci_disable_device(pdev); +} + +static int fthd_pci_init(struct fthd_private *dev_priv) +{ + struct pci_dev *pdev = dev_priv->pdev; + int ret; + + + ret = pci_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "Failed to enable device\n"); + return ret; + } + + /* ASPM must be disabled on the device or it hangs while streaming */ + pci_disable_link_state(pdev, PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1 | + PCIE_LINK_STATE_CLKPM); + + ret = fthd_pci_reserve_mem(dev_priv); + if (ret) + goto fail_enable; + + ret = pci_enable_msi(pdev); + if (ret) { + dev_err(&pdev->dev, "Failed to enable MSI\n"); + goto fail_reserve; + } + + ret = fthd_irq_install(dev_priv); + if (ret) + goto fail_msi; + + ret = fthd_pci_set_dma_mask(dev_priv, 64); + if (ret) + ret = fthd_pci_set_dma_mask(dev_priv, 32); + + if (ret) + goto fail_irq; + + dev_info(&pdev->dev, "Setting %ubit DMA mask\n", dev_priv->dma_mask); +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,18,0) + pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(dev_priv->dma_mask)); +#else + dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(dev_priv->dma_mask)); +#endif + + pci_set_master(pdev); + pci_set_drvdata(pdev, dev_priv); + return 0; + +fail_irq: + fthd_irq_uninstall(dev_priv); +fail_msi: + pci_disable_msi(pdev); +fail_reserve: + pci_release_region(pdev, FTHD_PCI_S2_IO); + pci_release_region(pdev, FTHD_PCI_S2_MEM); + pci_release_region(pdev, FTHD_PCI_ISP_IO); +fail_enable: + pci_disable_device(pdev); + return ret; +} + +static int fthd_firmware_start(struct fthd_private *dev_priv) +{ + int ret; + + ret = fthd_isp_cmd_start(dev_priv); + if (ret) + return ret; + + ret = fthd_isp_cmd_print_enable(dev_priv, 1); + if (ret) + return ret; + + ret = fthd_isp_cmd_camera_config(dev_priv); + if (ret) + return ret; + + ret = fthd_isp_cmd_channel_info(dev_priv); + if (ret) + return ret; + + return fthd_isp_cmd_set_loadfile(dev_priv); + +} + +static int fthd_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *entry) +{ + struct fthd_private *dev_priv; + int ret; + + dev_info(&pdev->dev, "Found FaceTime HD camera with device id: %x\n", + pdev->device); + + dev_priv = kzalloc(sizeof(struct fthd_private), GFP_KERNEL); + if (!dev_priv) { + dev_err(&pdev->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + dev_priv->ddr_model = 4; + dev_priv->ddr_speed = 450; + dev_priv->frametime = 40; /* 25 fps */ + + spin_lock_init(&dev_priv->io_lock); + mutex_init(&dev_priv->vb2_queue_lock); + + mutex_init(&dev_priv->ioctl_lock); + INIT_LIST_HEAD(&dev_priv->buffer_queue); + INIT_WORK(&dev_priv->irq_work, fthd_irq_work); + + dev_priv->pdev = pdev; + + ret = fthd_pci_init(dev_priv); + if (ret) + goto fail_work; + + ret = fthd_buffer_init(dev_priv); + if (ret) + goto fail_pci; + + ret = fthd_hw_init(dev_priv); + if (ret) + goto fail_buffer; + + ret = fthd_firmware_start(dev_priv); + if (ret) + goto fail_hw; + + ret = fthd_v4l2_register(dev_priv); + if (ret) + goto fail_firmware; + + ret = fthd_debugfs_init(dev_priv); + if (ret) + goto fail_v4l2; + return 0; +fail_v4l2: + fthd_v4l2_unregister(dev_priv); +fail_firmware: + fthd_stop_firmware(dev_priv); +fail_hw: + fthd_hw_deinit(dev_priv); +fail_buffer: + fthd_buffer_exit(dev_priv); +fail_pci: + fthd_irq_uninstall(dev_priv); + pci_disable_msi(pdev); + pci_release_region(pdev, FTHD_PCI_S2_IO); + pci_release_region(pdev, FTHD_PCI_S2_MEM); + pci_release_region(pdev, FTHD_PCI_ISP_IO); + pci_disable_device(pdev); + +fail_work: + cancel_work_sync(&dev_priv->irq_work); + kfree(dev_priv); + return ret; +} + +#ifdef CONFIG_PM +static int fthd_pci_suspend(struct pci_dev *pdev, pm_message_t state) +{ + fthd_pci_remove(pdev); + + return 0; +} + +static int fthd_pci_resume(struct pci_dev *pdev) +{ + fthd_pci_probe(pdev, NULL); + + return 0; +} +#endif /* CONFIG_PM */ + +static const struct pci_device_id fthd_pci_id_table[] = { + { PCI_DEVICE(0x14e4, 0x1570), 4 }, + { 0, }, +}; + +static struct pci_driver fthd_pci_driver = { + .name = KBUILD_MODNAME, + .probe = fthd_pci_probe, + .remove = fthd_pci_remove, + .shutdown = fthd_pci_remove, + .id_table = fthd_pci_id_table, +#ifdef CONFIG_PM + .suspend = fthd_pci_suspend, + .resume = fthd_pci_resume, +#endif +}; + +module_pci_driver(fthd_pci_driver); + +MODULE_FIRMWARE("facetimehd/firmware.bin"); +MODULE_DEVICE_TABLE(pci, fthd_pci_id_table); +MODULE_AUTHOR("Patrik Jakobsson "); +MODULE_DESCRIPTION("FacetimeHD camera driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/custom/facetimehd/fthd_drv.h b/drivers/custom/facetimehd/fthd_drv.h new file mode 100644 index 000000000000..c77fbd4d9242 --- /dev/null +++ b/drivers/custom/facetimehd/fthd_drv.h @@ -0,0 +1,122 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com) + * + */ + +#ifndef _FTHD_DRV_H +#define _FTHD_DRV_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "fthd_reg.h" +#include "fthd_ringbuf.h" +#include "fthd_buffer.h" +#include "fthd_v4l2.h" + +#define FTHD_PCI_S2_IO 0 +#define FTHD_PCI_S2_MEM 2 +#define FTHD_PCI_ISP_IO 4 + +#define FTHD_BUFFERS 4 + +enum FW_CHAN_TYPE { + FW_CHAN_TYPE_OUT=0, + FW_CHAN_TYPE_IN=1, + FW_CHAN_TYPE_UNI_IN=2, +}; + +struct fw_channel { + u32 offset; + u32 size; + u32 source; + u32 type; + struct fthd_ringbuf ringbuf; + spinlock_t lock; + /* waitqueue for signaling completion */ + wait_queue_head_t wq; + char *name; +}; + +struct fthd_private { + struct pci_dev *pdev; + unsigned int dma_mask; + + struct v4l2_device v4l2_dev; + struct video_device *videodev; + struct mutex ioctl_lock; + int users; + /* lock for synchronizing with irq/workqueue */ + spinlock_t io_lock; + + /* Mapped PCI resources */ + void __iomem *s2_io; + u32 s2_io_len; + + void __iomem *s2_mem; + u32 s2_mem_len; + + void __iomem *isp_io; + u32 isp_io_len; + + struct work_struct irq_work; + + /* Hardware info */ + u32 core_clk; + u32 ddr_model; + u32 ddr_speed; + u32 vdl_step_size; + + u32 ddr_phy_regs[DDR_PHY_NUM_REG]; + + /* Root resource for memory management */ + struct resource *mem; + /* Resource for managing IO mmu slots */ + struct resource *iommu; + /* ISP memory objects */ + struct isp_mem_obj *firmware; + struct isp_mem_obj *set_file; + struct isp_mem_obj *ipc_queue; + struct isp_mem_obj *heap; + + /* Firmware channels */ + int num_channels; + struct fw_channel **channels; + struct fw_channel *channel_terminal; + struct fw_channel *channel_io; + struct fw_channel *channel_debug; + struct fw_channel *channel_buf_h2t; + struct fw_channel *channel_buf_t2h; + struct fw_channel *channel_shared_malloc; + struct fw_channel *channel_io_t2h; + + /* camera config */ + int sensor_count; + int sensor_id0; + int sensor_id1; + + struct fthd_fmt fmt; + + struct vb2_queue vb2_queue; + struct mutex vb2_queue_lock; + struct list_head buffer_queue; +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0) + struct vb2_alloc_ctx *alloc_ctx; +#endif + struct h2t_buf_ctx h2t_bufs[FTHD_BUFFERS]; + + struct v4l2_ctrl_handler v4l2_ctrl_handler; + int frametime; + struct dentry *debugfs; +}; + +#endif diff --git a/drivers/custom/facetimehd/fthd_hw.c b/drivers/custom/facetimehd/fthd_hw.c new file mode 100644 index 000000000000..8180072922d4 --- /dev/null +++ b/drivers/custom/facetimehd/fthd_hw.c @@ -0,0 +1,712 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com) + * + */ + +#include +#include "fthd_drv.h" +#include "fthd_hw.h" +#include "fthd_ddr.h" +#include "fthd_isp.h" + +static int fthd_hw_s2_pll_reset(struct fthd_private *dev_priv) +{ + FTHD_S2_REG_WRITE(0x40, S2_PLL_CTRL_2C); + FTHD_S2_REG_WRITE(0x0, S2_PLL_CTRL_2C); + FTHD_S2_REG_WRITE(0xbcbc1500, S2_PLL_CTRL_100); + FTHD_S2_REG_WRITE(0x0, S2_PLL_CTRL_14); + + udelay(10000); + + FTHD_S2_REG_WRITE(0x3, S2_PLL_CTRL_14); + + dev_info(&dev_priv->pdev->dev, "PLL reset finished\n"); + + return 0; +} + +static int fthd_hw_s2_init_pcie_link(struct fthd_private *dev_priv) +{ + u32 reg; + + reg = FTHD_S2_REG_READ(S2_PCIE_LINK_D000); + FTHD_S2_REG_WRITE(reg | 0x10, S2_PCIE_LINK_D000); + + FTHD_S2_REG_WRITE(0x1804, S2_PCIE_LINK_D120); + FTHD_S2_REG_WRITE(0xac5800, S2_PCIE_LINK_D124); + FTHD_S2_REG_WRITE(0x1804, S2_PCIE_LINK_D120); + + /* Check if PLL is powered down when S2 PCIe link is in L1 state */ + reg = FTHD_S2_REG_READ(S2_PCIE_LINK_D124); + if (reg != 0xac5800) { + dev_err(&dev_priv->pdev->dev, + "Failed to init S2 PCIe link: %08x\n", reg); + return -EIO; + } + + /* PLL is powered down */ + dev_info(&dev_priv->pdev->dev, "S2 PCIe link init succeeded\n"); + + FTHD_S2_REG_WRITE(0x1f08, S2_PCIE_LINK_D128); + FTHD_S2_REG_WRITE(0x80008610, S2_PCIE_LINK_D12C); + FTHD_S2_REG_WRITE(0x1608, S2_PCIE_LINK_D128); + FTHD_S2_REG_WRITE(0x8000fc00, S2_PCIE_LINK_D12C); + FTHD_S2_REG_WRITE(0x1f08, S2_PCIE_LINK_D128); + FTHD_S2_REG_WRITE(0x80008610, S2_PCIE_LINK_D12C); + FTHD_S2_REG_WRITE(0x1708, S2_PCIE_LINK_D128); + FTHD_S2_REG_WRITE(0x800005bf, S2_PCIE_LINK_D12C); + + return 0; +} + +static int fthd_hw_s2_pll_init(struct fthd_private *dev_priv, u32 ddr_speed) +{ + u32 ref_clk_25; + u32 reg; + int retries = 0; + + reg = FTHD_S2_REG_READ(S2_PLL_REFCLK); + ref_clk_25 = reg & S2_PLL_REFCLK_25MHZ ? 1 : 0; + + if (ref_clk_25) + dev_info(&dev_priv->pdev->dev, "Refclk: 25MHz (0x%x)\n", reg); + else + dev_info(&dev_priv->pdev->dev, "Refclk: 24MHz (0x%x\n", reg); + + if (ddr_speed == 400) { + if (ref_clk_25) { + /* Ref clk 25 */ + FTHD_S2_REG_WRITE(0x00400078, S2_PLL_CTRL_510); + FTHD_S2_REG_WRITE(0x19280804, S2_PLL_CTRL_24); + } else { + /* Ref clk 24 */ + FTHD_S2_REG_WRITE(0x03200000, S2_PLL_CTRL_20); + FTHD_S2_REG_WRITE(0x14280603, S2_PLL_CTRL_24); + } + } else if (ddr_speed == 300) { + if (ref_clk_25) { + /* Ref clk 25 */ + FTHD_S2_REG_WRITE(0x00480078, S2_PLL_CTRL_510); + FTHD_S2_REG_WRITE(0x19280c06, S2_PLL_CTRL_24); + } else { + /* Ref clk 24 */ + FTHD_S2_REG_WRITE(0x03200000, S2_PLL_CTRL_20); + FTHD_S2_REG_WRITE(0x14280804, S2_PLL_CTRL_24); + } + } else if (ddr_speed == 200) { + if (ref_clk_25) { + /* Ref clk 25 */ + FTHD_S2_REG_WRITE(0x00400078, S2_PLL_CTRL_510); + FTHD_S2_REG_WRITE(0x19281008, S2_PLL_CTRL_24); + } else { + /* Ref clk 24 */ + FTHD_S2_REG_WRITE(0x03200000, S2_PLL_CTRL_20); + FTHD_S2_REG_WRITE(0x14280c06, S2_PLL_CTRL_24); + } + } else { + if (ddr_speed != 450) { + dev_err(&dev_priv->pdev->dev, + "Unsupported DDR speed %uMHz, using 450MHz\n", + ddr_speed); + ddr_speed = 450; + } + + if (ref_clk_25) { + /* Ref clk 25 */ + FTHD_S2_REG_WRITE(0x0048007d, S2_PLL_CTRL_510); + FTHD_S2_REG_WRITE(0x19280904, S2_PLL_CTRL_24); + } else { + /* Ref clk 24 */ + FTHD_S2_REG_WRITE(0x04b00000, S2_PLL_CTRL_20); + FTHD_S2_REG_WRITE(0x14280904, S2_PLL_CTRL_24); + + } + } + + fthd_hw_s2_pll_reset(dev_priv); + + dev_info(&dev_priv->pdev->dev, "Waiting for S2 PLL to lock at %d MHz\n", + ddr_speed); + + do { + reg = FTHD_S2_REG_READ(S2_PLL_CMU_STATUS); + udelay(10); + retries++; + } while (((reg & 0xff00) & S2_PLL_CMU_STATUS_LOCKED) && retries <= 10000); + + if (retries > 10000) { + dev_info(&dev_priv->pdev->dev, "Failed to lock S2 PLL: 0x%x\n", + reg); + return -EINVAL; + } else { + dev_info(&dev_priv->pdev->dev, "S2 PLL is locked after %d us\n", + (retries * 10)); + } + + reg = FTHD_S2_REG_READ(S2_PLL_STATUS_A8); + FTHD_S2_REG_WRITE(reg | S2_PLL_BYPASS, S2_PLL_STATUS_A8); + udelay(10000); + + reg = FTHD_S2_REG_READ(S2_PLL_STATUS_A8); + if (reg & S2_PLL_BYPASS) + dev_info(&dev_priv->pdev->dev, "S2 PLL is in bypass mode\n"); + else + dev_info(&dev_priv->pdev->dev, "S2 PLL is in non-bypass mode\n"); + + return 0; +} + +static int fthd_hw_s2_preinit_ddr_controller_soc(struct fthd_private *dev_priv) +{ + /* Wingardium leviosa */ + FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_1100); + FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_1104); + FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_1108); + FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_110C); + FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_1110); + FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_1114); + FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_1118); + FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_111C); + + return 0; +} + +static int fthd_hw_ddr_phy_soft_reset(struct fthd_private *dev_priv) +{ + /* Clear status bits? */ + FTHD_S2_REG_WRITE(0x281, S2_PLL_STATUS_A8); + + FTHD_S2_REG_WRITE(0xfffff, S2_PLL_CTRL_9C); + + udelay(10000); + + FTHD_S2_REG_WRITE(0xffbff, S2_PLL_CTRL_9C); + + return 0; +} + +static inline int fthd_hw_ddr_status_busy(struct fthd_private *dev_priv, + int retries, int delay) +{ + int reg = 0, i; + + for (i = 0; i < retries; i++) { + reg = FTHD_S2_REG_READ(S2_DDR_STATUS_2018); + if (!(reg & S2_DDR_STATUS_BUSY)) + break; + + if (delay > 0) + udelay(delay); + } + + if (i >= retries) { + dev_err(&dev_priv->pdev->dev, + "S2_DDR_STATUS_2018 busy: retries=%d, udelay=%d, reg=0x%08x\n", + retries, delay, reg); + return -EBUSY; + } + + return 0; +} + +static int fthd_hw_ddr_rewrite_mode_regs(struct fthd_private *dev_priv) +{ + int ret, val; + + FTHD_S2_REG_WRITE(0x02000802, S2_DDR_2014); + + ret = fthd_hw_ddr_status_busy(dev_priv, 500, 5); + if (ret != 0) + return ret; + + FTHD_S2_REG_WRITE(0x3, S2_DDR_2014); + + ret = fthd_hw_ddr_status_busy(dev_priv, 500, 5); + if (ret != 0) + return ret; + + FTHD_S2_REG_WRITE(0x1, S2_DDR_2014); + + ret = fthd_hw_ddr_status_busy(dev_priv, 500, 5); + if (ret != 0) + return ret; + + if (dev_priv->ddr_speed == 450) + val = 0x17003000; + else + val = 0x17002000; + + FTHD_S2_REG_WRITE(val, S2_DDR_2014); + + ret = fthd_hw_ddr_status_busy(dev_priv, 500, 5); + if (ret != 0) + return ret; + + dev_info(&dev_priv->pdev->dev, + "Rewrite DDR mode registers succeeded\n"); + + return 0; +} + +static int fthd_hw_s2_init_ddr_controller_soc(struct fthd_private *dev_priv) +{ + u32 cmd; + u32 val; + u32 reg; + u32 step_size, vdl_fine, vdl_coarse; + u32 vtt_cons, vtt_ovr; + int ret, i; + + /* Read PCI config command register */ + ret = pci_read_config_dword(dev_priv->pdev, 4, &cmd); + if (ret) { + dev_err(&dev_priv->pdev->dev, "Failed to read PCI config\n"); + return -EIO; + } + + if ((cmd & 0x07) == 0) { + dev_err(&dev_priv->pdev->dev, + "PCI link in illegal state, cfg_cmd_reg: 0x%x\n", cmd); + return -EIO; + } + + reg = FTHD_S2_REG_READ(S2_PLL_CTRL_9C); + FTHD_S2_REG_WRITE(reg & 0xfffffcff, S2_PLL_CTRL_9C); + FTHD_S2_REG_WRITE(reg | 0x300, S2_PLL_CTRL_9C); + + fthd_hw_s2_pll_init(dev_priv, dev_priv->ddr_speed); + + fthd_hw_ddr_phy_soft_reset(dev_priv); + + FTHD_S2_REG_WRITE(0x2, S2_DDR40_WL_DRV_PAD_CTL); + FTHD_S2_REG_WRITE(0x2, S2_DDR40_WL_CLK_PAD_DISABLE); + + /* Disable the hardware frequency change function */ + FTHD_S2_REG_WRITE(0x3f4, S2_20F8); + + /* Setup the PLL */ + FTHD_S2_REG_WRITE(0x40, S2_2434); + FTHD_S2_REG_WRITE(0x10000000, S2_2438); + FTHD_S2_REG_WRITE(0x4, S2_2424); + FTHD_S2_REG_WRITE(0x1f37291, S2_2430); + + /* Wait for DDR PLL to lock */ + for (i = 0; i <= 10000; i++) { + reg = FTHD_S2_REG_READ(S2_DDR_PLL_STATUS_2444); + if (reg & S2_DDR_PLL_STATUS_2444_LOCKED) + break; + udelay(10); + } + + if (i > 10000) { + dev_err(&dev_priv->pdev->dev, + "Failed to lock DDR PHY PLL in stage 1\n"); + return -EIO; + } + + FTHD_S2_REG_WRITE(0x1f37205, S2_2430); + + for (i = 0; i <= 10000; i++) { + reg = FTHD_S2_REG_READ(S2_DDR_PLL_STATUS_241C); + if (reg & S2_DDR_PLL_STATUS_241C_LOCKED) + break; + udelay(10); + } + + if (i > 10000) { + dev_err(&dev_priv->pdev->dev, + "Failed to lock DDR PHY PLL in stage 2\n"); + return -EIO; + } + + udelay(10000); + + /* WL */ + FTHD_S2_REG_WRITE(0x0c10, S2_DDR40_PHY_PLL_DIV); + FTHD_S2_REG_WRITE(0x0010, S2_DDR40_PHY_PLL_CFG); + + for (i = 0; i <= 10000; i++) { + reg = FTHD_S2_REG_READ(S2_DDR40_PHY_PLL_STATUS); + if (reg & S2_DDR40_PHY_PLL_STATUS_LOCKED) + break; + udelay(10); + } + + if (i > 10000) { + dev_err(&dev_priv->pdev->dev, + "Failed to lock DDR PHY PLL in stage 3\n"); + return -EIO; + } + + dev_info(&dev_priv->pdev->dev, + "DDR40 PHY PLL locked on safe settings\n"); + + /* Default is DDR model 4 */ + switch (dev_priv->ddr_model) { + case 4: + val = 0x46a00c2; + break; + case 2: + val = 0x42500c2; + break; + default: + val = 0; + } + + FTHD_S2_REG_WRITE(0x10737545, S2_DDR_20A0); + FTHD_S2_REG_WRITE(0x12643173, S2_DDR_20A4); + FTHD_S2_REG_WRITE(0xff3f, S2_DDR_20A8); + FTHD_S2_REG_WRITE(val, S2_DDR_20B0); + FTHD_S2_REG_WRITE(0x101f, S2_DDR_2118); + FTHD_S2_REG_WRITE(0x1c0, S2_DDR40_PHY_AUX_CTL); + + switch (dev_priv->ddr_model) { + case 4: + val = 0x2159518; + break; + case 2: + val = 0x2155558; + break; + } + + FTHD_S2_REG_WRITE(val, S2_DDR40_STRAP_CTL); + + if (dev_priv->ddr_speed == 450) + val = 0x108307; + else + val = 0x108286; + + FTHD_S2_REG_WRITE(val, S2_DDR40_STRAP_CTL_2); + + /* Strap control */ + FTHD_S2_REG_WRITE(0x2159559, S2_DDR40_STRAP_CTL); + + /* Polling for STRAP valid */ + for (i = 0; i < 10000; i++) { + reg = FTHD_S2_REG_READ(S2_DDR40_STRAP_STATUS); + if (reg & 0x1) + break; + udelay(10); + } + + if (i >= 10000) { + dev_err(&dev_priv->pdev->dev, + "Timeout waiting for STRAP valid\n"); + return -ENODEV; + } else { + dev_info(&dev_priv->pdev->dev, "STRAP valid\n"); + } + + /* Manual DDR40 PHY init */ + if (dev_priv->ddr_speed != 450) { + dev_warn(&dev_priv->pdev->dev, + "DDR frequency is %u (should be 450 MHz)", + dev_priv->ddr_speed); + } + + dev_info(&dev_priv->pdev->dev, + "Configuring DDR PLLs for %u MHz\n", dev_priv->ddr_speed); + + if ((dev_priv->ddr_speed * 2) < 500) + val = 0x2040; + else + val = 0x810; + + /* Start programming the DDR PLL */ + + reg = FTHD_S2_REG_READ(S2_DDR40_PHY_PLL_DIV); + reg &= 0xffffc700; + val |= reg; + + FTHD_S2_REG_WRITE(val, S2_DDR40_PHY_PLL_DIV); + + reg = FTHD_S2_REG_READ(S2_DDR40_PHY_PLL_CFG); + reg &= 0xfffffffd; + FTHD_S2_REG_WRITE(reg, S2_DDR40_PHY_PLL_CFG); + + /* Start polling for the lock */ + for (i = 0; i < 100; i++) { + reg = FTHD_S2_REG_READ(S2_DDR40_PHY_PLL_STATUS); + if (reg & S2_DDR40_PHY_PLL_STATUS_LOCKED) + break; + udelay(1); + } + + if (i >= 100) { + dev_err(&dev_priv->pdev->dev, "Failed to lock the DDR PLL\n"); + return -ENODEV; + } + + dev_info(&dev_priv->pdev->dev, "DDR40 PLL is locked after %d us\n", i); + + /* Configure DDR40 VDL */ + FTHD_S2_REG_WRITE(0, S2_DDR40_PHY_VDL_CTL); + FTHD_S2_REG_WRITE(0x103, S2_DDR40_PHY_VDL_CTL); + + /* Poll for VDL calibration */ + for (i = 0; i < 100; i++) { + reg = FTHD_S2_REG_READ(S2_DDR40_PHY_VDL_STATUS); + if (reg & 0x1) + break; + udelay(1); + } + + if (reg & 0x1) { + dev_info(&dev_priv->pdev->dev, + "First DDR40 VDL calibration completed after %d us", + i); + + if ((reg & 0x2) == 0) { + dev_info(&dev_priv->pdev->dev, + "...but failed to lock\n"); + } + + } else { + dev_err(&dev_priv->pdev->dev, + "First DDR40 VDL calibration failed\n"); + } + + FTHD_S2_REG_WRITE(0, S2_DDR40_PHY_VDL_CTL); + FTHD_S2_REG_WRITE(0, S2_DDR40_PHY_VDL_CTL); /* Needed? */ + FTHD_S2_REG_WRITE(0x200, S2_DDR40_PHY_VDL_CTL); /* calib steps */ + + for (i = 0; i < 1000; i++) { + reg = FTHD_S2_REG_READ(S2_DDR40_PHY_VDL_STATUS); + if (reg & 0x1) + break; + udelay(1); + } + + dev_info(&dev_priv->pdev->dev, + "Second DDR40 VDL calibration completed after %d us\n", i); + + if (reg & 0x2) { + step_size = (reg & S2_DDR40_PHY_VDL_STEP_MASK) >> + S2_DDR40_PHY_VDL_STEP_SHIFT; + dev_info(&dev_priv->pdev->dev, "Using step size %u\n", + step_size); + } else { + + val = 1000000 / dev_priv->ddr_speed; + step_size = (val * 0x4ec4ec4f) >> 22; + dev_info(&dev_priv->pdev->dev, "Using default step size (%u)\n", + step_size); + } + + dev_priv->vdl_step_size = step_size; + + vdl_fine = FTHD_S2_REG_READ(S2_DDR40_PHY_VDL_CHAN_STATUS); + + /* lock = 1 and byte_sel = 1 */ + if ((vdl_fine & 2) == 0) { + vdl_fine = (vdl_fine >> 8) & 0x3f; + vdl_fine |= 0x10100; + + FTHD_S2_REG_WRITE(vdl_fine, S2_DDR40_PHY_VDL_OVR_FINE); + + vdl_coarse = 0x10000; + + step_size >>= 4; + step_size += step_size * 2; + + if (step_size > 10) { + step_size = (step_size + 118) >> 1; + step_size &= 0x3f; + step_size |= 0x10000; + vdl_coarse = step_size; + } + + FTHD_S2_REG_WRITE(vdl_coarse, S2_DDR40_PHY_VDL_OVR_COARSE); + + dev_info(&dev_priv->pdev->dev, + "VDL set to: coarse=0x%x, fine=0x%x\n", + vdl_coarse, vdl_fine); + } + + /* Configure Virtual VTT connections and override */ + + vtt_cons = 0x1cf7fff; + FTHD_S2_REG_WRITE(vtt_cons, S2_DDR40_PHY_VTT_CONNECTIONS); + + vtt_ovr = 0x77fff; + FTHD_S2_REG_WRITE(vtt_ovr, S2_DDR40_PHY_VTT_OVERRIDE); + + FTHD_S2_REG_WRITE(0x4, S2_DDR40_PHY_VTT_CTL); + + dev_info(&dev_priv->pdev->dev, "Virtual VTT enabled"); + + /* Process, Voltage and Temperature compensation */ + FTHD_S2_REG_WRITE(0xc0fff, S2_DDR40_PHY_ZQ_PVT_COMP_CTL); + FTHD_S2_REG_WRITE(0x2, S2_DDR40_PHY_DRV_PAD_CTL); + FTHD_S2_REG_WRITE(0x2, S2_DDR40_WL_DRV_PAD_CTL); + + val = 1000000 / dev_priv->ddr_speed; + reg = 4; + + if (val >= 400) { + if (val > 900) + reg = 1; + + reg += 5; + } + + /* DDR read FIFO delay? */ + FTHD_S2_REG_WRITE(reg, S2_DDR40_WL_RD_DATA_DLY); + FTHD_S2_REG_WRITE(0x2, S2_DDR40_WL_READ_CTL); /* le_adj, te_adj */ + FTHD_S2_REG_WRITE(0x3, S2_DDR40_WL_WR_PREAMBLE_MODE); /* mode, long */ + + /* dq_oeb, dq_reb, dq_iddq, dq_rxenb */ + reg = FTHD_S2_REG_READ(S2_DDR40_WL_IDLE_PAD_CTL); + FTHD_S2_REG_WRITE(reg & 0xff0fffff, S2_DDR40_WL_IDLE_PAD_CTL); + udelay(500); + + FTHD_S2_REG_WRITE(0, S2_DDR_2004); + udelay(10000); + + FTHD_S2_REG_WRITE(0xab0a, S2_DDR_2014); + + /* Polling for BUSY */ + ret = fthd_hw_ddr_status_busy(dev_priv, 10000, 10); + if (ret != 0) + return -EBUSY; + + udelay(10000); + + FTHD_S2_REG_WRITE(0, S2_3204); + + /* Read DRAM mem address (FIXME: Need to mask a few bits here) */ + reg = FTHD_S2_REG_READ(S2_DDR40_STRAP_STATUS); + dev_info(&dev_priv->pdev->dev, + "S2 DRAM memory address: 0x%08x\n", reg); + + switch (dev_priv->ddr_model) { + case 4: + val = 0x1fffffff; + break; + case 2: + val = 0x0fffffff; + break; + default: + /* Probably just invalid model */ + val = dev_priv->ddr_model; + } + + FTHD_S2_REG_WRITE(val, S2_3208); + FTHD_S2_REG_WRITE(0x1040, S2_3200); + + fthd_hw_ddr_rewrite_mode_regs(dev_priv); + + FTHD_S2_REG_WRITE(0x20000, S2_DDR_2014); + FTHD_S2_REG_WRITE(1, S2_DDR_2008); + + return 0; +} + +void fthd_ddr_phy_save_regs(struct fthd_private *dev_priv) +{ + u32 offset; + int i; + + for (i = 0; i < DDR_PHY_NUM_REG; i++) { + offset = fthd_ddr_phy_reg_map[i]; + dev_priv->ddr_phy_regs[i] = + FTHD_ISP_REG_READ(DDR_PHY_REG_BASE + offset); + } +} + +void fthd_ddr_phy_restore_regs(struct fthd_private *dev_priv) +{ + u32 offset; + int i; + + for (i = 0; i < DDR_PHY_NUM_REG; i++) { + offset = fthd_ddr_phy_reg_map[i]; + FTHD_S2_REG_WRITE(dev_priv->ddr_phy_regs[i], + DDR_PHY_REG_BASE + offset); + } +} + +int fthd_irq_enable(struct fthd_private *dev_priv) +{ + FTHD_ISP_REG_WRITE(0xf8, ISP_IRQ_ENABLE); + pci_write_config_dword(dev_priv->pdev, 0x94, 0x200); + + return 0; +} + +int fthd_irq_disable(struct fthd_private *dev_priv) +{ + FTHD_ISP_REG_WRITE(0, ISP_IRQ_ENABLE); + pci_write_config_dword(dev_priv->pdev, 0x94, 0x0); + + return 0; +} + +int fthd_hw_init(struct fthd_private *dev_priv) +{ + int ret; + + ret = fthd_hw_s2_init_pcie_link(dev_priv); + if (ret) + goto out; + + fthd_hw_s2_preinit_ddr_controller_soc(dev_priv); + fthd_hw_s2_init_ddr_controller_soc(dev_priv); + +/* + dev_info(&dev_priv->pdev->dev, + "Dumping DDR PHY reg map before shmoo\n"); + + for (i = 0; i < DDR_PHY_NUM_REGS; i++) { + if (!(i % 3) && i > 0) + printk("\n"); + + val = FTHD_S2_REG_READ(ddr_phy_reg_map[i]); + printk(KERN_CONT "0x%.3x = 0x%.8x\t", + ddr_phy_reg_map[i], val); + } +*/ + + ret = fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM); + if (ret) { + dev_err(&dev_priv->pdev->dev, + "Full memory verification failed! (%d)\n", ret); + /* + * Here we should do a shmoo calibration but it's not yet + * fully implemented. + */ + + /* fthd_ddr_calibrate(dev_priv); */ + } else { + dev_info(&dev_priv->pdev->dev, + "Full memory verification succeeded! (%d)\n", ret); + } + + /* Save our working configuration */ + fthd_ddr_phy_save_regs(dev_priv); + + FTHD_S2_REG_WRITE(0x8, S2_D108); + FTHD_S2_REG_WRITE(0xc, S2_D104); + + FTHD_ISP_REG_WRITE(0, ISP_REG_40004); + + ret = isp_init(dev_priv); + if (ret) + goto out; + + dev_info(&dev_priv->pdev->dev, "Enabling interrupts\n"); + fthd_irq_enable(dev_priv); +out: + return ret; +} + +void fthd_hw_deinit(struct fthd_private *dev_priv) +{ + dev_info(&dev_priv->pdev->dev, "%s", __FUNCTION__); + FTHD_ISP_REG_WRITE(0, ISP_REG_41020); + fthd_irq_disable(dev_priv); +} diff --git a/drivers/custom/facetimehd/fthd_hw.h b/drivers/custom/facetimehd/fthd_hw.h new file mode 100644 index 000000000000..58d802488f67 --- /dev/null +++ b/drivers/custom/facetimehd/fthd_hw.h @@ -0,0 +1,128 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com) + * + */ + +#ifndef _FTHD_HW_H +#define _FTHD_HW_H + +#include + +/* Used after most PCI Link IO writes */ +static inline void fthd_hw_pci_post(struct fthd_private *dev_priv) +{ + pci_write_config_dword(dev_priv->pdev, 0, 0x12345678); +} + +#define FTHD_S2_REG_READ(offset) _FTHD_S2_REG_READ(dev_priv, (offset)) +#define FTHD_S2_REG_WRITE(val, offset) _FTHD_S2_REG_WRITE(dev_priv, (val), (offset)) + +#define FTHD_S2_MEM_READ(offset) _FTHD_S2_MEM_READ(dev_priv, (offset)) +#define FTHD_S2_MEM_WRITE(val, offset) _FTHD_S2_MEM_WRITE(dev_priv, (val), (offset)) +#define FTHD_S2_MEMCPY_TOIO(offset, buf, len) _FTHD_S2_MEMCPY_TOIO(dev_priv, (buf), (offset), (len)) +#define FTHD_S2_MEMCPY_FROMIO(buf, offset, len) _FTHD_S2_MEMCPY_FROMIO(dev_priv, (buf), (offset), (len)) + +#define FTHD_ISP_REG_READ(offset) _FTHD_ISP_REG_READ(dev_priv, (offset)) +#define FTHD_ISP_REG_WRITE(val, offset) _FTHD_ISP_REG_WRITE(dev_priv, (val), (offset)) + +static inline u32 _FTHD_S2_REG_READ(struct fthd_private *dev_priv, u32 offset) +{ + if (offset >= dev_priv->s2_io_len) { + dev_err(&dev_priv->pdev->dev, + "S2 IO read out of range at %u\n", offset); + return 0; + } + + // dev_info(&dev_priv->pdev->dev, "Link IO read at %u\n", offset); + return ioread32(dev_priv->s2_io + offset); +} + +static inline void _FTHD_S2_REG_WRITE(struct fthd_private *dev_priv, u32 val, + u32 offset) +{ + if (offset >= dev_priv->s2_io_len) { + dev_err(&dev_priv->pdev->dev, + "S2 IO write out of range at %u\n", offset); + return; + } + + // dev_info(&dev_priv->pdev->dev, "S2 IO write at %u\n", offset); + iowrite32(val, dev_priv->s2_io + offset); + fthd_hw_pci_post(dev_priv); +} + +static inline u32 _FTHD_S2_MEM_READ(struct fthd_private *dev_priv, u32 offset) +{ + if (offset >= dev_priv->s2_mem_len) { + dev_err(&dev_priv->pdev->dev, + "S2 MEM read out of range at %u\n", offset); + return 0; + } + + // dev_info(&dev_priv->pdev->dev, "Link IO read at %u\n", offset); + return ioread32(dev_priv->s2_mem + offset); +} + +static inline void _FTHD_S2_MEM_WRITE(struct fthd_private *dev_priv, u32 val, + u32 offset) +{ + if (offset >= dev_priv->s2_mem_len) { + dev_err(&dev_priv->pdev->dev, + "S2 MEM write out of range at %u\n", offset); + return; + } + + // dev_info(&dev_priv->pdev->dev, "S2 IO write at %u\n", offset); + iowrite32(val, dev_priv->s2_mem + offset); +} + +static inline void _FTHD_S2_MEMCPY_TOIO(struct fthd_private *dev_priv, const void *buf, + u32 offset, int len) +{ + memcpy_toio(dev_priv->s2_mem + offset, buf, len); +} + + +static inline void _FTHD_S2_MEMCPY_FROMIO(struct fthd_private *dev_priv, void *buf, + u32 offset, int len) +{ + memcpy_fromio(buf, dev_priv->s2_mem + offset, len); +} + +static inline u32 _FTHD_ISP_REG_READ(struct fthd_private *dev_priv, u32 offset) +{ + if (offset >= dev_priv->isp_io_len) { + dev_err(&dev_priv->pdev->dev, + "ISP IO read out of range at %u\n", offset); + return 0; + } + + // dev_info(&dev_priv->pdev->dev, "ISP IO read at %u\n", offset); + return ioread32(dev_priv->isp_io + offset); +} + +static inline void _FTHD_ISP_REG_WRITE(struct fthd_private *dev_priv, u32 val, + u32 offset) +{ + if (offset >= dev_priv->isp_io_len) { + dev_err(&dev_priv->pdev->dev, + "ISP IO write out of range at %u\n", offset); + return; + } + + // dev_info(&dev_priv->pdev->dev, "Dev IO write at %u\n", offset); + iowrite32(val, dev_priv->isp_io + offset); + fthd_hw_pci_post(dev_priv); +} + +extern int fthd_irq_enable(struct fthd_private *dev_priv); +extern int fthd_irq_disable(struct fthd_private *dev_priv); +extern int fthd_hw_init(struct fthd_private *dev_priv); +extern void fthd_hw_deinit(struct fthd_private *priv); +extern void fthd_ddr_phy_save_regs(struct fthd_private *dev_priv); +extern void fthd_ddr_phy_restore_regs(struct fthd_private *dev_priv); +#endif diff --git a/drivers/custom/facetimehd/fthd_isp.c b/drivers/custom/facetimehd/fthd_isp.c new file mode 100644 index 000000000000..f8dbd2dd9d1c --- /dev/null +++ b/drivers/custom/facetimehd/fthd_isp.c @@ -0,0 +1,1433 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com) + * + */ + +#include +#include +#include +#include +#include +#include "fthd_drv.h" +#include "fthd_hw.h" +#include "fthd_reg.h" +#include "fthd_ringbuf.h" +#include "fthd_isp.h" + +int isp_mem_init(struct fthd_private *dev_priv) +{ + struct resource *root = &dev_priv->pdev->resource[FTHD_PCI_S2_MEM]; + + dev_priv->mem = kzalloc(sizeof(struct resource), GFP_KERNEL); + if (!dev_priv->mem) + return -ENOMEM; + + dev_priv->mem->start = root->start; + dev_priv->mem->end = root->end; + + /* Preallocate 8mb for the firmware */ + dev_priv->firmware = isp_mem_create(dev_priv, FTHD_MEM_FIRMWARE, + FTHD_MEM_FW_SIZE); + + if (!dev_priv->firmware) { + dev_err(&dev_priv->pdev->dev, + "Failed to preallocate firmware memory\n"); + return -ENOMEM; + } + return 0; +} + +struct isp_mem_obj *isp_mem_create(struct fthd_private *dev_priv, + unsigned int type, resource_size_t size) +{ + struct isp_mem_obj *obj; + struct resource *root = dev_priv->mem; + int ret; + + obj = kzalloc(sizeof(struct isp_mem_obj), GFP_KERNEL); + if (!obj) + return NULL; + + obj->type = type; + obj->base.name = "S2 ISP"; + ret = allocate_resource(root, &obj->base, size, root->start, root->end, + PAGE_SIZE, NULL, NULL); + if (ret) { + dev_err(&dev_priv->pdev->dev, + "Failed to allocate resource (size: %Ld, start: %Ld, end: %Ld)\n", + size, root->start, root->end); + kfree(obj); + obj = NULL; + } + + obj->offset = obj->base.start - root->start; + obj->size = size; + obj->size_aligned = obj->base.end - obj->base.start; + return obj; +} + +int isp_mem_destroy(struct isp_mem_obj *obj) +{ + if (obj) { + release_resource(&obj->base); + kfree(obj); + obj = NULL; + } + + return 0; +} + +static int isp_acpi_set_power(struct fthd_private *dev_priv, int power) +{ + acpi_status status; + acpi_handle handle; + struct acpi_object_list arg_list; + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object args[1]; + union acpi_object *result; + int ret = 0; + + + handle = ACPI_HANDLE(&dev_priv->pdev->dev); + if(!handle) { + dev_err(&dev_priv->pdev->dev, + "Failed to get S2 CMPE ACPI handle\n"); + ret = -ENODEV; + goto out; + } + + args[0].type = ACPI_TYPE_INTEGER; + args[0].integer.value = power; + + arg_list.count = 1; + arg_list.pointer = args; + + status = acpi_evaluate_object(handle, "CMPE", &arg_list, &buffer); + if (ACPI_FAILURE(status)) { + dev_err(&dev_priv->pdev->dev, + "Failed to execute S2 CMPE ACPI method\n"); + ret = -ENODEV; + goto out; + } + + result = buffer.pointer; + + if (result->type != ACPI_TYPE_INTEGER || result->integer.value != 0) { + dev_err(&dev_priv->pdev->dev, + "Invalid ACPI response (len: %Ld)\n", buffer.length); + ret = -EINVAL; + } + +out: + kfree(buffer.pointer); + return ret; +} + +static int isp_enable_sensor(struct fthd_private *dev_priv) +{ + return 0; +} + +static int isp_load_firmware(struct fthd_private *dev_priv) +{ + const struct firmware *fw; + int ret = 0; + + ret = request_firmware(&fw, "facetimehd/firmware.bin", &dev_priv->pdev->dev); + if (ret) + return ret; + + /* Firmware memory is preallocated at init time */ + if (!dev_priv->firmware) + return -ENOMEM; + + if (dev_priv->firmware->base.start != dev_priv->mem->start) { + dev_err(&dev_priv->pdev->dev, + "Misaligned firmware memory object (offset: %lu)\n", + dev_priv->firmware->offset); + isp_mem_destroy(dev_priv->firmware); + dev_priv->firmware = NULL; + return -EBUSY; + } + + FTHD_S2_MEMCPY_TOIO(dev_priv->firmware->offset, fw->data, fw->size); + + /* Might need a flush here if we map ISP memory cached */ + + dev_info(&dev_priv->pdev->dev, "Loaded firmware, size: %lukb\n", + fw->size / 1024); + + release_firmware(fw); + + return ret; +} + +static void isp_free_channel_info(struct fthd_private *priv) +{ + struct fw_channel *chan; + int i; + for(i = 0; i < priv->num_channels; i++) { + chan = priv->channels[i]; + if (!chan) + continue; + + kfree(chan->name); + kfree(chan); + priv->channels[i] = NULL; + } + kfree(priv->channels); + priv->channels = NULL; +} + +static struct fw_channel *isp_get_chan_index(struct fthd_private *priv, const char *name) +{ + int i; + for(i = 0; i < priv->num_channels; i++) { + if (!strcasecmp(priv->channels[i]->name, name)) + return priv->channels[i]; + } + return NULL; +} + +static int isp_fill_channel_info(struct fthd_private *dev_priv, int offset, int num_channels) +{ + struct isp_channel_info info; + struct fw_channel *chan; + int i; + + if (!num_channels) + return -EINVAL; + + dev_priv->channels = kzalloc(num_channels * sizeof(struct fw_channel *), GFP_KERNEL); + if (!dev_priv->channels) + goto out; + + dev_priv->num_channels = num_channels; + + for(i = 0; i < num_channels; i++) { + FTHD_S2_MEMCPY_FROMIO(&info, offset + i * 256, sizeof(info)); + + chan = kzalloc(sizeof(struct fw_channel), GFP_KERNEL); + if (!chan) + goto out; + + dev_priv->channels[i] = chan; + + pr_debug("Channel %d: %s, type %d, source %d, size %d, offset %x\n", + i, info.name, info.type, info.source, info.size, info.offset); + + chan->name = kstrdup(info.name, GFP_KERNEL); + if (!chan->name) + goto out; + + chan->type = info.type; + chan->source = info.source; + chan->size = info.size; + chan->offset = info.offset; + spin_lock_init(&chan->lock); + init_waitqueue_head(&chan->wq); + } + + dev_priv->channel_terminal = isp_get_chan_index(dev_priv, "TERMINAL"); + dev_priv->channel_debug = isp_get_chan_index(dev_priv, "DEBUG"); + dev_priv->channel_shared_malloc = isp_get_chan_index(dev_priv, "SHAREDMALLOC"); + dev_priv->channel_io = isp_get_chan_index(dev_priv, "IO"); + dev_priv->channel_buf_h2t = isp_get_chan_index(dev_priv, "BUF_H2T"); + dev_priv->channel_buf_t2h = isp_get_chan_index(dev_priv, "BUF_T2H"); + dev_priv->channel_io_t2h = isp_get_chan_index(dev_priv, "IO_T2H"); + + if (!dev_priv->channel_terminal || !dev_priv->channel_debug + || !dev_priv->channel_shared_malloc || !dev_priv->channel_io + || !dev_priv->channel_buf_h2t || !dev_priv->channel_buf_t2h + || !dev_priv->channel_io_t2h) { + dev_err(&dev_priv->pdev->dev, "did not find all of the required channels\n"); + goto out; + } + return 0; +out: + isp_free_channel_info(dev_priv); + return -ENOMEM; +} + +static int fthd_isp_cmd(struct fthd_private *dev_priv, enum fthd_isp_cmds command, void *buf, + int request_len, int *response_len) +{ + struct isp_mem_obj *request; + struct isp_cmd_hdr cmd; + u32 address, request_size, response_size; + u32 entry; + int len, ret; + + memset(&cmd, 0, sizeof(cmd)); + + if (response_len) { + len = max(request_len, *response_len); + } else { + len = request_len; + } + len += sizeof(struct isp_cmd_hdr); + + pr_debug("sending cmd %d to firmware\n", command); + + request = isp_mem_create(dev_priv, FTHD_MEM_CMD, len); + if (!request) { + dev_err(&dev_priv->pdev->dev, "failed to allocate cmd memory object\n"); + return -ENOMEM; + } + + cmd.opcode = command; + + FTHD_S2_MEMCPY_TOIO(request->offset, &cmd, sizeof(struct isp_cmd_hdr)); + if (request_len) + FTHD_S2_MEMCPY_TOIO(request->offset + sizeof(struct isp_cmd_hdr), buf, request_len); + + ret = fthd_channel_ringbuf_send(dev_priv, dev_priv->channel_io, + request->offset, request_len + 8, (response_len ? *response_len : 0) + 8, &entry); + if (ret) + goto out; + + if (entry == (u32)-1) { + ret = -EIO; + goto out; + } + + if (command == CISP_CMD_POWER_DOWN) { + /* powerdown doesn't seem to generate a response */ + ret = 0; + goto out; + } + + ret = fthd_channel_wait_ready(dev_priv, dev_priv->channel_io, entry, 2000); + if (ret) { + if (response_len) + *response_len = 0; + goto out; + } + + FTHD_S2_MEMCPY_FROMIO(&cmd, request->offset, sizeof(struct isp_cmd_hdr)); + address = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS); + request_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE); + response_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE); + + /* XXX: response size in the ringbuf is zero after command completion, how is buffer size + verification done? */ + if (response_len && *response_len) + FTHD_S2_MEMCPY_FROMIO(buf, (address & ~3) + sizeof(struct isp_cmd_hdr), + *response_len); + + pr_debug("status %04x, request_len %d response len %d address_flags %x\n", cmd.status, + request_size, response_size, address); + + ret = cmd.status ? -EIO : 0; +out: + isp_mem_destroy(request); + return ret; +} + +int fthd_isp_debug_cmd(struct fthd_private *dev_priv, enum fthd_isp_cmds command, void *buf, + int request_len, int *response_len) +{ + struct isp_mem_obj *request; + struct isp_cmd_hdr cmd; + u32 address, request_size, response_size; + u32 entry; + int len, ret; + + memset(&cmd, 0, sizeof(cmd)); + + if (response_len) { + len = max(request_len, *response_len); + } else { + len = request_len; + } + len += sizeof(struct isp_cmd_hdr); + + pr_debug("sending debug cmd %d to firmware\n", command); + + request = isp_mem_create(dev_priv, FTHD_MEM_CMD, len); + if (!request) { + dev_err(&dev_priv->pdev->dev, "failed to allocate cmd memory object\n"); + return -ENOMEM; + } + + cmd.opcode = command; + + FTHD_S2_MEMCPY_TOIO(request->offset, &cmd, sizeof(struct isp_cmd_hdr)); + if (request_len) + FTHD_S2_MEMCPY_TOIO(request->offset + sizeof(struct isp_cmd_hdr), buf, request_len); + + ret = fthd_channel_ringbuf_send(dev_priv, dev_priv->channel_debug, + request->offset, request_len + 8, (response_len ? *response_len : 0) + 8, &entry); + if (ret) + goto out; + + if (entry == (u32)-1) { + ret = -EIO; + goto out; + } + + ret = fthd_channel_wait_ready(dev_priv, dev_priv->channel_debug, entry, 20000); + if (ret) { + if (response_len) + *response_len = 0; + goto out; + } + + FTHD_S2_MEMCPY_FROMIO(&cmd, request->offset, sizeof(struct isp_cmd_hdr)); + address = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS); + request_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE); + response_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE); + + /* XXX: response size in the ringbuf is zero after command completion, how is buffer size + verification done? */ + if (response_len && *response_len) + FTHD_S2_MEMCPY_FROMIO(buf, (address & ~3) + sizeof(struct isp_cmd_hdr), + *response_len); + + pr_info("status %04x, request_len %d response len %d address_flags %x\n", cmd.status, + request_size, response_size, address); + + ret = 0; +out: + isp_mem_destroy(request); + return ret; +} + + + +int fthd_isp_cmd_start(struct fthd_private *dev_priv) +{ + pr_debug("sending start cmd to firmware\n"); + return fthd_isp_cmd(dev_priv, CISP_CMD_START, NULL, 0, NULL); +} + +int fthd_isp_cmd_channel_start(struct fthd_private *dev_priv) +{ + struct isp_cmd_channel_start cmd; + pr_debug("sending channel start cmd to firmware\n"); + + cmd.channel = 0; + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_START, &cmd, sizeof(cmd), NULL); +} + +int fthd_isp_cmd_channel_stop(struct fthd_private *dev_priv) +{ + struct isp_cmd_channel_stop cmd; + + cmd.channel = 0; + pr_debug("sending channel stop cmd to firmware\n"); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_STOP, &cmd, sizeof(cmd), NULL); +} + +int fthd_isp_cmd_stop(struct fthd_private *dev_priv) +{ + return fthd_isp_cmd(dev_priv, CISP_CMD_STOP, NULL, 0, NULL); +} + +static int fthd_isp_cmd_powerdown(struct fthd_private *dev_priv) +{ + return fthd_isp_cmd(dev_priv, CISP_CMD_POWER_DOWN, NULL, 0, NULL); +} + +static void isp_free_set_file(struct fthd_private *dev_priv) +{ + if (dev_priv->set_file) + isp_mem_destroy(dev_priv->set_file); +} + +int isp_powerdown(struct fthd_private *dev_priv) +{ + int retries; + u32 reg; + + FTHD_ISP_REG_WRITE(0xf7fbdff9, 0xc3000); + fthd_isp_cmd_powerdown(dev_priv); + + for (retries = 0; retries < 100; retries++) { + reg = FTHD_ISP_REG_READ(0xc3000); + if (reg == 0x8042006) + break; + mdelay(10); + } + + if (retries >= 100) { + dev_info(&dev_priv->pdev->dev, "deinit failed!\n"); + return -EIO; + } + return 0; +} + +int isp_uninit(struct fthd_private *dev_priv) +{ + FTHD_ISP_REG_WRITE(0x00000000, 0x40004); + FTHD_ISP_REG_WRITE(0x00000000, ISP_IRQ_ENABLE); + FTHD_ISP_REG_WRITE(0xffffffff, 0xc0008); + FTHD_ISP_REG_WRITE(0xffffffff, 0xc000c); + FTHD_ISP_REG_WRITE(0xffffffff, 0xc0010); + FTHD_ISP_REG_WRITE(0x00000000, 0xc1004); + FTHD_ISP_REG_WRITE(0xffffffff, 0xc100c); + FTHD_ISP_REG_WRITE(0xffffffff, 0xc1014); + FTHD_ISP_REG_WRITE(0xffffffff, 0xc101c); + FTHD_ISP_REG_WRITE(0xffffffff, 0xc1024); + mdelay(1); + + FTHD_ISP_REG_WRITE(0, 0xc0000); + FTHD_ISP_REG_WRITE(0, 0xc0004); + FTHD_ISP_REG_WRITE(0, 0xc0008); + FTHD_ISP_REG_WRITE(0, 0xc000c); + FTHD_ISP_REG_WRITE(0, 0xc0010); + FTHD_ISP_REG_WRITE(0, 0xc0014); + FTHD_ISP_REG_WRITE(0, 0xc0018); + FTHD_ISP_REG_WRITE(0, 0xc001c); + FTHD_ISP_REG_WRITE(0, 0xc0020); + FTHD_ISP_REG_WRITE(0, 0xc0024); + + FTHD_ISP_REG_WRITE(0xffffffff, ISP_IRQ_CLEAR); + isp_free_channel_info(dev_priv); + isp_free_set_file(dev_priv); + isp_mem_destroy(dev_priv->firmware); + kfree(dev_priv->mem); + return 0; +} + + +int fthd_isp_cmd_print_enable(struct fthd_private *dev_priv, int enable) +{ + struct isp_cmd_print_enable cmd; + + cmd.enable = enable; + + return fthd_isp_cmd(dev_priv, CISP_CMD_PRINT_ENABLE, &cmd, sizeof(cmd), NULL); +} + +int fthd_isp_cmd_set_loadfile(struct fthd_private *dev_priv) +{ + struct isp_cmd_set_loadfile cmd; + struct isp_mem_obj *file; + const struct firmware *fw; + const char *filename = NULL; + const char *vendor, *board; + int ret = 0; + + pr_debug("set loadfile\n"); + + vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + board = dmi_get_system_info(DMI_BOARD_NAME); + + memset(&cmd, 0, sizeof(cmd)); + + switch(dev_priv->sensor_id1) { + case 0x164: + filename = "facetimehd/8221_01XX.dat"; + break; + case 0x190: + filename = "facetimehd/1222_01XX.dat"; + break; + case 0x8830: + filename = "facetimehd/9112_01XX.dat"; + break; + case 0x9770: + if (vendor && board && !strcmp(vendor, "Apple Inc.") && + !strncmp(board, "MacBookAir", sizeof("MacBookAir")-1)) { + filename = "facetimehd/1771_01XX.dat"; + break; + } + + switch(dev_priv->sensor_id0) { + case 4: + filename = "facetimehd/1874_01XX.dat"; + break; + default: + filename = "facetimehd/1871_01XX.dat"; + break; + } + break; + case 0x9774: + switch(dev_priv->sensor_id0) { + case 4: + filename = "facetimehd/1674_01XX.dat"; + break; + case 5: + filename = "facetimehd/1675_01XX.dat"; + break; + default: + filename = "facetimehd/1671_01XX.dat"; + break; + } + break; + default: + break; + + } + + if (!filename) { + pr_debug("no set file for sensorid %04x %04x found\n", + dev_priv->sensor_id0, dev_priv->sensor_id1); + return 0; + } + + /* The set file is allowed to be missing but we don't get calibration */ + ret = request_firmware(&fw, filename, &dev_priv->pdev->dev); + if (ret) + return 0; + + /* Firmware memory is preallocated at init time */ + BUG_ON(dev_priv->set_file); + + file = isp_mem_create(dev_priv, FTHD_MEM_SET_FILE, fw->size); + FTHD_S2_MEMCPY_TOIO(file->offset, fw->data, fw->size); + + release_firmware(fw); + + dev_priv->set_file = file; + pr_debug("set file: addr %08lx, size %d\n", file->offset, (int)file->size); + cmd.addr = file->offset; + cmd.length = file->size; + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_SET_FILE_LOAD, &cmd, sizeof(cmd), NULL); +} + +int fthd_isp_cmd_channel_info(struct fthd_private *dev_priv) +{ + struct isp_cmd_channel_info cmd; + int ret, len; + + pr_debug("sending ch info\n"); + + memset(&cmd, 0, sizeof(cmd)); + len = sizeof(cmd); + ret = fthd_isp_cmd(dev_priv, CISP_CMD_CH_INFO_GET, &cmd, sizeof(cmd), &len); + print_hex_dump_bytes("CHINFO ", DUMP_PREFIX_OFFSET, &cmd, sizeof(cmd)); + pr_debug("sensor id: %04x %04x\n", cmd.sensorid0, cmd.sensorid1); + pr_debug("sensor count: %d\n", cmd.sensor_count); + pr_debug("camera module serial number string: %s\n", cmd.camera_module_serial_number); + pr_debug("sensor serial number: %02X%02X%02X%02X%02X%02X%02X%02X\n", + cmd.sensor_serial_number[0], cmd.sensor_serial_number[1], + cmd.sensor_serial_number[2], cmd.sensor_serial_number[3], + cmd.sensor_serial_number[4], cmd.sensor_serial_number[5], + cmd.sensor_serial_number[6], cmd.sensor_serial_number[7]); + dev_priv->sensor_id0 = cmd.sensorid0; + dev_priv->sensor_id1 = cmd.sensorid1; + dev_priv->sensor_count = cmd.sensor_count; + return ret; +} + +int fthd_isp_cmd_camera_config(struct fthd_private *dev_priv) +{ + struct isp_cmd_config cmd; + int ret, len; + + pr_debug("sending camera config\n"); + + memset(&cmd, 0, sizeof(cmd)); + + len = sizeof(cmd); + ret = fthd_isp_cmd(dev_priv, CISP_CMD_CONFIG_GET, &cmd, sizeof(cmd), &len); + if (!ret) + print_hex_dump_bytes("CAMINFO ", DUMP_PREFIX_OFFSET, &cmd, sizeof(cmd)); + return ret; +} + +int fthd_isp_cmd_channel_camera_config(struct fthd_private *dev_priv) +{ + struct isp_cmd_channel_camera_config cmd; + int ret, len, i; + char prefix[16]; + pr_debug("sending ch camera config\n"); + + memset(&cmd, 0, sizeof(cmd)); + for(i = 0; i < dev_priv->sensor_count; i++) { + cmd.channel = i; + + len = sizeof(cmd); + ret = fthd_isp_cmd(dev_priv, CISP_CMD_CH_CAMERA_CONFIG_GET, &cmd, sizeof(cmd), &len); + if (ret) + break; + snprintf(prefix, sizeof(prefix)-1, "CAMCONF%d ", i); + print_hex_dump_bytes(prefix, DUMP_PREFIX_OFFSET, &cmd, sizeof(cmd)); + } + return ret; +} + +int fthd_isp_cmd_channel_camera_config_select(struct fthd_private *dev_priv, int channel, int config) +{ + struct isp_cmd_channel_camera_config_select cmd; + int len; + + pr_debug("set camera config: %d\n", config); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.config = config; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_CAMERA_CONFIG_SELECT, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_crop_set(struct fthd_private *dev_priv, int channel, + int x1, int y1, int x2, int y2) +{ + struct isp_cmd_channel_set_crop cmd; + int len; + + pr_debug("set crop: [%d, %d] -> [%d, %d]\n", x1, y1, x2, y2); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.x1 = x1; + cmd.y2 = y2; + cmd.x2 = x2; + cmd.y2 = y2; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_CROP_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_output_config_set(struct fthd_private *dev_priv, int channel, int x, int y, int pixelformat) +{ + struct isp_cmd_channel_output_config cmd; + int len; + + pr_debug("output config: [%d, %d]\n", x, y); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.x1 = x; /* Y size */ + cmd.x2 = x * 2; /* Chroma size? */ + cmd.x3 = x; + cmd.y1 = y; + + /* pixel formats: + * 0 - plane 0 Y plane 1 UV + 1 - YUYV + 2 - YVYU + */ + cmd.pixelformat = pixelformat; + cmd.unknown3 = 0; + cmd.unknown5 = 0x7ff; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_OUTPUT_CONFIG_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_recycle_mode(struct fthd_private *dev_priv, int channel, int mode) +{ + struct isp_cmd_channel_recycle_mode cmd; + int len; + + pr_debug("set recycle mode %d\n", mode); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.mode = mode; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_BUFFER_RECYCLE_MODE_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_buffer_return(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_buffer_return cmd; + int len; + + pr_debug("buffer return\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_BUFFER_RETURN, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_recycle_start(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_recycle_mode cmd; + int len; + + pr_debug("start recycle\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_BUFFER_RECYCLE_START, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_drc_start(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_drc_start cmd; + int len; + + pr_debug("start drc\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_DRC_START, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_tone_curve_adaptation_start(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_tone_curve_adaptation_start cmd; + int len; + + pr_debug("tone curve adaptation start\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_TONE_CURVE_ADAPTATION_START, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_sif_pixel_format(struct fthd_private *dev_priv, int channel, int param1, int param2) +{ + struct isp_cmd_channel_sif_format_set cmd; + int len; + + pr_debug("set pixel format %d, %d\n", param1, param2); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.param1 = param1; + cmd.param2 = param2; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_SIF_PIXEL_FORMAT_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_error_handling_config(struct fthd_private *dev_priv, int channel, int param1, int param2) +{ + struct isp_cmd_channel_camera_err_handle_config cmd; + int len; + + pr_debug("set error handling config %d, %d\n", param1, param2); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.param1 = param1; + cmd.param2 = param2; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_CAMERA_ERR_HANDLE_CONFIG, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_streaming_mode(struct fthd_private *dev_priv, int channel, int mode) +{ + struct isp_cmd_channel_streaming_mode cmd; + int len; + + pr_debug("set streaming mode %d\n", mode); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.mode = mode; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_STREAMING_MODE_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_frame_rate_min(struct fthd_private *dev_priv, int channel, int rate) +{ + struct isp_cmd_channel_frame_rate_set cmd; + int len; + + pr_debug("set ae frame rate min %d\n", rate); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.rate = rate; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_AE_FRAME_RATE_MIN_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_frame_rate_max(struct fthd_private *dev_priv, int channel, int rate) +{ + struct isp_cmd_channel_frame_rate_set cmd; + int len; + + pr_debug("set ae frame rate max %d\n", rate); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.rate = rate; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_AE_FRAME_RATE_MAX_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_ae_speed_set(struct fthd_private *dev_priv, int channel, int speed) +{ + struct isp_cmd_channel_ae_speed_set cmd; + int len; + + pr_debug("set ae speed %d\n", speed); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.speed = speed; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_AE_SPEED_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_ae_stability_set(struct fthd_private *dev_priv, int channel, int stability) +{ + struct isp_cmd_channel_ae_stability_set cmd; + int len; + + pr_debug("set ae stability %d\n", stability); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.stability = stability; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_AE_STABILITY_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_ae_stability_to_stable_set(struct fthd_private *dev_priv, int channel, int value) +{ + struct isp_cmd_channel_ae_stability_to_stable_set cmd; + int len; + + pr_debug("set ae stability to stable %d\n", value); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.value = value; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_AE_STABILITY_TO_STABLE_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_face_detection_start(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_face_detection_start cmd; + int len; + + pr_debug("face detection start\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_FACE_DETECTION_START, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_face_detection_stop(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_face_detection_stop cmd; + int len; + + pr_debug("face detection stop\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_FACE_DETECTION_STOP, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_face_detection_enable(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_face_detection_enable cmd; + int len; + + pr_debug("face detection enable\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_FACE_DETECTION_ENABLE, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_face_detection_disable(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_face_detection_disable cmd; + int len; + + pr_debug("face detection disable\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_FACE_DETECTION_DISABLE, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_temporal_filter_start(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_temporal_filter_start cmd; + int len; + + pr_debug("temporal filter start\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_TEMPORAL_FILTER_START, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_temporal_filter_stop(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_temporal_filter_stop cmd; + int len; + + pr_debug("temporal filter stop\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_TEMPORAL_FILTER_STOP, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_temporal_filter_enable(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_temporal_filter_enable cmd; + int len; + + pr_debug("temporal filter enable\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_TEMPORAL_FILTER_ENABLE, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_temporal_filter_disable(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_temporal_filter_disable cmd; + int len; + + pr_debug("temporal filter disable\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_TEMPORAL_FILTER_DISABLE, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_motion_history_start(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_motion_history_start cmd; + int len; + + pr_debug("motion history start\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_MOTION_HISTORY_START, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_motion_history_stop(struct fthd_private *dev_priv, int channel) +{ + struct isp_cmd_channel_motion_history_stop cmd; + int len; + + pr_debug("motion history stop\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_MOTION_HISTORY_STOP, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_ae_metering_mode_set(struct fthd_private *dev_priv, int channel, int mode) +{ + struct isp_cmd_channel_ae_metering_mode_set cmd; + int len; + + pr_debug("ae metering mode %d\n", mode); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.mode = mode; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_AE_METERING_MODE_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_brightness_set(struct fthd_private *dev_priv, int channel, int brightness) +{ + struct isp_cmd_channel_brightness_set cmd; + int len; + + pr_debug("set brightness %d\n", brightness); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.brightness = brightness; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_SCALER_BRIGHTNESS_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_contrast_set(struct fthd_private *dev_priv, int channel, int contrast) +{ + struct isp_cmd_channel_contrast_set cmd; + int len; + + pr_debug("set contrast %d\n", contrast); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.contrast = contrast; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_SCALER_CONTRAST_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_saturation_set(struct fthd_private *dev_priv, int channel, int saturation) +{ + struct isp_cmd_channel_saturation_set cmd; + int len; + + pr_debug("set saturation %d\n", saturation); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.contrast = saturation; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_SCALER_SATURATION_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_hue_set(struct fthd_private *dev_priv, int channel, int hue) +{ + struct isp_cmd_channel_hue_set cmd; + int len; + + pr_debug("set hue %d\n", hue); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = channel; + cmd.contrast = hue; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, CISP_CMD_CH_SCALER_HUE_SET, &cmd, sizeof(cmd), &len); +} + +int fthd_isp_cmd_channel_awb(struct fthd_private *dev_priv, int channel, int enable) +{ + struct isp_cmd_channel cmd; + enum fthd_isp_cmds op; + int len; + + pr_debug("set awb %s\n", enable ? "on" : "off"); + + cmd.channel = channel; + op = enable ? CISP_CMD_CH_AWB_START : CISP_CMD_CH_AWB_STOP; + len = sizeof(cmd); + return fthd_isp_cmd(dev_priv, op, &cmd, sizeof(cmd), &len); +} + +int fthd_start_channel(struct fthd_private *dev_priv, int channel) +{ + int ret, x1 = 0, x2 = 0, pixelformat; + + ret = fthd_isp_cmd_channel_camera_config(dev_priv); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_camera_config_select(dev_priv, 0, 0); + if (ret) + return ret; + + if (dev_priv->fmt.fmt.width < 1280 || + dev_priv->fmt.fmt.height < 720) { + x1 = 160; + x2 = 960; + } else { + x1 = 0; + x2 = 1280; + } + + ret = fthd_isp_cmd_channel_crop_set(dev_priv, 0, x1, 0, x2, 720); + if (ret) + return ret; + + switch(dev_priv->fmt.fmt.pixelformat) { + case V4L2_PIX_FMT_YUYV: + pixelformat = 1; + break; + case V4L2_PIX_FMT_YVYU: + pixelformat = 2; + break; + case V4L2_PIX_FMT_NV16: + pixelformat = 0; + break; + default: + pixelformat = 1; + WARN_ON(1); + } + ret = fthd_isp_cmd_channel_output_config_set(dev_priv, 0, + dev_priv->fmt.fmt.width, + dev_priv->fmt.fmt.height, + pixelformat); + if (ret) + return ret; + + ret = fthd_isp_cmd_channel_recycle_mode(dev_priv, 0, 1); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_recycle_start(dev_priv, 0); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_ae_metering_mode_set(dev_priv, 0, 3); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_drc_start(dev_priv, 0); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_tone_curve_adaptation_start(dev_priv, 0); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_ae_speed_set(dev_priv, 0, 60); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_ae_stability_set(dev_priv, 0, 75); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_ae_stability_to_stable_set(dev_priv, 0, 8); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_sif_pixel_format(dev_priv, 0, 1, 1); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_error_handling_config(dev_priv, 0, 2, 1); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_face_detection_enable(dev_priv, 0); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_face_detection_start(dev_priv, 0); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_frame_rate_max(dev_priv, 0, dev_priv->frametime * 256); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_frame_rate_min(dev_priv, 0, dev_priv->frametime * 256); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_temporal_filter_start(dev_priv, 0); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_motion_history_start(dev_priv, 0); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_temporal_filter_enable(dev_priv, 0); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_streaming_mode(dev_priv, 0, 0); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_brightness_set(dev_priv, 0, 0x80); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_contrast_set(dev_priv, 0, 0x80); + if (ret) + return ret; + ret = fthd_isp_cmd_channel_start(dev_priv); + if (ret) + return ret; + mdelay(1000); /* Needed to settle AE */ + return 0; +} + +int fthd_stop_channel(struct fthd_private *dev_priv, int channel) +{ + int ret; + + ret = fthd_isp_cmd_channel_stop(dev_priv); + if (ret) + return ret; + + ret = fthd_isp_cmd_channel_buffer_return(dev_priv, 0); + if (ret) + return ret; + + ret = fthd_isp_cmd_channel_face_detection_stop(dev_priv, 0); + if (ret) + return ret; + + ret = fthd_isp_cmd_channel_face_detection_disable(dev_priv, 0); + if (ret) + return ret; + + ret = fthd_isp_cmd_channel_temporal_filter_disable(dev_priv, 0); + if (ret) + return ret; + + ret = fthd_isp_cmd_channel_motion_history_stop(dev_priv, 0); + if (ret) + return ret; + + return fthd_isp_cmd_channel_temporal_filter_stop(dev_priv, 0); +} + +int isp_init(struct fthd_private *dev_priv) +{ + struct isp_mem_obj *fw_queue, *heap, *fw_args; + struct isp_fw_args fw_args_data; + u32 num_channels, queue_size, heap_size, reg, offset; + int i, retries, ret; + + ret = isp_mem_init(dev_priv); + if (ret) + return ret; + + ret = isp_load_firmware(dev_priv); + if (ret) + return ret; + + isp_acpi_set_power(dev_priv, 1); + mdelay(20); + + pci_set_power_state(dev_priv->pdev, PCI_D0); + mdelay(10); + + isp_enable_sensor(dev_priv); + FTHD_ISP_REG_WRITE(0, ISP_FW_CHAN_CTRL); + FTHD_ISP_REG_WRITE(0, ISP_FW_QUEUE_CTRL); + FTHD_ISP_REG_WRITE(0, ISP_FW_SIZE); + FTHD_ISP_REG_WRITE(0, ISP_FW_HEAP_SIZE); + FTHD_ISP_REG_WRITE(0, ISP_FW_HEAP_ADDR); + FTHD_ISP_REG_WRITE(0, ISP_FW_HEAP_SIZE2); + FTHD_ISP_REG_WRITE(0, ISP_REG_C3018); + FTHD_ISP_REG_WRITE(0, ISP_REG_C301C); + + FTHD_ISP_REG_WRITE(0xffffffff, ISP_IRQ_CLEAR); + + /* + * Probably the IPC queue + * FIXME: Check if we can do 64bit writes on PCIe + */ + for (i = ISP_FW_CHAN_START; i <= ISP_FW_CHAN_END; i += 8) { + FTHD_ISP_REG_WRITE(0xffffffff, i); + FTHD_ISP_REG_WRITE(0, i + 4); + } + + FTHD_ISP_REG_WRITE(0x80000000, ISP_REG_40008); + FTHD_ISP_REG_WRITE(0x1, ISP_REG_40004); + + for (retries = 0; retries < 1000; retries++) { + reg = FTHD_ISP_REG_READ(ISP_IRQ_STATUS); + if ((reg & 0xf0) > 0) + break; + mdelay(10); + } + + if (retries >= 1000) { + dev_info(&dev_priv->pdev->dev, "Init failed! No wake signal\n"); + return -EIO; + } + + dev_info(&dev_priv->pdev->dev, "ISP woke up after %dms\n", + (retries - 1) * 10); + + FTHD_ISP_REG_WRITE(0xffffffff, ISP_IRQ_CLEAR); + + num_channels = FTHD_ISP_REG_READ(ISP_FW_CHAN_CTRL); + queue_size = FTHD_ISP_REG_READ(ISP_FW_QUEUE_CTRL) + 1; + + dev_info(&dev_priv->pdev->dev, + "Number of IPC channels: %u, queue size: %u\n", + num_channels, queue_size); + + if (num_channels > 32) { + dev_info(&dev_priv->pdev->dev, "Too many IPC channels: %u\n", + num_channels); + return -EIO; + } + + fw_queue = isp_mem_create(dev_priv, FTHD_MEM_FW_QUEUE, queue_size); + if (!fw_queue) + return -ENOMEM; + + /* Firmware heap max size is 4mb */ + heap_size = FTHD_ISP_REG_READ(ISP_FW_HEAP_SIZE); + + if (heap_size == 0) { + FTHD_ISP_REG_WRITE(0, ISP_FW_CHAN_CTRL); + FTHD_ISP_REG_WRITE(fw_queue->offset, ISP_FW_QUEUE_CTRL); + FTHD_ISP_REG_WRITE(dev_priv->firmware->size_aligned, ISP_FW_SIZE); + FTHD_ISP_REG_WRITE(0x10000000 - dev_priv->firmware->size_aligned, + ISP_FW_HEAP_SIZE); + FTHD_ISP_REG_WRITE(0, ISP_FW_HEAP_ADDR); + FTHD_ISP_REG_WRITE(0, ISP_FW_HEAP_SIZE2); + } else { + /* Must be at least 0x1000 bytes */ + heap_size = (heap_size < 0x1000) ? 0x1000 : heap_size; + + if (heap_size > 0x400000) { + dev_info(&dev_priv->pdev->dev, + "Firmware heap request size too big (%ukb)\n", + heap_size / 1024); + return -ENOMEM; + } + + dev_info(&dev_priv->pdev->dev, "Firmware requested heap size: %ukb\n", + heap_size / 1024); + + heap = isp_mem_create(dev_priv, FTHD_MEM_HEAP, heap_size); + if (!heap) + return -ENOMEM; + + FTHD_ISP_REG_WRITE(0, ISP_FW_CHAN_CTRL); + + /* Set IPC queue base addr */ + FTHD_ISP_REG_WRITE(fw_queue->offset, ISP_FW_QUEUE_CTRL); + + FTHD_ISP_REG_WRITE(FTHD_MEM_FW_SIZE, ISP_FW_SIZE); + + FTHD_ISP_REG_WRITE(0x10000000 - FTHD_MEM_FW_SIZE, ISP_FW_HEAP_SIZE); + + FTHD_ISP_REG_WRITE(heap->offset, ISP_FW_HEAP_ADDR); + + FTHD_ISP_REG_WRITE(heap->size, ISP_FW_HEAP_SIZE2); + + /* Set FW args */ + fw_args = isp_mem_create(dev_priv, FTHD_MEM_FW_ARGS, sizeof(struct isp_fw_args)); + if (!fw_args) + return -ENOMEM; + + fw_args_data.__unknown = 2; + fw_args_data.fw_arg = 0; + fw_args_data.full_stats_mode = 0; + + FTHD_S2_MEMCPY_TOIO(fw_args->offset, &fw_args_data, sizeof(fw_args_data)); + + FTHD_ISP_REG_WRITE(fw_args->offset, ISP_REG_C301C); + + FTHD_ISP_REG_WRITE(0x10, ISP_REG_41020); + + for (retries = 0; retries < 1000; retries++) { + reg = FTHD_ISP_REG_READ(ISP_IRQ_STATUS); + if ((reg & 0xf0) > 0) + break; + mdelay(10); + } + + if (retries >= 1000) { + dev_info(&dev_priv->pdev->dev, "Init failed! No second int\n"); + return -EIO; + } /* FIXME: free on error path */ + + dev_info(&dev_priv->pdev->dev, "ISP second int after %dms\n", + (retries - 1) * 10); + + offset = FTHD_ISP_REG_READ(ISP_FW_CHAN_CTRL); + dev_info(&dev_priv->pdev->dev, "Channel description table at %08x\n", offset); + ret = isp_fill_channel_info(dev_priv, offset, num_channels); + if (ret) + return ret; + + fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_terminal); + fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_io); + fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_debug); + fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_buf_h2t); + fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_buf_t2h); + fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_shared_malloc); + fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_io_t2h); + + FTHD_ISP_REG_WRITE(0x8042006, ISP_FW_HEAP_SIZE); + + for (retries = 0; retries < 1000; retries++) { + reg = FTHD_ISP_REG_READ(ISP_FW_HEAP_SIZE); + if (!reg) + break; + mdelay(10); + } + + if (retries >= 1000) { + dev_info(&dev_priv->pdev->dev, "Init failed! No magic value\n"); + isp_uninit(dev_priv); + return -EIO; + } /* FIXME: free on error path */ + dev_info(&dev_priv->pdev->dev, "magic value: %08x after %d ms\n", reg, (retries - 1) * 10); + } + + return 0; +} diff --git a/drivers/custom/facetimehd/fthd_isp.h b/drivers/custom/facetimehd/fthd_isp.h new file mode 100644 index 000000000000..b12108f65193 --- /dev/null +++ b/drivers/custom/facetimehd/fthd_isp.h @@ -0,0 +1,767 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com) + * + */ + +#ifndef _ISP_H +#define _ISP_H + +/* ISP memory types */ +#define FTHD_MEM_FIRMWARE 1 +#define FTHD_MEM_HEAP 2 +#define FTHD_MEM_FW_QUEUE 3 +#define FTHD_MEM_FW_ARGS 4 +#define FTHD_MEM_CMD 5 +#define FTHD_MEM_SHAREDMALLOC 6 +#define FTHD_MEM_SET_FILE 7 +#define FTHD_MEM_BUFFER 8 + +#define FTHD_MEM_SIZE 0x8000000 /* 128mb */ +#define FTHD_MEM_FW_SIZE 0x800000 /* 8mb */ + +enum fthd_isp_cmds { + CISP_CMD_START = 0x0, + CISP_CMD_STOP = 0x1, + CISP_CMD_RESET = 0x2, + CISP_CMD_CONFIG_GET = 0x3, + CISP_CMD_PRINT_ENABLE = 0x4, + CISP_CMD_REG_FILE_LOAD = 0x5, + CISP_CMD_BUILDINFO = 0x6, + CISP_CMD_TIMEPROFILE_START = 0x7, + CISP_CMD_TIMEPROFILE_STOP = 0x8, + CISP_CMD_TIMEPROFILE_SHOW = 0x9, + CISP_CMD_POWER_DOWN = 0xa, + CISP_CMD_CH_START = 0x100, + CISP_CMD_CH_STOP = 0x101, + CISP_CMD_CH_RESET = 0x102, + CISP_CMD_CH_STANDBY = 0x103, + CISP_CMD_CH_BUFFER_RETURN = 0x104, + CISP_CMD_CH_CAMERA_CONFIG_CURRENT_GET = 0x105, + CISP_CMD_CH_CAMERA_CONFIG_GET = 0x106, + CISP_CMD_CH_CAMERA_CONFIG_SELECT = 0x107, + CISP_CMD_CH_RAW_FRAME_PROCESS = 0x108, + CISP_CMD_CH_RAW_FRAME_PROCESS_START = 0x109, + CISP_CMD_CH_RAW_FRAME_PROCESS_STOP = 0x10a, + CISP_CMD_CH_I2C_READ = 0x10b, + CISP_CMD_CH_I2C_WRITE = 0x10c, + CISP_CMD_CH_INFO_GET = 0x10d, + CISP_CMD_CH_BUFFER_RECYCLE_MODE_SET = 0x10e, + CISP_CMD_CH_BUFFER_RECYCLE_START = 0x10f, + CISP_CMD_CH_BUFFER_RECYCLE_STOP = 0x110, + CISP_CMD_CH_SET_FILE_LOAD = 0x111, + CISP_CMD_CH_CAPTURE_MODE_SET = 0x112, + CISP_CMD_CH_RAW_FRAME_PROCESS_GO = 0x113, + CISP_CMD_CH_EDGE_MAP_CONFIG_GET = 0x114, + CISP_CMD_CH_SIF_PIXEL_FORMAT_SET = 0x115, + CISP_CMD_CH_RPU_DMAOUT_CONFIG_GET = 0x116, + CISP_CMD_CH_RPU_DMAOUT_ENABLE = 0x117, + CISP_CMD_CH_RPU_DMAOUT_DISABLE = 0x118, + CISP_CMD_CH_CAMERA_MIPI_FREQ_CURRENT_GET = 0x119, + CISP_CMD_CH_CAMERA_MIPI_FREQUENCY_GET = 0x11a, + CISP_CMD_CH_CAMERA_MIPI_FREQ_SELECT = 0x11b, + CISP_CMD_CH_ISO_PARAMS_SET = 0x11c, + CISP_CMD_CH_ISO_PARAMS_GET = 0x11d, + CISP_CMD_CH_CAMERA_PIX_FREQ_CURRENT_GET = 0x11e, + CISP_CMD_CH_CAMERA_PIX_FREQUENCY_GET = 0x11f, + CISP_CMD_CH_CAMERA_PIX_FREQ_SELECT = 0x120, + CISP_CMD_CH_CAMERA_ERR_COUNT_GET = 0x121, + CISP_CMD_CH_CAMERA_CLOCK_DIVISOR_SET = 0x122, + CISP_CMD_CH_CAMERA_CLOCK_DIVISOR_AUTO_MODE_SET = 0x123, + CISP_CMD_CH_CAMERA_ERR_HANDLE_CONFIG = 0x124, + CISP_CMD_CH_CAMERA_CHROMATIC_TYPE_SET = 0x125, + CISP_CMD_CH_CAMERA_CHROMATIC_TYPE_GET = 0x126, + CISP_CMD_CH_AE_START = 0x200, + CISP_CMD_CH_AE_STOP = 0x201, + CISP_CMD_CH_AE_AGC_PARAM_SET = 0x202, + CISP_CMD_CH_AE_BIAS_EXPOSURE_GET = 0x203, + CISP_CMD_CH_AE_BIAS_EXPOSURE_SET = 0x204, + CISP_CMD_CH_AE_CLIP_GET = 0x205, + CISP_CMD_CH_AE_CLIP_SET = 0x206, + CISP_CMD_CH_AE_FRAME_RATE_MAX_GET = 0x207, + CISP_CMD_CH_AE_FRAME_RATE_MAX_SET = 0x208, + CISP_CMD_CH_AE_FRAME_RATE_MIN_GET = 0x209, + CISP_CMD_CH_AE_FRAME_RATE_MIN_SET = 0x20a, + CISP_CMD_CH_AE_GAIN_CAP_GET = 0x20b, + CISP_CMD_CH_AE_GAIN_CAP_SET = 0x20c, + CISP_CMD_CH_AE_INTEGRATION_TIME_MAX_GET = 0x20d, + CISP_CMD_CH_AE_INTEGRATION_TIME_MAX_SET = 0x20e, + CISP_CMD_CH_AE_INTEGRATION_TIME_SET = 0x20f, + CISP_CMD_CH_AE_NOISE_REDUCTION_CONTROL_PARAM_GET = 0x210, + CISP_CMD_CH_AE_NOISE_REDUCTION_CONTROL_PARAM_SET = 0x211, + CISP_CMD_CH_AE_PARAM_GET = 0x212, + CISP_CMD_CH_AE_PRE_FRAME_RATE_GET = 0x213, + CISP_CMD_CH_AE_PRE_FRAME_RATE_SET = 0x214, + CISP_CMD_CH_AE_RED_EYE_PARAM_GET = 0x215, + CISP_CMD_CH_AE_RED_EYE_PARAM_SET = 0x216, + CISP_CMD_CH_AE_SPEED_GET = 0x217, + CISP_CMD_CH_AE_SPEED_SET = 0x218, + CISP_CMD_CH_AE_STABILITY_GET = 0x219, + CISP_CMD_CH_AE_STABILITY_SET = 0x21a, + CISP_CMD_CH_AE_STROBE_PARAM_GET = 0x21b, + CISP_CMD_CH_AE_STROBE_PARAM_SET = 0x21c, + CISP_CMD_CH_AE_WINDOW_PARAM_GET = 0x21d, + CISP_CMD_CH_AE_WINDOW_PARAM_SET = 0x21e, + CISP_CMD_CH_AE_SDGC_PARAM_SET = 0x21f, + CISP_CMD_CH_AE_DGC_PARAM_SET = 0x220, + CISP_CMD_CH_AE_LUX_CALC_PARAMS_SET = 0x221, + CISP_CMD_CH_AE_BRACKETING_PARAMS_SET = 0x222, + CISP_CMD_CH_AE_TARGET_GET = 0x223, + CISP_CMD_CH_AE_TARGET_SET = 0x224, + CISP_CMD_CH_AE_PREFLASH_PARAM_SET = 0x225, + CISP_CMD_CH_AE_UPDATE_SUSPEND = 0x226, + CISP_CMD_CH_AE_UPDATE_RESUME = 0x227, + CISP_CMD_CH_AE_STABILITY_TO_STABLE_GET = 0x228, + CISP_CMD_CH_AE_STABILITY_TO_STABLE_SET = 0x229, + CISP_CMD_CH_AE_SENSOR_INTEGRATION_TIME_MIN_GET = 0x22a, + CISP_CMD_CH_AE_MANUAL_MODE_SET = 0x22b, + CISP_CMD_CH_AE_SENSOR_INTEGRATION_TIME_MAX_GET = 0x22c, + CISP_CMD_CH_AE_GAIN_CAP_MIN_GET = 0x22d, + CISP_CMD_CH_AE_GAIN_CAP_MIN_SET = 0x22e, + CISP_CMD_CH_AE_GAIN_CAP_MAX_WITH_EXP_GET = 0x22f, + CISP_CMD_CH_AE_GAIN_CAP_MAX_WITH_EXP_SET = 0x230, + CISP_CMD_CH_AE_GAIN_CAP_OFF_GET = 0x231, + CISP_CMD_CH_AE_GAIN_CAP_OFF_SET = 0x232, + CISP_CMD_CH_AE_BRACKETING_MANUAL_SET = 0x233, + CISP_CMD_CH_AE_BRACKETING_MODE_SET = 0x234, + CISP_CMD_CH_AE_MODE_SET = 0x235, + CISP_CMD_CH_AE_MODE_GET = 0x236, + CISP_CMD_CH_AE_PANO_LIMIT_SET = 0x237, + CISP_CMD_CH_AE_INTEGRATION_GAIN_SET = 0x238, + CISP_CMD_CH_AE_2WAYSPEED_SET = 0x239, + CISP_CMD_CH_AE_2WAYSPEED_GET = 0x23a, + CISP_CMD_CH_AWB_START = 0x300, + CISP_CMD_CH_AWB_STOP = 0x301, + CISP_CMD_CH_AWB_WINDOW_PARAM_GET = 0x302, + CISP_CMD_CH_AWB_WINDOW_PARAM_SET = 0x303, + CISP_CMD_CH_AWB_CCT_GET = 0x304, + CISP_CMD_CH_AWB_CCT_MANUAL = 0x305, + CISP_CMD_CH_AWB_CALIB_TABLE_SET = 0x306, + CISP_CMD_CH_AWB_BRACKETING_PARAMS_SET = 0x307, + CISP_CMD_CH_AWB_CCM_WARMUP_PARAMS_SET = 0x308, + CISP_CMD_CH_AWB_CCM_WARMUP_MATRIX_SET = 0x309, + CISP_CMD_CH_AWB_CCM_WARMUP_MATRIX_GET = 0x30a, + CISP_CMD_CH_AWB_2ND_GAIN_ADAPTIVE_THRESHOLDS_SET = 0x30b, + CISP_CMD_CH_AWB_2ND_GAIN_MANUAL = 0x30c, + CISP_CMD_CH_AWB_2ND_GAIN_GET = 0x30d, + CISP_CMD_CH_AWB_FLASH_GAIN_SET = 0x30e, + CISP_CMD_CH_AWB_UPDATE_SUSPEND = 0x30f, + CISP_CMD_CH_AWB_UPDATE_RESUME = 0x310, + CISP_CMD_CH_AWB_1ST_GAIN_MANUAL = 0x311, + CISP_CMD_CH_AF_START = 0x400, + CISP_CMD_CH_AF_STOP = 0x401, + CISP_CMD_CH_AF_EARLYOUT_GET = 0x402, + CISP_CMD_CH_AF_EARLYOUT_SET = 0x403, + CISP_CMD_CH_AF_FOCUS_POS_GET = 0x404, + CISP_CMD_CH_AF_SEARCH_POS_GET = 0x405, + CISP_CMD_CH_AF_SEARCH_POS_SET = 0x406, + CISP_CMD_CH_AF_ONE_SHOT = 0x407, + CISP_CMD_CH_AF_WINDOW_PARAM_GET = 0x408, + CISP_CMD_CH_AF_WINDOW_PARAM_SET = 0x409, + CISP_CMD_CH_AF_UPDATE_SUSPEND = 0x40a, + CISP_CMD_CH_AF_UPDATE_RESUME = 0x40b, + CISP_CMD_CH_AF_SOFTLANDING_SET = 0x40c, + CISP_CMD_CH_AF_SOFTLANDING_GET = 0x40d, + CISP_CMD_CH_SENSOR_FRAME_RATE_SET = 0x500, + CISP_CMD_CH_SENSOR_NVM_GET = 0x501, + CISP_CMD_CH_SENSOR_NVM_RELOAD = 0x502, + CISP_CMD_CH_SENSOR_TEST_PATTERN_CONFIG = 0x503, + CISP_CMD_CH_SENSOR_WARM_STARTUP_CONFIG = 0x504, + CISP_CMD_CH_SENSOR_CUSTOM_SETTING_CONFIG = 0x505, + CISP_CMD_CH_SENSOR_TEMPERATURE_GET = 0x506, + CISP_CMD_CH_SENSOR_PERMODULE_LSC_INFO_GET = 0x507, + CISP_CMD_CH_SENSOR_PERMODULE_LSC_GET = 0x508, + CISP_CMD_CH_SENSOR_BLC_UPDATE_SUSPEND = 0x509, + CISP_CMD_CH_SENSOR_BLC_UPDATE_RESUME = 0x50a, + CISP_CMD_CH_SENSOR_POWER_ON = 0x50b, + CISP_CMD_CH_SENSOR_POWER_OFF = 0x50c, + CISP_CMD_CH_FOCUS_LIMITS_SET = 0x700, + CISP_CMD_CH_FOCUS_LIMITS_GET = 0x701, + CISP_CMD_CH_FOCUS_POSITION_SET = 0x702, + CISP_CMD_CH_FOCUS_POSITION_GET = 0x703, + CISP_CMD_CH_FOCUS_STEP_SIZE_SET = 0x704, + CISP_CMD_CH_FOCUS_STEP_SIZE_GET = 0x705, + CISP_CMD_CH_FOCUS_CAL_BITSHIFT_SET = 0x706, + CISP_CMD_CH_FOCUS_REINIT = 0x707, + CISP_CMD_CH_LED_TORCH_PARAM_GET = 0x600, + CISP_CMD_CH_LED_TORCH_PARAM_SET = 0x601, + CISP_CMD_CH_LED_TORCH_OFF = 0x602, + CISP_CMD_CH_LED_TORCH_ON = 0x603, + CISP_CMD_CH_LED_TORCH_ON_INDICATOR = 0x604, + CISP_CMD_CH_LED_TORCH_MANUAL_SET = 0x605, + CISP_CMD_CH_STATUS_LED_BrightnessMan_SET = 0x606, + CISP_CMD_CH_STATUS_LED_BrightnessMan_GET = 0x607, + CISP_CMD_CH_STATUS_LED_DEBUG_SET = 0x608, + CISP_CMD_CH_CROP_GET = 0x800, + CISP_CMD_CH_CROP_SET = 0x801, + CISP_CMD_CH_BPC_START = 0x802, + CISP_CMD_CH_BPC_STOP = 0x803, + CISP_CMD_CH_COLOR_CAL_DATA_SET = 0x804, + CISP_CMD_CH_COLOR_CAL_DATA_GET = 0x805, + CISP_CMD_CH_COLOR_CAL_IDEAL_SET = 0x806, + CISP_CMD_CH_COLOR_CAL_IDEAL_GET = 0x807, + CISP_CMD_CH_COLOR_CAL_ABS_GET = 0x808, + CISP_CMD_CH_COLOR_CAL_ABS_OVERRIDE = 0x809, + CISP_CMD_CH_SCALER_CROP_SET = 0x80a, + CISP_CMD_CH_COLOR_SATURATION_GET = 0xa00, + CISP_CMD_CH_COLOR_SATURATION_SET = 0xa01, + CISP_CMD_CH_TONE_CURVE_CUSTOM_GET = 0xa02, + CISP_CMD_CH_TONE_CURVE_CUSTOM_SET = 0xa03, + CISP_CMD_CH_COLOR_LSC_TABLE_SET = 0xa04, + CISP_CMD_CH_COLOR_LSC_START = 0xa05, + CISP_CMD_CH_COLOR_LSC_STOP = 0xa06, + CISP_CMD_CH_SCALER_SHARPNESS_SET = 0xa07, + CISP_CMD_CH_SCALER_SHARPNESS_GET = 0xa08, + CISP_CMD_CH_SHARPNESS_SET = 0xa09, + CISP_CMD_CH_SHARPNESS_GET = 0xa0a, + CISP_CMD_CH_NOISE_REDUCTION_SET = 0xa0b, + CISP_CMD_CH_NOISE_REDUCTION_GET = 0xa0c, + CISP_CMD_CH_CHROMA_SUPPRESSION_SET = 0xa0d, + CISP_CMD_CH_CHROMA_SUPPRESSION_GET = 0xa0e, + CISP_CMD_CH_HISTOGRAM_ENABLE = 0xa0f, + CISP_CMD_CH_COLOR_FULL_RES_LSC_TABLE_SET = 0xa10, + CISP_CMD_CH_COLOR_FULL_RES_LSC_ENABLE = 0xa11, + CISP_CMD_CH_COLOR_FULL_RES_LSC_DISABLE = 0xa12, + CISP_CMD_CH_KNOB_MANUAL_CONTROL_ENABLE = 0xa13, + CISP_CMD_CH_KNOB_MANUAL_CONTROL_DISABLE = 0xa14, + CISP_CMD_CH_BE_LASETTING_SET = 0xa15, + CISP_CMD_CH_BE_LASETTING_GET = 0xa16, + CISP_CMD_CH_BE_LA_INPUTMODE_SET = 0xa17, + CISP_CMD_CH_BE_LA_INPUTMODE_GET = 0xa18, + CISP_CMD_CH_DRC_SET = 0xa19, + CISP_CMD_CH_DRC_GET = 0xa1a, + CISP_CMD_CH_RPU_HISTOGRAM_PARAM_SET = 0xa1b, + CISP_CMD_CH_ALS_ENABLE = 0xa1c, + CISP_CMD_CH_ALS_DISABLE = 0xa1d, + CISP_CMD_CH_ALS_MODE_SET = 0xa1e, + CISP_CMD_CH_ALS_CCT_MANUAL = 0xa1f, + CISP_CMD_CH_ALS_POLYNOMIAL_SET = 0xa20, + CISP_CMD_CH_ALS_POLYNOMIAL_GET = 0xa21, + CISP_CMD_CH_COLOR_LSC_TABLE_GET = 0xa22, + CISP_CMD_CH_ALS_TEST_PATTERN_SET = 0xa23, + CISP_CMD_CH_BE_LA_SUSPEND = 0xa24, + CISP_CMD_CH_BE_LA_RESUME = 0xa25, + CISP_CMD_CH_COLOR_LSC_IDEAL_TABLE_SET = 0xa26, + CISP_CMD_CH_ALS_CCT_LIMIT_SET = 0xa27, + CISP_CMD_CH_TONE_CURVE_CUSTOM_BRACKETING_SET = 0xa28, + CISP_CMD_CH_MANUAL_BPC_GAIN_THRESHOLD_SET = 0xa29, + CISP_CMD_CH_COLOR_LSC_TABLE_SOURCE_SET = 0xa2a, + CISP_CMD_CH_ALS_SUSPEND = 0xa2b, + CISP_CMD_CH_ALS_RESUME = 0xa2c, + CISP_CMD_CH_OUTPUT_CONFIG_GET = 0xb00, + CISP_CMD_CH_OUTPUT_CONFIG_SET = 0xb01, + CISP_CMD_CH_SCALER_BRIGHTNESS_SET = 0xb02, + CISP_CMD_CH_SCALER_CONTRAST_SET = 0xb03, + CISP_CMD_CH_SCALER_SATURATION_SET = 0xb04, + CISP_CMD_CH_SCALER_HUE_SET = 0xb05, + CISP_CMD_CH_DRC_START = 0xc00, + CISP_CMD_CH_DRC_STOP = 0xc01, + CISP_CMD_CH_DRC_OFFLINE = 0xc02, + CISP_CMD_CH_DRC_OFFLINE_START = 0xc03, + CISP_CMD_CH_DRC_OFFLINE_STOP = 0xc04, + CISP_CMD_CH_FACE_DETECTION_START = 0xd00, + CISP_CMD_CH_FACE_DETECTION_STOP = 0xd01, + CISP_CMD_CH_FACE_DETECTION_CONFIG_GET = 0xd02, + CISP_CMD_CH_FACE_DETECTION_CONFIG_SET = 0xd03, + CISP_CMD_CH_FACE_DETECTION_DISABLE = 0xd04, + CISP_CMD_CH_FACE_DETECTION_ENABLE = 0xd05, + CISP_CMD_CH_FACE_DETECTION_INPUT_SET = 0xd06, + CISP_CMD_CH_FACE_DETECTION_IMAGE_ORIENTATION_SET = 0xd07, + CISP_CMD_CH_FACE_DETECTION_OFFLINE = 0xd08, + CISP_CMD_CH_FACE_DETECTION_OFFLINE_START = 0xd09, + CISP_CMD_CH_FACE_DETECTION_OFFLINE_STOP = 0xd0a, + CISP_CMD_CH_FACE_DETECTION_MODE_SET = 0xd0b, + CISP_CMD_CH_FACE_DETECTION_WINDOW_PARAM_SET = 0xd0c, + CISP_CMD_CH_FACE_DETECTION_WINDOW_PARAM_GET = 0xd0d, + CISP_CMD_CH_FRAMEDONE_TIMEOUT = 0xe00, + CISP_CMD_CH_FOCUS_DRIVER_INIT_FAILED = 0xe01, + CISP_CMD_CH_NVSTORAGE_INFO_GET = 0xf00, + CISP_CMD_CH_NVSTORAGE_DATA_GET = 0xf01, + CISP_CMD_CH_NVSTORAGE_DATA_SET = 0xf02, + CISP_CMD_APPLE_SET_FILE_LOAD = 0x8000, + CISP_CMD_APPLE_BUFFER_INFO_SET_GET = 0x8001, + CISP_CMD_APPLE_CH_HARDWARE_BLOCK_ENABLE = 0x8100, + CISP_CMD_APPLE_CH_HARDWARE_BLOCK_DISABLE = 0x8101, + CISP_CMD_APPLE_CH_CONTEXTSWITCH_ENABLE = 0x8102, + CISP_CMD_APPLE_CH_CONTEXTSWITCH_DISABLE = 0x8103, + CISP_CMD_APPLE_CH_SENSOR_NOISE_MODEL_SET = 0x8104, + CISP_CMD_APPLE_CH_SENSOR_NOISE_MODEL_GET = 0x8105, + CISP_CMD_APPLE_CH_STREAMING_MODE_SET = 0x8106, + CISP_CMD_APPLE_CH_STREAMING_MODE_GET = 0x8107, + CISP_CMD_APPLE_CH_AE_WINDOW_PARAM_SET = 0x8200, + CISP_CMD_APPLE_CH_AE_DYNAMIC_SCENE_METERING_CONFIG_SET = 0x8201, + CISP_CMD_APPLE_CH_AE_DYNAMIC_SCENE_METERING_START = 0x8202, + CISP_CMD_APPLE_CH_AE_DYNAMIC_SCENE_METERING_STOP = 0x8203, + CISP_CMD_APPLE_CH_AE_WINDOW_PARAM_GET = 0x8204, + CISP_CMD_APPLE_CH_AE_WINDOW_WEIGHT_SET = 0x8205, + CISP_CMD_APPLE_CH_AE_METERING_MODE_SET = 0x8206, + CISP_CMD_APPLE_CH_AE_METERING_MODE_GET = 0x8207, + CISP_CMD_APPLE_CH_AE_FLICKER_FREQ_SET = 0x8208, + CISP_CMD_APPLE_CH_AE_MAX_FRAMERATE_GAIN_LIMIT_SET = 0x8209, + CISP_CMD_APPLE_CH_AE_MAX_FRAMERATE_GAIN_LIMIT_GET = 0x820a, + CISP_CMD_APPLE_CH_AE_TILES_MATRIX_METADATA_ENABLE = 0x820b, + CISP_CMD_APPLE_CH_AE_BINNING_GAIN_LUX_THRESHOLD_SET = 0x820c, + CISP_CMD_APPLE_CH_AE_PSEUDO_Y_WEIGHT_SET = 0x820d, + CISP_CMD_APPLE_CH_AE_FD_SCENE_METERING_CONFIG_SET = 0x820e, + CISP_CMD_APPLE_CH_AE_GAIN_CONVERGENCE_NORMALIZATION_SET = 0x820f, + CISP_CMD_APPLE_CH_AE_GAIN_CONVERGENCE_NORMALIZATION_GET = 0x8210, + CISP_CMD_APPLE_CH_AE_FD_SCENE_METERING_CONFIG_GET = 0x8211, + CISP_CMD_APPLE_CH_AWB_CCT_GET = 0x8300, + CISP_CMD_APPLE_CH_AWB_CCT_MANUAL = 0x8301, + CISP_CMD_APPLE_CH_AWB_WINDOW_PARAM_GET = 0x8302, + CISP_CMD_APPLE_CH_AWB_WINDOW_PARAM_SET = 0x8303, + CISP_CMD_APPLE_CH_AWB_CALIB_TABLE_SET = 0x8304, + CISP_CMD_APPLE_CH_AWB_SCHEME_SET = 0x8305, + CISP_CMD_APPLE_CH_AWB_SCHEME_GET = 0x8306, + CISP_CMD_APPLE_CH_AWB_HISTOGRAM_WEIGHT_SET = 0x8307, + CISP_CMD_APPLE_CH_AWB_LUXTABLE_PARAM_SET = 0x8308, + CISP_CMD_APPLE_CH_AWB_PROJECTION_POINT_SET = 0x8309, + CISP_CMD_APPLE_CH_AWB_HISTOGRAM_X_TO_CCT_LUT_SET = 0x830a, + CISP_CMD_APPLE_CH_AWB_2D_CCM_SET = 0x830b, + CISP_CMD_APPLE_CH_AWB_PRE_CCM_GAIN_GET = 0x830c, + CISP_CMD_APPLE_CH_AWB_CCM_GET = 0x830d, + CISP_CMD_APPLE_CH_AWB_TEMPORAL_COHERENCE_FILTER_SET = 0x830e, + CISP_CMD_APPLE_CH_AWB_SUSPEND_UPON_AE_STABLE_SET = 0x830f, + CISP_CMD_APPLE_CH_AWB_SUSPEND_UPON_AE_STABLE_GET = 0x8310, + CISP_CMD_APPLE_CH_AWB_POST_TINT_PARAM_SET = 0x8311, + CISP_CMD_APPLE_CH_AWB_MIX_LIGHTING_X_LOC_SET = 0x8312, + CISP_CMD_APPLE_CH_AWB_MIX_LIGHTING_CCM_SET = 0x8313, + CISP_CMD_APPLE_CH_AWB_TILE_STATS_Y_THRESHOLD_SET = 0x8314, + CISP_CMD_APPLE_CH_AWB_RATIO_SPACE_2ND_GAIN_THRESHOLD_SET = 0x8315, + CISP_CMD_APPLE_CH_AWB_HISTOGRAM_TRIM_FILTER_V_SET = 0x8316, + CISP_CMD_APPLE_CH_AWB_HISTOGRAM_TRIM_FILTER_H_SET = 0x8317, + CISP_CMD_APPLE_CH_AWB_HISTOGRAM_TRIM_SCALE_PROFILE_SET = 0x8318, + CISP_CMD_APPLE_CH_AWB_CCM_LUX_CLIP_SET = 0x8319, + CISP_CMD_APPLE_CH_AWB_MANUAL_WB_GAIN_SET = 0x831a, + CISP_CMD_APPLE_CH_AWB_CALIBRATION_MATRIX_GET = 0x831b, + CISP_CMD_APPLE_CH_AF_WINDOW_PARAM_GET = 0x8400, + CISP_CMD_APPLE_CH_AF_WINDOW_PARAM_SET = 0x8401, + CISP_CMD_APPLE_CH_AF_WINDOW_WEIGHT_GET = 0x8402, + CISP_CMD_APPLE_CH_AF_WINDOW_WEIGHT_SET = 0x8403, + CISP_CMD_APPLE_CH_AF_WINDOW_FD_CONFIG = 0x8404, + CISP_CMD_APPLE_CH_AF_PEAK_PREDICT_ENABLE_SET = 0x8405, + CISP_CMD_APPLE_CH_AF_FOCUS_POS_OVERRIDE_SET = 0x8406, + CISP_CMD_APPLE_CH_AF_PEAK_TRACKING_ENABLE = 0x8407, + CISP_CMD_APPLE_CH_AF_PEAK_TRACKING_START = 0x8408, + CISP_CMD_APPLE_CH_AF_FOCUS_MODE_SET = 0x8409, + CISP_CMD_APPLE_CH_AF_FOCUS_MODE_GET = 0x840a, + CISP_CMD_APPLE_CH_AF_MATRIX_MODE_CONFIG_SET = 0x840b, + CISP_CMD_APPLE_CH_AF_MATRIX_MODE_CONFIG_GET = 0x840c, + CISP_CMD_APPLE_CH_AF_MATRIX_MODE_DEBUG_GET = 0x840d, + CISP_CMD_APPLE_CH_AF_SCAN_HISTORY_GET = 0x840e, + CISP_CMD_APPLE_CH_FESTAT_CONFIG_GET = 0xc000, + CISP_CMD_APPLE_CH_TILE_REGION_SET = 0xc001, + CISP_CMD_APPLE_CH_TILE_WEIGHT_SET = 0xc002, + CISP_CMD_APPLE_CH_COLOR_LSC_TABLE_SET = 0xc003, + CISP_CMD_APPLE_CH_PIXEL_FILTER_TABLE_SET = 0xc004, + CISP_CMD_APPLE_CH_CSC_CONFIG_SET = 0xc005, + CISP_CMD_APPLE_CH_CSC_CONFIG_GET = 0xc006, + CISP_CMD_APPLE_CH_CSC2_CONFIG_SET = 0xc007, + CISP_CMD_APPLE_CH_CSC2_CONFIG_GET = 0xc008, + CISP_CMD_APPLE_CH_COLOR_HIST_CONFIG_SET = 0xc009, + CISP_CMD_APPLE_CH_COLOR_HIST_CONFIG_GET = 0xc00a, + CISP_CMD_APPLE_CH_CSC_GAMMA_SET = 0xc00b, + CISP_CMD_APPLE_CH_COLOR_HIST_CAPTURE = 0xc00c, + CISP_CMD_APPLE_CH_COLOR_LSC_IDEAL_TABLE_SET = 0xc00d, + CISP_CMD_APPLE_CH_STATPIXELDMAOUTPUT_SOURCE_SET = 0xc00e, + CISP_CMD_APPLE_CH_STATPIXELDMAOUTPUT_INFO_GET = 0xc00f, + CISP_CMD_APPLE_CH_AFFILTER_COEFF_SET = 0xc010, + CISP_CMD_APPLE_CH_AFFILTER_COEFF_GET = 0xc011, + CISP_CMD_APPLE_CH_EDGE_MAP_CONFIGURE = 0xc012, + CISP_CMD_APPLE_CH_AFHORZFILT_COEFF_SET = 0xc013, + CISP_CMD_APPLE_CH_AFHORZFILT_ENABLE_SET = 0xc014, + CISP_CMD_APPLE_CH_AFHORZFILT_SUMMODE_SET = 0xc015, + CISP_CMD_APPLE_CH_AFHORZFILT_THD_SET = 0xc016, + CISP_CMD_APPLE_CH_TEMPORAL_FILTER_START = 0xc100, + CISP_CMD_APPLE_CH_TEMPORAL_FILTER_STOP = 0xc101, + CISP_CMD_APPLE_CH_MOTION_HISTORY_START = 0xc102, + CISP_CMD_APPLE_CH_MOTION_HISTORY_STOP = 0xc103, + CISP_CMD_APPLE_CH_TEMPORAL_FILTER_CONFIG_SET = 0xc104, + CISP_CMD_APPLE_CH_TEMPORAL_FILTER_CONFIG_GET = 0xc105, + CISP_CMD_APPLE_CH_TEMPORAL_FILTER_GAIN_SET = 0xc106, + CISP_CMD_APPLE_CH_TEMPORAL_FILTER_GAIN_GET = 0xc107, + CISP_CMD_APPLE_CH_MOTION_LUT_SET = 0xc108, + CISP_CMD_APPLE_CH_MOTION_LUT_GET = 0xc109, + CISP_CMD_APPLE_CH_LUMA_LUT_SET = 0xc10a, + CISP_CMD_APPLE_CH_LUMA_LUT_GET = 0xc10b, + CISP_CMD_APPLE_CH_TNR_MODE_SET = 0xc10c, + CISP_CMD_APPLE_CH_TNR_MODE_GET = 0xc10d, + CISP_CMD_APPLE_CH_TNR_PARAM_SET = 0xc10e, + CISP_CMD_APPLE_CH_TNR_PARAM_GET = 0xc10f, + CISP_CMD_APPLE_CH_TNR_INTERPOLATION_ENABLE = 0xc110, + CISP_CMD_APPLE_CH_TNR_AVERAGE_START = 0xc111, + CISP_CMD_APPLE_CH_TNR_AVERAGE_STOP = 0xc112, + CISP_CMD_APPLE_CH_TEMPORAL_FILTER_ENABLE = 0xc113, + CISP_CMD_APPLE_CH_TEMPORAL_FILTER_DISABLE = 0xc114, + CISP_CMD_APPLE_CH_TNR_AVERAGE_FRAME_COUNT_SET = 0xc115, + CISP_CMD_APPLE_CH_TNR_TUNING_PARAMS_SET = 0xc116, + CISP_CMD_APPLE_CH_TNR_LSC_GAIN_SET = 0xc117, + CISP_CMD_APPLE_CH_BINNING_COMPENSATION_FILTER_START = 0xc200, + CISP_CMD_APPLE_CH_BINNING_COMPENSATION_FILTER_STOP = 0xc201, + CISP_CMD_APPLE_CH_TONE_CURVE_ADAPTATION_START = 0xc300, + CISP_CMD_APPLE_CH_TONE_CURVE_ADAPTATION_STOP = 0xc301, + CISP_CMD_APPLE_CH_TONE_CURVE_PARAM_SET = 0xc302, + CISP_CMD_APPLE_CH_TONE_CURVE_PARAM_GET = 0xc303, + CISP_CMD_APPLE_CH_TONE_CURVE_UPDATE_SUSPEND = 0xc304, + CISP_CMD_APPLE_CH_TONE_CURVE_UPDATE_RESUME = 0xc305, + CISP_CMD_APPLE_CH_TONE_CURVE_MANUAL_CONTROL_ENABLE = 0xc306, + CISP_CMD_APPLE_CH_TONE_CURVE_MANUAL_CONTROL_DISABLE = 0xc307, + CISP_CMD_APPLE_CH_TONE_CURVE_LUX_ADAPTATION_TABLE_SET = 0xc308, + CISP_CMD_APPLE_CH_TONE_CURVE_LUX_ADAPTATION_TABLE_GET = 0xc309, + CISP_CMD_APPLE_CH_TONE_CURVE_LUX_ADAPTATION_LUXSCALE_SET = 0xc30a, + CISP_CMD_APPLE_CH_TONE_CURVE_LUX_ADAPTATION_LUXSCALE_GET = 0xc30b, + CISP_CMD_APPLE_CH_TONE_CURVE_STABILITY_SET = 0xc30c, + CISP_CMD_APPLE_CH_TONE_CURVE_STABILITY_GET = 0xc30d, + CISP_CMD_APPLE_CH_AUTO_HDR_HISTOGRAM_ENABLE = 0xc30e, + CISP_CMD_APPLE_CH_AUTO_HDR_HISTOGRAM_DISABLE = 0xc30f, + CISP_CMD_APPLE_CH_SNF_START = 0xc400, + CISP_CMD_APPLE_CH_SNF_STOP = 0xc401, + CISP_CMD_APPLE_CH_SNF_SET = 0xc402, + CISP_CMD_APPLE_CH_SNF_GET = 0xc403, + CISP_CMD_APPLE_CH_SNF_RADIAL_GAIN_SET = 0xc404, + CISP_CMD_APPLE_CH_DPC_ENABLE = 0xc500, + CISP_CMD_APPLE_CH_DPC_START = 0xc501, + CISP_CMD_APPLE_CH_DPC_STOP = 0xc502, + CISP_CMD_APPLE_CH_DPC_CTRLVALUE_OVERRIDE = 0xc503, + CISP_CMD_APPLE_CH_DPC_MAX_DYN_COUNT_OVERRIDE = 0xc504, + CISP_CMD_APPLE_CH_DPC_DYN_THRESHOLD0_OVERRIDE = 0xc505, + CISP_CMD_APPLE_CH_DPC_DYN_THRESHOLD1_OVERRIDE = 0xc506, + CISP_CMD_APPLE_CH_DPC_DESP_THRESHOLD0_OVERRIDE = 0xc507, + CISP_CMD_APPLE_CH_DPC_DESP_THRESHOLD1_OVERRIDE = 0xc508, + CISP_CMD_APPLE_CH_DPC_MAX_CORNER_OVERRIDE = 0xc509, + CISP_CMD_APPLE_CH_DPC_MAX_EDGE_OVERRIDE = 0xc50a, + CISP_CMD_APPLE_CH_DPC_MAX_CENTER_OVERRIDE = 0xc50b, + CISP_CMD_APPLE_CH_DPC_STATIC_DEFECTS_TABLE_SET = 0xc50c, +}; + +enum isp_debug_cmds { + CISP_CMD_DEBUG_BANNER=0, + CISP_CMD_DEBUG_NOP1, + CISP_CMD_DEBUG_NOP2, + CISP_CMD_DEBUG_PS, + CISP_CMD_DEBUG_GET_ROOT_HANDLE, + CISP_CMD_DEBUG_GET_OBJECT_BY_NAME, + CISP_CMD_DEBUG_GET_NUMBER_OF_CHILDREN, + CISP_CMD_DEBUG_GET_CHILDREN_BY_INDEX, + CISP_CMD_DEBUG_SHOW_OBJECT_GRAPH, + CISP_CMD_DEBUG_DUMP_OBJECT, + CISP_CMD_DEBUG_DUMP_ALL_OBJECTS, + CISP_CMD_DEBUG_GET_DEBUG_LEVEL, + CISP_CMD_DEBUG_SET_DEBUG_LEVEL, + CISP_CMD_DEBUG_SET_DEBUG_LEVEL_RECURSIVE, + CISP_CMD_DEBUG_GET_FSM_COUNT, + CISP_CMD_DEBUG_GET_FSM_BY_INDEX, + CISP_CMD_DEBUG_GET_FSM_BY_NAME, + CISP_CMD_DEBUG_GET_FSM_DEBUG_LEVEL, + CISP_CMD_DEBUG_SET_FSM_DEBUG_LEVEL, + CISP_CMD_DEBUG_FSM_UNKNOWN, /* XXX: don't know what this cmd is doing yet */ + CISP_CMD_DEBUG_HEAP_STATISTICS, + CISP_CMD_DEBUG_IRQ_STATISTICS, + CISP_CMD_DEBUG_SHOW_SEMAPHORE_STATUS, + CISP_CMD_DEBUG_START_CPU_PERFORMANCE_COUNTER, + CISP_CMD_DEBUG_STOP_CPU_PERFORMANCE_COUNTER, + CISP_CMD_DEBUG_SHOW_WIRING_OPERATIONS, + CISP_CMD_DEBUG_SHOW_UNIT_TEST_STATUS, + CISP_CMD_DEBUG_GET_ENVIRONMENT, +}; + +struct isp_mem_obj { + struct resource base; + unsigned int type; + resource_size_t size; + resource_size_t size_aligned; + unsigned long offset; +}; + +struct isp_fw_args { + u32 __unknown; + u32 fw_arg; + u32 full_stats_mode; +}; + +struct isp_channel_info { + char name[64]; /* really that big? */ + u32 type; + u32 source; + u32 size; + u32 offset; +}; + +struct isp_cmd_hdr { + u32 unknown0; + u16 opcode; + u16 status; +} __attribute__((packed)); + +struct isp_cmd_print_enable { + u32 enable; +} __attribute__((packed)); + +struct isp_cmd_config { + u32 field0; + u32 field4; + u32 field8; + u32 fieldc; + u32 field10; + u32 field14; + u32 field18; + u32 field1c; +} __attribute__((packed)); + +struct isp_cmd_set_loadfile { + u32 unknown; + u32 addr; + u32 length; +} __attribute__((packed)); + +struct isp_cmd_channel_info { + u32 field_0; + u32 field_4; + u32 field_8; + u32 field_c; + u16 sensorid0; /* field 10 */ + u16 field_12; + u32 field_14; + u16 sensorid1; /* field 18 */ + u16 field_1a; + u32 field_1c; + u32 field_20; + u8 unknown[52]; + u32 sensor_count; + u8 unknown2[40]; + u8 sensor_serial_number[8]; + u8 camera_module_serial_number[18]; +} __attribute__((packed)); + +struct isp_cmd_channel_camera_config { + u32 unknown; + u32 channel; + u8 data[88]; +}; + +struct isp_cmd_channel_set_crop { + u32 channel; + u32 x1; + u32 y1; + u32 x2; + u32 y2; +}; + +struct isp_cmd_channel_output_config { + u32 channel; + u32 x1; + u32 y1; + u32 unknown3; + u32 pixelformat; + u32 x2; + u32 x3; + u32 unknown5; +}; + +struct isp_cmd_channel_recycle_mode { + u32 channel; + u32 mode; +}; + +struct isp_cmd_channel_camera_config_select { + u32 channel; + u32 config; +}; + +struct isp_cmd_channel_drc_start { + u32 channel; +}; + +struct isp_cmd_channel_tone_curve_adaptation_start { + u32 channel; +}; + +struct isp_cmd_channel_sif_format_set { + u32 channel; + u8 param1; + u8 param2; + u8 unknown0; + u8 unknown1; +}; + +struct isp_cmd_channel_camera_err_handle_config { + u32 channel; + u16 param1; + u16 param2; +}; + +struct isp_cmd_channel_streaming_mode { + u32 channel; + u16 mode; + u16 unknown; +}; + +struct isp_cmd_channel_frame_rate_set { + u32 channel; + u16 rate; +}; + +struct isp_cmd_channel_ae_speed_set { + u32 channel; + u16 speed; +}; + +struct isp_cmd_channel_ae_stability_set { + u32 channel; + u16 stability; +}; + +struct isp_cmd_channel_ae_stability_to_stable_set { + u32 channel; + u16 value; +}; + +struct isp_cmd_channel_face_detection_start { + u32 channel; +}; + +struct isp_cmd_channel_face_detection_stop { + u32 channel; +}; + +struct isp_cmd_channel_face_detection_enable { + u32 channel; +}; + +struct isp_cmd_channel_face_detection_disable { + u32 channel; +}; + +struct isp_cmd_channel_temporal_filter_start { + u32 channel; +}; + +struct isp_cmd_channel_temporal_filter_stop { + u32 channel; +}; + +struct isp_cmd_channel_temporal_filter_enable { + u32 channel; +}; + +struct isp_cmd_channel_temporal_filter_disable { + u32 channel; +}; + +struct isp_cmd_channel_motion_history_start { + u32 channel; +}; + +struct isp_cmd_channel_motion_history_stop { + u32 channel; +}; + +struct isp_cmd_channel_ae_metering_mode_set { + u32 channel; + u32 mode; +}; + +struct isp_cmd_channel_start { + u32 channel; +}; + +struct isp_cmd_channel_stop { + u32 channel; +}; + +struct isp_cmd_channel_brightness_set { + u32 channel; + u32 brightness; +}; + +struct isp_cmd_channel_contrast_set { + u32 channel; + u32 contrast; +}; + +struct isp_cmd_channel_saturation_set { + u32 channel; + u32 contrast; +}; + +struct isp_cmd_channel_hue_set { + u32 channel; + u32 contrast; +}; + +struct isp_cmd_channel { + u32 channel; +}; + +struct isp_cmd_channel_buffer_return { + u32 channel; +}; + + +struct fthd_isp_debug_cmd { + u32 show_errors; + u32 arg[64]; +}; + +#define to_isp_mem_obj(x) container_of((x), struct isp_mem_obj, base) + +extern int isp_init(struct fthd_private *dev_priv); +extern int isp_uninit(struct fthd_private *dev_priv); + +extern int isp_mem_init(struct fthd_private *dev_priv); +extern struct isp_mem_obj *isp_mem_create(struct fthd_private *dev_priv, + unsigned int type, + resource_size_t size); +extern int isp_mem_destroy(struct isp_mem_obj *obj); +extern int fthd_isp_cmd_start(struct fthd_private *dev_priv); +extern int fthd_isp_cmd_stop(struct fthd_private *dev_priv); +extern int isp_powerdown(struct fthd_private *dev_priv); +extern int fthd_isp_cmd_print_enable(struct fthd_private *dev_priv, int enable); +extern int fthd_isp_cmd_set_loadfile(struct fthd_private *dev_priv); +extern int fthd_isp_cmd_channel_info(struct fthd_private *dev_priv); +extern int fthd_isp_cmd_channel_start(struct fthd_private *dev_priv); +extern int fthd_isp_cmd_channel_stop(struct fthd_private *dev_priv); +extern int fthd_isp_cmd_channel_camera_config(struct fthd_private *dev_priv); +extern int fthd_isp_cmd_channel_crop_set(struct fthd_private *dev_priv, int channel, + int x1, int y1, int x2, int y2); +extern int fthd_isp_cmd_channel_output_config_set(struct fthd_private *dev_priv, int channel, int x, int y, int pixelformat); +extern int fthd_isp_cmd_channel_recycle_mode(struct fthd_private *dev_priv, int channel, int mode); +extern int fthd_isp_cmd_channel_recycle_start(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_camera_config_select(struct fthd_private *dev_priv, int channel, int config); +extern int fthd_isp_cmd_channel_drc_start(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_tone_curve_adaptation_start(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_sif_pixel_format(struct fthd_private *dev_priv, int channel, int param1, int param2); +extern int fthd_isp_cmd_channel_error_handling_config(struct fthd_private *dev_priv, int channel, int param1, int param2); +extern int fthd_isp_cmd_channel_streaming_mode(struct fthd_private *dev_priv, int channel, int mode); +extern int fthd_isp_cmd_channel_frame_rate_min(struct fthd_private *dev_priv, int channel, int rate); +extern int fthd_isp_cmd_channel_frame_rate_max(struct fthd_private *dev_priv, int channel, int rate); +extern int fthd_isp_cmd_camera_config(struct fthd_private *dev_priv); +extern int fthd_isp_cmd_channel_ae_speed_set(struct fthd_private *dev_priv, int channel, int speed); +extern int fthd_isp_cmd_channel_ae_stability_set(struct fthd_private *dev_priv, int channel, int stability); +extern int fthd_isp_cmd_channel_ae_stability_to_stable_set(struct fthd_private *dev_priv, int channel, int value); +extern int fthd_isp_cmd_channel_face_detection_enable(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_face_detection_disable(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_face_detection_start(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_face_detection_stop(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_temporal_filter_start(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_temporal_filter_stop(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_temporal_filter_enable(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_temporal_filter_disable(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_motion_history_start(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_motion_history_stop(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_cmd_channel_ae_metering_mode_set(struct fthd_private *dev_priv, int channel, int mode); +extern int fthd_isp_cmd_channel_brightness_set(struct fthd_private *dev_priv, int channel, int brightness); +extern int fthd_isp_cmd_channel_contrast_set(struct fthd_private *dev_priv, int channel, int contrast); +extern int fthd_isp_cmd_channel_saturation_set(struct fthd_private *dev_priv, int channel, int saturation); +extern int fthd_isp_cmd_channel_hue_set(struct fthd_private *dev_priv, int channel, int hue); +extern int fthd_isp_cmd_channel_awb(struct fthd_private *dev_priv, int channel, int hue); +extern int fthd_isp_cmd_channel_buffer_return(struct fthd_private *dev_priv, int channel); +extern int fthd_start_channel(struct fthd_private *dev_priv, int channel); +extern int fthd_stop_channel(struct fthd_private *dev_priv, int channel); +extern int fthd_isp_debug_cmd(struct fthd_private *dev_priv, enum fthd_isp_cmds command, void *buf, + int request_len, int *response_len); + +#endif diff --git a/drivers/custom/facetimehd/fthd_reg.h b/drivers/custom/facetimehd/fthd_reg.h new file mode 100644 index 000000000000..27da24a4a1d3 --- /dev/null +++ b/drivers/custom/facetimehd/fthd_reg.h @@ -0,0 +1,184 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com) + * + */ + +#ifndef _FTHD_REG_H +#define _FTHD_REG_H + +#include + +/* PCIE link regs */ +#define S2_PCIE_LINK_D000 0xd000 +#define S2_PCIE_LINK_D120 0xd120 +#define S2_PCIE_LINK_D124 0xd124 +#define S2_PCIE_LINK_D128 0xd128 +#define S2_PCIE_LINK_D12C 0xd12c + +/* Unknown */ +#define S2_D104 0xd104 +#define S2_D108 0xd108 + +/* These are written to 0x203 before DDR soc init */ +#define S2_DDR_REG_1100 0x1100 +#define S2_DDR_REG_1104 0x1104 +#define S2_DDR_REG_1108 0x1108 +#define S2_DDR_REG_110C 0x110c +#define S2_DDR_REG_1110 0x1110 +#define S2_DDR_REG_1114 0x1114 +#define S2_DDR_REG_1118 0x1118 +#define S2_DDR_REG_111C 0x111c + +#define S2_PLL_REFCLK 0x04 +#define S2_PLL_REFCLK_25MHZ (1 << 3) /* 1 = 25MHz, 0 = 24MHz */ + +#define S2_PLL_CMU_STATUS 0x0c /* Register is called CMU_R_PLL_STS_MEMADDR */ +#define S2_PLL_CMU_STATUS_LOCKED (1 << 15) /* 1 = PLL locked, 0 = PLL not locked */ + +#define S2_PLL_STATUS_A8 0xa8 +#define S2_PLL_BYPASS (1 << 0) /* 1 = bypass, 0 = non-bypass */ + +#define S2_PLL_CTRL_14 0x0014 +#define S2_PLL_CTRL_20 0x0020 +#define S2_PLL_CTRL_24 0x0024 +#define S2_PLL_CTRL_2C 0x002c +#define S2_PLL_CTRL_9C 0x009c +#define S2_PLL_CTRL_100 0x0100 +#define S2_PLL_CTRL_510 0x0510 + +/* Probably DDR PHY PLL registers */ +#define S2_DDR_2004 0x2004 +#define S2_DDR_2008 0x2008 +#define S2_DDR_2014 0x2014 +#define S2_DDR_STATUS_2018 0x2018 +#define S2_DDR_STATUS_BUSY (1 << 0) + +#define S2_DDR_20A0 0x20a0 +#define S2_DDR_20A4 0x20a4 +#define S2_DDR_20A8 0x20a8 +#define S2_DDR_20B0 0x20b0 + +#define S2_20F8 0x20f8 +#define S2_DDR_2118 0x2118 + +#define S2_2424 0x2424 +#define S2_2430 0x2430 +#define S2_2434 0x2434 +#define S2_2438 0x2438 + +/* PLL for stage 2 */ +#define S2_DDR_PLL_STATUS_241C 0x241c +#define S2_DDR_PLL_STATUS_241C_LOCKED (1 << 10) + +/* PLL for stage 1 */ +#define S2_DDR_PLL_STATUS_2444 0x2444 +#define S2_DDR_PLL_STATUS_2444_LOCKED (1 << 13) + +/* + * These registers must be saved and restored across suspend/resume + * FIXME: Double check these + */ +static const u32 fthd_ddr_phy_reg_map[] = { + 0x0000, 0x0004, 0x0010, 0x0014, 0x0018, 0x001c, 0x0020, 0x0030, + 0x0034, 0x0038, 0x003c, 0x0040, 0x0044, 0x0048, 0x004c, 0x0050, + 0x0054, 0x0058, 0x005c, 0x0060, 0x0064, 0x0068, 0x006c, 0x0070, + 0x0074, 0x0078, 0x007c, 0x0080, 0x0084, 0x0090, 0x0094, 0x0098, + 0x009c, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x00b8, 0x00bc, 0x00c0, + 0x0200, 0x0204, 0x0208, 0x020c, 0x0210, 0x0214, 0x0218, 0x021c, + 0x0220, 0x0224, 0x0228, 0x022c, 0x0230, 0x0234, 0x0238, 0x023c, + 0x0240, 0x0244, 0x0248, 0x024c, 0x0250, 0x0254, 0x0258, 0x025c, + 0x0260, 0x0264, 0x0268, 0x026c, 0x0270, 0x0274, 0x02a4, 0x02a8, + 0x02ac, 0x02b0, 0x02b4, 0x02b8, 0x02bc, 0x02c0, 0x02c4, 0x02c8, + 0x02cc, 0x02d0, 0x02d4, 0x02d8, 0x02dc, 0x02e0, 0x02e4, 0x02e8, + 0x02ec, 0x02f0, 0x02f4, 0x02f8, 0x02fc, 0x0300, 0x0304, 0x0308, + 0x030c, 0x0310, 0x0314, 0x0328, 0x032c, 0x0330, 0x0334, 0x0338, + 0x033c, 0x0348, 0x034c, 0x0350, 0x0354, 0x0358, 0x035c, 0x0360, + 0x0364, 0x0370, 0x0374, 0x0378, 0x037c, 0x0380, 0x0384, 0x0388, + 0x038c, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03a8, 0x03ac, +}; + +#define DDR_PHY_NUM_REG ARRAY_SIZE(fthd_ddr_phy_reg_map) +#define DDR_PHY_REG_BASE 0x2800 + +/* DDR40 */ +#define S2_DDR40_PHY_PLL_STATUS 0x2810 +#define S2_DDR40_PHY_PLL_STATUS_LOCKED (1 << 0) +#define S2_DDR40_PHY_PLL_CFG 0x2814 +#define S2_DDR40_PHY_PLL_DIV 0x281c +#define S2_DDR40_PHY_AUX_CTL 0x2820 + +#define S2_DDR40_PHY_VDL_OVR_COARSE 0x2830 +#define S2_DDR40_PHY_VDL_OVR_FINE 0x2834 + +#define S2_DDR40_PHY_ZQ_PVT_COMP_CTL 0x283c +#define S2_DDR40_PHY_DRV_PAD_CTL 0x2840 + +#define S2_DDR40_PHY_VDL_CTL 0x2848 + +#define S2_DDR40_PHY_VDL_STATUS 0x284c +#define S2_DDR40_PHY_VDL_STEP_MASK 0x0ffc +#define S2_DDR40_PHY_VDL_STEP_SHIFT 2 + +#define S2_DDR40_PHY_DQ_CALIB_STATUS 0x2850 +#define S2_DDR40_PHY_VDL_CHAN_STATUS 0x2854 + +#define S2_DDR40_PHY_VTT_CTL 0x285c +#define S2_DDR40_PHY_VTT_STATUS 0x2860 +#define S2_DDR40_PHY_VTT_CONNECTIONS 0x2864 +#define S2_DDR40_PHY_VTT_OVERRIDE 0x2868 + +#define S2_DDR40_STRAP_CTL 0x28b0 +#define S2_DDR40_STRAP_CTL_2 0x28b4 +#define S2_DDR40_STRAP_STATUS 0x28b8 + +/* FIXME: Come up with a better name */ +#define S2_DDR40_BYTE_LANE_SIZE 0xa0 +#define S2_DDR40_NUM_BYTE_LANES 2 + +#define S2_DDR40_RDEN_BYTE 0x2a00 +#define S2_DDR40_2A08 0x2a08 +#define S2_DDR40_2A0C 0x2a0c +#define S2_DDR40_2A10 0x2a10 +#define S2_DDR40_2A34 0x2a34 +#define S2_DDR40_2A38 0x2a38 +#define S2_DDR40_RDEN_BYTE0 0x2a74 +#define S2_DDR40_2AA8 0x2aa8 +#define S2_DDR40_2AAC 0x2aac +#define S2_DDR40_RDEN_BYTE1 0x2b14 +#define S2_DDR40_WL_RD_DATA_DLY 0x2b60 +#define S2_DDR40_WL_READ_CTL 0x2b64 +#define S2_DDR40_WL_READ_FIFO_STATUS 0x2b90 +#define S2_DDR40_WL_READ_FIFO_CLEAR 0x2b94 +#define S2_DDR40_WL_DRV_PAD_CTL 0x2ba4 +#define S2_DDR40_WL_CLK_PAD_DISABLE 0x2ba8 +#define S2_DDR40_WL_IDLE_PAD_CTL 0x2ba0 +#define S2_DDR40_WL_WR_PREAMBLE_MODE 0x2bac + +#define S2_3200 0x3200 +#define S2_3204 0x3204 +#define S2_3208 0x3208 + +/* On iomem with pointer at 0x0ff0 (Bar 4: 1MB) */ +#define ISP_FW_CHAN_CTRL 0xc3000 +#define ISP_FW_QUEUE_CTRL 0xc3004 +#define ISP_FW_SIZE 0xc3008 +#define ISP_FW_HEAP_SIZE 0xc300c +#define ISP_FW_HEAP_ADDR 0xc3010 +#define ISP_FW_HEAP_SIZE2 0xc3014 +#define ISP_REG_C3018 0xc3018 /* Module params or cmd buf? */ +#define ISP_REG_C301C 0xc301c +#define ISP_REG_40004 0x40004 +#define ISP_REG_40008 0x40008 +#define ISP_IRQ_STATUS 0x41000 +#define ISP_IRQ_ENABLE 0x41004 +#define ISP_REG_41020 0x41020 +#define ISP_IRQ_CLEAR 0x41024 + +#define ISP_FW_CHAN_START 0x0128 +#define ISP_FW_CHAN_END 0x0220 + +#endif diff --git a/drivers/custom/facetimehd/fthd_ringbuf.c b/drivers/custom/facetimehd/fthd_ringbuf.c new file mode 100644 index 000000000000..7b099ae56379 --- /dev/null +++ b/drivers/custom/facetimehd/fthd_ringbuf.c @@ -0,0 +1,135 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2015 Sven Schnelle + * + */ + +#include +#include +#include +#include +#include +#include "fthd_drv.h" +#include "fthd_hw.h" +#include "fthd_ringbuf.h" +#include "fthd_isp.h" + +u32 get_entry_addr(struct fthd_private *dev_priv, + struct fw_channel *chan, int num) +{ + return chan->offset + num * FTHD_RINGBUF_ENTRY_SIZE; +} + +void fthd_channel_ringbuf_dump(struct fthd_private *dev_priv, struct fw_channel *chan) +{ + u32 entry; + char pos; + int i; + + for( i = 0; i < chan->size; i++) { + if (chan->ringbuf.idx == i) + pos = '*'; + else + pos = ' '; + entry = get_entry_addr(dev_priv, chan, i); + pr_debug("%s: %c%3.3d: ADDRESS %08x REQUEST_SIZE %08x RESPONSE_SIZE %08x\n", + chan->name, pos, i, + FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS), + FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE), + FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE)); + } +} + +void fthd_channel_ringbuf_init(struct fthd_private *dev_priv, struct fw_channel *chan) +{ + u32 entry; + int i; + + chan->ringbuf.idx = 0; + + if (chan->type == RINGBUF_TYPE_H2T) { + pr_debug("clearing ringbuf %s at %08x (size %d)\n", + chan->name, chan->offset, chan->size); + + spin_lock_irq(&chan->lock); + for(i = 0; i < chan->size; i++) { + entry = get_entry_addr(dev_priv, chan, i); + FTHD_S2_MEM_WRITE(1, entry + FTHD_RINGBUF_ADDRESS_FLAGS); + FTHD_S2_MEM_WRITE(0, entry + FTHD_RINGBUF_REQUEST_SIZE); + FTHD_S2_MEM_WRITE(0, entry + FTHD_RINGBUF_RESPONSE_SIZE); + entry += FTHD_RINGBUF_ENTRY_SIZE; + } + spin_unlock_irq(&chan->lock); + } +} + +int fthd_channel_ringbuf_send(struct fthd_private *dev_priv, struct fw_channel *chan, + u32 data_offset, u32 request_size, u32 response_size, u32 *entryp) +{ + u32 entry; + + pr_debug("send %08x\n", data_offset); + + spin_lock_irq(&chan->lock); + entry = get_entry_addr(dev_priv, chan, chan->ringbuf.idx); + + if (++chan->ringbuf.idx >= chan->size) + chan->ringbuf.idx = 0; + + if (!(FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS) & 1) ^ (chan->type != 0)) { + spin_unlock_irq(&chan->lock); + return -EAGAIN; + } + + FTHD_S2_MEM_WRITE(request_size, entry + FTHD_RINGBUF_REQUEST_SIZE); + FTHD_S2_MEM_WRITE(response_size, entry + FTHD_RINGBUF_RESPONSE_SIZE); + wmb(); + FTHD_S2_MEM_WRITE(data_offset | (chan->type == 0 ? 0 : 1), + entry + FTHD_RINGBUF_ADDRESS_FLAGS); + spin_unlock_irq(&chan->lock); + + spin_lock_irq(&dev_priv->io_lock); + FTHD_ISP_REG_WRITE(0x10 << chan->source, ISP_REG_41020); + spin_unlock_irq(&dev_priv->io_lock); + if (entryp) + *entryp = entry; + return 0; +} + +u32 fthd_channel_ringbuf_receive(struct fthd_private *dev_priv, + struct fw_channel *chan) +{ + u32 entry, ret = (u32)-1; + + spin_lock_irq(&chan->lock); + + entry = get_entry_addr(dev_priv, chan, chan->ringbuf.idx); + + + if (!(FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS) & 1) ^ (chan->type != 0)) + goto out; + + ret = entry; + + if (chan->type == FW_CHAN_TYPE_OUT && ++chan->ringbuf.idx >= chan->size) + chan->ringbuf.idx = 0; + +out: + spin_unlock_irq(&chan->lock); + return ret; +} + +int fthd_channel_wait_ready(struct fthd_private *dev_priv, struct fw_channel *chan, u32 entry, int timeout) +{ + if (wait_event_interruptible_timeout(chan->wq, + (FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS) & 1) ^ (chan->type != 0), + msecs_to_jiffies(timeout)) <= 0) { + dev_err(&dev_priv->pdev->dev, "%s: timeout\n", chan->name); + fthd_channel_ringbuf_dump(dev_priv, chan); + return -ETIMEDOUT; + } + return 0; +} diff --git a/drivers/custom/facetimehd/fthd_ringbuf.h b/drivers/custom/facetimehd/fthd_ringbuf.h new file mode 100644 index 000000000000..a59fb5e0e21c --- /dev/null +++ b/drivers/custom/facetimehd/fthd_ringbuf.h @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2015 Sven Schnelle + * + */ + +#ifndef _FTHD_RINGBUF_H +#define _FTHD_RINGBUF_H + +#define FTHD_RINGBUF_ENTRY_SIZE 64 + +#define FTHD_RINGBUF_ADDRESS_FLAGS 0 +#define FTHD_RINGBUF_REQUEST_SIZE 4 +#define FTHD_RINGBUF_RESPONSE_SIZE 8 + +enum ringbuf_type_t { + RINGBUF_TYPE_H2T=0, + RINGBUF_TYPE_T2H=1, + RINGBUF_TYPE_UNIDIRECTIONAL, +}; + +struct fthd_ringbuf { + void *doorbell; + int idx; +}; + +struct fw_channel; +struct fthd_private; +extern void fthd_channel_ringbuf_dump(struct fthd_private *dev_priv, struct fw_channel *chan); +extern void fthd_channel_ringbuf_init(struct fthd_private *dev_priv, struct fw_channel *chan); +extern u32 fthd_channel_ringbuf_get_entry(struct fthd_private *, struct fw_channel *); +extern int fthd_channel_ringbuf_send(struct fthd_private *dev_priv, struct fw_channel *chan, + u32 data_offset, u32 request_size, u32 response_size, u32 *entry); + +extern u32 fthd_channel_ringbuf_receive(struct fthd_private *dev_priv, + struct fw_channel *chan); + +extern int fthd_channel_wait_ready(struct fthd_private *dev_priv, struct fw_channel *chan, u32 entry, int timeout); +extern u32 get_entry_addr(struct fthd_private *dev_priv, + struct fw_channel *chan, int num); +#endif diff --git a/drivers/custom/facetimehd/fthd_v4l2.c b/drivers/custom/facetimehd/fthd_v4l2.c new file mode 100644 index 000000000000..a3974a2c988d --- /dev/null +++ b/drivers/custom/facetimehd/fthd_v4l2.c @@ -0,0 +1,760 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2015 Sven Schnelle + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fthd_drv.h" +#include "fthd_hw.h" +#include "fthd_isp.h" +#include "fthd_ringbuf.h" +#include "fthd_buffer.h" + +#define FTHD_MAX_WIDTH 1280 +#define FTHD_MAX_HEIGHT 720 +#define FTHD_MIN_WIDTH 320 +#define FTHD_MIN_HEIGHT 240 +#define FTHD_NUM_FORMATS 2 /* NV16 is disabled for now */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0) +# define VFL_TYPE_VIDEO VFL_TYPE_GRABBER +#endif + +static int fthd_buffer_queue_setup( + struct vb2_queue *vq, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,4,0) + const struct v4l2_format *fmt, +#endif +#if !(LINUX_VERSION_CODE >= KERNEL_VERSION(4,5,0)) + const void *parg, +#endif + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) + struct device *alloc_devs[] +#else + void *alloc_ctxs[] +#endif +) { + + struct fthd_private *dev_priv = vb2_get_drv_priv(vq); + struct v4l2_pix_format *cur_fmt = &dev_priv->fmt.fmt; + int i, total_size = 0; + + if (*nplanes) + return sizes[0] < (cur_fmt->bytesperline * cur_fmt->height) ? -EINVAL : 0; + + *nplanes = dev_priv->fmt.planes; + + if (!*nplanes) + return -EINVAL; + + /* FIXME: We assume single plane format here but not below */ + for (i = 0; i < *nplanes; i++) { + sizes[i] = cur_fmt->sizeimage; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) + alloc_devs[i] = &dev_priv->pdev->dev; +#else + alloc_ctxs[i] = dev_priv->alloc_ctx; +#endif + total_size += sizes[i]; + } + + *nbuffers = (4096 * 4096) / total_size; + if (*nbuffers > 4) + *nbuffers = 4; + if (*nbuffers <= 1) + return -ENOMEM; + pr_debug("using %d buffers\n", *nbuffers); + + return 0; +} + +static void fthd_buffer_cleanup(struct vb2_buffer *vb) +{ + struct fthd_private *dev_priv = vb2_get_drv_priv(vb->vb2_queue); + struct h2t_buf_ctx *ctx = NULL; + int i; + + pr_debug("%p\n", vb); + for(i = 0; i < FTHD_BUFFERS; i++) { + if (dev_priv->h2t_bufs[i].vb == vb) { + ctx = dev_priv->h2t_bufs + i; + break; + }; + } + if (!ctx || ctx->state == BUF_FREE) + return; + + ctx->state = BUF_FREE; + ctx->vb = NULL; + isp_mem_destroy(ctx->dma_desc_obj); + for(i = 0; i < dev_priv->fmt.planes; i++) { + iommu_free(dev_priv, ctx->plane[i]); + ctx->plane[i] = NULL; + } + ctx->dma_desc_obj = NULL; +} + +static int fthd_send_h2t_buffer(struct fthd_private *dev_priv, struct h2t_buf_ctx *ctx) +{ + u32 entry; + int ret; + + pr_debug("sending buffer %p size %ld, ctx %p\n", ctx->vb, sizeof(ctx->dma_desc_list), ctx); + FTHD_S2_MEMCPY_TOIO(ctx->dma_desc_obj->offset, &ctx->dma_desc_list, sizeof(ctx->dma_desc_list)); + ret = fthd_channel_ringbuf_send(dev_priv, dev_priv->channel_buf_h2t, + ctx->dma_desc_obj->offset, 0x180, 0x30000000, &entry); + + if (ret) { + pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret); + return ret; + } + return fthd_channel_wait_ready(dev_priv, dev_priv->channel_buf_h2t, entry, 2000); +} + +static void fthd_buffer_queue(struct vb2_buffer *vb) +{ + struct fthd_private *dev_priv = vb2_get_drv_priv(vb->vb2_queue); + struct dma_descriptor_list *list; + struct h2t_buf_ctx *ctx = NULL; + + int i; + pr_debug("vb = %p\n", vb); + for(i = 0; i < FTHD_BUFFERS; i++) { + if (dev_priv->h2t_bufs[i].vb == vb) { + ctx = dev_priv->h2t_bufs + i; + break; + }; + } + + if (!ctx) + return; + + if (ctx->state != BUF_ALLOC) + return; + + if (!vb->vb2_queue->streaming) { + ctx->state = BUF_DRV_QUEUED; + } else { + list = &ctx->dma_desc_list; + list->field0 = 1; + ctx->state = BUF_HW_QUEUED; + wmb(); + pr_debug("%d: field0: %d, count %d, pool %d, addr0 0x%08x, addr1 0x%08x tag 0x%08llx vb = %p\n", i, list->field0, + list->desc[i].count, list->desc[i].pool, list->desc[i].addr0, list->desc[i].addr1, list->desc[i].tag, ctx->vb); + + if (fthd_send_h2t_buffer(dev_priv, ctx)) { + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + ctx->state = BUF_ALLOC; + } + } + return; +} + +static int fthd_buffer_prepare(struct vb2_buffer *vb) +{ + struct fthd_private *dev_priv = vb2_get_drv_priv(vb->vb2_queue); + struct sg_table *sgtable; + struct h2t_buf_ctx *ctx = NULL; + struct dma_descriptor_list *dma_list; + int i; + + pr_debug("%p\n", vb); + for(i = 0; i < FTHD_BUFFERS; i++) { + if (dev_priv->h2t_bufs[i].state == BUF_FREE || + (dev_priv->h2t_bufs[i].state == BUF_ALLOC && dev_priv->h2t_bufs[i].vb == vb)) { + ctx = dev_priv->h2t_bufs + i; + break; + } + } + + if (!ctx) + return -ENOBUFS; + + if (ctx->state == BUF_FREE) { + pr_debug("allocating new entry\n"); + ctx->dma_desc_obj = isp_mem_create(dev_priv, FTHD_MEM_BUFFER, 0x180); + if (!ctx->dma_desc_obj) + return -ENOMEM; + + ctx->vb = vb; + ctx->state = BUF_ALLOC; + + for(i = 0; i < dev_priv->fmt.planes; i++) { + sgtable = vb2_dma_sg_plane_desc(vb, i); + ctx->plane[i] = iommu_allocate_sgtable(dev_priv, sgtable); + if(!ctx->plane[i]) + return -ENOMEM; + } + } + + vb2_set_plane_payload(vb, 0, dev_priv->fmt.fmt.sizeimage); + + dma_list = &ctx->dma_desc_list; + memset(dma_list, 0, 0x180); + + dma_list->field0 = 1; + dma_list->count = 1; + dma_list->desc[0].count = 1; + dma_list->desc[0].pool = 0x02; + dma_list->desc[0].addr0 = (ctx->plane[0]->offset << 12) | 0xc0000000; + + if (dev_priv->fmt.planes >= 2) + dma_list->desc[0].addr1 = (ctx->plane[1]->offset << 12) | 0xc0000000; + if (dev_priv->fmt.planes >= 3) + dma_list->desc[0].addr2 = (ctx->plane[2]->offset << 12) | 0xc0000000; + + dma_list->desc[0].tag = (u64)ctx; + init_waitqueue_head(&ctx->wq); + return 0; +} + +void fthd_buffer_return_handler(struct fthd_private *dev_priv, u32 offset, int size) +{ + struct dma_descriptor_list list; + struct h2t_buf_ctx *ctx; + int i; + + FTHD_S2_MEMCPY_FROMIO(&list, offset, sizeof(list)); + + for(i = 0; i < list.count; i++) { + ctx = (struct h2t_buf_ctx *)list.desc[i].tag; + pr_debug("%d: field0: %d, count %d, pool %d, addr0 0x%08x, addr1 0x%08x tag 0x%08llx vb = %p, ctx = %p\n", i, list.field0, + list.desc[i].count, list.desc[i].pool, list.desc[i].addr0, list.desc[i].addr1, list.desc[i].tag, ctx->vb, ctx); + + if (ctx->state == BUF_HW_QUEUED || ctx->state == BUF_DRV_QUEUED) { + ctx->state = BUF_ALLOC; + vb2_buffer_done(ctx->vb, VB2_BUF_STATE_DONE); + } + + } +} + +static int fthd_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct fthd_private *dev_priv = vb2_get_drv_priv(vq); + struct h2t_buf_ctx *ctx; + int i, ret; + + pr_debug("count = %d\n", count); + ret = fthd_start_channel(dev_priv, 0); + if (ret) + return ret; + + for(i = 0; i < FTHD_BUFFERS && count; i++, count--) { + ctx = dev_priv->h2t_bufs + i; + if (ctx->state != BUF_DRV_QUEUED) + continue; + + if (fthd_send_h2t_buffer(dev_priv, ctx)) { + vb2_buffer_done(ctx->vb, VB2_BUF_STATE_ERROR); + ctx->state = BUF_ALLOC; + } + ctx->state = BUF_HW_QUEUED; + } + return 0; +} + +static void fthd_stop_streaming(struct vb2_queue *vq) +{ + struct fthd_private *dev_priv = vb2_get_drv_priv(vq); + struct h2t_buf_ctx *ctx; + int ret, i; + + ret = fthd_stop_channel(dev_priv, 0); + if (!ret) { + pr_debug("waiting for buffers...\n"); + vb2_wait_for_all_buffers(vq); + pr_debug("done\n"); + } else { + /* Firmware doesn't respond. */ + for(i = 0; i < FTHD_BUFFERS;i++) { + ctx = dev_priv->h2t_bufs + i; + if (ctx->state == BUF_DRV_QUEUED || ctx->state == BUF_HW_QUEUED) { + vb2_buffer_done(ctx->vb, VB2_BUF_STATE_DONE); + ctx->vb = NULL; + ctx->state = BUF_ALLOC; + } + } + } +} + +static struct vb2_ops vb2_queue_ops = { + .queue_setup = fthd_buffer_queue_setup, + .buf_prepare = fthd_buffer_prepare, + .buf_cleanup = fthd_buffer_cleanup, + .start_streaming = fthd_start_streaming, + .stop_streaming = fthd_stop_streaming, + .buf_queue = fthd_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static struct v4l2_file_operations fthd_vdev_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + + .read = vb2_fop_read, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2 +}; + +static int fthd_v4l2_ioctl_enum_input(struct file *filp, void *priv, + struct v4l2_input *input) +{ + if (input->index != 0) + return -EINVAL; + + memset(input, 0, sizeof(*input)); + strcpy(input->name, "Camera"); + input->type = V4L2_INPUT_TYPE_CAMERA; + input->std = 0; + + return 0; +} + +static int fthd_v4l2_ioctl_g_input(struct file *filp, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int fthd_v4l2_ioctl_s_input(struct file *filp, void *priv, unsigned int i) +{ + if (i != 0) + return -EINVAL; + return 0; +} + +static int fthd_v4l2_ioctl_querycap(struct file *filp, void *priv, + struct v4l2_capability *cap) +{ + struct fthd_private *dev_priv = video_drvdata(filp); + + strcpy(cap->driver, "facetimehd"); + strcpy(cap->card, "Apple Facetime HD"); + snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s", + pci_name(dev_priv->pdev)); + + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int fthd_v4l2_ioctl_enum_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_fmtdesc *fmt) +{ + char *desc = NULL; + + switch (fmt->index) { + case 0: + fmt->pixelformat = V4L2_PIX_FMT_YUYV; + desc = "YUYV"; + break; + case 1: + fmt->pixelformat = V4L2_PIX_FMT_YVYU; + desc = "YVYU"; + break; + /* We don't support the mplane yet + case 2: + fmt->pixelformat = V4L2_PIX_FMT_NV16; + desc = "NV16"; + break; + */ + default: + return -EINVAL; + } + + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + strncpy(fmt->description, desc, sizeof(fmt->description)); + + return 0; +} + +static int fthd_v4l2_adjust_format(struct fthd_private *dev_priv, + struct v4l2_pix_format *pix) +{ + + if (pix->pixelformat != V4L2_PIX_FMT_YUYV && + pix->pixelformat != V4L2_PIX_FMT_YVYU) + pix->pixelformat = V4L2_PIX_FMT_YUYV; + + if (pix->width < FTHD_MIN_WIDTH) + pix->width = FTHD_MIN_WIDTH; + if (pix->width > FTHD_MAX_WIDTH) + pix->width = FTHD_MAX_WIDTH; + if (pix->height < FTHD_MIN_HEIGHT) + pix->height = FTHD_MIN_HEIGHT; + if (pix->height > FTHD_MAX_HEIGHT) + pix->height = FTHD_MAX_HEIGHT; + + pix->colorspace = V4L2_COLORSPACE_SRGB; + pix->field = V4L2_FIELD_NONE; + pix->width = ALIGN(pix->width, 7); + + switch (pix->pixelformat) { +/* + case V4L2_PIX_FMT_NV16: + pix->sizeimage = pix->width * pix->height; + pix->bytesperline = pix->width; + break; +*/ + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + default: + pix->bytesperline = pix->width * 2; + pix->sizeimage = pix->bytesperline * pix->height; + break; + } + + return 0; +} + +static int fthd_v4l2_ioctl_try_fmt_vid_cap(struct file *filp, void *_priv, + struct v4l2_format *fmt) +{ + struct fthd_private *dev_priv = video_drvdata(filp); + + pr_debug("%s: %dx%d\n", __FUNCTION__, fmt->fmt.pix.width, fmt->fmt.pix.height); + + if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return fthd_v4l2_adjust_format(dev_priv, &fmt->fmt.pix); +} + +static int fthd_v4l2_ioctl_g_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_format *fmt) +{ + struct fthd_private *dev_priv = video_drvdata(filp); + + pr_debug("%s\n", __FUNCTION__); + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt->fmt.pix = dev_priv->fmt.fmt; + + return 0; +} + +static int fthd_v4l2_ioctl_s_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_format *fmt) +{ + struct fthd_private *dev_priv = video_drvdata(filp); + int ret; + + if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* FIXME: Check if hardware is busy */ + + ret = fthd_v4l2_adjust_format(dev_priv, &fmt->fmt.pix); + if (ret) + return ret; + + pr_debug("%c%c%c%c\n", fmt->fmt.pix.pixelformat, fmt->fmt.pix.pixelformat >> 8, + fmt->fmt.pix.pixelformat >> 16, fmt->fmt.pix.pixelformat >> 24); + + dev_priv->fmt.fmt = fmt->fmt.pix; + + switch (fmt->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_NV16: + dev_priv->fmt.planes = 2; + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + dev_priv->fmt.planes = 1; + break; + } + + return 0; +} + + +static int fthd_v4l2_ioctl_g_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + struct fthd_private *priv_dev = video_drvdata(filp); + struct v4l2_fract timeperframe = { + .numerator = priv_dev->frametime, + .denominator = 1000, + }; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + parm->parm.capture.readbuffers = FTHD_BUFFERS; + parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + parm->parm.capture.timeperframe = timeperframe; + return 0; +} + +static int fthd_v4l2_ioctl_s_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + + struct fthd_private *dev_priv = video_drvdata(filp); + struct v4l2_fract *timeperframe; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + timeperframe = &parm->parm.capture.timeperframe; + + if(timeperframe->denominator == 0) { + timeperframe->numerator = 20; + timeperframe->denominator = 1000; + } + + dev_priv->frametime = clamp_t(unsigned int, timeperframe->numerator * 1000 / + timeperframe->denominator, 20, 500); + + return fthd_v4l2_ioctl_g_parm(filp, priv, parm); +} + +static int fthd_v4l2_ioctl_enum_framesizes(struct file *filp, void *priv, + struct v4l2_frmsizeenum *sizes) +{ + if (sizes->index) + return -EINVAL; + + if (sizes->pixel_format != V4L2_PIX_FMT_YUYV && + sizes->pixel_format != V4L2_PIX_FMT_YVYU) + return -EINVAL; + + sizes->type = V4L2_FRMSIZE_TYPE_DISCRETE; + sizes->discrete.width = FTHD_MAX_WIDTH; + sizes->discrete.height = FTHD_MAX_HEIGHT; + + return 0; +} + +static int fthd_v4l2_ioctl_enum_frameintervals(struct file *filp, void *priv, + struct v4l2_frmivalenum *interval) +{ + pr_debug("%s\n", __FUNCTION__); + + if (interval->index) + return -EINVAL; + + if (interval->pixel_format != V4L2_PIX_FMT_YUYV && + interval->pixel_format != V4L2_PIX_FMT_YVYU && + interval->pixel_format != V4L2_PIX_FMT_NV16) + return -EINVAL; + + if (interval->width & 7 + || interval->width > FTHD_MAX_WIDTH + || interval->height > FTHD_MAX_HEIGHT) + return -EINVAL; + + interval->type = V4L2_FRMIVAL_TYPE_DISCRETE; + interval->discrete.numerator = 1; + interval->discrete.denominator = 30; + + return 0; +} + +static int fthd_v4l2_ioctl_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + } + + return -EINVAL; +} + +static struct v4l2_ioctl_ops fthd_ioctl_ops = { + .vidioc_enum_input = fthd_v4l2_ioctl_enum_input, + .vidioc_g_input = fthd_v4l2_ioctl_g_input, + .vidioc_s_input = fthd_v4l2_ioctl_s_input, + .vidioc_enum_fmt_vid_cap = fthd_v4l2_ioctl_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = fthd_v4l2_ioctl_try_fmt_vid_cap, + + .vidioc_g_fmt_vid_cap = fthd_v4l2_ioctl_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = fthd_v4l2_ioctl_s_fmt_vid_cap, + .vidioc_querycap = fthd_v4l2_ioctl_querycap, + + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_g_parm = fthd_v4l2_ioctl_g_parm, + .vidioc_s_parm = fthd_v4l2_ioctl_s_parm, + .vidioc_enum_framesizes = fthd_v4l2_ioctl_enum_framesizes, + .vidioc_enum_frameintervals = fthd_v4l2_ioctl_enum_frameintervals, + + .vidioc_subscribe_event = fthd_v4l2_ioctl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int fthd_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + pr_debug("id = %x\n", ctrl->id); + return -EINVAL; +} + +static int fthd_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct fthd_private *dev_priv = container_of(ctrl->handler, struct fthd_private, v4l2_ctrl_handler); + int ret = -EINVAL; + + pr_info("id = %x, val = %d\n", ctrl->id, ctrl->val); + + switch(ctrl->id) { + case V4L2_CID_CONTRAST: + ret = fthd_isp_cmd_channel_contrast_set(dev_priv, 0, ctrl->val); + break; + case V4L2_CID_BRIGHTNESS: + ret = fthd_isp_cmd_channel_brightness_set(dev_priv, 0, ctrl->val); + break; + case V4L2_CID_SATURATION: + ret = fthd_isp_cmd_channel_saturation_set(dev_priv, 0, ctrl->val); + break; + case V4L2_CID_HUE: + ret = fthd_isp_cmd_channel_hue_set(dev_priv, 0, ctrl->val); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = fthd_isp_cmd_channel_awb(dev_priv, 0, ctrl->val); + + default: + break; + + } + pr_debug("ret = %d\n", ret); + return ret; +} + +static const struct v4l2_ctrl_ops fthd_ctrl_ops = { + .g_volatile_ctrl = fthd_g_volatile_ctrl, + .s_ctrl = fthd_s_ctrl, +}; + +int fthd_v4l2_register(struct fthd_private *dev_priv) +{ + struct v4l2_device *v4l2_dev = &dev_priv->v4l2_dev; + struct video_device *vdev; + struct vb2_queue *q; + int ret; + + ret = v4l2_device_register(&dev_priv->pdev->dev, v4l2_dev); + if (ret) { + pr_err("v4l2_device_register: %d\n", ret); + return ret; + } + + vdev = video_device_alloc(); + if (!vdev) { + ret = -ENOMEM; + goto fail; + } + dev_priv->videodev = vdev; + + q = &dev_priv->vb2_queue; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ; + q->drv_priv = dev_priv; + q->ops = &vb2_queue_ops; + q->mem_ops = &vb2_dma_sg_memops; + q->buf_struct_size = 0;//sizeof(struct vpif_cap_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; +#if LINUX_VERSION_CODE < KERNEL_VERSION(6,8,0) + q->min_buffers_needed = 1; +#else + q->min_queued_buffers = 1; +#endif + q->lock = &dev_priv->vb2_queue_lock; + + ret = vb2_queue_init(q); + if (ret) + goto fail; + + v4l2_ctrl_handler_init(&dev_priv->v4l2_ctrl_handler, 4); + v4l2_ctrl_new_std(&dev_priv->v4l2_ctrl_handler, &fthd_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 0xff, 1, 0x80); + v4l2_ctrl_new_std(&dev_priv->v4l2_ctrl_handler, &fthd_ctrl_ops, + V4L2_CID_CONTRAST, 0, 0xff, 1, 0x80); + v4l2_ctrl_new_std(&dev_priv->v4l2_ctrl_handler, &fthd_ctrl_ops, + V4L2_CID_SATURATION, 0, 0xff, 1, 0x80); + v4l2_ctrl_new_std(&dev_priv->v4l2_ctrl_handler, &fthd_ctrl_ops, + V4L2_CID_HUE, 0, 0xff, 1, 0x80); + v4l2_ctrl_new_std(&dev_priv->v4l2_ctrl_handler, &fthd_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + + if (dev_priv->v4l2_ctrl_handler.error) { + pr_err("failed to setup control handlers\n"); + v4l2_ctrl_handler_free(&dev_priv->v4l2_ctrl_handler); + goto fail; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0) + dev_priv->alloc_ctx = vb2_dma_sg_init_ctx(&dev_priv->pdev->dev); +#endif + vdev->v4l2_dev = v4l2_dev; + strcpy(vdev->name, "Apple Facetime HD"); // XXX: Length? + vdev->vfl_dir = VFL_DIR_RX; + vdev->fops = &fthd_vdev_fops; + vdev->ioctl_ops = &fthd_ioctl_ops; + vdev->queue = q; + vdev->release = video_device_release; + vdev->ctrl_handler = &dev_priv->v4l2_ctrl_handler; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0) + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; +#endif + video_set_drvdata(vdev, dev_priv); + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + video_device_release(vdev); + goto fail_vdev; + } + dev_priv->fmt.fmt.sizeimage = 1280 * 720 * 2; + dev_priv->fmt.fmt.pixelformat = V4L2_PIX_FMT_YUYV; + dev_priv->fmt.fmt.width = 1280; + dev_priv->fmt.fmt.height = 720; + dev_priv->fmt.planes = 1; + + fthd_v4l2_adjust_format(dev_priv, &dev_priv->fmt.fmt); + + return 0; +fail_vdev: + v4l2_ctrl_handler_free(&dev_priv->v4l2_ctrl_handler); +fail: + v4l2_device_unregister(&dev_priv->v4l2_dev); + return ret; +} + +void fthd_v4l2_unregister(struct fthd_private *dev_priv) +{ + + v4l2_ctrl_handler_free(&dev_priv->v4l2_ctrl_handler); +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0) + vb2_dma_sg_cleanup_ctx(dev_priv->alloc_ctx); +#endif + video_unregister_device(dev_priv->videodev); + v4l2_device_unregister(&dev_priv->v4l2_dev); +} diff --git a/drivers/custom/facetimehd/fthd_v4l2.h b/drivers/custom/facetimehd/fthd_v4l2.h new file mode 100644 index 000000000000..8e4f9d574904 --- /dev/null +++ b/drivers/custom/facetimehd/fthd_v4l2.h @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * FacetimeHD camera driver + * + * Copyright (C) 2015 Sven Schnelle + * + */ + +#ifndef _FTHD_V4L2_H +#define _FTHD_V4L2_H + +#include +#include +#include +#include +#include + +struct fthd_fmt { + struct v4l2_pix_format fmt; + const char *desc; + int range; /* CISP_COMMAND_CH_OUTPUT_CONFIG_SET */ + int planes; + int x1; /* for CISP_CMD_CH_CROP_SET */ + int y1; + int x2; + int y2; +}; + +struct fthd_private; +extern int fthd_v4l2_register(struct fthd_private *dev_priv); +extern void fthd_v4l2_unregister(struct fthd_private *dev_priv); + +#endif # ---------------------------------------- # Module: fanatecff # Version: 33ee9ab707e7 # ---------------------------------------- diff --git a/drivers/custom/fanatecff/Kbuild b/drivers/custom/fanatecff/Kbuild new file mode 100644 index 000000000000..341ab3d4df42 --- /dev/null +++ b/drivers/custom/fanatecff/Kbuild @@ -0,0 +1,2 @@ +obj-m := hid-fanatec.o +hid-fanatec-y := hid-ftec.o hid-ftecff.o hid-ftecff-tuning.o diff --git a/drivers/custom/fanatecff/Makefile b/drivers/custom/fanatecff/Makefile new file mode 100644 index 000000000000..686e32a6379c --- /dev/null +++ b/drivers/custom/fanatecff/Makefile @@ -0,0 +1,27 @@ +KVERSION ?= `uname -r` +KERNEL_SRC ?= /lib/modules/${KVERSION}/build +MODULEDIR ?= /lib/modules/${KVERSION}/kernel/drivers/hid + +default: + @echo -e "\n::\033[32m Compiling Fanatec kernel module\033[0m" + @echo "========================================" + $(MAKE) -C $(KERNEL_SRC) M=$$PWD + +clean: + @echo -e "\n::\033[32m Cleaning Fanatec kernel module\033[0m" + @echo "========================================" + $(MAKE) -C $(KERNEL_SRC) M=$$PWD clean + +install: + @echo -e "\n::\033[34m Installing Fanatec kernel module/udev rule\033[0m" + @echo "=====================================================" + @cp -v hid-fanatec.ko ${MODULEDIR} + @cp -v fanatec.rules /etc/udev/rules.d/99-fanatec.rules + depmod + +uninstall: + @echo -e "\n::\033[34m Uninstalling Fanatec kernel module/udev rule\033[0m" + @echo "=====================================================" + @rm -fv ${MODULEDIR}/hid-fanatec.ko + @rm -fv /etc/udev/rules.d/99-fanatec.rules + depmod diff --git a/drivers/custom/fanatecff/hid-ftec-pid.h b/drivers/custom/fanatecff/hid-ftec-pid.h new file mode 100644 index 000000000000..f6f0e3055f91 --- /dev/null +++ b/drivers/custom/fanatecff/hid-ftec-pid.h @@ -0,0 +1,427 @@ +0x35, 0x00, /* Physical Minimum (0), */ +0x45, 0x00, /* Physical Maximum (0), */ +0x05, 0x0F, /* Usage Page (PID), */ +0x09, 0x92, /* Usage (92h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x10, /* Report ID (16), */ +0x09, 0x9F, /* Usage (9Fh), */ +0x09, 0xA0, /* Usage (A0h), */ +0x09, 0x94, /* Usage (94h), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x75, 0x01, /* Report Size (1), */ +0x95, 0x08, /* Report Count (8), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x22, /* Usage (22h), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x28, /* Logical Maximum (40), */ +0x75, 0x07, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x81, 0x02, /* Input (Variable), */ +0xC0, /* End Collection, */ +0x09, 0x21, /* Usage (21h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x11, /* Report ID (17), */ +0x09, 0x22, /* Usage (22h), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x28, /* Logical Maximum (40), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x25, /* Usage (25h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x09, 0x26, /* Usage (26h), */ +0x09, 0x27, /* Usage (27h), */ +0x09, 0x28, /* Usage (28h), */ +0x09, 0x30, /* Usage (30h), */ +0x09, 0x31, /* Usage (31h), */ +0x09, 0x32, /* Usage (32h), */ +0x09, 0x33, /* Usage (33h), */ +0x09, 0x34, /* Usage (34h), */ +0x09, 0x40, /* Usage (40h), */ +0x09, 0x41, /* Usage (41h), */ +0x09, 0x42, /* Usage (42h), */ +0x09, 0x43, /* Usage (43h), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x12, /* Logical Maximum (12), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x00, /* Output, */ +0xC0, /* End Collection, */ +0x09, 0x50, /* Usage (50h), */ +0x09, 0x54, /* Usage (54h), */ +0x09, 0x51, /* Usage (51h), */ +0x09, 0xA7, /* Usage (A7h), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ +0x66, 0x03, 0x10, /* Unit (Seconds), */ +0x55, 0xFD, /* Unit Exponent (-3), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x04, /* Report Count (4), */ +0x91, 0x02, /* Output (Variable), */ +0x55, 0x00, /* Unit Exponent (0), */ +0x66, 0x00, 0x00, /* Unit, */ +0x09, 0x52, /* Usage (52h), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0x64, 0x00, /* Logical Maximum (100), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x53, /* Usage (53h), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x55, /* Usage (55h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x0b, 0x30, 0x00, 0x01, 0x00, /* Usage (X), */ +0x0b, 0x31, 0x00, 0x01, 0x00, /* Usage (Y), */ +// 0x09, 0x32, /* Usage (Z), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x75, 0x01, /* Report Size (1), */ +0x95, 0x02, /* Report Count (2), */ +0x91, 0x02, /* Output (Variable), */ +0xC0, /* End Collection, */ +0x09, 0x56, /* Usage (56h), */ +0x75, 0x01, /* Report Size (1), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x75, 0x05, /* Report Size (5), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x03, /* Output (Constant, Variable), */ +0x09, 0x57, /* Usage (57h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x0B, 0x01, 0x00, 0x0A, 0x00, /* Usage (000A0001h), */ +0x0B, 0x02, 0x00, 0x0A, 0x00, /* Usage (000A0002h), */ +0x66, 0x14, 0x00, /* Unit (Degrees), */ +0x55, 0xFE, /* Unit Exponent (-2), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x27, 0x3C, 0x8C, 0x00, 0x00, /* Logical Maximum (35900), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x02, /* Report Count (2), */ +0x91, 0x02, /* Output (Variable), */ +0x55, 0x00, /* Unit Exponent (0), */ +0x66, 0x00, 0x00, /* Unit, */ +0xC0, /* End Collection, */ +0xC0, /* End Collection, */ +0x05, 0x0F, /* Usage Page (PID), */ +0x09, 0x5A, /* Usage (5Ah), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x12, /* Report ID (18), */ +0x09, 0x22, /* Usage (22h), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x28, /* Logical Maximum (40), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x5B, /* Usage (5Bh), */ +0x09, 0x5D, /* Usage (5Dh), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ +0x46, 0x10, 0x27, /* Physical Maximum (10000), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x02, /* Report Count (2), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x5C, /* Usage (5Ch), */ +0x09, 0x5E, /* Usage (5Eh), */ +0x66, 0x03, 0x10, /* Unit (Seconds), */ +0x55, 0xFD, /* Unit Exponent (-3), */ +0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x02, /* Report Count (2), */ +0x91, 0x02, /* Output (Variable), */ +0x45, 0x00, /* Physical Maximum (0), */ +0x66, 0x00, 0x00, /* Unit, */ +0x55, 0x00, /* Unit Exponent (0), */ +0xC0, /* End Collection, */ +0x09, 0x5F, /* Usage (5Fh), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x13, /* Report ID (19), */ +0x09, 0x22, /* Usage (22h), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x28, /* Logical Maximum (40), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x23, /* Usage (23h), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x60, /* Usage (60h), */ +0x09, 0x61, /* Usage (61h), */ +0x09, 0x62, /* Usage (62h), */ +0x16, 0x00, 0x80, /* Logical Minimum (-32767), */ +0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ +0x36, 0xF0, 0xD8, /* Physical Minimum (-10000), */ +0x46, 0x10, 0x27, /* Physical Maximum (10000), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x03, /* Report Count (3), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x63, /* Usage (63h), */ +0x09, 0x64, /* Usage (64h), */ +0x09, 0x65, /* Usage (65h), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x27, 0xFF, 0xFF, 0x00, 0x00, /* Logical Maximum (65535), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x46, 0x10, 0x27, /* Physical Maximum (10000), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x03, /* Report Count (3), */ +0x91, 0x02, /* Output (Variable), */ +0x45, 0x00, /* Physical Maximum (0), */ +0xC0, /* End Collection, */ +0x09, 0x6E, /* Usage (6Eh), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x1D, /* Report ID (29), */ +0x09, 0x22, /* Usage (22h), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x28, /* Logical Maximum (40), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x70, /* Usage (70h), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x46, 0x10, 0x27, /* Physical Maximum (10000), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x6F, /* Usage (6Fh), */ +0x16, 0x00, 0x80, /* Logical Minimum (-32767), */ +0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ +0x36, 0xF0, 0xD8, /* Physical Minimum (-10000), */ +0x46, 0x10, 0x27, /* Physical Maximum (10000), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x45, 0x00, /* Physical Maximum (0), */ +0x09, 0x71, /* Usage (71h), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x27, 0x3C, 0x8C, 0x00, 0x00, /* Logical Maximum (35900), */ +0x66, 0x14, 0x00, /* Unit (Degrees), */ +0x55, 0xFE, /* Unit Exponent (-2), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x72, /* Usage (72h), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ +0x66, 0x03, 0x10, /* Unit (Seconds), */ +0x55, 0xFD, /* Unit Exponent (-3), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x65, 0x00, /* Unit (None), */ +0x55, 0x00, /* Unit Exponent (0), */ +0xC0, /* End Collection, */ +0x09, 0x73, /* Usage (73h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x15, /* Report ID (21), */ +0x09, 0x22, /* Usage (22h), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x28, /* Logical Maximum (40), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x70, /* Usage (70h), */ +0x16, 0x00, 0x80, /* Logical Minimum (-32767), */ +0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ +0x36, 0xF0, 0xD8, /* Physical Minimum (-10000), */ +0x46, 0x10, 0x27, /* Physical Maximum (10000), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x45, 0x00, /* Physical Maximum (0), */ +0xC0, /* End Collection, */ +0x05, 0x0F, /* Usage Page (PID), */ +0x09, 0x77, /* Usage (77h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x1A, /* Report ID (26), */ +0x09, 0x22, /* Usage (22h), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x28, /* Logical Maximum (40), */ +0x35, 0x01, /* Physical Minimum (1), */ +0x45, 0x28, /* Physical Maximum (40), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x45, 0x00, /* Physical Maximum (0), */ +0x09, 0x78, /* Usage (78h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x09, 0x79, /* Usage (79h), */ +0x09, 0x7A, /* Usage (7Ah), */ +0x09, 0x7B, /* Usage (7Bh), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x03, /* Logical Maximum (3), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x00, /* Output, */ +0xC0, /* End Collection, */ +0x09, 0x7C, /* Usage (7Ch), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x91, 0x02, /* Output (Variable), */ +0x45, 0x00, /* Physical Maximum (0), */ +0xC0, /* End Collection, */ +#ifdef BLOCK_FREE_REPORT +0x09, 0x90, /* Usage (90h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x1B, /* Report ID (27), */ +0x09, 0x22, /* Usage (22h), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x28, /* Logical Maximum (40), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0xC0, /* End Collection, */ +#endif +0x09, 0x96, /* Usage (96h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x10, /* Report ID (16), */ +0x09, 0x97, /* Usage (97h), */ +0x09, 0x98, /* Usage (98h), */ +0x09, 0x99, /* Usage (99h), */ +0x09, 0x9A, /* Usage (9Ah), */ +0x09, 0x9B, /* Usage (9Bh), */ +0x09, 0x9C, /* Usage (9Ch), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x06, /* Logical Maximum (6), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x00, /* Output, */ +0xC0, /* End Collection, */ +#ifdef CREATE_NEW_EFFECT_REPORT +0x09, 0xAB, /* Usage (ABh), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x14, /* Report ID (20), */ +0x09, 0x25, /* Usage (25h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x09, 0x26, /* Usage (26h), */ +0x09, 0x27, /* Usage (27h), */ +0x09, 0x28, /* Usage (28h), */ +0x09, 0x30, /* Usage (30h), */ +0x09, 0x31, /* Usage (31h), */ +0x09, 0x32, /* Usage (32h), */ +0x09, 0x33, /* Usage (33h), */ +0x09, 0x34, /* Usage (34h), */ +0x09, 0x40, /* Usage (40h), */ +0x09, 0x41, /* Usage (41h), */ +0x09, 0x42, /* Usage (42h), */ +0x09, 0x43, /* Usage (43h), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x12, /* Logical Maximum (12), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0xB1, 0x00, /* Feature, */ +0xC0, /* End Collection, */ +0x05, 0x01, /* Usage Page (Desktop), */ +0x09, 0x3B, /* Usage (Byte Count), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x01, /* Logical Maximum (511), */ +0x46, 0xFF, 0x01, /* Physical Maximum (511), */ +0x75, 0x0A, /* Report Size (10), */ +0x95, 0x01, /* Report Count (1), */ +0xB1, 0x02, /* Feature (Variable), */ +0x75, 0x06, /* Report Size (6), */ +0xB1, 0x01, /* Feature (Constant), */ +0x45, 0x00, /* Physical Maximum (0), */ +0xC0, /* End Collection, */ +#endif +#ifdef BLOCK_LOAD_REPORT +0x05, 0x0F, /* Usage Page (PID), */ +0x09, 0x89, /* Usage (89h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x16, /* Report ID (22), */ +0x09, 0x22, /* Usage (22h), */ +0x25, 0x28, /* Logical Maximum (40), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x35, 0x01, /* Physical Minimum (1), */ +0x45, 0x28, /* Physical Maximum (40), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0xB1, 0x02, /* Feature (Variable), */ +0x09, 0x8B, /* Usage (8Bh), */ +0xA1, 0x02, /* Collection (Logical), */ +0x09, 0x8C, /* Usage (8Ch), */ +0x09, 0x8D, /* Usage (8Dh), */ +0x09, 0x8E, /* Usage (8Eh), */ +0x25, 0x03, /* Logical Maximum (3), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x35, 0x01, /* Physical Minimum (1), */ +0x45, 0x03, /* Physical Maximum (3), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0xB1, 0x00, /* Feature, */ +0xC0, /* End Collection, */ +0x09, 0xAC, /* Usage (ACh), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x27, 0xFF, 0xFF, 0x00, 0x00, /* Logical Maximum (65535), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x47, 0xFF, 0xFF, 0x00, 0x00, /* Physical Maximum (65535), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x01, /* Report Count (1), */ +0xB1, 0x00, /* Feature, */ +0x45, 0x00, /* Physical Maximum (0), */ +0xC0, /* End Collection, */ +#endif +#ifdef DEVICE_POOL_REPORT +0x09, 0x7F, /* Usage (7Fh), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x17, /* Report ID (23), */ +0x09, 0x80, /* Usage (80h), */ +0x75, 0x10, /* Report Size (16), */ +0x95, 0x01, /* Report Count (1), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x27, 0xFF, 0xFF, 0x00, 0x00, /* Logical Maximum (65535), */ +0xB1, 0x02, /* Feature (Variable), */ +0x09, 0x83, /* Usage (83h), */ +0x26, 0xFF, 0x00, /* Logical Maximum (40), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0xB1, 0x02, /* Feature (Variable), */ +0x09, 0xA9, /* Usage (A9h), */ +0x09, 0xAA, /* Usage (AAh), */ +0x75, 0x01, /* Report Size (1), */ +0x95, 0x02, /* Report Count (2), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x25, 0x01, /* Logical Maximum (1), */ +0xB1, 0x02, /* Feature (Variable), */ +0x75, 0x06, /* Report Size (6), */ +0x95, 0x01, /* Report Count (1), */ +0xB1, 0x03, /* Feature (Constant, Variable), */ +0xC0, /* End Collection, */ +#endif +0x09, 0x7D, /* Usage (7Dh), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x19, /* Report ID (25), */ +0x09, 0x7E, /* Usage (7Eh), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0xC0, /* End Collection, */ +0x09, 0x74, /* Usage (74h), */ +0xA1, 0x02, /* Collection (Logical), */ +0x85, 0x18, /* Report ID (24), */ +0x09, 0x22, /* Usage (22h), */ +0x15, 0x01, /* Logical Minimum (1), */ +0x25, 0x28, /* Logical Maximum (40), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x91, 0x02, /* Output (Variable), */ +0x09, 0x75, /* Usage (75h), */ +0x09, 0x76, /* Usage (76h), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (2), */ +0x91, 0x02, /* Output (Variable), */ +0xC0, /* End Collection, */ diff --git a/drivers/custom/fanatecff/hid-ftec.c b/drivers/custom/fanatecff/hid-ftec.c new file mode 100644 index 000000000000..328873a61165 --- /dev/null +++ b/drivers/custom/fanatecff/hid-ftec.c @@ -0,0 +1,928 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ftec.h" + +#define BTN_RANGE (BTN_9 - BTN_0 + 1) +#define JOY_RANGE (BTN_DEAD - BTN_JOYSTICK + 1) + +// adjustabel initial value for break load cell +int init_load = 4; +module_param(init_load, int, 0); +// expose PID HID descriptor via hidraw +bool hidraw_pid = true; +module_param(hidraw_pid, bool, 0); + + +static u8 ftec_get_load(struct hid_device *hid) +{ + struct list_head *report_list = &hid->report_enum[HID_INPUT_REPORT].report_list; + struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + struct ftec_drv_data *drv_data; + unsigned long flags; + s32 *value; + + dbg_hid(" ... get_load; %i\n", hid_report_len(report)); + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return -EINVAL; + } + + value = drv_data->report->field[0]->value; + + spin_lock_irqsave(&drv_data->report_lock, flags); + value[0] = 0xf8; + value[1] = 0x09; + value[2] = 0x01; + value[3] = 0x06; + value[4] = 0x00; + value[5] = 0x00; + value[6] = 0x00; + + hid_hw_request(hid, drv_data->report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&drv_data->report_lock, flags); + + hid_hw_request(hid, report, HID_REQ_GET_REPORT); + hid_hw_wait(hid); + + return report->field[report->maxfield-1]->value[4]; +} + +static void ftec_set_load(struct hid_device *hid, u8 val) +{ + struct ftec_drv_data *drv_data; + unsigned long flags; + s32 *value; + + dbg_hid(" ... set_load %02X\n", val); + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return; + } + + value = drv_data->report->field[0]->value; + + spin_lock_irqsave(&drv_data->report_lock, flags); + value[0] = 0xf8; + value[1] = 0x09; + value[2] = 0x01; + value[3] = 0x16; + value[4] = val+1; // actual value has an offset of 1 + value[5] = 0x00; + value[6] = 0x00; + + hid_hw_request(hid, drv_data->report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&drv_data->report_lock, flags); +} + +static void ftec_set_rumble(struct hid_device *hid, u32 val) +{ + struct ftec_drv_data *drv_data; + unsigned long flags; + s32 *value; + int i; + + dbg_hid(" ... set_rumble %02X\n", val); + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return; + } + + value = drv_data->report->field[0]->value; + + spin_lock_irqsave(&drv_data->report_lock, flags); + value[0] = 0xf8; + value[1] = 0x09; + value[2] = 0x01; + value[3] = drv_data->quirks & FTEC_PEDALS ? 0x04 : 0x03; + value[4] = (val>>16)&0xff; + value[5] = (val>>8)&0xff; + value[6] = (val)&0xff; + + // TODO: see ftecff.c::fix_values + if (!(drv_data->quirks & FTEC_PEDALS)) { + for(i=0;i<7;i++) { + if (value[i]>=0x80) + value[i] = -0x100 + value[i]; + } + } + + hid_hw_request(hid, drv_data->report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&drv_data->report_lock, flags); +} + +static ssize_t ftec_load_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct ftec_drv_data *drv_data; + size_t count; + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return 0; + } + + count = scnprintf(buf, PAGE_SIZE, "%u\n", ftec_get_load(hid) - 1); + return count; +} + +static ssize_t ftec_load_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + u8 load; + if (kstrtou8(buf, 0, &load) == 0) { + ftec_set_load(hid, load); + } + return count; +} +static DEVICE_ATTR(load, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, ftec_load_show, ftec_load_store); + +static ssize_t ftec_rumble_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + u32 rumble; + if (kstrtou32(buf, 0, &rumble) == 0) { + ftec_set_rumble(hid, rumble); + } + return count; +} +static DEVICE_ATTR(rumble, S_IWUSR | S_IWGRP, NULL, ftec_rumble_store); + +static int ftec_init(struct hid_device *hdev) { + struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list; + struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + struct ftec_drv_data *drv_data; + + dbg_hid(" ... %i %i %i %i\n%i %i %i %i\n\n", + report->id, report->type, // report->application, + report->maxfield, report->size, + report->field[0]->logical_minimum,report->field[0]->logical_maximum, + report->field[0]->physical_minimum,report->field[0]->physical_maximum + ); + + /* Check that the report looks ok */ + if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 7)) + return -1; + + drv_data = hid_get_drvdata(hdev); + if (!drv_data) { + hid_err(hdev, "Cannot add device, private driver data not allocated\n"); + return -1; + } + + drv_data->report = report; + drv_data->hid = hdev; + spin_lock_init(&drv_data->report_lock); + return 0; +} + + +#define PID_REPORT_STATE 16 // usage 0x92 (input) +#define PID_REPORT_DEVICE_CONTROL 16 // usage 0x96 +#define PID_REPORT_SET_EFFECT 17 // usage 0x21 +#define PID_REPORT_SET_ENVELOPE 18 // usage 0x5a +#define PID_REPORT_SET_CONDITION 19 // usage 0x5f +#define PID_REPORT_CREATE_NEW_EFFECT 20 // usage 0xab +#define PID_REPORT_SET_CONSTANT_FORCE 21 // usage 0x73 +#define PID_REPORT_BLOCK_LOAD 22 // usage 0x89 (input) +#define PID_REPORT_PID_POOL 23 // usage 0x7f +#define PID_REPORT_SET_RAMP_FORCE 24 // usage 0x74 +#define PID_REPORT_DEVICE_GAIN 25 // usage 0x7d +#define PID_REPORT_EFFECT_OPERATION 26 // usage 0x77 +#define PID_REPORT_BLOCK_FREE 27 // usage 0x90 +#define PID_REPORT_SET_PERIODIC 29 // usage 0x6e + +const u8 rdesc_pid_ffb[] = { +#include "hid-ftec-pid.h" +}; + +static int ftec_client_rdesc_fixup(struct ftec_drv_data_client *client, const u8 *dev_rdesc, size_t dev_rsize) +{ + const u8 *rdesc = client->rdesc, *ref_pos, *ref_end, *end; + u8 *pos; + unsigned depth = 0; + u8 size, report_id = 255; + + for (ref_pos = dev_rdesc, + ref_end = dev_rdesc + dev_rsize, + pos = (u8*)rdesc, end = rdesc + sizeof(client->rdesc); + ref_pos != ref_end && pos != end; + ref_pos += size + 1, pos += size + 1) { + + if (*ref_pos == 0xC0 && --depth == 0 && report_id == 1) { + // inject the pid ffb collection + if (pos + sizeof(rdesc_pid_ffb) > end) { + hid_err(client->hdev, "Need %lu bytes to inject ffb\n", sizeof(rdesc_pid_ffb) ); + return 1; + } + memcpy(pos, rdesc_pid_ffb, sizeof(rdesc_pid_ffb)); + pos += sizeof(rdesc_pid_ffb); + } + + size = (*ref_pos & 0x03); + if (size == 3) size = 4; + if (ref_pos + size > ref_end || pos + size > end) + { + hid_err(client->hdev, "Need %d bytes to read item value\n", size ); + return 1; + } + + memcpy(pos, ref_pos, size + 1); + + if (*ref_pos == 0x85) + report_id = *(ref_pos + 1); + if (*ref_pos == 0xA1) + ++depth; + } + client->rsize = pos - rdesc; + return 0; +} + +static int ftec_client_ll_parse(struct hid_device *hdev) +{ + struct ftec_drv_data *drv_data = hdev->driver_data; + int ret; + if ((ret = ftec_client_rdesc_fixup( + &drv_data->client, + drv_data->hid->dev_rdesc, + drv_data->hid->dev_rsize))) + return ret; + return hid_parse_report(hdev, drv_data->client.rdesc, drv_data->client.rsize); +} + +static int ftec_client_ll_start(struct hid_device *hdev) +{ + return 0; +} + +static void ftec_client_ll_stop(struct hid_device *hdev) +{ +} + +static int ftec_client_ll_open(struct hid_device *hdev) +{ + struct ftec_drv_data *drv_data = hdev->driver_data; + drv_data->client.opened++; + return 0; +} + +static void ftec_client_ll_close(struct hid_device *hdev) +{ + struct ftec_drv_data *drv_data = hdev->driver_data; + drv_data->client.opened--; +} + +#ifdef DEVICE_MANAGED +struct __attribute__((packed)) create_new_effect { + u8 report_id; + u8 type_idx; + u16 byte_count; +}; + +struct __attribute__((packed)) block_load { + u8 report_id; + u8 id; + u8 status; + u16 ram_pool_avail; +}; + +struct __attribute__((packed)) block_free { + u8 report_id; + u8 id; +}; +#endif + +struct __attribute__((packed)) set_constant { + u8 report_id; + u8 id; + s16 level; +}; + +struct __attribute__((packed)) set_envelope { + u8 report_id; + u8 id; + u16 attack_level; + u16 fade_level; + u16 attack_time; + u16 fade_time; +}; + +struct __attribute__((packed)) set_condition { + u8 report_id; + u8 id; + u8 block_offset; + s16 offset; + s16 positive_coeff; + s16 negative_coeff; + u16 positive_saturation; + u16 negative_saturation; + u16 dead_band; +}; + +struct __attribute__((packed)) set_periodic { + u8 report_id; + u8 id; + u16 magnitude; + s16 offset; + u16 phase; + u16 period; +}; + +struct __attribute__((packed)) set_effect { + u8 report_id; + u8 id; + u8 type_idx; + u16 duration; + u16 trigger_repeat_interval; + u16 sample_period; + u16 start_delay; + u8 gain; + u8 button; + u8 enable; + u16 direction[2]; +}; + +struct __attribute__((packed)) effect_operation { + u8 report_id; + u8 id; + u8 op; + u8 count; +}; + +struct __attribute__((packed)) device_control { + u8 report_id; + u8 ctrl; +}; + + +static int check_idx(struct ftec_drv_data *drv_data, s16 idx, unsigned char reportnum) { + if (idx < 0 || idx >= ARRAY_SIZE(drv_data->client.effects)) { + hid_err(drv_data->hid, "Report %u: Invalid index %i\n", reportnum, idx); + return 0; + } + return 1; +} + +static struct ff_effect *get_effect(struct ftec_drv_data *drv_data, size_t id, unsigned char reportnum) { + if (!check_idx(drv_data, id - 1, reportnum)) + return NULL; + return &drv_data->client.effects[id - 1]; +} + +static void ftec_client_report_state(struct hid_device *hdev, u8 device_state, u8 effect_state, u8 effect_id) { + u8 buf[3]; + buf[0] = PID_REPORT_STATE; + buf[1] = device_state | effect_state; + buf[2] = effect_id; + hidraw_report_event(hdev, buf, sizeof(buf)); +} + +static int ftec_client_ll_raw_request(struct hid_device *hdev, + unsigned char reportnum, u8 *buf, + size_t count, unsigned char report_type, + int reqtype) +{ + struct ftec_drv_data *drv_data = hdev->driver_data; + struct hid_input *hidinput = list_entry(drv_data->hid->inputs.next, struct hid_input, list); + struct input_dev *inputdev = hidinput->input; + struct ff_device *ff = hidinput->input->ff; + // supported effects, have to be in order as listed in usage 21 !! + const u8 ff_effects[] = { + FF_CONSTANT, + FF_RAMP, + FF_CUSTOM, + FF_SQUARE, + FF_SINE, + FF_TRIANGLE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_SPRING, + FF_DAMPER, + FF_INERTIA, + FF_FRICTION, + }; + + if (reportnum <= 2 || reportnum == 255) { + // forward these reports directly to the device + return hid_hw_output_report(drv_data->hid, buf, count); + } + + if (0) { + printk(KERN_CONT "ftec_client_ll_raw_request num %2u, count %2lu, rep type %#02x, req type %#02x, data ", reportnum, count, report_type, reqtype); + // only print set reports + if (reqtype == 0x9) { + for (int i = 0; i < count; ++i) + printk(KERN_CONT " %#02x", buf[i]); + } + printk(KERN_CONT "\n"); + } + +#define map_pid_type( __type) \ + __type *params; \ + if (sizeof(__type) != count) { \ + hid_err(hdev, "Report %u: Invalid report size %lu, expected %lu", reportnum, count, sizeof(__type)); \ + return 0; \ + } \ + params = (__type*)buf; + +#define get_effect \ + struct ff_effect *effect; \ + if (!(effect = get_effect(drv_data, params->id, reportnum))) \ + return 0; \ + + switch (reportnum) { +#ifdef DEVICE_MANAGED + case PID_REPORT_CREATE_NEW_EFFECT: { + map_pid_type(struct create_new_effect); + size_t type_idx = params->type_idx - 1; + if (drv_data->client.current_id == ARRAY_SIZE(drv_data->client.effects)) { + hid_err(hdev, "can't find free slot for new effect"); + return 0; + } + effect = &drv_data->client.effects[drv_data->client.current_id]; + if (ff_effects[type_idx] == FF_SINE) { + effect->type = FF_PERIODIC; + effect->u.periodic.waveform = ff_effects[type_idx]; + } else { + effect->type = ff_effects[type_idx]; + } + break; + } + case PID_REPORT_BLOCK_LOAD: { + map_pid_type(struct block_load); + params->report_id = reportnum; + if (drv_data->client.current_id == ARRAY_SIZE(drv_data->client.effects)) { + params->id = 0x00; + params->status = 0x02; + } else { + drv_data->client.current_id++; + params->id = drv_data->client.current_id; + params->status = 0x01; // load status: 1 success, 2 full, 3 error + } + params->ram_pool_avail = 0xffff; + break; + } + case PID_REPORT_BLOCK_FREE: { + map_pid_type(struct block_free); + if (!(effect = get_effect(drv_data, params->id, reportnum))) + return 0; + (void)ff->playback(inputdev, effect->id, 0); + memset(effect, 0, sizeof(struct ff_effect)); + break; + } +#endif + case PID_REPORT_SET_CONSTANT_FORCE: { + map_pid_type(struct set_constant); + get_effect + effect->u.constant.level = params->level; + if (effect->id) + (void)ff->upload(inputdev, effect, NULL); + break; + } + case PID_REPORT_SET_ENVELOPE: { + struct ff_envelope *envelope; + map_pid_type(struct set_envelope); + get_effect + switch (effect->type) { + case FF_CONSTANT: + envelope = &effect->u.constant.envelope; + break; + case FF_RAMP: + envelope = &effect->u.ramp.envelope; + break; + case FF_PERIODIC: + envelope = &effect->u.periodic.envelope; + break; + default: + hid_err(hdev, "invalid effect type for envelope: %u", effect->type); + return 0; + } + envelope->fade_level = params->fade_level; + envelope->fade_length = params->fade_time; + envelope->attack_level = params->attack_level; + envelope->attack_length = params->attack_time; + if (effect->id) + (void)ff->upload(inputdev, effect, NULL); + break; + } + case PID_REPORT_SET_CONDITION: { + map_pid_type(struct set_condition); + get_effect + struct ff_condition_effect *condition = &effect->u.condition[params->block_offset&0x1]; + condition->center = params->offset; + condition->right_coeff = params->positive_coeff; + condition->left_coeff = params->negative_coeff; + condition->right_saturation = params->positive_saturation; + condition->left_saturation = params->negative_saturation; + condition->deadband = params->dead_band; + if (effect->id) + (void)ff->upload(inputdev, effect, NULL); + break; + } + case PID_REPORT_SET_PERIODIC: { + map_pid_type(struct set_periodic); + get_effect + effect->u.periodic.period = params->period; + // FIXME: what's this all about ?? + if (effect->u.periodic.period != 0) { + effect->u.periodic.magnitude = params->magnitude; + effect->u.periodic.offset = params->offset; + effect->u.periodic.phase = params->phase * 0x10000 / 36000; + } else { + effect->type = FF_CONSTANT; + effect->u.constant.level = params->magnitude; + } + if (effect->id) + (void)ff->upload(inputdev, effect, NULL); + break; + } + case PID_REPORT_DEVICE_GAIN: { + hid_dbg(hdev, "gain not implemented"); + break; + } + case PID_REPORT_SET_EFFECT: { + map_pid_type(struct set_effect); + get_effect + if (!effect->type) { + size_t type_idx = params->type_idx - 1; + if (ff_effects[type_idx] == FF_SINE) { + effect->type = FF_PERIODIC; + effect->u.periodic.waveform = ff_effects[type_idx]; + } else { + effect->type = ff_effects[type_idx]; + } + } + effect->replay.length = params->duration == 0xffff ? 0 : params->duration; + effect->replay.delay = params->start_delay; + effect->trigger.interval = params->trigger_repeat_interval; + // buf[9]: gain + effect->trigger.button = params->button; + + // FIXME: default effect direction + effect->direction = 0x4000; + // Note: needs to be aligned to Usage 0x55/0x56 + if (params->enable & 0x1) { + effect->direction = params->direction[0] * 0x10000 / 36000; + } + if (effect->id) + (void)ff->upload(inputdev, effect, NULL); + break; + } + case PID_REPORT_EFFECT_OPERATION: { + map_pid_type(struct effect_operation); + get_effect + + if (effect->type == 0) { + hid_err(hdev, "Invalid type %u\n", effect->type); + return 0; + } + + // Effect Operation: Op Effect Start/Start Solo/Stop + if (params->op == 0x3 || params->count == 0) { + (void)ff->playback(inputdev, effect->id, 0); + ftec_client_report_state(drv_data->client.hdev, 0x2, 0x0, effect->id); + effect->id = 0; + break; + } else { + if (params->op == 0x2) { + // stop all other effects + struct ff_effect *other_effect; + for (size_t i = 0; i < ARRAY_SIZE(drv_data->client.effects); ++i) { + other_effect = &drv_data->client.effects[i]; + if (other_effect->id > 0 && other_effect != effect) { + (void)ff->playback(inputdev, other_effect->id, 0); + ftec_client_report_state(drv_data->client.hdev, 0x2, 0x0, other_effect->id); + other_effect->id = 0; + } + } + + } + if (params->op == 0x1 || params->op == 0x2) { + if (!effect->id) { + effect->id = params->id; + (void)ff->upload(inputdev, effect, NULL); + ftec_client_report_state(drv_data->client.hdev, 0x2, 0x4, effect->id); + } + } + } + + (void)ff->playback(inputdev, effect->id, params->count); + break; + } + case PID_REPORT_DEVICE_CONTROL: { + map_pid_type(struct device_control); + if (params->ctrl == 0x4) { + // reset: stop and unload all effects + for (size_t i = 0; i < ARRAY_SIZE(drv_data->client.effects); ++i) { + if (drv_data->client.effects[i].id) { + (void)ff->playback(inputdev, drv_data->client.effects[i].id, 0); + ftec_client_report_state(drv_data->client.hdev, 0x2, 0x0, drv_data->client.effects[i].id); + } + memset(&drv_data->client.effects[i], 0, sizeof(struct ff_effect)); + } + drv_data->client.current_id = 0; + } + break; + } + default: + hid_err(hdev, "Not implemented report %u\n", reportnum); + } + return count; +} + +static const struct hid_ll_driver ftec_client_ll_driver = { + .parse = ftec_client_ll_parse, + .start = ftec_client_ll_start, + .stop = ftec_client_ll_stop, + .open = ftec_client_ll_open, + .close = ftec_client_ll_close, + .raw_request = ftec_client_ll_raw_request, +}; + +static struct hid_device *ftec_create_client_hid(struct hid_device *hdev) +{ + struct hid_device *client_hdev; + + client_hdev = hid_allocate_device(); + if (IS_ERR(client_hdev)) + return client_hdev; + + client_hdev->ll_driver = &ftec_client_ll_driver; + client_hdev->dev.parent = hdev->dev.parent; + client_hdev->bus = hdev->bus; + client_hdev->vendor = hdev->vendor; + client_hdev->product = hdev->product; + client_hdev->version = hdev->version; + client_hdev->type = hdev->type; + client_hdev->country = hdev->country; + strscpy(client_hdev->name, hdev->name, + sizeof(client_hdev->name)); + strscpy(client_hdev->phys, hdev->phys, + sizeof(client_hdev->phys)); + /* + * Since we use the same device info than the real interface to + * trick userspace, we will be calling ftec_probe recursively. + * We need to recognize the client interface somehow. + * Note: this technique is 'stolen' from hid-steam + */ + client_hdev->group = HID_GROUP_STEAM; + return client_hdev; +} + +static int ftec_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct usb_interface *iface = to_usb_interface(hdev->dev.parent); + __u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber; + unsigned int connect_mask = HID_CONNECT_HIDINPUT | HID_CONNECT_HIDDEV; + struct ftec_drv_data *drv_data; + int ret; + + dbg_hid("%s: ifnum %d\n", __func__, iface_num); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "%s:parse of hid interface failed\n", __func__); + return ret; + } + + /* + * The virtual client_dev is only used for hidraw. + * Also avoid the recursive probe. + * Note: this technique is 'stolen' from hid-steam + */ + if (hdev->group == HID_GROUP_STEAM) { + return hid_hw_start(hdev, HID_CONNECT_HIDRAW); + } + + drv_data = kzalloc(sizeof(struct ftec_drv_data), GFP_KERNEL); + if (!drv_data) { + hid_err(hdev, "Insufficient memory, cannot allocate driver data\n"); + return -ENOMEM; + } + memset(&drv_data->client, 0, sizeof(drv_data->client)); + drv_data->quirks = id->driver_data; + drv_data->min_range = 90; + drv_data->max_range = 1090; // technically max_range is 1080, but 1090 is used as 'auto' + if (hdev->product == CLUBSPORT_V2_WHEELBASE_DEVICE_ID || + hdev->product == CLUBSPORT_V25_WHEELBASE_DEVICE_ID || + hdev->product == CSR_ELITE_WHEELBASE_DEVICE_ID) { + drv_data->max_range = 900; + } else if (hdev->product == PODIUM_WHEELBASE_DD1_DEVICE_ID || + hdev->product == PODIUM_WHEELBASE_DD2_DEVICE_ID || + hdev->product == CSL_DD_WHEELBASE_DEVICE_ID) { + drv_data->max_range = 2530; // technically max_range is 2520, but 2530 is used as 'auto' + } + + hid_set_drvdata(hdev, (void *)drv_data); + + if (drv_data->quirks & FTEC_PEDALS || !hidraw_pid) { + connect_mask |= HID_CONNECT_HIDRAW; + } + + if (drv_data->quirks & FTEC_FF) { + connect_mask |= HID_CONNECT_FF; + } + + ret = hid_hw_start(hdev, connect_mask); + if (ret) { + hid_err(hdev, "hw start failed\n"); + goto err_free; + } + + ret = ftec_init(hdev); + if (ret) { + hid_err(hdev, "hw init failed\n"); + goto err_stop; + } + + if (drv_data->quirks & FTEC_TUNING_MENU || hidraw_pid) { + /* Open the device to receive reports with tuning menu data and device state */ + ret = hid_hw_open(hdev); + if (ret < 0) { + hid_err(hdev, "hw open failed\n"); + goto err_client; + } + } + + if (drv_data->quirks & FTEC_FF && hidraw_pid) { + drv_data->client.hdev = ftec_create_client_hid(hdev); + if (IS_ERR(drv_data->client.hdev)) { + ret = PTR_ERR(drv_data->client.hdev); + goto err_stop; + } + drv_data->client.hdev->driver_data = drv_data; + + ret = hid_add_device(drv_data->client.hdev); + if (ret) + goto err_client; + } + + if (drv_data->quirks & FTEC_FF) { + ret = ftecff_init(hdev); + if (ret) { + hid_err(hdev, "ff init failed\n"); + goto err_client; + } + } + + if (hdev->product == CSL_ELITE_WHEELBASE_DEVICE_ID || + hdev->product == CSL_ELITE_PS4_WHEELBASE_DEVICE_ID || + hdev->product == CLUBSPORT_PEDALS_V3_DEVICE_ID) { + ret = device_create_file(&hdev->dev, &dev_attr_rumble); + if (ret) + hid_warn(hdev, "Unable to create sysfs interface for \"rumble\", errno %d\n", ret); + } + + if (drv_data->quirks & FTEC_PEDALS) { + struct hid_input *hidinput = list_entry(hdev->inputs.next, struct hid_input, list); + struct input_dev *inputdev = hidinput->input; + + // if these bits are not set, the pedals are not recognized in newer proton/wine verisons + set_bit(EV_KEY, inputdev->evbit); + set_bit(BTN_WHEEL, inputdev->keybit); + + if (init_load >= 0 && init_load < 10) { + ftec_set_load(hdev, init_load); + } + + ret = device_create_file(&hdev->dev, &dev_attr_load); + if (ret) + hid_warn(hdev, "Unable to create sysfs interface for \"load\", errno %d\n", ret); + } + + return 0; + +err_client: + if (drv_data->client.hdev) + hid_destroy_device(drv_data->client.hdev); +err_stop: + hid_hw_stop(hdev); +err_free: + kfree(drv_data); + return ret; +} + +static void ftec_remove(struct hid_device *hdev) +{ + struct ftec_drv_data *drv_data = hid_get_drvdata(hdev); + + if (!drv_data || hdev->group == HID_GROUP_STEAM) { + hid_hw_stop(hdev); + return; + } + + if (drv_data->client.hdev) { + hid_destroy_device(drv_data->client.hdev); + drv_data->client.hdev = NULL; + } + + if (drv_data->quirks & FTEC_PEDALS) { + device_remove_file(&hdev->dev, &dev_attr_load); + } + + if (hdev->product == CSL_ELITE_WHEELBASE_DEVICE_ID || + hdev->product == CSL_ELITE_PS4_WHEELBASE_DEVICE_ID || + hdev->product == CLUBSPORT_PEDALS_V3_DEVICE_ID) { + device_remove_file(&hdev->dev, &dev_attr_rumble); + } + + if (drv_data->quirks & FTEC_FF) { + ftecff_remove(hdev); + } + + hid_hw_close(hdev); + hid_hw_stop(hdev); + kfree(drv_data); +} + +static int ftec_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { + struct ftec_drv_data *drv_data = hid_get_drvdata(hdev); + // printk("ftec_raw_event %#02x %i; client opened: %i\n", report->id, size, drv_data->client.opened); + if (drv_data->client.opened) { + hidraw_report_event(drv_data->client.hdev, data, size); + } + if (drv_data->quirks & FTEC_FF) { + ftecff_raw_event(hdev, report, data, size); + } + return 0; +} + +/* + * Map buttons manually to extend the default joystick buttn limit + */ +static int ftec_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + // Let the default behavior handle mapping if usage is not a button + if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON) + return 0; + + int button = ((usage->hid - 1) & HID_USAGE); + int code = button + BTN_0; + + // Detect the end of BTN_* range + if (code > BTN_9) + code = button + BTN_JOYSTICK - BTN_RANGE; + + // Detect the end of JOYSTICK buttons range + if (code > BTN_DEAD) + code = button + KEY_MACRO1 - BTN_RANGE - JOY_RANGE; + + // Map overflowing buttons to KEY_RESERVED for the upcoming new input event + // It will handle button presses differently and won't depend on defined + // ranges. KEY_RESERVED usage is needed for the button to not be ignored. + if (code > KEY_MAX) + code = KEY_RESERVED; + + hid_map_usage(hi, usage, bit, max, EV_KEY, code); + hid_dbg(hdev, "Button %d: usage %d", button, code); + return 1; +} + +static const struct hid_device_id devices[] = { + { HID_USB_DEVICE(FANATEC_VENDOR_ID, CLUBSPORT_V2_WHEELBASE_DEVICE_ID), .driver_data = FTEC_FF }, + { HID_USB_DEVICE(FANATEC_VENDOR_ID, CLUBSPORT_V25_WHEELBASE_DEVICE_ID), .driver_data = FTEC_FF }, + { HID_USB_DEVICE(FANATEC_VENDOR_ID, CLUBSPORT_PEDALS_V3_DEVICE_ID), .driver_data = FTEC_PEDALS }, + { HID_USB_DEVICE(FANATEC_VENDOR_ID, CSL_ELITE_WHEELBASE_DEVICE_ID), .driver_data = FTEC_FF | FTEC_TUNING_MENU | FTEC_WHEELBASE_LEDS}, + { HID_USB_DEVICE(FANATEC_VENDOR_ID, CSL_ELITE_PS4_WHEELBASE_DEVICE_ID), .driver_data = FTEC_FF | FTEC_TUNING_MENU | FTEC_WHEELBASE_LEDS}, + { HID_USB_DEVICE(FANATEC_VENDOR_ID, CSL_ELITE_PEDALS_DEVICE_ID), .driver_data = FTEC_PEDALS }, + { HID_USB_DEVICE(FANATEC_VENDOR_ID, CSL_LC_PEDALS_DEVICE_ID), .driver_data = FTEC_PEDALS }, + { HID_USB_DEVICE(FANATEC_VENDOR_ID, CSL_LC_V2_PEDALS_DEVICE_ID), .driver_data = FTEC_PEDALS }, + { HID_USB_DEVICE(FANATEC_VENDOR_ID, PODIUM_WHEELBASE_DD1_DEVICE_ID), .driver_data = FTEC_FF | FTEC_TUNING_MENU | FTEC_HIGHRES }, + { HID_USB_DEVICE(FANATEC_VENDOR_ID, PODIUM_WHEELBASE_DD2_DEVICE_ID), .driver_data = FTEC_FF | FTEC_TUNING_MENU | FTEC_HIGHRES }, + { HID_USB_DEVICE(FANATEC_VENDOR_ID, CSL_DD_WHEELBASE_DEVICE_ID), .driver_data = FTEC_FF | FTEC_TUNING_MENU | FTEC_HIGHRES }, + { HID_USB_DEVICE(FANATEC_VENDOR_ID, CSR_ELITE_WHEELBASE_DEVICE_ID), .driver_data = FTEC_FF }, + { } +}; + +MODULE_DEVICE_TABLE(hid, devices); + +static struct hid_driver fanatec_driver = { + .name = "fanatec", + .id_table = devices, + .input_mapping = ftec_input_mapping, + .probe = ftec_probe, + .remove = ftec_remove, + .raw_event = ftec_raw_event, +}; +module_hid_driver(fanatec_driver) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("gotzl"); +MODULE_DESCRIPTION("A driver for the Fanatec CSL Elite"); diff --git a/drivers/custom/fanatecff/hid-ftec.h b/drivers/custom/fanatecff/hid-ftec.h new file mode 100644 index 000000000000..9e131bc4338b --- /dev/null +++ b/drivers/custom/fanatecff/hid-ftec.h @@ -0,0 +1,161 @@ +#ifndef __HID_FTEC_H +#define __HID_FTEC_H + +#include +#include + +#define FANATEC_VENDOR_ID 0x0eb7 + +// wheelbases +#define CLUBSPORT_V2_WHEELBASE_DEVICE_ID 0x0001 +#define CLUBSPORT_V25_WHEELBASE_DEVICE_ID 0x0004 +#define CLUBSPORT_PEDALS_V3_DEVICE_ID 0x183b +#define PODIUM_WHEELBASE_DD1_DEVICE_ID 0x0006 +#define PODIUM_WHEELBASE_DD2_DEVICE_ID 0x0007 +#define CSL_ELITE_WHEELBASE_DEVICE_ID 0x0E03 +#define CSL_ELITE_PS4_WHEELBASE_DEVICE_ID 0x0005 +#define CSL_ELITE_PEDALS_DEVICE_ID 0x6204 +#define CSL_LC_PEDALS_DEVICE_ID 0x6205 +#define CSL_LC_V2_PEDALS_DEVICE_ID 0x6206 +#define CSL_DD_WHEELBASE_DEVICE_ID 0x0020 +#define CSR_ELITE_WHEELBASE_DEVICE_ID 0x0011 + +// wheels +#define CSL_STEERING_WHEEL_P1_V2 0x08 +#define CSL_ELITE_STEERING_WHEEL_WRC_ID 0x12 +#define CSL_ELITE_STEERING_WHEEL_MCLAREN_GT3_V2_ID 0x0b +#define CLUBSPORT_STEERING_WHEEL_F1_IS_ID 0x12 +#define CLUBSPORT_STEERING_WHEEL_FORMULA_V2_ID 0x0a +#define PODIUM_STEERING_WHEEL_PORSCHE_911_GT3_R_ID 0x0c + + +// quirks +#define FTEC_FF 0x001 +#define FTEC_PEDALS 0x002 +#define FTEC_WHEELBASE_LEDS 0x004 +#define FTEC_HIGHRES 0x008 +#define FTEC_TUNING_MENU 0x010 + +// report sizes +#define FTEC_TUNING_REPORT_SIZE 64 +#define FTEC_WHEEL_REPORT_SIZE 34 + + +// misc +#define LEDS 9 +#define FTECFF_MAX_EFFECTS 16 + +struct ftecff_effect_state { + struct ff_effect effect; + struct ff_envelope *envelope; + unsigned long start_at; + unsigned long play_at; + unsigned long stop_at; + unsigned long flags; + unsigned long time_playing; + unsigned long updated_at; + unsigned int phase; + unsigned int phase_adj; + unsigned int count; + unsigned int cmd; + unsigned int cmd_start_time; + unsigned int cmd_start_count; + int direction_gain; + int slope; +}; + +struct ftecff_effect_parameters { + int level; + int d1; + int d2; + int k1; + int k2; + unsigned int clip; +}; + +struct ftecff_slot { + int id; + struct ftecff_effect_parameters parameters; + u8 current_cmd[7]; + int is_updated; + int effect_type; + u8 cmd; +}; + +struct ftec_tuning_classdev { + struct device *dev; + // the data from the last update we got from the device, shifted by 1 + u8 ftec_tuning_data[FTEC_TUNING_REPORT_SIZE]; + u8 advanced_mode; +}; + +struct ftec_drv_data_client { + struct hid_device *hdev; + u8 rdesc[4096]; + size_t rsize; + int opened; + struct ff_effect effects[40]; + u8 current_id; +}; + +struct ftec_drv_data { + unsigned long quirks; + spinlock_t report_lock; /* Protect output HID report */ + spinlock_t timer_lock; + struct hrtimer hrtimer; + struct hid_device *hid; + struct hid_report *report; + struct ftec_drv_data_client client; + struct ftecff_slot slots[5]; + struct ftecff_effect_state states[FTECFF_MAX_EFFECTS]; + int effects_used; + u16 range; + u16 max_range; + u16 min_range; +#if IS_REACHABLE(CONFIG_LEDS_CLASS) + u16 led_state; + struct led_classdev *led[LEDS]; +#endif + u8 wheel_id; + struct ftec_tuning_classdev tuning; +}; + +#define FTEC_TUNING_ATTRS \ + FTEC_TUNING_ATTR(SLOT, 0x02, "Slot", ftec_conv_noop_to, ftec_conv_noop_from, 1, 5) \ + FTEC_TUNING_ATTR(SEN, 0x03, "Sensivity", ftec_conv_sens_to, ftec_conv_sens_from, 90, 0) \ + FTEC_TUNING_ATTR(FF, 0x04, "Force Feedback Strength", ftec_conv_noop_to, ftec_conv_noop_from, 0, 100) \ + FTEC_TUNING_ATTR(SHO, 0x05, "Wheel Vibration Motor", ftec_conv_times_ten, ftec_conv_div_ten, 0, 100) \ + FTEC_TUNING_ATTR(BLI, 0x06, "Break Level Indicator", ftec_conv_noop_to, ftec_conv_noop_from, 0, 101) \ + FTEC_TUNING_ATTR(FFS, 0x07, "Force Feedback Scaling", ftec_conv_noop_to, ftec_conv_noop_from, 0, 1) \ + FTEC_TUNING_ATTR(DRI, 0x09, "Drift Mode", ftec_conv_signed_to, ftec_conv_noop_from, -5, 3) \ + FTEC_TUNING_ATTR(FOR, 0x0a, "Force Effect Strength", ftec_conv_times_ten, ftec_conv_div_ten, 0, 120) \ + FTEC_TUNING_ATTR(SPR, 0x0b, "Spring Effect Strength", ftec_conv_times_ten, ftec_conv_div_ten, 0, 120) \ + FTEC_TUNING_ATTR(DPR, 0x0c, "Damper Effect Strength", ftec_conv_times_ten, ftec_conv_div_ten, 0, 120) \ + FTEC_TUNING_ATTR(NDP, 0x0d, "Natural Damber", ftec_conv_noop_to, ftec_conv_noop_from, 0, 100) \ + FTEC_TUNING_ATTR(NFR, 0x0e, "Natural Friction", ftec_conv_noop_to, ftec_conv_noop_from, 0, 100) \ + FTEC_TUNING_ATTR(FEI, 0x11, "Force Effect Intensity", ftec_conv_noop_to, ftec_conv_steps_ten, 0, 100) \ + FTEC_TUNING_ATTR(ACP, 0x13, "Analogue Paddles", ftec_conv_noop_to, ftec_conv_noop_from, 1, 4) \ + FTEC_TUNING_ATTR(INT, 0x14, "FFB Interpolation Filter", ftec_conv_noop_to, ftec_conv_noop_from, 0, 20) \ + FTEC_TUNING_ATTR(NIN, 0x15, "Natural Inertia", ftec_conv_noop_to, ftec_conv_noop_from, 0, 100) \ + FTEC_TUNING_ATTR(FUL, 0x16, "FullForce", ftec_conv_noop_to, ftec_conv_noop_from, 0, 100) \ + +enum ftec_tuning_attrs_enum { +#define FTEC_TUNING_ATTR(id, addr, desc, conv_to, conv_from, min, max) \ + id, +FTEC_TUNING_ATTRS + FTEC_TUNING_ATTR_NONE +#undef FTEC_TUNING_ATTR +}; + + +int ftecff_init(struct hid_device*); +void ftecff_remove(struct hid_device*); +int ftecff_raw_event(struct hid_device*, struct hid_report*, u8*, int); + +int ftec_tuning_classdev_register(struct device*, struct ftec_tuning_classdev*); +void ftec_tuning_classdev_unregister(struct ftec_tuning_classdev*); +ssize_t _ftec_tuning_show(struct device*, enum ftec_tuning_attrs_enum, char*); +ssize_t _ftec_tuning_store(struct device*, enum ftec_tuning_attrs_enum, const char*, size_t); + + +#endif diff --git a/drivers/custom/fanatecff/hid-ftecff-tuning.c b/drivers/custom/fanatecff/hid-ftecff-tuning.c new file mode 100644 index 000000000000..0784db5101d2 --- /dev/null +++ b/drivers/custom/fanatecff/hid-ftecff-tuning.c @@ -0,0 +1,354 @@ +#include +#include + +#include "hid-ftec.h" + +struct ftec_tuning_attr_t { + const char* name; + const enum ftec_tuning_attrs_enum id; + const u8 addr; + const char* description; + int (*conv_to)(struct ftec_drv_data *, u8); + u8 (*conv_from)(struct ftec_drv_data *, int); + const int min; + const int max; +}; + +static int ftec_conv_sens_to(struct ftec_drv_data *drv_data, u8 val) { + if (drv_data->max_range <= 1090) { + return val * 10; + } + if (val < (u8)0x8a) { + return 1080 + 10 * (0x100 + val - 0xed); + } else if (val >= (u8)0xed) { + return 1080 + 10 * (val - 0xed); + } + return 90 + 10 * (val - 0x8a); +}; + +static u8 ftec_conv_sens_from(struct ftec_drv_data *drv_data, int val) { + if (drv_data->max_range <= 1090) { + return val / 10; + } + if (val >= 1080) { + // overflow of u8 is expected behavior + return 0xed + ((val - 1080) / 10); + } + return 0x8a + (val - 90) / 10; +}; + +static int ftec_conv_times_ten(struct ftec_drv_data *drv_data, u8 val) { + return val * 10; +}; + +static u8 ftec_conv_div_ten(struct ftec_drv_data *drv_data, int val) { + return val / 10; +}; + +static u8 ftec_conv_steps_ten(struct ftec_drv_data *drv_data, int val) { + return 10 * (val / 10); +} + +static int ftec_conv_signed_to(struct ftec_drv_data *drv_data, u8 val) { + return (s8)val; +}; + +static int ftec_conv_noop_to(struct ftec_drv_data *drv_data, u8 val) { + return val; +}; + +static u8 ftec_conv_noop_from(struct ftec_drv_data *drv_data, int val) { + return val; +}; + + +static const struct ftec_tuning_attr_t ftec_tuning_attrs[] = { +#define FTEC_TUNING_ATTR(id, addr, desc, conv_to, conv_from, min, max) \ + { #id, id, addr, desc, conv_to, conv_from, min, max }, +FTEC_TUNING_ATTRS +#undef FTEC_TUNING_ATTR +}; + + +static int ftec_tuning_write(struct hid_device *hid, int addr, int val) { + struct ftec_drv_data *drv_data = hid_get_drvdata(hid); + drv_data->tuning.ftec_tuning_data[0] = 0xff; + drv_data->tuning.ftec_tuning_data[1] = 0x03; + drv_data->tuning.ftec_tuning_data[2] = 0x00; + drv_data->tuning.ftec_tuning_data[addr+1] = val; + return hid_hw_output_report(hid, &drv_data->tuning.ftec_tuning_data[0], FTEC_TUNING_REPORT_SIZE); +} + +static int ftec_tuning_select(struct hid_device *hid, int slot) { + u8 *buf = kcalloc(FTEC_TUNING_REPORT_SIZE, sizeof(u8), GFP_KERNEL); + int ret; + + buf[0] = 0xff; + buf[1] = 0x03; + buf[2] = 0x01; + buf[3] = slot&0xff; + + ret = hid_hw_output_report(hid, buf, FTEC_TUNING_REPORT_SIZE); + kfree(buf); + return ret; +} + +static enum ftec_tuning_attrs_enum ftec_tuning_get_id(struct device_attribute *attr) { + int idx = 0; + for (; idx < sizeof(ftec_tuning_attrs); idx++) { + if (strcmp(attr->attr.name, ftec_tuning_attrs[idx].name) == 0) { + return ftec_tuning_attrs[idx].id; + } + } + dbg_hid("Unknown attribute %s\n", attr->attr.name); + return FTEC_TUNING_ATTR_NONE; +} + +ssize_t _ftec_tuning_show(struct device *dev, enum ftec_tuning_attrs_enum id, char *buf) +{ + struct hid_device *hid = to_hid_device(dev->parent); + struct ftec_drv_data *drv_data = hid_get_drvdata(hid); + const struct ftec_tuning_attr_t *tuning_attr = &ftec_tuning_attrs[id]; + return scnprintf(buf, PAGE_SIZE, "%i\n", tuning_attr->conv_to(drv_data, drv_data->tuning.ftec_tuning_data[tuning_attr->addr+1])); +} + +static ssize_t ftec_tuning_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + enum ftec_tuning_attrs_enum id = ftec_tuning_get_id(attr); + if (id == FTEC_TUNING_ATTR_NONE) { + return -EINVAL; + } + + return _ftec_tuning_show(dev, id, buf); +} + +ssize_t _ftec_tuning_store(struct device *dev, enum ftec_tuning_attrs_enum id, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev->parent); + struct ftec_drv_data *drv_data = hid_get_drvdata(hid); + const struct ftec_tuning_attr_t *tuning_attr = &ftec_tuning_attrs[id]; + int val, _max = tuning_attr->max; + + /* guard from writing w/o having read values */ + if (drv_data->tuning.ftec_tuning_data[ftec_tuning_attrs[SLOT].addr+1] == 0x0) { + hid_err(hid, "Cannot write to tuning-menu, not yet read data from device.\n"); + return -EINVAL; + } + + if (kstrtos32(buf, 0, &val) != 0) { + hid_err(hid, "Invalid value %s!\n", buf); + return -EINVAL; + } + + /* special case for SEN, max value is device specific */ + if (id == SEN) { + _max = drv_data->max_range; + /* set max value if 0 is given */ + if (val == 0) { + val = _max; + } + } + + /* check if value is in range */ + if (val < tuning_attr->min || val > _max) { + hid_err(hid, "Value %i out of range [%i, %i]!\n", val, tuning_attr->min, _max); + return -EINVAL; + } + + /* convert value to device specific value */ + val = tuning_attr->conv_from(drv_data, val); + dbg_hid(" ... ftec_tuning_store %s %i\n", tuning_attr->name, val); + + if (id == SLOT) { + if (ftec_tuning_select(hid, val) < 0) { + return -EIO; + } + } else { + if (ftec_tuning_write(hid, tuning_attr->addr, val) < 0) { + return -EIO; + } + } + return count; +} + + +static ssize_t ftec_tuning_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + enum ftec_tuning_attrs_enum id = ftec_tuning_get_id(attr); + if (id == FTEC_TUNING_ATTR_NONE) { + return -EINVAL; + } + return _ftec_tuning_store(dev, id, buf, count); +} + +static ssize_t ftec_tuning_reset(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev->parent); + u8 *buffer = kcalloc(FTEC_TUNING_REPORT_SIZE, sizeof(u8), GFP_KERNEL); + int ret; + + // request current values + buffer[0] = 0xff; + buffer[1] = 0x03; + buffer[2] = 0x04; + + ret = hid_hw_output_report(hid, buffer, FTEC_TUNING_REPORT_SIZE); + + return count; +} + +static DEVICE_ATTR(RESET, S_IWUSR | S_IWGRP, NULL, ftec_tuning_reset); +#define FTEC_TUNING_ATTR(id, addr, desc, conv_to, conv_from, min, max) \ + static DEVICE_ATTR(id, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, ftec_tuning_show, ftec_tuning_store); +FTEC_TUNING_ATTRS +#undef FTEC_TUNING_ATTR + +static ssize_t ftec_tuning_advanced_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct hid_device *hid = to_hid_device(dev->parent); + struct ftec_drv_data *drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return 0; + } + return scnprintf(buf, PAGE_SIZE, "%u\n", drv_data->tuning.advanced_mode); +} + +static ssize_t ftec_tuning_advanced_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev->parent); + struct ftec_drv_data *drv_data = hid_get_drvdata(hid); + u8 advanced_mode; + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return 0; + } + if (kstrtou8(buf, 0, &advanced_mode) == 0) { + if (advanced_mode != drv_data->tuning.advanced_mode) { + u8 *buffer = kcalloc(FTEC_TUNING_REPORT_SIZE, sizeof(u8), GFP_KERNEL); + buffer[0] = 0xff; + buffer[1] = 0x03; + buffer[2] = 0x06; + (void)hid_hw_output_report(hid, buffer, FTEC_TUNING_REPORT_SIZE); + } + } + return count; +} +static DEVICE_ATTR(advanced_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, ftec_tuning_advanced_mode_show, ftec_tuning_advanced_mode_store); + +static struct class ftec_tuning_class = { + .name = "ftec_tuning", +}; + +int ftec_tuning_classdev_register(struct device *parent, + struct ftec_tuning_classdev *ftec_tuning_cdev) +{ + struct hid_device *hdev = to_hid_device(parent); + int ret; + + ret = class_register(&ftec_tuning_class); + if (ret) + return 0; + + ftec_tuning_cdev->dev = device_create(&ftec_tuning_class, parent, 0, NULL, "%s", dev_name(&hdev->dev)); + +#define CREATE_SYSFS_FILE(name) \ + ret = device_create_file(ftec_tuning_cdev->dev, &dev_attr_##name); \ + if (ret) \ + hid_warn(hdev, "Unable to create sysfs interface for '%s', errno %d\n", #name, ret); \ + + CREATE_SYSFS_FILE(advanced_mode) + + CREATE_SYSFS_FILE(RESET) + CREATE_SYSFS_FILE(SLOT) + CREATE_SYSFS_FILE(SEN) + CREATE_SYSFS_FILE(FF) + CREATE_SYSFS_FILE(FEI) + CREATE_SYSFS_FILE(FOR) + CREATE_SYSFS_FILE(SPR) + CREATE_SYSFS_FILE(DPR) + CREATE_SYSFS_FILE(ACP) + + if (hdev->product == CSL_ELITE_WHEELBASE_DEVICE_ID || + hdev->product == CSL_ELITE_PS4_WHEELBASE_DEVICE_ID) { + CREATE_SYSFS_FILE(DRI) + } + if (hdev->product == CSL_ELITE_WHEELBASE_DEVICE_ID || + hdev->product == CSL_ELITE_PS4_WHEELBASE_DEVICE_ID || + hdev->product == CSL_DD_WHEELBASE_DEVICE_ID || + hdev->product == PODIUM_WHEELBASE_DD1_DEVICE_ID || + hdev->product == PODIUM_WHEELBASE_DD2_DEVICE_ID) { + CREATE_SYSFS_FILE(BLI) + CREATE_SYSFS_FILE(SHO) + } + if (hdev->product == CSL_DD_WHEELBASE_DEVICE_ID || + hdev->product == PODIUM_WHEELBASE_DD1_DEVICE_ID || + hdev->product == PODIUM_WHEELBASE_DD2_DEVICE_ID) { + CREATE_SYSFS_FILE(NDP) + CREATE_SYSFS_FILE(NFR) + CREATE_SYSFS_FILE(NIN) + CREATE_SYSFS_FILE(INT) + CREATE_SYSFS_FILE(FFS) + } + // FIXME: this is ClubSport DD specific, but not yet understood how + // to discriminate these + if (hdev->product == CSL_DD_WHEELBASE_DEVICE_ID) { + CREATE_SYSFS_FILE(FUL) + } + + return 0; +} + +void ftec_tuning_classdev_unregister(struct ftec_tuning_classdev *ftec_tuning_cdev) +{ + struct hid_device *hdev = to_hid_device(ftec_tuning_cdev->dev->parent); + + if (IS_ERR_OR_NULL(ftec_tuning_cdev->dev)) + return; + +#define REMOVE_SYSFS_FILE(name) device_remove_file(ftec_tuning_cdev->dev, &dev_attr_##name); \ + + REMOVE_SYSFS_FILE(advanced_mode) + + REMOVE_SYSFS_FILE(RESET) + REMOVE_SYSFS_FILE(SLOT) + REMOVE_SYSFS_FILE(SEN) + REMOVE_SYSFS_FILE(FF) + REMOVE_SYSFS_FILE(FEI) + REMOVE_SYSFS_FILE(FOR) + REMOVE_SYSFS_FILE(SPR) + REMOVE_SYSFS_FILE(DPR) + REMOVE_SYSFS_FILE(ACP) + + if (hdev->product == CSL_ELITE_WHEELBASE_DEVICE_ID || + hdev->product == CSL_ELITE_PS4_WHEELBASE_DEVICE_ID) { + REMOVE_SYSFS_FILE(DRI) + } + if (hdev->product == CSL_ELITE_WHEELBASE_DEVICE_ID || + hdev->product == CSL_ELITE_PS4_WHEELBASE_DEVICE_ID || + hdev->product == CSL_DD_WHEELBASE_DEVICE_ID || + hdev->product == PODIUM_WHEELBASE_DD1_DEVICE_ID || + hdev->product == PODIUM_WHEELBASE_DD2_DEVICE_ID) { + REMOVE_SYSFS_FILE(BLI) + REMOVE_SYSFS_FILE(SHO) + } + if (hdev->product == CSL_DD_WHEELBASE_DEVICE_ID || + hdev->product == PODIUM_WHEELBASE_DD1_DEVICE_ID || + hdev->product == PODIUM_WHEELBASE_DD2_DEVICE_ID) { + REMOVE_SYSFS_FILE(NDP) + REMOVE_SYSFS_FILE(NFR) + REMOVE_SYSFS_FILE(NIN) + REMOVE_SYSFS_FILE(INT) + REMOVE_SYSFS_FILE(FFS) + } + if (hdev->product == CSL_DD_WHEELBASE_DEVICE_ID) { + REMOVE_SYSFS_FILE(FUL) + } + + device_unregister(ftec_tuning_cdev->dev); + class_unregister(&ftec_tuning_class); +} diff --git a/drivers/custom/fanatecff/hid-ftecff.c b/drivers/custom/fanatecff/hid-ftecff.c new file mode 100644 index 000000000000..ed0fdf6926ee --- /dev/null +++ b/drivers/custom/fanatecff/hid-ftecff.c @@ -0,0 +1,1211 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ftec.h" + +// parameter to set the initial range +// if unset, the wheels max range is used +int init_range = 0; +module_param(init_range, int, 0); + +// parameter to enable an LED indicating that the module is loaded +int enable_module_init_led = 0; +module_param(enable_module_init_led, int, 0); + +#define DEFAULT_TIMER_PERIOD 2 + +#define FF_EFFECT_STARTED 0 +#define FF_EFFECT_ALLSET 1 +#define FF_EFFECT_PLAYING 2 +#define FF_EFFECT_UPDATING 3 + +#define STOP_EFFECT(state) ((state)->flags = 0) + +#undef fixp_sin16 +#define fixp_sin16(v) (((v % 360) > 180)? -(fixp_sin32((v % 360) - 180) >> 16) : fixp_sin32(v) >> 16) + +#define DEBUG(...) pr_debug("ftecff: " __VA_ARGS__) +#define time_diff(a,b) ({ \ + typecheck(unsigned long, a); \ + typecheck(unsigned long, b); \ + ((a) - (long)(b)); }) +#define JIFFIES2MS(jiffies) ((jiffies) * 1000 / HZ) + +static int timer_msecs = DEFAULT_TIMER_PERIOD; +static int spring_level = 100; +static int damper_level = 100; +static int inertia_level = 100; +static int friction_level = 100; + +static int profile = 1; +module_param(profile, int, 0660); +MODULE_PARM_DESC(profile, "Enable profile debug messages."); + + +static const signed short ftecff_wheel_effects[] = { + FF_CONSTANT, + FF_SPRING, + FF_DAMPER, + FF_INERTIA, + FF_FRICTION, + FF_PERIODIC, + FF_SINE, + FF_SQUARE, + FF_TRIANGLE, + FF_SAW_UP, + FF_SAW_DOWN, + -1 +}; + +/* This is realy weird... if i put a value >0x80 into the report, + the actual value send to the device will be 0x7f. I suspect it has + s.t. todo with the report fields min/max range, which is -127 to 128 + but I don't know how to handle this properly... So, here a hack around + this issue +*/ +static void fix_values(s32 *values) { + int i; + for(i=0;i<7;i++) { + if (values[i]>=0x80) + values[i] = -0x100 + values[i]; + } +} + +/* + bits 7 6 5 4 3 2 1 0 + 7 segments bits and dot + 0 + 5 1 + 6 + 4 2 + 3 7 +*/ +static u8 segbits[42] ={ + 63, // 0 + 6, // 1 + 91, // 2 + 79, // 3 + 102, // 4 + 109, // 5 + 125, // 6 + 7, // 7 + 127, // 8 + 103, // 9 + 128, // dot + 0, // blank + 57, // [ + 15, // ] + 64, // - + 8, // _ + 119, // a + 124, // b + 88, // c + 94, // d + 121, // e + 113, // f + 61, // g + 118, // h + 48, // i + 14, // j + 0, // k - placeholder/blank + 56, // l + 0, // m - placeholder/blank + 84, // n + 92, // o + 115, // p + 103, // q + 80, // r + 109, // s + 120, // t + 62, // u + 0, // v - placeholder/blank + 0, // w - placeholder/blank + 0, // x - placeholder/blank + 110, // y + 91 // z +}; + +static u8 seg_bits(u8 value, bool point) { + u8 num_index = 11; // defaults to blank + + // capital letters ASCII 65 - 90 / poor mans toLower + if(value>63 && value<91) { + value=value+32; + } + // point + if(value==46) { + num_index = 10; + } + // opening square bracket + else if(value==91) { + num_index = 12; + } + // closing square bracket + else if(value==93) { + num_index = 13; + } + // hyphen + else if(value==45) { + num_index = 14; + } + // underscore + else if(value==95) { + num_index = 15; + } + // ascii numbers + else if(value>47 && value<58) { + num_index=value-48; + } + // lower case letters ASCII 98 - 117 + else if(value>96 && value<123) { + num_index=value-81; + } + + // if a point has to be set, flip it in the segment + return point ? segbits[num_index]+segbits[10] : segbits[num_index]; +} + +static void send_report_request_to_device(struct ftec_drv_data *drv_data) +{ + struct hid_device *hdev = drv_data->hid; + struct hid_report *report = drv_data->report; + + if (hdev->product != CSR_ELITE_WHEELBASE_DEVICE_ID) + { + fix_values(report->field[0]->value); + } + + hid_hw_request(hdev, report, HID_REQ_SET_REPORT); +} + +static void ftec_set_range(struct hid_device *hid, u16 range) +{ + struct ftec_drv_data *drv_data; + unsigned long flags; + s32 *value; + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return; + } + value = drv_data->report->field[0]->value; + dbg_hid("setting range to %u\n", range); + + /* Prepare "coarse" limit command */ + spin_lock_irqsave(&drv_data->report_lock, flags); + value[0] = 0xf5; + value[1] = 0x00; + value[2] = 0x00; + value[3] = 0x00; + value[4] = 0x00; + value[5] = 0x00; + value[6] = 0x00; + send_report_request_to_device(drv_data); + + value[0] = 0xf8; + value[1] = 0x09; + value[2] = 0x01; + value[3] = 0x06; + value[4] = 0x01; + value[5] = 0x00; + value[6] = 0x00; + send_report_request_to_device(drv_data); + + value[0] = 0xf8; + value[1] = 0x81; + value[2] = range&0xff; + value[3] = (range>>8)&0xff; + value[4] = 0x00; + value[5] = 0x00; + value[6] = 0x00; + send_report_request_to_device(drv_data); + spin_unlock_irqrestore(&drv_data->report_lock, flags); +} + +/* Export the currently set range of the wheel */ +static ssize_t ftec_range_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct ftec_drv_data *drv_data = hid_get_drvdata(hid); + size_t count; + + /* new wheelbases have tuning menu, so use this to get the range */ + if (drv_data->quirks & FTEC_TUNING_MENU) { + count = _ftec_tuning_show(drv_data->tuning.dev, SEN, buf); + return count; + } + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return 0; + } + + count = scnprintf(buf, PAGE_SIZE, "%u\n", drv_data->range); + return count; +} + +/* Set range to user specified value, call appropriate function + * according to the type of the wheel */ +static ssize_t ftec_range_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct ftec_drv_data *drv_data = hid_get_drvdata(hid); + u16 range; + + /* new wheelbases have tuning menu, so use this to set the range */ + if (drv_data->quirks & FTEC_TUNING_MENU) { + count = _ftec_tuning_store(drv_data->tuning.dev, SEN, buf, count); + return count; + } + + if (kstrtou16(buf, 0, &range) != 0) { + hid_err(hid, "Invalid range %s!\n", buf); + return -EINVAL; + } + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return -EINVAL; + } + + if (range == 0) + range = drv_data->max_range; + + /* Check if the wheel supports range setting + * and that the range is within limits for the wheel */ + if (range >= drv_data->min_range && range <= drv_data->max_range) { + ftec_set_range(hid, range); + drv_data->range = range; + } + + return count; +} +static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, ftec_range_show, ftec_range_store); + + +/* Export the current wheel id */ +static ssize_t ftec_wheel_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ftec_drv_data *drv_data = hid_get_drvdata(to_hid_device(dev)); + return scnprintf(buf, PAGE_SIZE, "0x%02x\n", drv_data->wheel_id); +} +static DEVICE_ATTR(wheel_id, S_IRUSR | S_IRGRP | S_IROTH, ftec_wheel_show, NULL); + + +static ssize_t ftec_set_display(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct ftec_drv_data *drv_data; + unsigned long flags; + s32 *value; + unsigned int valueIndex, bufIndex = 0; + + // check the buffer size, note that in lack of points or commas, only the first 3 characters will be processed + if (count > 7) { + hid_err(hid, "Invalid value %s!\n", buf); + return -EINVAL; + } + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return -EINVAL; + } + + // dbg_hid(" ... set_display %i\n", val); + + value = drv_data->report->field[0]->value; + + spin_lock_irqsave(&drv_data->report_lock, flags); + value[0] = 0xf8; + value[1] = 0x09; + value[2] = 0x01; + value[3] = 0x02; + value[4] = 0x00; + value[5] = 0x00; + value[6] = 0x00; + + // set each of the segments as long there is input data + for(valueIndex = 4; valueIndex <= 6 && bufIndex < count; valueIndex++) { + bool point = false; + // check if next char is a point or comma if not end of the string + if(bufIndex+1 < count) { + point = buf[bufIndex+1] == '.' || buf[bufIndex+1] == ','; + } + value[valueIndex] = seg_bits(buf[bufIndex], point); + // determinate next value, if a dot was encountered we need to step one index further + bufIndex += point ? 2 : 1; + } + // 'center' values by shifting shorter inputs to the right + if(value[4] > 0x00 && value[6] == 0x00) { + if(value[5] == 0x00) { + value[5] = value[4]; + value[4] = 0x00; + } + else if(value[5] > 0) { + value[6] = value[5]; + value[5] = value[4]; + value[4] = 0x00; + } + } + + send_report_request_to_device(drv_data); + spin_unlock_irqrestore(&drv_data->report_lock, flags); + + return count; +} +static DEVICE_ATTR(display, S_IWUSR | S_IWGRP, NULL, ftec_set_display); + +#if IS_REACHABLE(CONFIG_LEDS_CLASS) +static void ftec_set_leds(struct hid_device *hid, u16 leds) +{ + struct ftec_drv_data *drv_data; + unsigned long flags; + s32 *value; + u16 _leds = 0; + int i; + + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return; + } + + spin_lock_irqsave(&drv_data->report_lock, flags); + + if (drv_data->quirks & FTEC_WHEELBASE_LEDS) { + // dbg_hid(" ... set_leds base %04X\n", leds); + + value = drv_data->report->field[0]->value; + + value[0] = 0xf8; + value[1] = 0x13; + value[2] = leds&0xff; + value[3] = 0x00; + value[4] = 0x00; + value[5] = 0x00; + value[6] = 0x00; + + send_report_request_to_device(drv_data); + } + + // reshuffle, since first led is highest bit + for( i=0; i>i & 1) _leds |= 1 << (LEDS-i-1); + } + + // dbg_hid(" ... set_leds wheel %04X\n", _leds); + + value = drv_data->report->field[0]->value; + + value[0] = 0xf8; + value[1] = 0x09; + value[2] = 0x08; + value[3] = (_leds>>8)&0xff; + value[4] = _leds&0xff; + value[5] = 0x00; + value[6] = 0x00; + + send_report_request_to_device(drv_data); + spin_unlock_irqrestore(&drv_data->report_lock, flags); +} + +static void ftec_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct device *dev = led_cdev->dev->parent; + struct hid_device *hid = to_hid_device(dev); + struct ftec_drv_data *drv_data = hid_get_drvdata(hid); + int i, state = 0; + + if (!drv_data) { + hid_err(hid, "Device data not found."); + return; + } + + for (i = 0; i < LEDS; i++) { + if (led_cdev != drv_data->led[i]) + continue; + state = (drv_data->led_state >> i) & 1; + if (value == LED_OFF && state) { + drv_data->led_state &= ~(1 << i); + ftec_set_leds(hid, drv_data->led_state); + } else if (value != LED_OFF && !state) { + drv_data->led_state |= 1 << i; + ftec_set_leds(hid, drv_data->led_state); + } + break; + } +} + +static enum led_brightness ftec_led_get_brightness(struct led_classdev *led_cdev) +{ + struct device *dev = led_cdev->dev->parent; + struct hid_device *hid = to_hid_device(dev); + struct ftec_drv_data *drv_data = hid_get_drvdata(hid); + int i, value = 0; + + if (!drv_data) { + hid_err(hid, "Device data not found."); + return LED_OFF; + } + + for (i = 0; i < LEDS; i++) + if (led_cdev == drv_data->led[i]) { + value = (drv_data->led_state >> i) & 1; + break; + } + + return value ? LED_FULL : LED_OFF; +} +#endif + +#if IS_REACHABLE(CONFIG_LEDS_CLASS) +static int ftec_init_led(struct hid_device *hid) { + struct led_classdev *led; + size_t name_sz; + char *name; + struct ftec_drv_data *drv_data; + int ret, j; + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Cannot add device, private driver data not allocated\n"); + return -1; + } + + if (enable_module_init_led) { + // wheel LED initialization sequence + // not sure what's needed + s32 *value; + value = drv_data->report->field[0]->value; + + value[0] = 0xf8; + value[1] = 0x09; + value[2] = 0x08; + value[3] = 0x01; // set green led to indicate driver is loaded + value[4] = 0x00; + value[5] = 0x00; + value[6] = 0x00; + + send_report_request_to_device(drv_data); + } + + drv_data->led_state = 0; + for (j = 0; j < LEDS; j++) + drv_data->led[j] = NULL; + + name_sz = strlen(dev_name(&hid->dev)) + 8; + + for (j = 0; j < LEDS; j++) { + led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); + if (!led) { + hid_err(hid, "can't allocate memory for LED %d\n", j); + goto err_leds; + } + + name = (void *)(&led[1]); + snprintf(name, name_sz, "%s::RPM%d", dev_name(&hid->dev), j+1); + led->name = name; + led->brightness = 0; + led->max_brightness = 1; + led->brightness_get = ftec_led_get_brightness; + led->brightness_set = ftec_led_set_brightness; + + drv_data->led[j] = led; + ret = led_classdev_register(&hid->dev, led); + + if (ret) { + hid_err(hid, "failed to register LED %d. Aborting.\n", j); +err_leds: + /* Deregister LEDs (if any) */ + for (j = 0; j < LEDS; j++) { + led = drv_data->led[j]; + drv_data->led[j] = NULL; + if (!led) + continue; + led_classdev_unregister(led); + kfree(led); + } + return -1; + } + } + return 0; +} +#endif + +static void ftecff_send_cmd(struct ftec_drv_data *drv_data, u8 *cmd) +{ + unsigned short i; + unsigned long flags; + s32 *value = drv_data->report->field[0]->value; + + spin_lock_irqsave(&drv_data->report_lock, flags); + + for(i = 0; i < 7; i++) + value[i] = cmd[i]; + + send_report_request_to_device(drv_data); + spin_unlock_irqrestore(&drv_data->report_lock, flags); + + if (unlikely(profile)) + DEBUG("send_cmd: %02X %02X %02X %02X %02X %02X %02X", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]); +} + +static __always_inline struct ff_envelope *ftecff_effect_envelope(struct ff_effect *effect) +{ + switch (effect->type) { + case FF_CONSTANT: + return &effect->u.constant.envelope; + case FF_RAMP: + return &effect->u.ramp.envelope; + case FF_PERIODIC: + return &effect->u.periodic.envelope; + } + + return NULL; +} + +static __always_inline void ftecff_update_state(struct ftecff_effect_state *state, const unsigned long now) +{ + struct ff_effect *effect = &state->effect; + unsigned long phase_time; + + if (!__test_and_set_bit(FF_EFFECT_ALLSET, &state->flags)) { + state->play_at = state->start_at + effect->replay.delay; + if (!test_bit(FF_EFFECT_UPDATING, &state->flags)) { + state->updated_at = state->play_at; + } + state->direction_gain = fixp_sin16(effect->direction * 360 / 0x10000); + if (effect->type == FF_PERIODIC) { + state->phase_adj = effect->u.periodic.phase * 360 / effect->u.periodic.period; + } + if (effect->replay.length) { + state->stop_at = state->play_at + effect->replay.length; + } + } + + if (__test_and_clear_bit(FF_EFFECT_UPDATING, &state->flags)) { + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + state->play_at = state->start_at + effect->replay.delay; + state->direction_gain = fixp_sin16(effect->direction * 360 / 0x10000); + if (effect->replay.length) { + state->stop_at = state->play_at + effect->replay.length; + } + if (effect->type == FF_PERIODIC) { + state->phase_adj = state->phase; + } + } + + state->envelope = ftecff_effect_envelope(effect); + + state->slope = 0; + if (effect->type == FF_RAMP && effect->replay.length) { + state->slope = ((effect->u.ramp.end_level - effect->u.ramp.start_level) << 16) / (effect->replay.length - state->envelope->attack_length - state->envelope->fade_length); + } + + if (!test_bit(FF_EFFECT_PLAYING, &state->flags) && time_after_eq(now, + state->play_at) && (effect->replay.length == 0 || + time_before(now, state->stop_at))) { + __set_bit(FF_EFFECT_PLAYING, &state->flags); + } + + if (test_bit(FF_EFFECT_PLAYING, &state->flags)) { + state->time_playing = time_diff(now, state->play_at); + if (effect->type == FF_PERIODIC) { + phase_time = time_diff(now, state->updated_at); + state->phase = (phase_time % effect->u.periodic.period) * 360 / effect->u.periodic.period; + state->phase += state->phase_adj % 360; + } + } +} + +static void ftecff_update_slot(struct ftecff_slot *slot, struct ftecff_effect_parameters *parameters, const bool highres) +{ + u8 original_cmd[7]; + unsigned short i; + int d1; + int d2; + int s1; + int s2; + + memcpy(original_cmd, slot->current_cmd, sizeof(original_cmd)); + memset(slot->current_cmd, 0, sizeof(slot->current_cmd)); + + // select slot + slot->current_cmd[0] = (slot->id<<4) | 0x1; + // set command + slot->current_cmd[1] = slot->cmd; + + if ((slot->effect_type == FF_CONSTANT && parameters->level == 0) || + (slot->effect_type != FF_CONSTANT && parameters->clip == 0)) { + // disable slot + slot->current_cmd[0] |= 0x2; + // reset values + if (slot->effect_type != FF_CONSTANT && slot->effect_type != FF_SPRING) { + slot->current_cmd[2] = 0x00; + slot->current_cmd[4] = 0x00; + slot->current_cmd[6] = 0xff; + } + if (original_cmd[0] != slot->current_cmd[0]) { + slot->is_updated = 1; + } + return; + } + +#define CLAMP_VALUE_U16(x) ((unsigned short)((x) > 0xffff ? 0xffff : (x))) +#define CLAMP_VALUE_S16(x) ((unsigned short)((x) <= -0x8000 ? -0x8000 : ((x) > 0x7fff ? 0x7fff : (x)))) +#define TRANSLATE_FORCE(x, bits) ((CLAMP_VALUE_S16(x) + 0x8000) >> (16 - bits)) +#define SCALE_VALUE_U16(x, bits) (CLAMP_VALUE_U16(x) >> (16 - bits)) +#define SCALE_COEFF(x, bits) SCALE_VALUE_U16(abs(x) * 2, bits) + + switch (slot->effect_type) { + case FF_CONSTANT: + if (highres) { + d1 = TRANSLATE_FORCE(parameters->level, 16); + slot->current_cmd[2] = d1&0xff; + slot->current_cmd[3] = (d1>>8)&0xff; + slot->current_cmd[6] = 0x01; + } else { + slot->current_cmd[2] = TRANSLATE_FORCE(parameters->level, 8); + } + // dbg_hid("constant: %i 0x%x 0x%x 0x%x\n", + // parameters->level, slot->current_cmd[2], slot->current_cmd[3], slot->current_cmd[6]); + break; + case FF_SPRING: + d1 = SCALE_VALUE_U16(((parameters->d1) + 0x8000) & 0xffff, 11); + d2 = SCALE_VALUE_U16(((parameters->d2) + 0x8000) & 0xffff, 11); + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[2] = d1 >> 3; + slot->current_cmd[3] = d2 >> 3; + slot->current_cmd[4] = (SCALE_COEFF(parameters->k2, 4) << 4) + SCALE_COEFF(parameters->k1, 4); + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + // dbg_hid("spring: %i %i %i %i %i %i %i %i %i\n", + // parameters->d1, parameters->d2, parameters->k1, parameters->k2, parameters->clip, + // slot->current_cmd[2], slot->current_cmd[3], slot->current_cmd[4], slot->current_cmd[6]); + break; + case FF_DAMPER: + case FF_INERTIA: + case FF_FRICTION: + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 4); + slot->current_cmd[4] = SCALE_COEFF(parameters->k2, 4); + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + // dbg_hid("damper/friction/inertia: 0x%x %i %i %i %i %i %i %i %i\n", + // slot->effect_type, parameters->d1, parameters->d2, parameters->k1, parameters->k2, parameters->clip, + // slot->current_cmd[2], slot->current_cmd[4], slot->current_cmd[6]); + break; + } + + // check if slot needs to be updated + for(i = 0; i < 7; i++) { + if (original_cmd[i] != slot->current_cmd[i]) { + slot->is_updated = 1; + break; + } + } +} + +static __always_inline int ftecff_calculate_constant(struct ftecff_effect_state *state) +{ + int level = state->effect.u.constant.level; + int level_sign; + long d, t; + + if (state->time_playing < state->envelope->attack_length) { + level_sign = level < 0 ? -1 : 1; + d = level - level_sign * state->envelope->attack_level; + level = level_sign * state->envelope->attack_level + d * state->time_playing / state->envelope->attack_length; + } else if (state->effect.replay.length) { + t = state->time_playing - state->effect.replay.length + state->envelope->fade_length; + if (t > 0) { + level_sign = level < 0 ? -1 : 1; + d = level - level_sign * state->envelope->fade_level; + level = level - d * t / state->envelope->fade_length; + } + } + + return state->direction_gain * level / 0x7fff; +} + +static __always_inline int ftecff_calculate_periodic(struct ftecff_effect_state *state) +{ + struct ff_periodic_effect *periodic = &state->effect.u.periodic; + int level = periodic->offset; + int magnitude = periodic->magnitude; + int magnitude_sign = magnitude < 0 ? -1 : 1; + long d, t; + + if (state->time_playing < state->envelope->attack_length) { + d = magnitude - magnitude_sign * state->envelope->attack_level; + magnitude = magnitude_sign * state->envelope->attack_level + d * state->time_playing / state->envelope->attack_length; + } else if (state->effect.replay.length) { + t = state->time_playing - state->effect.replay.length + state->envelope->fade_length; + if (t > 0) { + d = magnitude - magnitude_sign * state->envelope->fade_level; + magnitude = magnitude - d * t / state->envelope->fade_length; + } + } + + switch (periodic->waveform) { + case FF_SINE: + level += fixp_sin16(state->phase) * magnitude / 0x7fff; + break; + case FF_SQUARE: + level += (state->phase < 180 ? 1 : -1) * magnitude; + break; + case FF_TRIANGLE: + level += abs(state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude; + break; + case FF_SAW_UP: + level += state->phase * magnitude * 2 / 360 - magnitude; + break; + case FF_SAW_DOWN: + level += magnitude - state->phase * magnitude * 2 / 360; + break; + } + + return state->direction_gain * level / 0x7fff; +} + +static __always_inline void ftecff_calculate_spring(struct ftecff_effect_state *state, struct ftecff_effect_parameters *parameters) +{ + struct ff_condition_effect *condition = &state->effect.u.condition[0]; + + parameters->d1 = ((int)condition->center) - condition->deadband / 2; + parameters->d2 = ((int)condition->center) + condition->deadband / 2; + parameters->k1 = condition->left_coeff; + parameters->k2 = condition->right_coeff; + parameters->clip = (unsigned)condition->right_saturation; +} + +static __always_inline void ftecff_calculate_resistance(struct ftecff_effect_state *state, struct ftecff_effect_parameters *parameters) +{ + struct ff_condition_effect *condition = &state->effect.u.condition[0]; + + parameters->k1 = condition->left_coeff; + parameters->k2 = condition->right_coeff; + parameters->clip = (unsigned)condition->right_saturation; +} + +static __always_inline int ftecff_timer(struct ftec_drv_data *drv_data) +{ + struct ftecff_slot *slot; + struct ftecff_effect_state *state; + struct ftecff_effect_parameters parameters[5]; + unsigned long jiffies_now = jiffies; + unsigned long now = JIFFIES2MS(jiffies_now); + unsigned long flags; + unsigned int gain; + int count; + int effect_id; + int i; + + + memset(parameters, 0, sizeof(parameters)); + + gain = 0xffff; + + spin_lock_irqsave(&drv_data->timer_lock, flags); + + count = drv_data->effects_used; + + for (effect_id = 0; effect_id < FTECFF_MAX_EFFECTS; effect_id++) { + + if (!count) { + break; + } + + state = &drv_data->states[effect_id]; + + if (!test_bit(FF_EFFECT_STARTED, &state->flags)) { + continue; + } + + count--; + + if (test_bit(FF_EFFECT_ALLSET, &state->flags)) { + if (state->effect.replay.length && time_after_eq(now, state->stop_at)) { + STOP_EFFECT(state); + if (!--state->count) { + drv_data->effects_used--; + continue; + } + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->start_at = state->stop_at; + } + } + + ftecff_update_state(state, now); + + if (!test_bit(FF_EFFECT_PLAYING, &state->flags)) { + continue; + } + + switch (state->effect.type) { + case FF_CONSTANT: + parameters[0].level += ftecff_calculate_constant(state); + break; + case FF_SPRING: + ftecff_calculate_spring(state, ¶meters[1]); + break; + case FF_DAMPER: + ftecff_calculate_resistance(state, ¶meters[2]); + break; + case FF_INERTIA: + ftecff_calculate_resistance(state, ¶meters[3]); + break; + case FF_FRICTION: + ftecff_calculate_resistance(state, ¶meters[4]); + break; + case FF_PERIODIC: + parameters[0].level += ftecff_calculate_periodic(state); + break; + } + } + + spin_unlock_irqrestore(&drv_data->timer_lock, flags); + + parameters[0].level = (long)parameters[0].level * gain / 0xffff; + parameters[1].clip = (long)parameters[1].clip * spring_level / 100; + parameters[2].clip = (long)parameters[2].clip * damper_level / 100; + parameters[3].clip = (long)parameters[3].clip * inertia_level / 100; + parameters[4].clip = (long)parameters[4].clip * friction_level / 100; + + for (i = 1; i < 5; i++) { + parameters[i].k1 = (long)parameters[i].k1 * gain / 0xffff; + parameters[i].k2 = (long)parameters[i].k2 * gain / 0xffff; + parameters[i].clip = (long)parameters[i].clip * gain / 0xffff; + } + + for (i = 0; i < 5; i++) { + slot = &drv_data->slots[i]; + ftecff_update_slot(slot, ¶meters[i], drv_data->quirks & FTEC_HIGHRES); + if (slot->is_updated) { + ftecff_send_cmd(drv_data, slot->current_cmd); + slot->is_updated = 0; + } + } + + return 0; +} + +static enum hrtimer_restart ftecff_timer_hires(struct hrtimer *t) +{ + struct ftec_drv_data *drv_data = container_of(t, struct ftec_drv_data, hrtimer); + int delay_timer; + int overruns; + + delay_timer = ftecff_timer(drv_data); + + if (delay_timer) { + hrtimer_forward_now(&drv_data->hrtimer, ms_to_ktime(delay_timer)); + return HRTIMER_RESTART; + } + + if (drv_data->effects_used) { + overruns = hrtimer_forward_now(&drv_data->hrtimer, ms_to_ktime(timer_msecs)); + overruns--; + if (unlikely(profile && overruns > 0)) + DEBUG("Overruns: %d", overruns); + return HRTIMER_RESTART; + } else { + if (unlikely(profile)) + DEBUG("Stop timer."); + return HRTIMER_NORESTART; + } +} + +static void ftecff_init_slots(struct ftec_drv_data *drv_data) +{ + struct ftecff_effect_parameters parameters; + int i; + + memset(&drv_data->states, 0, sizeof(drv_data->states)); + memset(&drv_data->slots, 0, sizeof(drv_data->slots)); + memset(¶meters, 0, sizeof(parameters)); + + drv_data->slots[0].effect_type = FF_CONSTANT; + drv_data->slots[1].effect_type = FF_SPRING; + drv_data->slots[2].effect_type = FF_DAMPER; + drv_data->slots[3].effect_type = FF_INERTIA; + drv_data->slots[4].effect_type = FF_FRICTION; + + drv_data->slots[0].cmd = 0x08; + drv_data->slots[1].cmd = 0x0b; + drv_data->slots[2].cmd = 0x0c; + drv_data->slots[3].cmd = 0x0c; + drv_data->slots[4].cmd = 0x0c; + + for (i = 0; i < 5; i++) { + drv_data->slots[i].id = i; + ftecff_update_slot(&drv_data->slots[i], ¶meters, drv_data->quirks & FTEC_HIGHRES); + ftecff_send_cmd(drv_data, drv_data->slots[i].current_cmd); + drv_data->slots[i].is_updated = 0; + } +} + +static void ftecff_stop_effects(struct ftec_drv_data *drv_data) +{ + u8 cmd[7] = {0}; + + cmd[0] = 0xf3; + ftecff_send_cmd(drv_data, cmd); +} + +static int ftecff_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old) +{ + struct hid_device *hdev = input_get_drvdata(dev); + struct ftec_drv_data *drv_data = hid_get_drvdata(hdev); + struct ftecff_effect_state *state; + unsigned long now = JIFFIES2MS(jiffies); + unsigned long flags; + + state = &drv_data->states[effect->id]; + + if (0) { + struct ff_condition_effect *condition = &effect->u.condition[0]; + printk("id %u, state %lu, delay %u, interval %u, type %#02x, effect direction %#04x", effect->id, state->flags, effect->replay.delay, effect->trigger.interval, effect->type, effect->direction); + if (effect->type == FF_PERIODIC) { + printk(KERN_CONT ", magnitude %i, offset %i, phase %#02x, period %u\n", effect->u.periodic.magnitude, effect->u.periodic.offset, effect->u.periodic.phase, effect->u.periodic.period); + } else { + printk(KERN_CONT ", level %i, left_coeff %i, right_coeff %i, left_saturation %i, right_saturation %i\n", effect->u.constant.level, condition->left_coeff, condition->right_coeff, condition->left_saturation, condition->right_saturation); + } + // return 0; + } + + if (effect->type == FF_PERIODIC && effect->u.periodic.period == 0) { + return -EINVAL; + } + + if (test_bit(FF_EFFECT_STARTED, &state->flags) && effect->type != state->effect.type) { + return -EINVAL; + } + + spin_lock_irqsave(&drv_data->timer_lock, flags); + + state->effect = *effect; + + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + __set_bit(FF_EFFECT_UPDATING, &state->flags); + state->updated_at = now; + } + + spin_unlock_irqrestore(&drv_data->timer_lock, flags); + + return 0; +} + +static int ftecff_play_effect(struct input_dev *dev, int effect_id, int value) +{ + struct hid_device *hdev = input_get_drvdata(dev); + struct ftec_drv_data *drv_data = hid_get_drvdata(hdev); + struct ftecff_effect_state *state; + unsigned long now = JIFFIES2MS(jiffies); + unsigned long flags; + + state = &drv_data->states[effect_id]; + + spin_lock_irqsave(&drv_data->timer_lock, flags); + + if (value > 0) { + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + STOP_EFFECT(state); + } else { + drv_data->effects_used++; + if (!hrtimer_active(&drv_data->hrtimer)) { + hrtimer_start(&drv_data->hrtimer, ms_to_ktime(timer_msecs), HRTIMER_MODE_REL); + if (unlikely(profile)) + DEBUG("Start timer."); + } + } + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->start_at = now; + state->count = value; + } else { + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + STOP_EFFECT(state); + drv_data->effects_used--; + } + } + + spin_unlock_irqrestore(&drv_data->timer_lock, flags); + + return 0; +} + +static void ftecff_destroy(struct ff_device *ff) +{ +} + +int ftecff_init(struct hid_device *hdev) { + struct ftec_drv_data *drv_data = hid_get_drvdata(hdev); + struct hid_input *hidinput = list_entry(hdev->inputs.next, struct hid_input, list); + struct input_dev *inputdev = hidinput->input; + struct ff_device *ff; + unsigned long flags; + int ret,j; + s32 *value; + + dbg_hid(" ... setting FF bits"); + for (j = 0; ftecff_wheel_effects[j] >= 0; j++) + set_bit(ftecff_wheel_effects[j], inputdev->ffbit); + + ret = input_ff_create(hidinput->input, FTECFF_MAX_EFFECTS); + if (ret) { + hid_err(hdev, "Unable to create ff: %i\n", ret); + return ret; + } + + ff = hidinput->input->ff; + ff->upload = ftecff_upload_effect; + ff->playback = ftecff_play_effect; + ff->destroy = ftecff_destroy; + + /* init sequence */ + { + /* tuning menu initialization? */ + if (drv_data->quirks & FTEC_TUNING_MENU) { + u8 buf[] = { 0xff, 0x08, 0x01, 0xff, 0x0, 0x0, 0x0, 0x0 }; + ret = hid_hw_output_report(hdev, &buf[0], 8); + buf[2] = 0x2; + buf[3] = 0x0; + ret = hid_hw_output_report(hdev, &buf[0], 8); + // FIXME: does this work to fetch current tuning menu values? + buf[1] = 0x03; + buf[2] = 0x02; + ret = hid_hw_output_report(hdev, &buf[0], 8); + } + + /* common initialization? */ + spin_lock_irqsave(&drv_data->report_lock, flags); + value = drv_data->report->field[0]->value; + value[0] = 0xf8; + value[1] = 0x09; + value[2] = 0x01; + value[3] = 0x06; + value[4] = 0xff; + value[5] = 0x01; + value[6] = 0x00; + send_report_request_to_device(drv_data); + value[5] = 0x0; + send_report_request_to_device(drv_data); + value[5] = 0x4; + send_report_request_to_device(drv_data); + spin_unlock_irqrestore(&drv_data->report_lock, flags); + } + + /* Set range so that centering spring gets disabled */ + if (init_range > 0 && (init_range > drv_data->max_range || init_range < drv_data->min_range)) { + hid_warn(hdev, "Invalid init_range %i; using max range of %i instead\n", init_range, drv_data->max_range); + init_range = -1; + } + drv_data->range = init_range > 0 ? init_range : drv_data->max_range; + ftec_set_range(hdev, drv_data->range); + + /* Create sysfs interface */ +#define CREATE_SYSFS_FILE(name) \ + ret = device_create_file(&hdev->dev, &dev_attr_##name); \ + if (ret) \ + hid_warn(hdev, "Unable to create sysfs interface for '%s', errno %d\n", #name, ret); \ + + CREATE_SYSFS_FILE(display) + CREATE_SYSFS_FILE(range) + CREATE_SYSFS_FILE(wheel_id) + + if (drv_data->quirks & FTEC_TUNING_MENU) { + ftec_tuning_classdev_register(&hdev->dev, &drv_data->tuning); + } + +#if IS_REACHABLE(CONFIG_LEDS_CLASS) + if (ftec_init_led(hdev)) + hid_err(hdev, "LED init failed\n"); /* Let the driver continue without LEDs */ +#endif + + drv_data->effects_used = 0; + + ftecff_init_slots(drv_data); + spin_lock_init(&drv_data->timer_lock); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6,15,0) + hrtimer_init(&drv_data->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + drv_data->hrtimer.function = ftecff_timer_hires; +#else + hrtimer_setup(&drv_data->hrtimer, ftecff_timer_hires, CLOCK_MONOTONIC, HRTIMER_MODE_REL); +#endif + hid_info(hdev, "Hires timer: period = %d ms", timer_msecs); + + return 0; +} + + +void ftecff_remove(struct hid_device *hdev) +{ + struct ftec_drv_data *drv_data = hid_get_drvdata(hdev); + + hrtimer_cancel(&drv_data->hrtimer); + ftecff_stop_effects(drv_data); + + device_remove_file(&hdev->dev, &dev_attr_display); + device_remove_file(&hdev->dev, &dev_attr_range); + device_remove_file(&hdev->dev, &dev_attr_wheel_id); + +#define REMOVE_SYSFS_FILE(name) device_remove_file(&hdev->dev, &dev_attr_##name); \ + + if (drv_data->quirks & FTEC_TUNING_MENU) { + ftec_tuning_classdev_unregister(&drv_data->tuning); + } + +#if IS_REACHABLE(CONFIG_LEDS_CLASS) + { + int j; + struct led_classdev *led; + + /* Deregister LEDs (if any) */ + for (j = 0; j < LEDS; j++) { + + led = drv_data->led[j]; + drv_data->led[j] = NULL; + if (!led) + continue; + led_classdev_unregister(led); + kfree(led); + } + } +#endif +} + +int ftecff_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { + struct ftec_drv_data *drv_data = hid_get_drvdata(hdev); + if (data[0] == 0xff && size == FTEC_TUNING_REPORT_SIZE && data[1] == 0x03) { + // shift by 1 so that we can use this as the buffer when writing back to the device + memcpy(&drv_data->tuning.ftec_tuning_data[0] + 1, data, sizeof(drv_data->tuning.ftec_tuning_data) - 1); + drv_data->tuning.advanced_mode = !(drv_data->tuning.ftec_tuning_data[3] & 0x80); + drv_data->tuning.ftec_tuning_data[3] = drv_data->tuning.ftec_tuning_data[3]&0xf; + // notify userspace about value change + if (!(IS_ERR_OR_NULL(drv_data->tuning.dev))) + kobject_uevent(&drv_data->tuning.dev->kobj, KOBJ_CHANGE); + } else if (data[0] == 0x01) { + // TODO: detect wheel change and react on it in some way? + bool changed = drv_data->wheel_id != data[0x1f]; + drv_data->wheel_id = data[0x1f]; + // notify userspace about value change + if (changed) + kobject_uevent(&hdev->dev.kobj, KOBJ_CHANGE); + } + return 0; +} # ---------------------------------------- # Module: framework # Version: 6164bc3dec24 # ---------------------------------------- diff --git a/drivers/custom/framework/Makefile b/drivers/custom/framework/Makefile new file mode 100644 index 000000000000..968eb7877b2e --- /dev/null +++ b/drivers/custom/framework/Makefile @@ -0,0 +1,15 @@ +ifneq ($(KERNELRELEASE),) +# kbuild part of makefile +obj-m := framework_laptop.o + +else +# normal makefile +KDIR ?= /lib/modules/`uname -r`/build + +modules: + +%: + $(MAKE) -C $(KDIR) M=$$PWD $@ + +endif + diff --git a/drivers/custom/framework/framework_laptop.c b/drivers/custom/framework/framework_laptop.c new file mode 100644 index 000000000000..b13fa49388ab --- /dev/null +++ b/drivers/custom/framework/framework_laptop.c @@ -0,0 +1,841 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Framework Laptop ACPI Driver + * + * Copyright (C) 2022 Dustin L. Howett + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRV_NAME "framework_laptop" +#define FRAMEWORK_LAPTOP_EC_DEVICE_NAME "cros-ec-dev" + +static struct platform_device *fwdevice; +static struct device *ec_device; +struct framework_data { + struct platform_device *pdev; + struct led_classdev kb_led; + struct device *hwmon_dev; +}; + +#define EC_CMD_CHARGE_LIMIT_CONTROL 0x3E03 + +enum ec_chg_limit_control_modes { + /* Disable all setting, charge control by charge_manage */ + CHG_LIMIT_DISABLE = BIT(0), + /* Set maximum and minimum percentage */ + CHG_LIMIT_SET_LIMIT = BIT(1), + /* Host read current setting */ + CHG_LIMIT_GET_LIMIT = BIT(3), + /* Enable override mode, allow charge to full this time */ + CHG_LIMIT_OVERRIDE = BIT(7), +}; + +struct ec_params_ec_chg_limit_control { + /* See enum ec_chg_limit_control_modes */ + uint8_t modes; + uint8_t max_percentage; + uint8_t min_percentage; +} __ec_align1; + +struct ec_response_chg_limit_control { + uint8_t max_percentage; + uint8_t min_percentage; +} __ec_align1; + +#define EC_CMD_PRIVACY_SWITCHES_CHECK_MODE 0x3E14 + +struct ec_response_privacy_switches_check { + uint8_t microphone; + uint8_t camera; +} __ec_align1; + +static int charge_limit_control(enum ec_chg_limit_control_modes modes, uint8_t max_percentage) { + struct { + struct cros_ec_command msg; + union { + struct ec_params_ec_chg_limit_control params; + struct ec_response_chg_limit_control resp; + }; + } __packed buf; + struct ec_params_ec_chg_limit_control *params = &buf.params; + struct ec_response_chg_limit_control *resp = &buf.resp; + struct cros_ec_command *msg = &buf.msg; + struct cros_ec_device *ec; + int ret; + + if (!ec_device) + return -ENODEV; + + ec = dev_get_drvdata(ec_device); + + memset(&buf, 0, sizeof(buf)); + + msg->version = 0; + msg->command = EC_CMD_CHARGE_LIMIT_CONTROL; + msg->outsize = sizeof(*params); + msg->insize = sizeof(*resp); + + params->modes = modes; + params->max_percentage = max_percentage; + + ret = cros_ec_cmd_xfer_status(ec, msg); + if (ret < 0) { + return -EIO; + } + + return resp->max_percentage; +} + +// Get the last set keyboard LED brightness +static enum led_brightness kb_led_get(struct led_classdev *led) +{ + struct { + struct cros_ec_command msg; + union { + struct ec_params_pwm_get_duty p; + struct ec_response_pwm_get_duty resp; + }; + } __packed buf; + + struct ec_params_pwm_get_duty *p = &buf.p; + struct ec_response_pwm_get_duty *resp = &buf.resp; + struct cros_ec_command *msg = &buf.msg; + struct cros_ec_device *ec; + int ret; + if (!ec_device) + goto out; + + ec = dev_get_drvdata(ec_device); + + memset(&buf, 0, sizeof(buf)); + + p->pwm_type = EC_PWM_TYPE_KB_LIGHT; + + msg->version = 0; + msg->command = EC_CMD_PWM_GET_DUTY; + msg->insize = sizeof(*resp); + msg->outsize = sizeof(*p); + + ret = cros_ec_cmd_xfer_status(ec, msg); + if (ret < 0) { + goto out; + } + + return resp->duty * 100 / EC_PWM_MAX_DUTY; + +out: + return 0; +} + +// Set the keyboard LED brightness +static int kb_led_set(struct led_classdev *led, enum led_brightness value) +{ + struct { + struct cros_ec_command msg; + union { + struct ec_params_pwm_set_keyboard_backlight params; + }; + } __packed buf; + + struct ec_params_pwm_set_keyboard_backlight *params = &buf.params; + struct cros_ec_command *msg = &buf.msg; + struct cros_ec_device *ec; + int ret; + + if (!ec_device) + return -EIO; + + ec = dev_get_drvdata(ec_device); + + memset(&buf, 0, sizeof(buf)); + + msg->version = 0; + msg->command = EC_CMD_PWM_SET_KEYBOARD_BACKLIGHT; + msg->insize = 0; + msg->outsize = sizeof(*params); + + params->percent = value; + + ret = cros_ec_cmd_xfer_status(ec, msg); + if (ret < 0) { + return -EIO; + } + + return 0; +} + + +static ssize_t battery_get_threshold(char *buf) +{ + int ret; + + ret = charge_limit_control(CHG_LIMIT_GET_LIMIT, 0); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", (int)ret); +} + +static ssize_t battery_set_threshold(const char *buf, size_t count) +{ + int ret; + int value; + + ret = kstrtouint(buf, 10, &value); + if (ret) + return ret; + + if (value > 100) + return -EINVAL; + + ret = charge_limit_control(CHG_LIMIT_SET_LIMIT, (uint8_t)value); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t charge_control_end_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return battery_get_threshold(buf); +} + +static ssize_t charge_control_end_threshold_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return battery_set_threshold(buf, count); +} + +static DEVICE_ATTR_RW(charge_control_end_threshold); + +static struct attribute *framework_laptop_battery_attrs[] = { + &dev_attr_charge_control_end_threshold.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(framework_laptop_battery); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 2, 0) +static int framework_laptop_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) +#else +static int framework_laptop_battery_add(struct power_supply *battery) +#endif +{ + // Framework EC only supports 1 battery + if (strcmp(battery->desc->name, "BAT1") != 0) + return -ENODEV; + + if (device_add_groups(&battery->dev, framework_laptop_battery_groups)) + return -ENODEV; + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 2, 0) +static int framework_laptop_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook) +#else +static int framework_laptop_battery_remove(struct power_supply *battery) +#endif +{ + device_remove_groups(&battery->dev, framework_laptop_battery_groups); + return 0; +} + +// --- fanN_input --- +// Read the current fan speed from the EC's memory +static ssize_t ec_get_fan_speed(u8 idx, u16 *val) +{ + if (!ec_device) + return -ENODEV; + + struct cros_ec_device *ec = dev_get_drvdata(ec_device); + + const u8 offset = EC_MEMMAP_FAN + 2 * idx; + + return ec->cmd_readmem(ec, offset, sizeof(*val), val); +} + +static ssize_t fw_fan_speed_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr); + + u16 val; + if (ec_get_fan_speed(sen_attr->index, &val) < 0) { + return -EIO; + } + + if (val == EC_FAN_SPEED_NOT_PRESENT || val == EC_FAN_SPEED_STALLED) { + return sysfs_emit(buf, "%u\n", 0); + } + + // Format as string for sysfs + return sysfs_emit(buf, "%u\n", val); +} + +// --- fanN_target --- +static ssize_t ec_set_target_rpm(u8 idx, u32 *val) +{ + int ret; + if (!ec_device) + return -ENODEV; + + struct cros_ec_device *ec = dev_get_drvdata(ec_device); + + struct ec_params_pwm_set_fan_target_rpm_v1 params = { + .rpm = *val, + .fan_idx = idx, + }; + + ret = cros_ec_cmd(ec, 1, EC_CMD_PWM_SET_FAN_TARGET_RPM, ¶ms, + sizeof(params), NULL, 0); + if (ret < 0) + return -EIO; + + return 0; +} + +static ssize_t ec_get_target_rpm(u8 idx, u32 *val) +{ + int ret; + if (!ec_device) + return -ENODEV; + + struct cros_ec_device *ec = dev_get_drvdata(ec_device); + + struct ec_response_pwm_get_fan_rpm resp; + + // index isn't supported, it should only return fan 0's target + + ret = cros_ec_cmd(ec, 0, EC_CMD_PWM_GET_FAN_TARGET_RPM, NULL, 0, &resp, + sizeof(resp)); + if (ret < 0) + return -EIO; + + *val = resp.rpm; + + return 0; +} + +static ssize_t fw_fan_target_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr); + u32 val; + + int err; + err = kstrtou32(buf, 10, &val); + if (err < 0) + return err; + + if (ec_set_target_rpm(sen_attr->index, &val) < 0) { + return -EIO; + } + + return count; +} + +static ssize_t fw_fan_target_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr); + + // Only fan 0 is supported + if (sen_attr->index != 0) { + return -EINVAL; + } + + u32 val; + if (ec_get_target_rpm(sen_attr->index, &val) < 0) { + return -EIO; + } + + // Format as string for sysfs + return sysfs_emit(buf, "%u\n", val); +} + +// --- fanN_fault --- +static ssize_t fw_fan_fault_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr); + + u16 val; + if (ec_get_fan_speed(sen_attr->index, &val) < 0) { + return -EIO; + } + + // Format as string for sysfs + return sysfs_emit(buf, "%u\n", val == EC_FAN_SPEED_NOT_PRESENT); +} + +// --- fanN_alarm --- +static ssize_t fw_fan_alarm_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr); + + u16 val; + if (ec_get_fan_speed(sen_attr->index, &val) < 0) { + return -EIO; + } + + // Format as string for sysfs + return sysfs_emit(buf, "%u\n", val == EC_FAN_SPEED_STALLED); +} + +// --- pwmN_enable --- +static ssize_t ec_set_auto_fan_ctrl(u8 idx) +{ + int ret; + if (!ec_device) + return -ENODEV; + + struct cros_ec_device *ec = dev_get_drvdata(ec_device); + + struct ec_params_auto_fan_ctrl_v1 params = { + .fan_idx = idx, + }; + + ret = cros_ec_cmd(ec, 1, EC_CMD_THERMAL_AUTO_FAN_CTRL, ¶ms, + sizeof(params), NULL, 0); + if (ret < 0) + return -EIO; + + return 0; +} + +static ssize_t fw_pwm_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr); + + // The EC doesn't take any arguments for this command, + // so we don't need to parse the buffer + // u32 val; + + // int err; + // err = kstrtou32(buf, 10, &val); + // if (err < 0) + // return err; + + if (ec_set_auto_fan_ctrl(sen_attr->index) < 0) { + return -EIO; + } + + return count; +} + +// --- pwmN --- +static ssize_t ec_set_fan_duty(u8 idx, u32 *val) +{ + int ret; + if (!ec_device) + return -ENODEV; + + struct cros_ec_device *ec = dev_get_drvdata(ec_device); + + struct ec_params_pwm_set_fan_duty_v1 params = { + .percent = *val, + .fan_idx = idx, + }; + + ret = cros_ec_cmd(ec, 1, EC_CMD_PWM_SET_FAN_DUTY, ¶ms, + sizeof(params), NULL, 0); + if (ret < 0) + return -EIO; + + return 0; +} + +static ssize_t fw_pwm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr); + u32 val; + + int err; + err = kstrtou32(buf, 10, &val); + if (err < 0) + return err; + + if (ec_set_fan_duty(sen_attr->index, &val) < 0) { + return -EIO; + } + + return count; +} + +static ssize_t fw_pwm_min_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%i\n", 0); +} + +static ssize_t fw_pwm_max_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%i\n", 100); +} + +static ssize_t ec_count_fans(size_t *val) +{ + if (!ec_device) + return -ENODEV; + + struct cros_ec_device *ec = dev_get_drvdata(ec_device); + + u16 fans[EC_FAN_SPEED_ENTRIES]; + + int ret = ec->cmd_readmem(ec, EC_MEMMAP_FAN, sizeof(fans), fans); + if (ret < 0) + return -EIO; + + for (size_t i = 0; i < EC_FAN_SPEED_ENTRIES; i++) { + if (fans[i] == EC_FAN_SPEED_NOT_PRESENT) { + *val = i; + return 0; + } + } + + *val = EC_FAN_SPEED_ENTRIES; + return 0; +} + +// --- framework_privacy --- +static ssize_t framework_privacy_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + if (!ec_device) + return -ENODEV; + + struct cros_ec_device *ec = dev_get_drvdata(ec_device); + + struct ec_response_privacy_switches_check resp; + + ret = cros_ec_cmd(ec, 0, EC_CMD_PRIVACY_SWITCHES_CHECK_MODE, NULL, 0, + &resp, sizeof(resp)); + if (ret < 0) + return -EIO; + + // Output following dell-privacy's format + return sysfs_emit(buf, "[Microphone] [%s]\n[Camera] [%s]\n", + resp.microphone ? "unmuted" : "muted", + resp.camera ? "unmuted" : "muted"); +} + +#define FW_ATTRS_PER_FAN 8 + +// --- hwmon sysfs attributes --- +// clang-format off +static SENSOR_DEVICE_ATTR_RO(fan1_input, fw_fan_speed, 0); // Fan Reading +static SENSOR_DEVICE_ATTR_RW(fan1_target, fw_fan_target, 0); // Target RPM (RW on fan 0 only) +static SENSOR_DEVICE_ATTR_RO(fan1_fault, fw_fan_fault, 0); // Fan Not Present +static SENSOR_DEVICE_ATTR_RO(fan1_alarm, fw_fan_alarm, 0); // Fan Stalled +static SENSOR_DEVICE_ATTR_WO(pwm1_enable, fw_pwm_enable, 0); // Set Fan Control Mode +static SENSOR_DEVICE_ATTR_WO(pwm1, fw_pwm, 0); // Set Fan Speed +static SENSOR_DEVICE_ATTR_RO(pwm1_min, fw_pwm_min, 0); // Min Fan Speed +static SENSOR_DEVICE_ATTR_RO(pwm1_max, fw_pwm_max, 0); // Max Fan Speed +// clang-format on + +static SENSOR_DEVICE_ATTR_RO(fan2_input, fw_fan_speed, 1); +static SENSOR_DEVICE_ATTR_WO(fan2_target, fw_fan_target, 1); +static SENSOR_DEVICE_ATTR_RO(fan2_fault, fw_fan_fault, 1); +static SENSOR_DEVICE_ATTR_RO(fan2_alarm, fw_fan_alarm, 1); +static SENSOR_DEVICE_ATTR_WO(pwm2_enable, fw_pwm_enable, 1); +static SENSOR_DEVICE_ATTR_WO(pwm2, fw_pwm, 1); +static SENSOR_DEVICE_ATTR_RO(pwm2_min, fw_pwm_min, 1); +static SENSOR_DEVICE_ATTR_RO(pwm2_max, fw_pwm_max, 1); + +static SENSOR_DEVICE_ATTR_RO(fan3_input, fw_fan_speed, 2); +static SENSOR_DEVICE_ATTR_WO(fan3_target, fw_fan_target, 2); +static SENSOR_DEVICE_ATTR_RO(fan3_fault, fw_fan_fault, 2); +static SENSOR_DEVICE_ATTR_RO(fan3_alarm, fw_fan_alarm, 2); +static SENSOR_DEVICE_ATTR_WO(pwm3_enable, fw_pwm_enable, 2); +static SENSOR_DEVICE_ATTR_WO(pwm3, fw_pwm, 2); +static SENSOR_DEVICE_ATTR_RO(pwm3_min, fw_pwm_min, 2); +static SENSOR_DEVICE_ATTR_RO(pwm3_max, fw_pwm_max, 2); + +static SENSOR_DEVICE_ATTR_RO(fan4_input, fw_fan_speed, 3); +static SENSOR_DEVICE_ATTR_WO(fan4_target, fw_fan_target, 3); +static SENSOR_DEVICE_ATTR_RO(fan4_fault, fw_fan_fault, 3); +static SENSOR_DEVICE_ATTR_RO(fan4_alarm, fw_fan_alarm, 3); +static SENSOR_DEVICE_ATTR_WO(pwm4_enable, fw_pwm_enable, 3); +static SENSOR_DEVICE_ATTR_WO(pwm4, fw_pwm, 3); +static SENSOR_DEVICE_ATTR_RO(pwm4_min, fw_pwm_min, 3); +static SENSOR_DEVICE_ATTR_RO(pwm4_max, fw_pwm_max, 3); + +static struct attribute + *fw_hwmon_attrs[(EC_FAN_SPEED_ENTRIES * FW_ATTRS_PER_FAN) + 1] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_target.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_min.dev_attr.attr, + &sensor_dev_attr_pwm1_max.dev_attr.attr, + + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_target.dev_attr.attr, + &sensor_dev_attr_fan2_fault.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_min.dev_attr.attr, + &sensor_dev_attr_pwm2_max.dev_attr.attr, + + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_target.dev_attr.attr, + &sensor_dev_attr_fan3_fault.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm3_min.dev_attr.attr, + &sensor_dev_attr_pwm3_max.dev_attr.attr, + + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan4_target.dev_attr.attr, + &sensor_dev_attr_fan4_fault.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + &sensor_dev_attr_pwm4_enable.dev_attr.attr, + &sensor_dev_attr_pwm4.dev_attr.attr, + &sensor_dev_attr_pwm4_min.dev_attr.attr, + &sensor_dev_attr_pwm4_max.dev_attr.attr, + + NULL, + }; + +ATTRIBUTE_GROUPS(fw_hwmon); + +// --- generic sysfs attributes --- +static DEVICE_ATTR_RO(framework_privacy); + +static struct attribute *framework_laptop_attrs[] = { + &dev_attr_framework_privacy.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(framework_laptop); + +// --- platform driver --- +static struct acpi_battery_hook framework_laptop_battery_hook = { + .add_battery = framework_laptop_battery_add, + .remove_battery = framework_laptop_battery_remove, + .name = "Framework Laptop Battery Extension", +}; + +static const struct acpi_device_id device_ids[] = { + {"FRMW0001", 0}, + {"FRMW0004", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, device_ids); + +static const struct dmi_system_id framework_laptop_dmi_table[] __initconst = { + { + /* the Framework Laptop */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Framework"), + DMI_MATCH(DMI_PRODUCT_NAME, "Laptop"), + }, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(dmi, framework_laptop_dmi_table); + +static int device_match_cros_ec(struct device *dev, const void* foo) { + const char* name = dev_name(dev); + if (strncmp(name, "cros-ec-dev", 11)) + return 0; + return 1; +} + +static int framework_probe(struct platform_device *pdev) +{ + struct device *dev; + struct framework_data *data; + int ret = 0; + + dev = &pdev->dev; + + ec_device = bus_find_device(&platform_bus_type, NULL, NULL, device_match_cros_ec); + if (!ec_device) { + dev_err(dev, DRV_NAME ": failed to find EC %s.\n", FRAMEWORK_LAPTOP_EC_DEVICE_NAME); + return -EINVAL; + } + ec_device = ec_device->parent; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + data->pdev = pdev; + + data->kb_led.name = DRV_NAME "::kbd_backlight"; + data->kb_led.brightness_get = kb_led_get; + data->kb_led.brightness_set_blocking = kb_led_set; + data->kb_led.max_brightness = 100; + ret = devm_led_classdev_register(&pdev->dev, &data->kb_led); + if (ret) + return ret; + +#if 0 + /* Register the driver */ + ret = platform_driver_register(&cros_ec_lpc_driver); + if (ret) { + pr_err(DRV_NAME ": can't register driver: %d\n", ret); + return ret; + } + + /* Register the device, and it'll get hooked up automatically */ + ret = platform_device_register(&cros_ec_lpc_device); + if (ret) { + pr_err(DRV_NAME ": can't register device: %d\n", ret); + platform_driver_unregister(&cros_ec_lpc_driver); + } +#endif + + struct cros_ec_device *ec = dev_get_drvdata(ec_device); + if (ec->cmd_readmem) { + // Count the number of fans + size_t fan_count; + if (ec_count_fans(&fan_count) < 0) { + dev_err(dev, DRV_NAME ": failed to count fans.\n"); + return -EINVAL; + } + // NULL terminates the list after the last detected fan + fw_hwmon_attrs[fan_count * FW_ATTRS_PER_FAN] = NULL; + + data->hwmon_dev = hwmon_device_register_with_groups( + dev, DRV_NAME, NULL, fw_hwmon_groups); + if (IS_ERR(data->hwmon_dev)) + return PTR_ERR(data->hwmon_dev); + + } else { + dev_err(dev, DRV_NAME ": fan readings could not be enabled for this EC %s.\n", + FRAMEWORK_LAPTOP_EC_DEVICE_NAME); + } + + battery_hook_register(&framework_laptop_battery_hook); + + return ret; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0) +static void framework_remove(struct platform_device *pdev) +#else +static int framework_remove(struct platform_device *pdev) +#endif +{ + struct framework_data *data; + + data = (struct framework_data *)platform_get_drvdata(pdev); + + battery_hook_unregister(&framework_laptop_battery_hook); + + // Make sure it's not null before we try to unregister it + if (data && data->hwmon_dev) + hwmon_device_unregister(data->hwmon_dev); + + put_device(ec_device); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0) + return; +#else + return 0; +#endif +} + +static struct platform_driver framework_driver = { + .driver = { + .name = DRV_NAME, + .acpi_match_table = device_ids, + .dev_groups = framework_laptop_groups, + }, + .probe = framework_probe, + .remove = framework_remove, +}; + +static int __init framework_laptop_init(void) +{ + int ret; + + if (!dmi_check_system(framework_laptop_dmi_table)) { + pr_err(DRV_NAME ": unsupported system.\n"); + return -ENODEV; + } + + ret = platform_driver_register(&framework_driver); + if (ret) + goto fail; + + fwdevice = platform_device_alloc(DRV_NAME, PLATFORM_DEVID_NONE); + if (!fwdevice) + { + ret = -ENOMEM; + goto fail_platform_driver; + } + + ret = platform_device_add(fwdevice); + if (ret) + goto fail_device_add; + + return 0; + +fail_device_add: + platform_device_del(fwdevice); + fwdevice = NULL; + +fail_platform_driver: + platform_driver_unregister(&framework_driver); + +fail: + return ret; +} + +static void __exit framework_laptop_exit(void) +{ + if (fwdevice) + { + platform_device_unregister(fwdevice); + platform_driver_unregister(&framework_driver); + } +} + +module_init(framework_laptop_init); +module_exit(framework_laptop_exit); + +MODULE_DESCRIPTION("Framework Laptop Platform Driver"); +MODULE_AUTHOR("Dustin L. Howett "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_SOFTDEP("pre: cros_ec_lpcs"); # ---------------------------------------- # Module: gcadapter_oc # Version: 0a7d7027d0dc # ---------------------------------------- diff --git a/drivers/custom/gcadapter_oc/Makefile b/drivers/custom/gcadapter_oc/Makefile new file mode 100644 index 000000000000..aa794b3b8478 --- /dev/null +++ b/drivers/custom/gcadapter_oc/Makefile @@ -0,0 +1,10 @@ +obj-m += gcadapter_oc.o +ccflags-y := -std=gnu99 -Wno-declaration-after-statement +KERNEL_SOURCE_DIR := /lib/modules/$(shell uname -r)/build + +all: + make -C "$(KERNEL_SOURCE_DIR)" M="$(PWD)" modules + +clean: + make -C "$(KERNEL_SOURCE_DIR)" M="$(PWD)" clean + diff --git a/drivers/custom/gcadapter_oc/gcadapter_oc.c b/drivers/custom/gcadapter_oc/gcadapter_oc.c new file mode 100644 index 000000000000..dd4d98e34c84 --- /dev/null +++ b/drivers/custom/gcadapter_oc/gcadapter_oc.c @@ -0,0 +1,153 @@ +#include +#include +#include +#include + +#define GCADAPTER_VID 0x057e +#define GCADAPTER_PID 0x0337 + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Hannes Mann"); +MODULE_DESCRIPTION("Filter kernel module to set the polling rate of the Wii U/Mayflash GameCube Adapter to a custom value."); +MODULE_VERSION("1.4"); + +static struct usb_device* adapter_device = NULL; + +static unsigned short restore_interval = 8; +static unsigned short configured_interval = 1; + +/* Patches all applicable endpoints. Returns the bInterval value used before patching. */ +static unsigned short patch_endpoints(unsigned short interval) { + static unsigned short old_interval = 8; + + if(adapter_device != NULL && adapter_device->actconfig != NULL) { + struct usb_interface* interface = adapter_device->actconfig->interface[0]; + + if(interface != NULL) { + for(unsigned int altsetting = 0; altsetting < interface->num_altsetting; altsetting++) { + struct usb_host_interface* altsettingptr = &interface->altsetting[altsetting]; + + for(__u8 endpoint = 0; endpoint < altsettingptr->desc.bNumEndpoints; endpoint++) { + if(altsettingptr->endpoint[endpoint].desc.bEndpointAddress == 0x81 || altsettingptr->endpoint[endpoint].desc.bEndpointAddress == 0x02) { + old_interval = altsettingptr->endpoint[endpoint].desc.bInterval; + altsettingptr->endpoint[endpoint].desc.bInterval = interval; + + printk(KERN_INFO "gcadapter_oc: bInterval value of endpoint 0x%.2x set to %u.\n", altsettingptr->endpoint[endpoint].desc.bEndpointAddress, interval); + } + } + } + + /* + * Attempt to lock the device. + * This is required by the kernel documentation but it seems that some systems won't let you lock the USB device. + * Older versions before 1.2 never called this function and still worked so we proceed even if locking fails. + */ + int ret = usb_lock_device_for_reset(adapter_device, NULL); + if(ret) { + printk(KERN_ERR "gcadapter_oc: Warning! Failed to acquire lock for USB device (error: %d). Resetting device anyway...\n", ret); + } + /* TODO: It might be possible to make the new bInterval value take effect without calling usb_reset_device? */ + if(usb_reset_device(adapter_device)) { + printk(KERN_ERR "gcadapter_oc: Could not reset device (error: %d). bInterval value was NOT changed.\n", ret); + } + /* Only unlock the device if usb_lock_device_for_reset succeeded. */ + if(!ret) { + usb_unlock_device(adapter_device); + } + } + } + + return old_interval; +} + +static int on_usb_notify(struct notifier_block* self, unsigned long action, void* _device) { + struct usb_device* device = _device; + + switch(action) { + case USB_DEVICE_ADD: + if(device->descriptor.idVendor == GCADAPTER_VID && device->descriptor.idProduct == GCADAPTER_PID && adapter_device == NULL) { + adapter_device = device; + printk(KERN_INFO "gcadapter_oc: Adapter connected\n"); + + restore_interval = patch_endpoints(configured_interval); + } + break; + + case USB_DEVICE_REMOVE: + if(adapter_device == device) { + adapter_device = NULL; + printk(KERN_INFO "gcadapter_oc: Adapter disconnected\n"); + } + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block usb_nb = { .notifier_call = on_usb_notify }; + +static int usb_device_cb(struct usb_device* device, void* data) { + if(device->descriptor.idVendor == GCADAPTER_VID && device->descriptor.idProduct == GCADAPTER_PID && adapter_device == NULL) { + adapter_device = device; + printk(KERN_INFO "gcadapter_oc: Adapter connected\n"); + + restore_interval = patch_endpoints(configured_interval); + } + + return 0; +} + +static int __init on_module_init(void) { + if(configured_interval > 255) { + printk(KERN_WARNING "gcadapter_oc: Invalid interval parameter specified.\n"); + configured_interval = 255; + } + + if(configured_interval == 0) { + printk(KERN_WARNING "gcadapter_oc: Invalid interval parameter specified.\n"); + configured_interval = 1; + } + + usb_for_each_dev(NULL, &usb_device_cb); + usb_register_notify(&usb_nb); + + return 0; +} + +static void __exit on_module_exit(void) { + if(adapter_device != NULL) { + patch_endpoints(restore_interval); + } + + usb_unregister_notify(&usb_nb); +} + +module_init(on_module_init); +module_exit(on_module_exit); + +static int on_interval_changed(const char* value, const struct kernel_param* kp) { + int ret = param_set_ushort(value, kp); + + if(!ret) { + if(configured_interval > 255) { + printk(KERN_WARNING "gcadapter_oc: Invalid interval parameter specified.\n"); + configured_interval = 255; + } + else if(configured_interval == 0) { + printk(KERN_WARNING "gcadapter_oc: Invalid interval parameter specified.\n"); + configured_interval = 1; + } + + patch_endpoints(configured_interval); + } + + return ret; +} + +static struct kernel_param_ops interval_ops = { + .set = &on_interval_changed, + .get = ¶m_get_ushort +}; + +module_param_cb(rate, &interval_ops, &configured_interval, 0644); +MODULE_PARM_DESC(rate, "Polling rate (default: 1)"); # ---------------------------------------- # Module: hid-tmff2 # Version: 26995429a3b9 # ---------------------------------------- diff --git a/drivers/custom/hid-tmff2/Kbuild b/drivers/custom/hid-tmff2/Kbuild new file mode 100644 index 000000000000..8cabb2b4c189 --- /dev/null +++ b/drivers/custom/hid-tmff2/Kbuild @@ -0,0 +1,8 @@ +obj-m := hid-tmff-new.o +hid-tmff-new-y := \ + src/hid-tmff2.o \ + src/tmt300rs/hid-tmt300rs.o \ + src/tmt248/hid-tmt248.o \ + src/tmtx/hid-tmtx.o \ + src/tmtsxw/hid-tmtsxw.o \ + src/tmtspc/hid-tmtspc.o diff --git a/drivers/custom/hid-tmff2/Makefile b/drivers/custom/hid-tmff2/Makefile new file mode 100644 index 000000000000..ad52621189ae --- /dev/null +++ b/drivers/custom/hid-tmff2/Makefile @@ -0,0 +1,24 @@ +KDIR ?= /lib/modules/$(shell uname -r)/build + +all: deps/hid-tminit + $(MAKE) -C $(KDIR) M=$(shell pwd) modules + +install: deps/hid-tminit + $(MAKE) -C $(KDIR) M=$(shell pwd) modules_install + depmod -A + +.PHONY: udev-rules +udev-rules: + install -m 0644 udev/99-thrustmaster.rules /etc/udev/rules.d/ + +.PHONY: steamdeck-rules +steamdeck-rules: + install -m 0644 udev/71-thrustmaster-steamdeck.rules /etc/udev/rules.d/ + +clean: deps/hid-tminit + $(MAKE) -C $(KDIR) M=$(shell pwd) clean + + +.PHONY: deps/hid-tminit +deps/hid-tminit: + $(MAKE) -C deps/hid-tminit KDIR="$(KDIR)" $(MAKECMDGOALS) diff --git a/drivers/custom/hid-tmff2/src/hid-tmff2.c b/drivers/custom/hid-tmff2/src/hid-tmff2.c new file mode 100644 index 000000000000..162a30afdc28 --- /dev/null +++ b/drivers/custom/hid-tmff2/src/hid-tmff2.c @@ -0,0 +1,831 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include "hid-tmff2.h" + + +int open_mode = 1; +module_param(open_mode, int, 0660); +MODULE_PARM_DESC(open_mode, + "Whether to send mode change commands on open/close"); + +int timer_msecs = DEFAULT_TIMER_PERIOD; +module_param(timer_msecs, int, 0660); +MODULE_PARM_DESC(timer_msecs, + "Timer resolution in msecs"); + +/* should these be removed and just rely on /sys? */ +int spring_level = 30; +module_param(spring_level, int, 0); +MODULE_PARM_DESC(spring_level, + "Level of spring force (0-100), as per Oversteer standards"); + +int damper_level = 30; +module_param(damper_level, int, 0); +MODULE_PARM_DESC(damper_level, + "Level of damper force (0-100), as per Oversteer standards"); + +int friction_level = 30; +module_param(friction_level, int, 0); +MODULE_PARM_DESC(friction_level, + "Level of friction force (0-100), as per Oversteer standards"); + +int range = 900; +module_param(range, int, 0); +MODULE_PARM_DESC(range, + "Range of wheel, depends on the wheel. Invalid values are ignored"); + +int alt_mode = 0; +module_param(alt_mode, int, 0); +MODULE_PARM_DESC(alt_mode, + "Alternate mode, eg. F1 mode"); + +#define GAIN_MAX 65535 +int gain = 40000; +module_param(gain, int, 0); +MODULE_PARM_DESC(gain, + "Level of gain (0-65535)"); + +static spinlock_t lock; + +static struct tmff2_device_entry *tmff2_from_hdev(struct hid_device *hdev) +{ + unsigned long lock_flags = 0; + struct tmff2_device_entry *tmff2; + spin_lock_irqsave(&lock, lock_flags); + + if (!(tmff2 = hid_get_drvdata(hdev))) + dev_err(&hdev->dev, "hdev private data not found\n"); + + spin_unlock_irqrestore(&lock, lock_flags); + + return tmff2; +} + +static struct tmff2_device_entry *tmff2_from_input(struct input_dev *input_dev) +{ + unsigned long lock_flags = 0; + struct hid_device *hdev; + spin_lock_irqsave(&lock, lock_flags); + + if (!(hdev = input_get_drvdata(input_dev))) + dev_err(&input_dev->dev, "input_dev private data not found\n"); + + spin_unlock_irqrestore(&lock, lock_flags); + + return tmff2_from_hdev(hdev); +} + +static ssize_t spring_level_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int value; + int ret; + + ret = kstrtouint(buf, 0, &value); + if (ret) { + dev_err(dev, "kstrtouint failed at spring_level_store: %i", ret); + return ret; + } + + if (value > 100) { + dev_info(dev, "value %i larger than max 100, clamping to 100.\n", value); + value = 100; + } + + spring_level = value; + + return count; +} + +static ssize_t spring_level_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%u\n", spring_level); +} +static DEVICE_ATTR_RW(spring_level); + +static ssize_t damper_level_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int value; + int ret; + + + ret = kstrtouint(buf, 0, &value); + if (ret) { + dev_err(dev, "kstrtouint failed at damper_level_store: %i", ret); + return ret; + } + + if (value > 100) { + dev_info(dev, "value %i larger than max 100, clamping to 100.\n", value); + value = 100; + } + + damper_level = value; + + return count; +} + +static ssize_t damper_level_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%u\n", damper_level); +} +static DEVICE_ATTR_RW(damper_level); + +static ssize_t friction_level_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int value; + int ret; + + + ret = kstrtouint(buf, 0, &value); + if (ret) { + dev_err(dev, "kstrtouint failed at friction_level_store: %i", ret); + return ret; + } + + if (value > 100) { + dev_info(dev, "value %i larger than max 100, clamping to 100.\n", value); + value = 100; + } + + friction_level = value; + + return count; +} + +static ssize_t friction_level_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%u\n", friction_level); +} +static DEVICE_ATTR_RW(friction_level); + +static ssize_t range_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); + unsigned int value; + int ret; + + if (!tmff2) + return -ENODEV; + + if ((ret = kstrtouint(buf, 0, &value))) { + hid_err(tmff2->hdev, "kstrtouint failed at range_store: %i", ret); + return ret; + } + + if (tmff2->set_range) { + if ((ret = tmff2->set_range(tmff2->data, value))) + return ret; + } + + return count; +} + +static ssize_t range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%u\n", range); +} +static DEVICE_ATTR_RW(range); + +static ssize_t alternate_modes_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); + + if (!tmff2) + return -ENODEV; + + if (tmff2->alt_mode_store) + return tmff2->alt_mode_store(tmff2->data, buf, count); + + return 0; +} + +static ssize_t alternate_modes_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); + + if (!tmff2) + return -ENODEV; + + if (tmff2->alt_mode_show) + return tmff2->alt_mode_show(tmff2->data, buf); + + return 0; +} +static DEVICE_ATTR_RW(alternate_modes); + +static ssize_t gain_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); + unsigned int value; + int ret; + + if (!tmff2) + return -ENODEV; + + if ((ret = kstrtouint(buf, 0, &value))) { + dev_err(dev, "kstrtouint failed at gain_store: %i", ret); + return ret; + } + + gain = value; + if (tmff2->set_gain) /* if we can, update gain immediately */ + tmff2->set_gain(tmff2->data, (GAIN_MAX * gain) / GAIN_MAX); + + return count; +} + +static ssize_t gain_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%i\n", gain); +} +static DEVICE_ATTR_RW(gain); + +static void tmff2_set_gain(struct input_dev *dev, uint16_t value) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); + + if (!tmff2) + return; + + if (!tmff2->set_gain) { + hid_err(tmff2->hdev, "missing set_gain\n"); + return; + } + + if (tmff2->set_gain(tmff2->data, (value * gain) / GAIN_MAX)) + hid_warn(tmff2->hdev, "unable to set gain\n"); +} + +static void tmff2_set_autocenter(struct input_dev *dev, uint16_t value) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); + + if (!tmff2) + return; + + if (!tmff2->set_autocenter) { + hid_err(tmff2->hdev, "missing set_autocenter\n"); + return; + } + + if (tmff2->set_autocenter(tmff2->data, value)) + hid_warn(tmff2->hdev, "unable to set autocenter\n"); +} + +static void tmff2_work_handler(struct work_struct *w) +{ + unsigned long lock_flags = 0; + struct delayed_work *dw = container_of(w, struct delayed_work, work); + struct tmff2_device_entry *tmff2 = container_of(dw, struct tmff2_device_entry, work); + struct tmff2_effect_state *state; + int max_count = 0, effect_id; + unsigned long time_now; + __u16 effect_delay, effect_length; + + + if (!tmff2) + return; + + for (effect_id = 0; effect_id < tmff2->max_effects; ++effect_id) { + unsigned long actions = 0; + struct tmff2_effect_state effect; + + time_now = JIFFIES2MS(jiffies); + + state = &tmff2->states[effect_id]; + + /* critical section for updating state flags, keep log of what + * actions to take after the critical section with actions */ + spin_lock_irqsave(&tmff2->lock, lock_flags); + + effect_delay = state->effect.replay.delay; + effect_length = state->effect.replay.length; + if (test_bit(FF_EFFECT_PLAYING, &state->flags) && effect_length) { + if ((time_now - state->start_time) >= + (effect_delay + effect_length) * state->count) { + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); + + state->count = 0; + } + } + + if (test_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags)) { + __set_bit(FF_EFFECT_QUEUE_UPLOAD, &actions); + __clear_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags); + /* if we're uploading an effect, it's bound to be up + * to date */ + __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); + } + + if (test_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags)) { + __set_bit(FF_EFFECT_QUEUE_UPDATE, &actions); + __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); + } + + if (test_bit(FF_EFFECT_QUEUE_START, &state->flags)) { + __set_bit(FF_EFFECT_QUEUE_START, &actions); + __clear_bit(FF_EFFECT_QUEUE_START, &state->flags); + /* effect is playing since we're started it right now */ + __set_bit(FF_EFFECT_PLAYING, &state->flags); + } + + if (test_bit(FF_EFFECT_QUEUE_STOP, &state->flags)) { + __set_bit(FF_EFFECT_QUEUE_STOP, &actions); + __clear_bit(FF_EFFECT_QUEUE_STOP, &state->flags); + + /* the effect can't be playing if we're stopped, aye? */ + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + } + + if (state->count > max_count) + max_count = state->count; + + /* copy effect state to local variable so we can pass it around + * after the atomic section */ + effect = *state; + + spin_unlock_irqrestore(&tmff2->lock, lock_flags); + + /* perform the identified actions */ + if (test_bit(FF_EFFECT_QUEUE_UPLOAD, &actions) + && tmff2->upload_effect(tmff2->data, &effect)) { + hid_warn(tmff2->hdev, "failed uploading effect\n"); + } + + if (test_bit(FF_EFFECT_QUEUE_UPDATE, &actions) + && tmff2->update_effect(tmff2->data, &effect)) { + hid_warn(tmff2->hdev, "failed updating effect\n"); + } + + if (test_bit(FF_EFFECT_QUEUE_START, &actions) + && tmff2->play_effect(tmff2->data, &effect)) { + hid_warn(tmff2->hdev, "failed starting effect\n"); + } + + if (test_bit(FF_EFFECT_QUEUE_STOP, &actions) + && tmff2->stop_effect(tmff2->data, &effect)) { + hid_warn(tmff2->hdev, "failed stopping effect\n"); + } + + + /* wait for each effect update to actually be sent out to avoid + * filling up usb output queue */ + hid_hw_wait(tmff2->hdev); + } + + if (max_count && tmff2->allow_scheduling) + schedule_delayed_work(&tmff2->work, msecs_to_jiffies(timer_msecs)); +} + +static void tmff2_rewrite_rumble(struct ff_effect *effect) +{ + /* this is more or less directly copied from + * https://elixir.bootlin.com/linux/latest/source/drivers/input/ff-core.c#L48 + * except we set the direction to east, to make sure the direction + * scaling doesn't do any funny business */ + int magnitude = 0; + if (effect->type != FF_RUMBLE) + return; + + magnitude = effect->u.rumble.strong_magnitude / 3 + + effect->u.rumble.weak_magnitude / 6; + + effect->type = FF_PERIODIC; + effect->direction = 16384; + effect->u.periodic.waveform = FF_SINE; + effect->u.periodic.period = 50; + effect->u.periodic.magnitude = magnitude; + effect->u.periodic.offset = 0; + effect->u.periodic.phase = 0; + effect->u.periodic.envelope.attack_length = 0; + effect->u.periodic.envelope.attack_level = 0; + effect->u.periodic.envelope.fade_length = 0; + effect->u.periodic.envelope.fade_level = 0; +} + +static int tmff2_upload(struct input_dev *dev, + struct ff_effect *effect, struct ff_effect *old) +{ + unsigned long lock_flags = 0; + struct tmff2_effect_state *state; + struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); + + if (!tmff2) + return -ENODEV; + + if (effect->type == FF_PERIODIC && effect->u.periodic.period == 0) + return -EINVAL; + + state = &tmff2->states[effect->id]; + + spin_lock_irqsave(&tmff2->lock, lock_flags); + + state->effect = *effect; + tmff2_rewrite_rumble(&state->effect); + + if (old) { + if (!test_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags)) { + state->old = *old; + tmff2_rewrite_rumble(&state->old); + } + + __set_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags); + } else { + __set_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags); + } + + spin_unlock_irqrestore(&tmff2->lock, lock_flags); + return 0; +} + +static int tmff2_play(struct input_dev *dev, int effect_id, int value) +{ + unsigned long lock_flags = 0; + struct tmff2_effect_state *state; + struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); + + if (!tmff2) + return -ENODEV; + + state = &tmff2->states[effect_id]; + if (!state) + return 0; + + spin_lock_irqsave(&tmff2->lock, lock_flags); + if (value > 0) { + state->count = value; + state->start_time = JIFFIES2MS(jiffies); + __set_bit(FF_EFFECT_QUEUE_START, &state->flags); + __clear_bit(FF_EFFECT_QUEUE_STOP, &state->flags); + } else { + __set_bit(FF_EFFECT_QUEUE_STOP, &state->flags); + __clear_bit(FF_EFFECT_QUEUE_START, &state->flags); + } + + spin_unlock_irqrestore(&tmff2->lock, lock_flags); + + if (!delayed_work_pending(&tmff2->work) && tmff2->allow_scheduling) + schedule_delayed_work(&tmff2->work, 0); + + return 0; +} + +static int tmff2_open(struct input_dev *dev) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); + + if (!tmff2) + return -ENODEV; + + if (tmff2->open) + return tmff2->open(tmff2->data, open_mode); + + hid_err(tmff2->hdev, "no open callback set\n"); + return -EINVAL; +} + +static void tmff2_close(struct input_dev *dev) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); + + if (!tmff2) + return; + + /* since we're closing the device, no need to continue feeding it new data */ + /* TODO: check somewhere that multiple users can't open us at the same + * time */ + cancel_delayed_work_sync(&tmff2->work); + + if (tmff2->close) { + tmff2->close(tmff2->data, open_mode); + return; + } + + hid_err(tmff2->hdev, "no close callback set\n"); +} + +static int tmff2_create_files(struct tmff2_device_entry *tmff2) +{ + struct device *dev = &tmff2->hdev->dev; + int ret; + + + /* could use short circuiting but this is more explicit */ + if (tmff2->params & PARAM_GAIN) { + if ((ret = device_create_file(dev, &dev_attr_gain))) { + hid_err(tmff2->hdev, "unable to create sysfs for gain\n"); + goto gain_err; + } + } + + if (tmff2->params & PARAM_ALT_MODE) { + if ((ret = device_create_file(dev, &dev_attr_alternate_modes))) { + hid_err(tmff2->hdev, "unable to create sysfs for alternate_modes\n"); + goto alt_err; + } + } + + if (tmff2->params & PARAM_RANGE) { + if ((ret = device_create_file(dev, &dev_attr_range))) { + hid_warn(tmff2->hdev, "unable to create sysfs for range\n"); + goto range_err; + } + } + + if (tmff2->params & PARAM_SPRING_LEVEL) { + if ((ret = device_create_file(dev, &dev_attr_spring_level))) { + hid_warn(tmff2->hdev, "unable to create sysfs for spring_level\n"); + goto spring_err; + } + } + + if (tmff2->params & PARAM_DAMPER_LEVEL) { + if ((ret = device_create_file(dev, &dev_attr_damper_level))) { + hid_warn(tmff2->hdev, "unable to create sysfs for damper_level\n"); + goto damper_err; + } + } + + if (tmff2->params & PARAM_FRICTION_LEVEL) { + if ((ret = device_create_file(dev, &dev_attr_friction_level))) { + hid_warn(tmff2->hdev, "unable to create sysfs for friction_level\n"); + goto friction_err; + } + } + + return 0; + +friction_err: + device_remove_file(dev, &dev_attr_damper_level); +damper_err: + device_remove_file(dev, &dev_attr_spring_level); +spring_err: + device_remove_file(dev, &dev_attr_range); +range_err: + device_remove_file(dev, &dev_attr_alternate_modes); +alt_err: + device_remove_file(dev, &dev_attr_gain); +gain_err: + return ret; +} + +static int tmff2_wheel_init(struct tmff2_device_entry *tmff2) +{ + int ret, i; + struct ff_device *ff; + + spin_lock_init(&lock); + spin_lock_init(&tmff2->lock); + INIT_DELAYED_WORK(&tmff2->work, tmff2_work_handler); + + /* get parameters etc from backend */ + if ((ret = tmff2->wheel_init(tmff2, open_mode))) + goto err; + + + tmff2->states = kzalloc(sizeof(struct tmff2_effect_state) * tmff2->max_effects, + GFP_KERNEL); + + if (!tmff2->states) { + ret = -ENOMEM; + goto err; + } + + /* set supported effects into input_dev->ffbit */ + for (i = 0; tmff2->supported_effects[i] >= 0; ++i) + __set_bit(tmff2->supported_effects[i], tmff2->input_dev->ffbit); + + /* tell ffb subsystem that we want to handle our own rumble effects, + * this is a (maybe kind of silly) workaround to the ffb subsystem + * setting the direction of rumble to 0, which in our case means 0 + * force. Instead, do exactly what the subsystem would do, but update + * the direction. + * + * It *might* be a good idea to change this in the subsystem proper, but + * I don't know if anyone relies on the rumble to be 0 degrees, consider + * this just a proof of concept for now */ + if (test_bit(FF_PERIODIC, tmff2->input_dev->ffbit)) + __set_bit(FF_RUMBLE, tmff2->input_dev->ffbit); + + /* create actual ff device*/ + if ((ret = input_ff_create(tmff2->input_dev, tmff2->max_effects))) { + hid_err(tmff2->hdev, "could not create input_ff\n"); + goto ff_err; + } + + /* set ff callbacks */ + ff = tmff2->input_dev->ff; + ff->upload = tmff2_upload; + ff->playback = tmff2_play; + + if (tmff2->open) + tmff2->input_dev->open = tmff2_open; + + if (tmff2->close) + tmff2->input_dev->close = tmff2_close; + + /* set defaults wherever possible */ + if (tmff2->set_gain) { + ff->set_gain = tmff2_set_gain; + tmff2->set_gain(tmff2->data, (GAIN_MAX * gain) / GAIN_MAX); + } + + if (tmff2->set_autocenter) + ff->set_autocenter = tmff2_set_autocenter; + + if (tmff2->set_range) + tmff2->set_range(tmff2->data, range); + + if (tmff2->switch_mode) + tmff2->switch_mode(tmff2->data, alt_mode); + + /* create files */ + if ((ret = tmff2_create_files(tmff2))) + goto file_err; + + tmff2->allow_scheduling = 1; + return 0; + +file_err: + input_ff_destroy(tmff2->input_dev); +ff_err: + kfree(tmff2->states); +err: + return ret; +} + +static int tmff2_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct tmff2_device_entry *tmff2 = + kzalloc(sizeof(struct tmff2_device_entry), GFP_KERNEL); + + int ret; + + + if (!tmff2) { + ret = -ENOMEM; + goto oom_err; + } + + tmff2->hdev = hdev; + hid_set_drvdata(tmff2->hdev, tmff2); + + switch (tmff2->hdev->product) { + case TMT300RS_PS3_NORM_ID: + case TMT300RS_PS3_ADV_ID: + case TMT300RS_PS4_NORM_ID: + if ((ret = t300rs_populate_api(tmff2))) + goto wheel_err; + break; + + case TMT248_PC_ID: + if ((ret = t248_populate_api(tmff2))) + goto wheel_err; + break; + + case TX_ACTIVE: + if ((ret = tx_populate_api(tmff2))) + goto wheel_err; + break; + case TSXW_ACTIVE: + if ((ret = tsxw_populate_api(tmff2))) + goto wheel_err; + break; + case TMTS_PC_RACER_ID: + if ((ret = tspc_populate_api(tmff2))) + goto wheel_err; + break; + default: + ret = -ENODEV; + goto wheel_err; + } + + if ((ret = hid_parse(tmff2->hdev))) { + hid_err(hdev, "parse failed\n"); + goto hid_err; + } + + if ((ret = hid_hw_start(tmff2->hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF))) { + hid_err(hdev, "hw start failed\n"); + goto hid_err; + } + + tmff2->input_dev = list_entry(hdev->inputs.next, struct hid_input, list)->input; + + if ((ret = tmff2_wheel_init(tmff2))) { + hid_err(hdev, "init failed\n"); + goto init_err; + } + + return 0; + +init_err: + hid_hw_stop(hdev); +hid_err: + tmff2->wheel_destroy(tmff2->data); +wheel_err: + kfree(tmff2); +oom_err: + return ret; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6,12,0) +static __u8 *tmff2_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +#else +static const __u8 *tmff2_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +#endif +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(hdev); + + if (!tmff2) /* not entirely sure what the best course of action would be here */ + return rdesc; + + if (tmff2->wheel_fixup) + return tmff2->wheel_fixup(hdev, rdesc, rsize); + + return rdesc; +} + +static void tmff2_remove(struct hid_device *hdev) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(hdev); + struct device *dev; + + if (!tmff2) + return; + + tmff2->allow_scheduling = 0; + cancel_delayed_work_sync(&tmff2->work); + + dev = &tmff2->hdev->dev; + if (tmff2->params & PARAM_FRICTION_LEVEL) + device_remove_file(dev, &dev_attr_friction_level); + + if (tmff2->params & PARAM_DAMPER_LEVEL) + device_remove_file(dev, &dev_attr_damper_level); + + if (tmff2->params & PARAM_SPRING_LEVEL) + device_remove_file(dev, &dev_attr_spring_level); + + if (tmff2->params & PARAM_RANGE) + device_remove_file(dev, &dev_attr_range); + + if (tmff2->params & PARAM_ALT_MODE) + device_remove_file(dev, &dev_attr_alternate_modes); + + if (tmff2->params & PARAM_GAIN) + device_remove_file(dev, &dev_attr_gain); + + hid_hw_stop(hdev); + tmff2->wheel_destroy(tmff2->data); + + kfree(tmff2->states); + kfree(tmff2); +} + +static const struct hid_device_id tmff2_devices[] = { + /* t300rs and variations */ + {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT300RS_PS3_NORM_ID)}, + {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT300RS_PS3_ADV_ID)}, + {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT300RS_PS4_NORM_ID)}, + /* t248 PC*/ + {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMT248_PC_ID)}, + /* tx */ + {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TX_ACTIVE)}, + /* TS-PC RACER */ + {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TMTS_PC_RACER_ID)}, + /* tsxw */ + {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, TSXW_ACTIVE)}, + + {} +}; +MODULE_DEVICE_TABLE(hid, tmff2_devices); + +static struct hid_driver tmff2_driver = { + .name = "tmff2", + .id_table = tmff2_devices, + .probe = tmff2_probe, + .remove = tmff2_remove, + .report_fixup = tmff2_report_fixup, +}; +module_hid_driver(tmff2_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/custom/hid-tmff2/src/hid-tmff2.h b/drivers/custom/hid-tmff2/src/hid-tmff2.h new file mode 100644 index 000000000000..6ad1d823aad8 --- /dev/null +++ b/drivers/custom/hid-tmff2/src/hid-tmff2.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __HID_TMFF2_H +#define __HID_TMFF2_H + +#include +#include +#include + +extern int timer_msecs; +extern int spring_level; +extern int damper_level; +extern int friction_level; +extern int range; +extern int gain; +extern int alt_mode; + +#define USB_VENDOR_ID_THRUSTMASTER 0x044f + +/* the wheel seems to only be capable of processing a certain number of + * interrupts per second, and if this value is too low the kernel urb buffer (or + * some buffer at least) fills up. Optimally I would figure out some way to + * space out the interrupts so that they all leave at regular intervals, but + * for now this is good enough, go slow enough that everything works. + */ +#define DEFAULT_TIMER_PERIOD 8 + +#define FF_EFFECT_QUEUE_UPLOAD 0 +#define FF_EFFECT_QUEUE_START 1 +#define FF_EFFECT_QUEUE_STOP 2 +#define FF_EFFECT_QUEUE_UPDATE 3 +#define FF_EFFECT_PLAYING 4 + +#define PARAM_SPRING_LEVEL (1 << 0) +#define PARAM_DAMPER_LEVEL (1 << 1) +#define PARAM_FRICTION_LEVEL (1 << 2) +#define PARAM_RANGE (1 << 3) +#define PARAM_ALT_MODE (1 << 4) +#define PARAM_GAIN (1 << 5) + +#undef fixp_sin16 +#define fixp_sin16(v) (((v % 360) > 180) ?\ + -(fixp_sin32((v % 360) - 180) >> 16)\ + : fixp_sin32(v) >> 16) + +#define JIFFIES2MS(jiffies) ((jiffies) * 1000 / HZ) + +struct tmff2_effect_state { + struct ff_effect effect; + struct ff_effect old; + + unsigned long flags; + unsigned long count; + unsigned long start_time; +}; + +struct tmff2_device_entry { + struct hid_device *hdev; + struct input_dev *input_dev; + + /* pointer to array */ + struct tmff2_effect_state *states; + + struct delayed_work work; + + spinlock_t lock; + + int allow_scheduling; + + /* fields relevant to each actual device (T300, T248...) */ + void *data; + unsigned long params; + unsigned long max_effects; + signed short supported_effects[FF_CNT]; + + /* obligatory callbacks */ + int (*play_effect)(void *data, const struct tmff2_effect_state *state); + int (*upload_effect)(void *data, const struct tmff2_effect_state *state); + int (*update_effect)(void *data, const struct tmff2_effect_state *state); + int (*stop_effect)(void *data, const struct tmff2_effect_state *state); + + int (*wheel_init)(struct tmff2_device_entry *tmff2, int open_mode); + int (*wheel_destroy)(void *data); + + /* optional callbacks */ + int (*open)(void *data, int); + int (*close)(void *data, int); + int (*set_gain)(void *data, uint16_t gain); + int (*set_range)(void *data, uint16_t range); + /* switch_mode is required to not do anything if we're alredy in the + * specified mode */ + int (*switch_mode)(void *data, uint16_t mode); + ssize_t (*alt_mode_show)(void *data, char *buf); + ssize_t (*alt_mode_store)(void *data, const char *buf, size_t count); + int (*set_autocenter)(void *data, uint16_t autocenter); + __u8 *(*wheel_fixup)(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize); + + /* void pointers are dangerous, I know, but in this case likely the + * best option... */ +}; + +/* external */ +int t300rs_populate_api(struct tmff2_device_entry *tmff2); +int t248_populate_api(struct tmff2_device_entry *tmff2); +int tx_populate_api(struct tmff2_device_entry *tmff2); +int tsxw_populate_api(struct tmff2_device_entry *tmff2); +int tspc_populate_api(struct tmff2_device_entry *tmff2); + +#define TMT300RS_PS3_NORM_ID 0xb66e +#define TMT300RS_PS3_ADV_ID 0xb66f +#define TMT300RS_PS4_NORM_ID 0xb66d + +#define TMT248_PC_ID 0xb696 + +#define TX_ACTIVE 0xb669 + +#define TSXW_ACTIVE 0xb692 + +#define TMTS_PC_RACER_ID 0xb689 + +/* APIs to different wheel families */ +/* T248 and TX at least uses the T300RS api, not sure if there are other wheels + * but that's why these functions are given global linkage */ + +struct t300rs_device_entry { + struct hid_device *hdev; + struct input_dev *input_dev; + struct hid_report *report; + struct hid_field *ff_field; + struct usb_device *usbdev; + + int (*open)(struct input_dev *dev); + void (*close)(struct input_dev *dev); + + int mode; + int attachment; + u8 buffer_length; + u8 *send_buffer; +}; + +int t300rs_play_effect(void *, const struct tmff2_effect_state *); +int t300rs_upload_effect(void *, const struct tmff2_effect_state *); +int t300rs_update_effect(void *, const struct tmff2_effect_state *); +int t300rs_stop_effect(void *, const struct tmff2_effect_state *); + +int t300rs_open(void *, int); +int t300rs_close(void *, int); +int t300rs_set_gain(void *, uint16_t); +int t300rs_set_range(void *, uint16_t); +int t300rs_set_autocenter(void *, uint16_t); + +int t300rs_send_buf(struct t300rs_device_entry *t300rs, u8 *send_buffer, size_t len); +int t300rs_send_int(struct t300rs_device_entry *t300rs); + +#endif diff --git a/drivers/custom/hid-tmff2/src/tmt248/hid-tmt248.c b/drivers/custom/hid-tmff2/src/tmt248/hid-tmt248.c new file mode 100644 index 000000000000..9ac43d9d5b93 --- /dev/null +++ b/drivers/custom/hid-tmff2/src/tmt248/hid-tmt248.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include "../hid-tmff2.h" + +#define T248_MAX_EFFECTS 16 +#define T248_BUFFER_LENGTH 63 + +static const u8 setup_0[64] = { 0x42, 0x01 }; +static const u8 setup_1[64] = { 0x0a, 0x04, 0x90, 0x03 }; +static const u8 setup_2[64] = { 0x0a, 0x04, 0x00, 0x0c }; +static const u8 setup_3[64] = { 0x0a, 0x04, 0x12, 0x10 }; +static const u8 setup_4[64] = { 0x0a, 0x04, 0x00, 0x06 }; +static const u8 setup_5[64] = { 0x0a, 0x04, 0x00, 0x0e }; +static const u8 setup_6[64] = { 0x0a, 0x04, 0x00, 0x0e, 0x01 }; +static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4, setup_5, setup_6 }; +static const unsigned int setup_arr_sizes[] = { + ARRAY_SIZE(setup_0), + ARRAY_SIZE(setup_1), + ARRAY_SIZE(setup_2), + ARRAY_SIZE(setup_3), + ARRAY_SIZE(setup_4), + ARRAY_SIZE(setup_5), + ARRAY_SIZE(setup_6) +}; + +static const unsigned long t248_params = + PARAM_SPRING_LEVEL + | PARAM_DAMPER_LEVEL + | PARAM_FRICTION_LEVEL + | PARAM_RANGE + | PARAM_GAIN + ; + +static const signed short t248_effects[] = { + FF_CONSTANT, + FF_RAMP, + FF_SPRING, + FF_DAMPER, + FF_FRICTION, + FF_INERTIA, + FF_PERIODIC, + FF_SINE, + FF_TRIANGLE, + FF_SQUARE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_AUTOCENTER, + FF_GAIN, + -1 +}; + +/* TODO: sort through this stuff */ +static u8 t248_pc_rdesc_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) TODO: clutch? */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) TODO: brake? */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x36, /* Usage (Slider) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x08, /* Report size (8) */ + 0x26, 0xff, 0x00, /* Logical maximum (255) */ + 0x46, 0xff, 0x00, /* Physical maximum (255) */ + 0x09, 0x40, /* Usage (Vx) TODO: what is this? */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x41, /* Usage (Vy) TODO: --||-- */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x33, /* Usage (Rx) TODO: --||-- */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x34, /* Usage (Ry) TODO: --||-- */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x32, /* Usage (Z) TODO: --||-- (gas?) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x37, /* Usage (Dial) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x1a, /* Usage maximum (13) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x1a, /* Report count (26) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x06, /* Report size (6) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Input (None) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x00, /* Logical maximum (256) */ + 0x46, 0xff, 0x00, /* Physical maximum (256) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static int t248_interrupts(struct t300rs_device_entry *t248) +{ + u8 *send_buf = kmalloc(256, GFP_KERNEL); + struct usb_interface *usbif = to_usb_interface(t248->hdev->dev.parent); + struct usb_host_endpoint *ep; + int ret, trans, b_ep, i; + + if (!send_buf) { + hid_err(t248->hdev, "failed allocating send buffer\n"); + return -ENOMEM; + } + + ep = &usbif->cur_altsetting->endpoint[1]; + b_ep = ep->desc.bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { + memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); + + ret = usb_interrupt_msg(t248->usbdev, + usb_sndintpipe(t248->usbdev, b_ep), + send_buf, setup_arr_sizes[i], + &trans, + USB_CTRL_SET_TIMEOUT); + + if (ret) { + hid_err(t248->hdev, "setup data couldn't be sent\n"); + goto err; + } + } + +err: + kfree(send_buf); + return ret; +} + +static int t248_wheel_destroy(void *data) +{ + struct t300rs_device_entry *t300rs = data; + + if (!t300rs) + return -ENODEV; + + kfree(t300rs->send_buffer); + kfree(t300rs); + return 0; +} + +static int t248_set_range(void *data, uint16_t value) +{ + struct t300rs_device_entry *t248 = data; + + if (value < 140) { + hid_info(t248->hdev, "value %i too small, clamping to 140\n", value); + value = 140; + } + + if (value > 900) { + hid_info(t248->hdev, "value %i too large, clamping to 900\n", value); + value = 900; + } + + return t300rs_set_range(data, value); +} + +static int t248_send_open(struct t300rs_device_entry *t248) +{ + int r1, r2; + t248->send_buffer[0] = 0x01; + t248->send_buffer[1] = 0x04; + if ((r1 = t300rs_send_int(t248))) + return r1; + + t248->send_buffer[0] = 0x01; + t248->send_buffer[1] = 0x05; + if ((r2 = t300rs_send_int(t248))) + return r2; + + return 0; +} + +static int t248_open(void *data, int open_mode) +{ + struct t300rs_device_entry *t248 = data; + + if (!t248) + return -ENODEV; + + if (open_mode) + t248_send_open(t248); + + return t248->open(t248->input_dev); +} + +static int t248_send_close(struct t300rs_device_entry *t248) +{ + int r1, r2; + t248->send_buffer[0] = 0x01; + t248->send_buffer[1] = 0x05; + if ((r1 = t300rs_send_int(t248))) + return r1; + + t248->send_buffer[0] = 0x01; + t248->send_buffer[1] = 0x00; + if ((r2 = t300rs_send_int(t248))) + return r2; + + return 0; +} + +static int t248_close(void *data, int open_mode) +{ + struct t300rs_device_entry *t248 = data; + + if (!t248) + return -ENODEV; + + if (open_mode) + t248_send_close(t248); + + t248->close(t248->input_dev); + return 0; +} + +static int t248_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) +{ + struct t300rs_device_entry *t248 = kzalloc(sizeof(struct t300rs_device_entry), + GFP_KERNEL); + struct list_head *report_list; + int ret; + + + if (!t248) { + ret = -ENOMEM; + goto t248_err; + } + + t248->hdev = tmff2->hdev; + t248->input_dev = tmff2->input_dev; + t248->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); + t248->buffer_length = T248_BUFFER_LENGTH; + + t248->send_buffer = kzalloc(t248->buffer_length, GFP_KERNEL); + if (!t248->send_buffer) { + ret = -ENOMEM; + goto send_err; + } + + report_list = &t248->hdev->report_enum[HID_OUTPUT_REPORT].report_list; + t248->report = list_entry(report_list->next, struct hid_report, list); + t248->ff_field = t248->report->field[0]; + + t248->open = t248->input_dev->open; + t248->close = t248->input_dev->close; + + if ((ret = t248_interrupts(t248))) + goto interrupt_err; + + /* everything went OK */ + tmff2->data = t248; + tmff2->params = t248_params; + tmff2->max_effects = T248_MAX_EFFECTS; + memcpy(tmff2->supported_effects, t248_effects, sizeof(t248_effects)); + + if (!open_mode) + t248_send_open(t248); + + hid_info(t248->hdev, "force feedback for T248\n"); + return 0; + +interrupt_err: +send_err: + kfree(t248); +t248_err: + hid_err(tmff2->hdev, "failed initializing T248\n"); + return ret; +} + +static __u8 *t248_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + rdesc = t248_pc_rdesc_fixed; + *rsize = sizeof(t248_pc_rdesc_fixed); + return rdesc; +} + +int t248_populate_api(struct tmff2_device_entry *tmff2) +{ + tmff2->play_effect = t300rs_play_effect; + tmff2->upload_effect = t300rs_upload_effect; + tmff2->update_effect = t300rs_update_effect; + tmff2->stop_effect = t300rs_stop_effect; + + tmff2->set_gain = t300rs_set_gain; + tmff2->set_autocenter = t300rs_set_autocenter; + /* T248 only has 900 degree range, instead of T300RS 1080 */ + tmff2->set_range = t248_set_range; + tmff2->wheel_fixup = t248_wheel_fixup; + + tmff2->open = t248_open; + tmff2->close = t248_close; + + tmff2->wheel_init = t248_wheel_init; + tmff2->wheel_destroy = t248_wheel_destroy; + + return 0; +} diff --git a/drivers/custom/hid-tmff2/src/tmt300rs/hid-tmt300rs.c b/drivers/custom/hid-tmff2/src/tmt300rs/hid-tmt300rs.c new file mode 100644 index 000000000000..00eecee5a7a9 --- /dev/null +++ b/drivers/custom/hid-tmff2/src/tmt300rs/hid-tmt300rs.c @@ -0,0 +1,1560 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include "../hid-tmff2.h" + +#define T300RS_MAX_EFFECTS 16 +#define T300RS_NORM_BUFFER_LENGTH 63 +#define T300RS_PS4_BUFFER_LENGTH 31 + +#define T300RS_DEFAULT_ATTACHMENT 0x06 +#define T300RS_F1_ATTACHMENT 0x03 + +static const unsigned long t300rs_params = + PARAM_SPRING_LEVEL + | PARAM_DAMPER_LEVEL + | PARAM_FRICTION_LEVEL + | PARAM_GAIN + | PARAM_RANGE + | PARAM_ALT_MODE + ; + +static const signed short t300rs_effects[] = { + FF_CONSTANT, + FF_RAMP, + FF_SPRING, + FF_DAMPER, + FF_FRICTION, + FF_INERTIA, + FF_PERIODIC, + FF_SINE, + FF_TRIANGLE, + FF_SQUARE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_AUTOCENTER, + FF_GAIN, + -1 +}; + +struct __packed t300rs_fw_response { + uint8_t unused1[2]; + uint8_t fw_version; + uint8_t unused2; +}; + + +struct __packed t300rs_packet_header { + uint8_t zero1; + uint8_t id; + uint8_t code; +}; + +struct __packed t300rs_setup_header { + uint8_t cmd; + uint8_t code; +}; + +struct __packed t300rs_packet_envelope { + uint16_t attack_length; + uint16_t attack_level; + uint16_t fade_length; + uint16_t fade_level; +}; + +struct __packed t300rs_packet_timing { + uint8_t start_marker; + uint16_t duration; + uint8_t zero1[2]; + uint16_t offset; + uint8_t zero2; + uint16_t end_marker; +}; + +struct usb_ctrlrequest t300rs_fw_request = { + .bRequestType = 0xc1, + .bRequest = 86, + .wValue = 0, + .wIndex = 0, + .wLength = 8 +}; + +struct t300rs_data { + unsigned long quirks; + void *device_props; +}; + +static u8 t300rs_rdesc_nrm_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) (Brake) */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x32, /* Usage (Z) (Gas) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) (Clutch) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x0d, /* Usage maximum (13) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x0d, /* Report count (13) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x0b, /* Report size (13) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Unit (None) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x7f, /* Logical maximum (32767) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x46, 0xff, 0x7f, /* Physical maximum (32767) */ + 0x36, 0x00, 0x80, /* Physical minimum (-32768) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static u8 t300rs_rdesc_adv_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x36, /* Usage (Slider) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) ? */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x19, /* Usage maximum (25) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x19, /* Report count (25) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x03, /* Report size (3) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Unit (None) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 (why?) */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x00, /* Logical maximum (255) */ + 0x46, 0xff, 0x00, /* Physical maximum (255) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (Mouse) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static u8 t300rs_rdesc_ps4_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x05, /* Usage (GamePad) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x85, 0x01, /* Report ID (1) */ + 0x09, 0x00, /* Usage (U) (was X) */ + 0x09, 0x00, /* Usage (U) (was Y) */ + 0x09, 0x00, /* Usage (U) (was Z) */ + 0x09, 0x00, /* Usage (U) (was Rz)*/ + 0x15, 0x00, /* Logical minimum (0) */ + 0x26, 0xff, 0x00, /* Logical maximum (255) */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x04, /* Report count (4) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x25, 0x07, /* Logical maximum (7)*/ + 0x35, 0x00, /* Physical minimum (0) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Input (None) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x0e, /* Usage maximum (14) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x0e, /* Report size (14) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x20, /* Usage (32) */ + 0x75, 0x06, /* Report size (6) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x00, /* Usage (U) (was Rx)*/ + 0x09, 0x00, /* Usage (U) (was Ry) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x26, 0xff, 0x00, /* Logical maximum (255) */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x02, /* Report count (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x05, 0x01, /* Usage page (Vendor 1) */ + /* constant zero? */ + 0x09, 0x00, /* Usage (33) */ + 0x95, 0x21, /* Report count (33) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* wheel */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* gas */ + 0x09, 0x31, /* Usage (Y) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* brake */ + 0x09, 0x32, /* Usage (Z) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* clutch */ + 0x09, 0x35, /* Usage (Rz) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* stick shifter (check model no) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x0f, /* Usage minimum (15) */ + 0x29, 0x17, /* Usage maximum (23) */ + 0x15, 0x00, /* Logical minimum (1) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x08, /* Report count (8) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* no clue */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x00, /* Usage (U) */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x0c, /* Report count (12) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + /* continue unmodified */ + 0x06, 0x00, 0xff, /* Usage page (Vendor defined 1) */ + 0x85, 0x60, /* Report ID (5) (change to 0x60?) */ + 0x09, 0x60, /* Usage (34) (change to 0x60?) */ + 0x95, 0x1f, /* Report count (31) () */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x03, /* Report ID (3) */ + 0x0a, 0x21, 0x27, /* ??? */ + 0x95, 0x2f, /* Report count (47) */ + 0xb1, 0x02, /* Feature (Data, Var, Abs) */ + 0xc0, /* End collection */ + + /* From here on out no clue */ + 0x06, 0xf0, + 0xff, 0x09, + 0x40, 0xa1, + 0x01, 0x85, + 0xf0, 0x09, + 0x47, 0x95, + 0x3f, 0xb1, + 0x02, 0x85, + 0xf1, 0x09, + 0x48, 0x95, + 0x3f, 0xb1, + 0x02, 0x85, + 0xf2, 0x09, + 0x49, 0x95, + 0x0f, 0xb1, + 0x02, 0x85, + 0xf3, 0x0a, + 0x01, 0x47, + 0x95, 0x07, + 0xb1, 0x02, + 0xc0, +}; + +static u8 condition_values[] = { + 0xfe, 0xff, 0xfe, 0xff, 0xfe, + 0xff, 0xfe, 0xff +}; + +static uint16_t t300rs_calculate_length(uint16_t length) +{ + /* translate infinite effect length from Linux's 0 to the wheel's 0xffff */ + if (length == 0) + return 0xffff; + + return length; +} + +static int16_t t300rs_calculate_constant_level(int16_t level, uint16_t direction) +{ + level = (level * fixp_sin16(direction * 360 / 0x10000)) / 0x7fff; + + /* the Windows driver uses the range [-16385;16381] */ + level = level / 2; + + return level; +} + +static void t300rs_calculate_periodic_values(struct ff_effect *effect) +{ + struct ff_periodic_effect *periodic = &effect->u.periodic; + int16_t headroom; + + periodic->magnitude = (periodic->magnitude * fixp_sin16(effect->direction * 360 / 0x10000)) / 0x7fff; + + if (periodic->magnitude < 0){ + /* the wheel handles positive magnitudes only */ + periodic->magnitude = -periodic->magnitude; + + /* to give the expected result 180 deg is added to the phase */ + periodic->phase = (periodic->phase + (0x10000 / 2)) % 0x10000; + } + + /* the interval [0; 32677[ is used by the wheel for the [0; 360[ degree phase shift */ + periodic->phase = periodic->phase * 32677 / 0x10000; + + headroom = 0x7fff - periodic->magnitude; + /* magnitude + offset cannot be outside the valid magnitude range, */ + /* otherwise the wheel behaves incorrectly */ + periodic->offset = clamp(periodic->offset, -headroom, headroom); +} + +static uint16_t t300rs_condition_max_saturation(uint16_t effect_type) +{ + if(effect_type == FF_SPRING) + return 0x6aa6; + + return 0x7ffc; +} + +static uint8_t t300rs_condition_effect_type(uint16_t effect_type) +{ + if(effect_type == FF_SPRING) + return 0x06; + + return 0x07; +} + +static int16_t t300rs_calculate_coefficient(int16_t coeff, uint16_t effect_type) +{ + uint8_t input_level; + + switch (effect_type) + { + case FF_SPRING: + input_level = spring_level; + break; + case FF_DAMPER: + input_level = damper_level; + break; + case FF_FRICTION: + input_level = friction_level; + break; + default: + input_level = 100; + break; + } + + return coeff * input_level / 100; +} + +static uint16_t t300rs_calculate_saturation(uint16_t sat, uint16_t effect_type) +{ + uint16_t max = t300rs_condition_max_saturation(effect_type); + + if(sat == 0) + return max; + + return sat * max / 0xffff; +} + +static void t300rs_calculate_deadband(int16_t *out_rband, int16_t *out_lband, + uint16_t deadband, int16_t offset) +{ + /* max deadband value is 0x7fff in either direction */ + /* deadband is the width of the deadzone, one direction is half of it */ + *out_rband = clamp(offset + (deadband / 2), -0x7fff, 0x7fff); + *out_lband = clamp(offset - (deadband / 2), -0x7fff, 0x7fff); +} + +static void t300rs_calculate_ramp_parameters(uint16_t *out_slope, + int16_t *out_center, + uint8_t *out_invert, + struct ff_effect *effect) +{ + struct ff_ramp_effect *ramp = &effect->u.ramp; + + int16_t start_level, end_level; + + start_level = (ramp->start_level * fixp_sin16(effect->direction * 360 / 0x10000)) / 0x7fff; + end_level = (ramp->end_level * fixp_sin16(effect->direction * 360 / 0x10000)) / 0x7fff; + + *out_slope = abs(start_level - end_level) / 2; + *out_center = (start_level + end_level) / 2; + + *out_invert = (start_level < end_level) ? 0x04 : 0x05; +} + +int t300rs_send_buf(struct t300rs_device_entry *t300rs, u8 *send_buffer, size_t len) +{ + int i; + /* check that send_buffer fits into our report */ + if (len > t300rs->buffer_length) + return -EINVAL; + + /* fill with actual data */ + for (i = 0; i < len; ++i) + t300rs->ff_field->value[i] = send_buffer[i]; + + /* fill the rest with zeroes */ + for (i = len; i < t300rs->buffer_length; ++i) + t300rs->ff_field->value[i] = 0; + + hid_hw_request(t300rs->hdev, t300rs->report, HID_REQ_SET_REPORT); + return 0; +} + +int t300rs_send_int(struct t300rs_device_entry *t300rs) +{ + t300rs_send_buf(t300rs, t300rs->send_buffer, t300rs->buffer_length); + memset(t300rs->send_buffer, 0, t300rs->buffer_length); + + return 0; +} + +static void t300rs_fill_header(struct t300rs_packet_header *packet_header, + uint8_t id, uint8_t code) +{ + packet_header->id = id + 1; + packet_header->code = code; +} + +int t300rs_play_effect(void *data, const struct tmff2_effect_state *state) +{ + struct t300rs_device_entry *t300rs = data; + struct __packed t300rs_packet_play { + struct t300rs_packet_header header; + uint8_t code; + uint16_t count; + } *play_packet = (struct t300rs_packet_play *)t300rs->send_buffer; + + int ret; + + + t300rs_fill_header(&play_packet->header, state->effect.id, 0x89); + play_packet->code = 0x41; + + if (state->count == 0 || state->count >= 65535) + play_packet->count = 0; + else + play_packet->count = cpu_to_le16(state->count); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed starting effect play\n"); + + return ret; +} + +int t300rs_stop_effect(void *data, const struct tmff2_effect_state *state) +{ + struct t300rs_device_entry *t300rs = data; + struct __packed t300rs_packet_stop { + struct t300rs_packet_header header; + uint8_t value; + } *stop_packet = (struct t300rs_packet_stop *)t300rs->send_buffer; + + int ret; + + + t300rs_fill_header(&stop_packet->header, state->effect.id, 0x89); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed stopping effect play\n"); + + return ret; +} + +static void t300rs_fill_envelope(struct t300rs_packet_envelope *packet_envelope, + struct ff_envelope *envelope) +{ + // Note: Minimal length limitations are not enforced, + // as testing shows that the wheel can handle lower values well + packet_envelope->attack_length = cpu_to_le16(envelope->attack_length); + packet_envelope->attack_level = cpu_to_le16(envelope->attack_level); + packet_envelope->fade_length = cpu_to_le16(envelope->fade_length); + packet_envelope->fade_level = cpu_to_le16(envelope->fade_level); +} + +static int t300rs_is_envelope_changed(struct ff_envelope *new, + struct ff_envelope *old) +{ + return new->attack_length != old->attack_length + || new->attack_level != old->attack_level + || new->fade_length != old->fade_length + || new->fade_level != old->fade_level; +} + +static void t300rs_fill_timing(struct t300rs_packet_timing *packet_timing, + uint16_t duration, uint16_t offset){ + packet_timing->start_marker = 0x4f; + + packet_timing->duration = cpu_to_le16(duration); + packet_timing->offset = cpu_to_le16(offset); + + packet_timing->end_marker = 0xffff; +} + +static int t300rs_update_constant(struct t300rs_device_entry *t300rs, + const struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_effect old = state->old; + struct ff_constant_effect constant = effect.u.constant; + struct ff_constant_effect constant_old = old.u.constant; + struct __packed t300rs_packet_mod_constant { + struct t300rs_packet_header header; + uint16_t magnitude; + struct t300rs_packet_envelope envelope; + uint8_t effect_type; + uint8_t update_type; + uint16_t duration; + uint16_t offset; + } *packet_mod_constant = (struct t300rs_packet_mod_constant *)t300rs->send_buffer; + + int ret = 0; + int16_t level, old_level; + uint16_t length, old_length; + + level = t300rs_calculate_constant_level(constant.level, effect.direction); + old_level = t300rs_calculate_constant_level(constant_old.level, old.direction); + + length = t300rs_calculate_length(effect.replay.length); + old_length = t300rs_calculate_length(old.replay.length); + + if (!t300rs_is_envelope_changed(&constant.envelope, &constant_old.envelope) + && level == old_level + && length == old_length + && effect.replay.delay == old.replay.delay) + return ret; + + t300rs_fill_header(&packet_mod_constant->header, effect.id, 0x6a); + packet_mod_constant->magnitude = cpu_to_le16(level); + + t300rs_fill_envelope(&packet_mod_constant->envelope, &constant.envelope); + + packet_mod_constant->effect_type = 0x00; + packet_mod_constant->update_type = 0x45; + packet_mod_constant->duration = cpu_to_le16(length); + packet_mod_constant->offset = cpu_to_le16(effect.replay.delay); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed modifying constant effect\n"); + + return ret; +} + +static int t300rs_update_ramp(struct t300rs_device_entry *t300rs, + const struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_effect old = state->old; + struct ff_ramp_effect ramp = effect.u.ramp; + struct ff_ramp_effect ramp_old = old.u.ramp; + struct __packed t300rs_packet_mod_ramp { + struct t300rs_packet_header header; + uint8_t type; + uint16_t slope; + uint16_t center; + uint16_t length; + struct t300rs_packet_envelope envelope; + uint8_t effect_type; + uint8_t update_type; + uint16_t length2; + uint16_t offset; + } *packet_mod_ramp = (struct t300rs_packet_mod_ramp *)t300rs->send_buffer; + + int ret = 0; + + uint8_t invert, old_invert; + uint16_t slope, old_slope, length, old_length; + int16_t center, old_center; + + t300rs_calculate_ramp_parameters(&slope, ¢er, &invert, &effect); + t300rs_calculate_ramp_parameters(&old_slope, &old_center, &old_invert, &old); + + length = t300rs_calculate_length(effect.replay.length); + old_length = t300rs_calculate_length(old.replay.length); + + if (!t300rs_is_envelope_changed(&ramp.envelope, &ramp_old.envelope) + && slope == old_slope + && center == old_center + && invert == old_invert + && length == old_length + && effect.replay.delay == old.replay.delay) + return ret; + + t300rs_fill_header(&packet_mod_ramp->header, effect.id, 0x6e); + packet_mod_ramp->type = 0x0b; + packet_mod_ramp->slope = cpu_to_le16(slope); + packet_mod_ramp->center = cpu_to_le16(center); + packet_mod_ramp->length = cpu_to_le16(length); + + t300rs_fill_envelope(&packet_mod_ramp->envelope, &ramp.envelope); + + packet_mod_ramp->effect_type = invert; + packet_mod_ramp->update_type = 0x45; + packet_mod_ramp->length2 = packet_mod_ramp->length; + packet_mod_ramp->offset = cpu_to_le16(effect.replay.delay); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed modifying ramp effect\n"); + + return ret; +} + +static int t300rs_update_condition(struct t300rs_device_entry *t300rs, + const struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_effect old = state->old; + struct ff_condition_effect cond = effect.u.condition[0]; + struct ff_condition_effect cond_old = old.u.condition[0]; + struct __packed t300rs_packet_mod_condition + { + struct t300rs_packet_header header; + uint16_t right_coeff; + uint16_t left_coeff; + uint16_t right_deadband; + uint16_t left_deadband; + uint16_t right_saturation; + uint16_t left_saturation; + uint8_t effect_type; + uint8_t update_type; + uint16_t duration; + uint16_t delay; + } *packet_mod_condition = (struct t300rs_packet_mod_condition *)t300rs->send_buffer; + + int ret = 0; + uint16_t duration, duration_old; + uint16_t right_sat, right_sat_old, left_sat, left_sat_old; + uint16_t right_coeff, right_coeff_old, left_coeff, left_coeff_old; + int16_t right_deadband, right_deadband_old, left_deadband, left_deadband_old; + + right_coeff = t300rs_calculate_coefficient(cond.right_coeff, effect.type); + right_coeff_old = t300rs_calculate_coefficient(cond_old.right_coeff, old.type); + + left_coeff = t300rs_calculate_coefficient(cond.left_coeff, effect.type); + left_coeff_old = t300rs_calculate_coefficient(cond_old.left_coeff, old.type); + + t300rs_calculate_deadband(&right_deadband, &left_deadband, + cond.deadband, cond.center); + t300rs_calculate_deadband(&right_deadband_old, &left_deadband_old, + cond_old.deadband, cond_old.center); + + right_sat = t300rs_calculate_saturation(cond.right_saturation, effect.type); + right_sat_old = t300rs_calculate_saturation(cond_old.right_saturation, old.type); + + left_sat = t300rs_calculate_saturation(cond.left_saturation, effect.type); + left_sat_old = t300rs_calculate_saturation(cond_old.left_saturation, old.type); + + duration = t300rs_calculate_length(effect.replay.length); + duration_old = t300rs_calculate_length(old.replay.length); + + if (right_coeff == right_coeff_old + && left_coeff == left_coeff_old + && right_deadband == right_deadband_old + && left_deadband == left_deadband_old + && right_sat == right_sat_old + && left_sat == left_sat_old + && duration == duration_old + && effect.replay.delay == old.replay.delay) + return ret; + + packet_mod_condition->right_coeff = cpu_to_le16(right_coeff); + packet_mod_condition->left_coeff = cpu_to_le16(left_coeff); + packet_mod_condition->right_deadband = cpu_to_le16(right_deadband); + packet_mod_condition->left_deadband = cpu_to_le16(left_deadband); + packet_mod_condition->right_saturation = cpu_to_le16(right_sat); + packet_mod_condition->left_saturation = cpu_to_le16(left_sat); + packet_mod_condition->effect_type = 0x06; + packet_mod_condition->update_type = 0x45; + packet_mod_condition->duration = cpu_to_le16(duration); + packet_mod_condition->delay = cpu_to_le16(effect.replay.delay); + + t300rs_fill_header(&packet_mod_condition->header, effect.id, 0x4c); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed modifying condition effect\n"); + + return ret; +} + +static int t300rs_update_periodic(struct t300rs_device_entry *t300rs, + const struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_effect old = state->old; + struct __packed t300rs_packet_mod_periodic { + struct t300rs_packet_header header; + uint8_t type; + uint16_t magnitude; + uint16_t offset; + uint16_t phase; + uint16_t period; + struct t300rs_packet_envelope envelope; + uint8_t effect_type; + uint8_t update_type; + uint16_t duration; + uint16_t play_offset; + } *packet_mod_periodic = (struct t300rs_packet_mod_periodic *)t300rs->send_buffer; + + struct ff_periodic_effect periodic, periodic_old; + int ret = 0; + uint16_t length, old_length; + + t300rs_calculate_periodic_values(&effect); + periodic = effect.u.periodic; + + t300rs_calculate_periodic_values(&old); + periodic_old = old.u.periodic; + + length = t300rs_calculate_length(effect.replay.length); + old_length = t300rs_calculate_length(old.replay.length); + + if (!t300rs_is_envelope_changed(&periodic.envelope, &periodic_old.envelope) + && periodic.magnitude == periodic_old.magnitude + && periodic.offset == periodic_old.offset + && periodic.phase == periodic_old.phase + && periodic.period == periodic_old.period + && length == old_length + && effect.replay.delay == old.replay.delay) + return ret; + + t300rs_fill_header(&packet_mod_periodic->header, effect.id, 0x6e); + packet_mod_periodic->type = 0x0f; + packet_mod_periodic->magnitude = cpu_to_le16(periodic.magnitude); + packet_mod_periodic->offset = cpu_to_le16(periodic.offset); + packet_mod_periodic->phase = cpu_to_le16(periodic.phase); + packet_mod_periodic->period = cpu_to_le16(periodic.period); + + t300rs_fill_envelope(&packet_mod_periodic->envelope, &periodic.envelope); + + packet_mod_periodic->effect_type = periodic.waveform - 0x57; + packet_mod_periodic->update_type = 0x45; + packet_mod_periodic->duration = cpu_to_le16(length); + packet_mod_periodic->play_offset = cpu_to_le16(effect.replay.delay); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed modifying periodic effect\n"); + + return ret; +} + +static int t300rs_upload_constant(struct t300rs_device_entry *t300rs, + const struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_constant_effect constant = state->effect.u.constant; + struct __packed t300rs_packet_constant { + struct t300rs_packet_header header; + uint16_t level; + struct t300rs_packet_envelope envelope; + uint8_t zero; + struct t300rs_packet_timing timing; + } *packet_constant = (struct t300rs_packet_constant *)t300rs->send_buffer; + + int16_t level; + uint16_t duration, offset; + + int ret; + + level = t300rs_calculate_constant_level(constant.level, effect.direction); + duration = t300rs_calculate_length(effect.replay.length); + + offset = effect.replay.delay; + + t300rs_fill_header(&packet_constant->header, effect.id, 0x6a); + + packet_constant->level = cpu_to_le16(level); + + t300rs_fill_envelope(&packet_constant->envelope, &constant.envelope); + t300rs_fill_timing(&packet_constant->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading constant effect\n"); + + return ret; +} + +static int t300rs_upload_ramp(struct t300rs_device_entry *t300rs, + const struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct ff_ramp_effect ramp = state->effect.u.ramp; + struct __packed t300rs_packet_ramp { + struct t300rs_packet_header header; + uint16_t slope; + uint16_t center; + uint8_t zero1[2]; + uint16_t duration; + uint16_t marker; + struct t300rs_packet_envelope envelope; + uint8_t invert; + struct t300rs_packet_timing timing; + } *packet_ramp = (struct t300rs_packet_ramp *)t300rs->send_buffer; + + int ret; + uint8_t invert; + uint16_t slope, offset, duration; + int16_t center; + + t300rs_calculate_ramp_parameters(&slope, ¢er, &invert, &effect); + + duration = t300rs_calculate_length(effect.replay.length); + offset = effect.replay.delay; + + t300rs_fill_header(&packet_ramp->header, effect.id, 0x6b); + + packet_ramp->slope = cpu_to_le16(slope); + packet_ramp->center = cpu_to_le16(center); + packet_ramp->duration = cpu_to_le16(duration); + + packet_ramp->marker = cpu_to_le16(0x8000); + + t300rs_fill_envelope(&packet_ramp->envelope, &ramp.envelope); + + packet_ramp->invert = invert; + t300rs_fill_timing(&packet_ramp->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading ramp"); + + return ret; +} + +static int t300rs_upload_condition(struct t300rs_device_entry *t300rs, + const struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + /* we only care about the first axis */ + struct ff_condition_effect cond = state->effect.u.condition[0]; + struct __packed t300rs_packet_condition { + struct t300rs_packet_header header; + int16_t right_coeff; + int16_t left_coeff; + int16_t right_deadband; + int16_t left_deadband; + uint16_t right_saturation; + uint16_t left_saturation; + uint8_t hardcoded[ARRAY_SIZE(condition_values)]; + uint16_t max_right_saturation; + uint16_t max_left_saturation; + uint8_t type; + struct t300rs_packet_timing timing; + } *packet_condition = (struct t300rs_packet_condition *)t300rs->send_buffer; + + int ret; + uint16_t duration, right_sat, left_sat, right_coeff, left_coeff, max_sat, offset; + int16_t right_deadband, left_deadband; + + right_coeff = t300rs_calculate_coefficient(cond.right_coeff, effect.type); + left_coeff = t300rs_calculate_coefficient(cond.left_coeff, effect.type); + + t300rs_calculate_deadband(&right_deadband, &left_deadband, + cond.deadband, cond.center); + + right_sat = t300rs_calculate_saturation(cond.right_saturation, effect.type); + left_sat = t300rs_calculate_saturation(cond.left_saturation, effect.type); + + duration = t300rs_calculate_length(effect.replay.length); + + offset = effect.replay.delay; + + t300rs_fill_header(&packet_condition->header, effect.id, 0x64); + + packet_condition->right_coeff = cpu_to_le16(right_coeff); + packet_condition->left_coeff = cpu_to_le16(left_coeff); + packet_condition->right_deadband = cpu_to_le16(right_deadband); + packet_condition->left_deadband = cpu_to_le16(left_deadband); + packet_condition->right_saturation = cpu_to_le16(right_sat); + packet_condition->left_saturation = cpu_to_le16(left_sat); + + memcpy(&packet_condition->hardcoded, condition_values, + ARRAY_SIZE(condition_values)); + + max_sat = t300rs_condition_max_saturation(effect.type); + /* it seems that the maximum values do not affect the wheel. */ + packet_condition->max_right_saturation = cpu_to_le16(max_sat); + packet_condition->max_left_saturation = cpu_to_le16(max_sat); + packet_condition->type = t300rs_condition_effect_type(effect.type); + + t300rs_fill_timing(&packet_condition->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading condition\n"); + + return ret; +} + +static int t300rs_upload_periodic(struct t300rs_device_entry *t300rs, + const struct tmff2_effect_state *state) +{ + struct ff_effect effect = state->effect; + struct __packed t300rs_packet_periodic { + struct t300rs_packet_header header; + uint16_t magnitude; + uint16_t periodic_offset; + uint16_t phase; + uint16_t period; + uint16_t marker; + struct t300rs_packet_envelope envelope; + uint8_t waveform; + struct t300rs_packet_timing timing; + } *packet_periodic = (struct t300rs_packet_periodic *)t300rs->send_buffer; + + struct ff_periodic_effect periodic; + int ret; + uint16_t duration, period, phase, offset, periodic_offset; + int16_t magnitude; + + t300rs_calculate_periodic_values(&effect); + periodic = effect.u.periodic; + duration = t300rs_calculate_length(effect.replay.length); + offset = effect.replay.delay; + magnitude = periodic.magnitude; + period = periodic.period; + phase = periodic.phase; + periodic_offset = periodic.offset; + + t300rs_fill_header(&packet_periodic->header, effect.id, 0x6b); + + packet_periodic->magnitude = cpu_to_le16(magnitude); + packet_periodic->periodic_offset = cpu_to_le16(periodic_offset); + packet_periodic->phase = cpu_to_le16(phase); + packet_periodic->period = cpu_to_le16(period); + + packet_periodic->marker = cpu_to_le16(0x8000); + + t300rs_fill_envelope(&packet_periodic->envelope, &periodic.envelope); + + packet_periodic->waveform = periodic.waveform - 0x57; + + t300rs_fill_timing(&packet_periodic->timing, duration, offset); + + ret = t300rs_send_int(t300rs); + if (ret) + hid_err(t300rs->hdev, "failed uploading periodic effect"); + + return ret; +} + +int t300rs_update_effect(void *data, const struct tmff2_effect_state *state) +{ + struct t300rs_device_entry *t300rs = data; + switch (state->effect.type) { + case FF_CONSTANT: + return t300rs_update_constant(t300rs, state); + case FF_RAMP: + return t300rs_update_ramp(t300rs, state); + case FF_SPRING: + case FF_DAMPER: + case FF_FRICTION: + case FF_INERTIA: + return t300rs_update_condition(t300rs, state); + case FF_PERIODIC: + return t300rs_update_periodic(t300rs, state); + default: + hid_err(t300rs->hdev, "invalid effect type: %x", + state->effect.type); + return -1; + } +} + +int t300rs_upload_effect(void *data, const struct tmff2_effect_state *state) +{ + struct t300rs_device_entry *t300rs = data; + switch (state->effect.type) { + case FF_CONSTANT: + return t300rs_upload_constant(t300rs, state); + case FF_RAMP: + return t300rs_upload_ramp(t300rs, state); + case FF_SPRING: + case FF_DAMPER: + case FF_FRICTION: + case FF_INERTIA: + return t300rs_upload_condition(t300rs, state); + case FF_PERIODIC: + return t300rs_upload_periodic(t300rs, state); + default: + hid_err(t300rs->hdev, "invalid effect type: %x", + state->effect.type); + return -1; + } +} + +static int t300rs_switch_mode(void *data, uint16_t mode) +{ + struct t300rs_device_entry *t300rs = data; + if (!t300rs) + return -ENODEV; + + if(t300rs->mode == mode) /* already in specified mode */ + return 0; + + if (mode == 0) + /* go to normal mode */ + usb_control_msg(t300rs->usbdev, + usb_sndctrlpipe(t300rs->usbdev, 0), + 83, 0x41, 5, 0, 0, 0, + USB_CTRL_SET_TIMEOUT + ); + else if (mode == 1) + /* go to advanced mode */ + usb_control_msg(t300rs->usbdev, + usb_sndctrlpipe(t300rs->usbdev, 0), + 83, 0x41, 3, 0, 0, 0, + USB_CTRL_SET_TIMEOUT + ); + else + hid_warn(t300rs->hdev, "mode %i not supported\n", mode); + + + return 0; +} + +static struct t300rs_alt_modes { + char *id; + char *label; + uint16_t mode; +} t300rs_modes[] = { + {"base", "T300RS base", 0}, + {"F1", "T300RS with F1 wheel attachment", 1} +}; + +static ssize_t t300rs_alt_mode_show(void *data, char *buf) +{ + struct t300rs_device_entry *t300rs = data; + ssize_t count = 0; + int i; + if (!t300rs) + return -ENODEV; + + if (t300rs->attachment != T300RS_F1_ATTACHMENT) + /* we only support one base mode */ + return scnprintf(buf, PAGE_SIZE, "%s: %s *\n", + t300rs_modes[0].id, t300rs_modes[0].label); + + for (i = 0; i < ARRAY_SIZE(t300rs_modes); ++i) { + count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s", + t300rs_modes[i].id, t300rs_modes[i].label); + + if (count >= PAGE_SIZE - 1) + return count; + + if (t300rs_modes[i].mode == t300rs->mode) + count += scnprintf(buf + count, PAGE_SIZE - count, " *\n"); + else + count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); + + if (count >= PAGE_SIZE - 1) + return count; + } + + return count; +} + +static ssize_t t300rs_alt_mode_store(void *data, const char *buf, size_t count) +{ + struct t300rs_device_entry *t300rs = data; + int i, len, mode_len; + char *lbuf; + if (!t300rs) + return -ENODEV; + + if (t300rs->attachment != T300RS_F1_ATTACHMENT) + return count; /* don't do anything */ + + lbuf = kasprintf(GFP_KERNEL, "%s", buf); + if (!lbuf) + return -ENOMEM; + + len = strlen(buf); + for (i = 0; i < ARRAY_SIZE(t300rs_modes); ++i) { + mode_len = strlen(t300rs_modes[i].id); + if (mode_len > len) + continue; + + if (strncmp(lbuf, t300rs_modes[i].id, mode_len) == 0) { + t300rs_switch_mode(data, t300rs_modes[i].mode); + break; + } + } + + kfree(lbuf); + return count; +} + +int t300rs_set_autocenter(void *data, uint16_t value) +{ + struct t300rs_device_entry *t300rs = data; + struct __packed t300rs_packet_autocenter { + struct t300rs_setup_header header; + uint16_t value; + } *autocenter_packet; + int ret; + + if (!t300rs) + return -ENODEV; + + /* TODO: this should probably also use a separately allocated buffer? + * someone might change autocentering while we're updating the buffer + * which would cause corruption */ + autocenter_packet = (struct t300rs_packet_autocenter *)t300rs->send_buffer; + + autocenter_packet->header.cmd = 0x08; + autocenter_packet->header.code = 0x04; + autocenter_packet->value = cpu_to_le16(0x01); + + if ((ret = t300rs_send_int(t300rs))) { + hid_err(t300rs->hdev, "failed setting autocenter"); + return ret; + } + + autocenter_packet->header.cmd = 0x08; + autocenter_packet->header.code = 0x03; + + autocenter_packet->value = cpu_to_le16(value); + + if ((ret = t300rs_send_int(t300rs))) + hid_err(t300rs->hdev, "failed setting autocenter"); + + return ret; +} + +int t300rs_set_gain(void *data, uint16_t gain) +{ + struct t300rs_device_entry *t300rs = data; + struct __packed t300rs_packet_gain { + struct t300rs_setup_header header; + } *gain_packet; + int ret; + + if (!t300rs) + return -ENODEV; + + gain_packet = (struct t300rs_packet_gain *)t300rs->send_buffer; + gain_packet->header.cmd = 0x02; + gain_packet->header.code = (gain >> 8) & 0xff; + + if ((ret = t300rs_send_int(t300rs))) + hid_err(t300rs->hdev, "failed setting gain: %i\n", ret); + + return ret; +} + +int t300rs_set_range(void *data, uint16_t value) +{ + struct t300rs_device_entry *t300rs = data; + /* it's important that we don't use t300rs->send_buffer, as range can be + * set from outside of the FFB environment, and we don't want to + * accidentally overwrite any data. */ + u8 *send_buffer = kzalloc(t300rs->buffer_length, GFP_KERNEL); + uint16_t scaled_value; + int ret; + + if (value < 40) { + hid_info(t300rs->hdev, "value %i too small, clamping to 40\n", value); + value = 40; + } + + if (value > 1080) { + hid_info(t300rs->hdev, "value %i too large, clamping to 1080\n", value); + value = 1080; + } + + if (!send_buffer) { + ret = -EINVAL; + hid_err(t300rs->hdev, "could not allocate send_buffer\n"); + goto err; + } + + scaled_value = value * 0x3c; + send_buffer[0] = 0x08; + send_buffer[1] = 0x11; + send_buffer[2] = scaled_value & 0xff; + send_buffer[3] = scaled_value >> 8; + + if ((ret = t300rs_send_buf(t300rs, send_buffer, t300rs->buffer_length))) + hid_warn(t300rs->hdev, "failed setting range\n"); + + /* since everythin went OK, update the current range */ + range = value; +err: + kfree(send_buffer); + return ret; +} + +static int t300rs_send_open(struct t300rs_device_entry *t300rs) +{ + struct __packed t300rs_packet_open { + struct t300rs_setup_header header; + } *open_packet; + + open_packet = (struct t300rs_packet_open *)t300rs->send_buffer; + open_packet->header.cmd = 0x01; + open_packet->header.code = 0x05; + + return t300rs_send_int(t300rs); +} + +static int t300rs_send_close(struct t300rs_device_entry *t300rs) +{ + struct __packed t300rs_packet_open { + struct t300rs_setup_header header; + } *open_packet; + + open_packet = (struct t300rs_packet_open *)t300rs->send_buffer; + open_packet->header.cmd = 0x01; + + return t300rs_send_int(t300rs); +} + +int t300rs_open(void *data, int open_mode) +{ + struct t300rs_device_entry *t300rs = data; + if (!t300rs) + return -ENODEV; + + if (open_mode && t300rs_send_open(t300rs)) + hid_warn(t300rs->hdev, "failed sending open command\n"); + + return t300rs->open(t300rs->input_dev); +} + +int t300rs_close(void *data, int open_mode) +{ + struct t300rs_device_entry *t300rs = data; + int ret = 0; + + if (!t300rs) + return -ENODEV; + + if (open_mode && (ret = t300rs_send_close(t300rs))) + hid_warn(t300rs->hdev, "failed sending close command\n"); + + t300rs->close(t300rs->input_dev); + return ret; +} + +static int t300rs_check_firmware(struct t300rs_device_entry *t300rs) +{ + int ret = 0; + struct t300rs_fw_response *fw_response = + kzalloc(sizeof(struct t300rs_fw_response), GFP_KERNEL); + + if (!fw_response) { + hid_err(t300rs->hdev, "could not allocate fw_response\n"); + return -ENOMEM; + } + + /* fetch firmware version */ + ret = usb_control_msg(t300rs->usbdev, + usb_rcvctrlpipe(t300rs->usbdev, 0), + t300rs_fw_request.bRequest, + t300rs_fw_request.bRequestType, + t300rs_fw_request.wValue, + t300rs_fw_request.wIndex, + fw_response, + t300rs_fw_request.wLength, + USB_CTRL_SET_TIMEOUT + ); + + if (ret < 0) { + hid_err(t300rs->hdev, "could not fetch firmware version: %i\n", ret); + goto out; + } + + /* educated guess */ + if (fw_response->fw_version < 31 && ret >= 0) { + hid_warn(t300rs->hdev, + "firmware version %i might be too old, consider updating\n", + fw_response->fw_version + ); + + hid_info(t300rs->hdev, "note: this has to be done through Windows.\n"); + } + + /* everything OK */ + ret = 0; + +out: + kfree(fw_response); + return ret; +} + +static int t300rs_get_attachment(struct t300rs_device_entry *t300rs) +{ + /* taken directly from hid_tminit */ + struct __packed t300rs_attachment_response + { + uint16_t type; + + union { + struct __packed { + uint16_t field0; + uint16_t field1; + uint8_t attachment; + uint8_t model; + uint16_t field2; + uint16_t field3; + uint16_t field4; + uint16_t field5; + } a; + + struct __packed { + uint16_t field0; + uint16_t field1; + uint8_t attachment; + uint8_t model; + } b; + }; + } *response = kzalloc(GFP_KERNEL, sizeof(struct t300rs_attachment_response)); + struct usb_ctrlrequest t300rs_attachment_rq = { + .bRequestType = 0xc1, + .bRequest = 73, + .wValue = 0, + .wIndex = 0, + .wLength = sizeof(struct t300rs_attachment_response) + }; + int ret, attachment; + if (!response) + return -ENODEV; + + ret = usb_control_msg(t300rs->usbdev, + usb_rcvctrlpipe(t300rs->usbdev, 0), + t300rs_attachment_rq.bRequest, + t300rs_attachment_rq.bRequestType, + t300rs_attachment_rq.wValue, + t300rs_attachment_rq.wIndex, + response, + sizeof(struct t300rs_attachment_response), + USB_CTRL_SET_TIMEOUT + ); + + if (ret < 0) { + hid_err(t300rs->hdev, "could not fetch attachment: %i\n", ret); + goto out; + } + + if (response->type == cpu_to_le16(0x49)) { + attachment = response->a.attachment; + } else if (response->type == cpu_to_le16(0x47)) { + attachment = response->b.attachment; + } else { + hid_err(t300rs->hdev, + "unknown packet type %hx\n, please contact a maintainer", + response->type); + ret = -EINVAL; + goto out; + } + + kfree(response); + return attachment; + +out: + kfree(response); + return ret; +} + +static int t300rs_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) +{ + struct t300rs_device_entry *t300rs = kzalloc(sizeof(struct t300rs_device_entry), GFP_KERNEL); + struct list_head *report_list; + int ret; + + if (!t300rs) { + ret = -ENOMEM; + goto t300rs_err; + } + + t300rs->hdev = tmff2->hdev; + t300rs->input_dev = tmff2->input_dev; + t300rs->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); + + if(t300rs->hdev->product == TMT300RS_PS4_NORM_ID) + t300rs->buffer_length = T300RS_PS4_BUFFER_LENGTH; + else + t300rs->buffer_length = T300RS_NORM_BUFFER_LENGTH; + + t300rs->send_buffer = kzalloc(t300rs->buffer_length, GFP_KERNEL); + if (!t300rs->send_buffer) { + ret = -ENOMEM; + goto send_err; + } + + if ((ret = t300rs_check_firmware(t300rs))) + goto firmware_err; + + report_list = &t300rs->hdev->report_enum[HID_OUTPUT_REPORT].report_list; + + /* because we set the rdesc, we know exactly which report and field to use */ + t300rs->report = list_entry(report_list->next, struct hid_report, list); + t300rs->ff_field = t300rs->report->field[0]; + + t300rs->open = t300rs->input_dev->open; + t300rs->close = t300rs->input_dev->close; + + /* TODO: PS4 advanced mode? */ + alt_mode = (t300rs->mode = (t300rs->hdev->product == TMT300RS_PS3_ADV_ID)); + if ((t300rs->attachment = t300rs_get_attachment(t300rs)) < 0) + t300rs->attachment = T300RS_DEFAULT_ATTACHMENT; + + /* everythin went OK */ + tmff2->data = t300rs; + tmff2->params = t300rs_params; + tmff2->max_effects = T300RS_MAX_EFFECTS; + memcpy(tmff2->supported_effects, t300rs_effects, sizeof(t300rs_effects)); + + if (!open_mode) + t300rs_send_open(t300rs); + + hid_info(t300rs->hdev, "force feedback for T300RS\n"); + return 0; + +firmware_err: + kfree(t300rs->send_buffer); +send_err: + kfree(t300rs); +t300rs_err: + hid_err(tmff2->hdev, "failed initializing T300RS\n"); + return ret; +} + +static int t300rs_wheel_destroy(void *data) +{ + struct t300rs_device_entry *t300rs = data; + if (!t300rs) + return -ENODEV; + + kfree(t300rs->send_buffer); + kfree(t300rs); + return 0; +} + +static __u8 *t300rs_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + switch (hdev->product) { + case TMT300RS_PS3_NORM_ID: + /* normal PS3 mode */ + rdesc = t300rs_rdesc_nrm_fixed; + *rsize = sizeof(t300rs_rdesc_nrm_fixed); + break; + + case TMT300RS_PS4_NORM_ID: + /* PS4 normal mode */ + rdesc = t300rs_rdesc_ps4_fixed; + *rsize = sizeof(t300rs_rdesc_ps4_fixed); + break; + + case TMT300RS_PS3_ADV_ID: + /* PS3 advanced mode */ + rdesc = t300rs_rdesc_adv_fixed; + *rsize = sizeof(t300rs_rdesc_adv_fixed); + break; + } + + return rdesc; +} + +int t300rs_populate_api(struct tmff2_device_entry *tmff2) +{ + /* set callbacks */ + tmff2->play_effect = t300rs_play_effect; + tmff2->upload_effect = t300rs_upload_effect; + tmff2->update_effect = t300rs_update_effect; + tmff2->stop_effect = t300rs_stop_effect; + + tmff2->wheel_init = t300rs_wheel_init; + tmff2->wheel_destroy = t300rs_wheel_destroy; + + tmff2->open = t300rs_open; + tmff2->close = t300rs_close; + tmff2->set_gain = t300rs_set_gain; + tmff2->set_range = t300rs_set_range; + tmff2->switch_mode = t300rs_switch_mode; + tmff2->alt_mode_show = t300rs_alt_mode_show; + tmff2->alt_mode_store = t300rs_alt_mode_store; + tmff2->set_autocenter = t300rs_set_autocenter; + tmff2->wheel_fixup = t300rs_wheel_fixup; + + return 0; +} diff --git a/drivers/custom/hid-tmff2/src/tmt300rs/hid-tmt300rs.h b/drivers/custom/hid-tmff2/src/tmt300rs/hid-tmt300rs.h new file mode 100644 index 000000000000..2305f4b4c88e --- /dev/null +++ b/drivers/custom/hid-tmff2/src/tmt300rs/hid-tmt300rs.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __HID_TMT300RS_H +#define __HID_TMT300RS_H + +#include "../hid-tmff2.h" + +int t300rs_populate_api(struct tmff2_device_entry *tmff2); + +#endif diff --git a/drivers/custom/hid-tmff2/src/tmtspc/hid-tmtspc.c b/drivers/custom/hid-tmff2/src/tmtspc/hid-tmtspc.c new file mode 100644 index 000000000000..a51ec0199040 --- /dev/null +++ b/drivers/custom/hid-tmff2/src/tmtspc/hid-tmtspc.c @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include "../hid-tmff2.h" + +#define TMTSPC_MAX_EFFECTS 16 +#define TMTSPC_BUFFER_LENGTH 63 + +static const u8 setup_0[64] = { 0x42, 0x01 }; +static const u8 setup_1[64] = { 0x0a, 0x04, 0x90, 0x03 }; +static const u8 setup_2[64] = { 0x0a, 0x04, 0x00, 0x0c }; +static const u8 setup_3[64] = { 0x0a, 0x04, 0x12, 0x10 }; +static const u8 setup_4[64] = { 0x0a, 0x04, 0x00, 0x06 }; +static const u8 setup_5[64] = { 0x0a, 0x04, 0x00, 0x0e }; +static const u8 setup_6[64] = { 0x0a, 0x04, 0x00, 0x0e, 0x01 }; +static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4, setup_5, setup_6 }; +static const unsigned int setup_arr_sizes[] = { + ARRAY_SIZE(setup_0), + ARRAY_SIZE(setup_1), + ARRAY_SIZE(setup_2), + ARRAY_SIZE(setup_3), + ARRAY_SIZE(setup_4), + ARRAY_SIZE(setup_5), + ARRAY_SIZE(setup_6) +}; + +static const unsigned long tspc_params = + PARAM_SPRING_LEVEL + | PARAM_DAMPER_LEVEL + | PARAM_FRICTION_LEVEL + | PARAM_ALT_MODE + | PARAM_RANGE + | PARAM_GAIN + ; + +static const signed short tspc_effects[] = { + FF_CONSTANT, + FF_RAMP, + FF_SPRING, + FF_DAMPER, + FF_FRICTION, + FF_INERTIA, + FF_PERIODIC, + FF_SINE, + FF_TRIANGLE, + FF_SQUARE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_AUTOCENTER, + FF_GAIN, + -1 +}; + +/* TODO: sort through this stuff */ +static u8 tspc_pc_rdesc_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) (Brake) */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x32, /* Usage (Z) (Gas) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) (Clutch) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x0d, /* Usage maximum (13) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x0d, /* Report count (13) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x0b, /* Report size (13) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Unit (None) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x7f, /* Logical maximum (32767) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x46, 0xff, 0x7f, /* Physical maximum (32767) */ + 0x36, 0x00, 0x80, /* Physical minimum (-32768) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static int tspc_interrupts(struct t300rs_device_entry *tspc) +{ + u8 *send_buf = kmalloc(256, GFP_KERNEL); + struct usb_interface *usbif = to_usb_interface(tspc->hdev->dev.parent); + struct usb_host_endpoint *ep; + int ret, trans, b_ep, i; + + if (!send_buf) { + hid_err(tspc->hdev, "failed allocating send buffer\n"); + return -ENOMEM; + } + + ep = &usbif->cur_altsetting->endpoint[1]; + b_ep = ep->desc.bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { + memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); + + ret = usb_interrupt_msg(tspc->usbdev, + usb_sndintpipe(tspc->usbdev, b_ep), + send_buf, setup_arr_sizes[i], + &trans, + USB_CTRL_SET_TIMEOUT); + + if (ret) { + hid_err(tspc->hdev, "setup data couldn't be sent\n"); + goto err; + } + } + +err: + kfree(send_buf); + return ret; +} + +static int tspc_wheel_destroy(void *data) +{ + struct t300rs_device_entry *t300rs = data; + + if (!t300rs) + return -ENODEV; + + kfree(t300rs->send_buffer); + kfree(t300rs); + return 0; +} + +static int tspc_set_range(void *data, uint16_t value) +{ + struct t300rs_device_entry *tspc = data; + + if (value < 140) { + hid_info(tspc->hdev, "value %i too small, clamping to 140\n", value); + value = 140; + } + + if (value > 1080) { + hid_info(tspc->hdev, "value %i too large, clamping to 1080\n", value); + value = 1080; + } + + return t300rs_set_range(data, value); +} + +static int tspc_send_open(struct t300rs_device_entry *tspc) +{ + int r1, r2; + tspc->send_buffer[0] = 0x01; + tspc->send_buffer[1] = 0x04; + if ((r1 = t300rs_send_int(tspc))) + return r1; + + tspc->send_buffer[0] = 0x01; + tspc->send_buffer[1] = 0x05; + if ((r2 = t300rs_send_int(tspc))) + return r2; + + return 0; +} + +static int tspc_open(void *data, int open_mode) +{ + struct t300rs_device_entry *tspc = data; + + if (!tspc) + return -ENODEV; + + if (open_mode) + tspc_send_open(tspc); + + return tspc->open(tspc->input_dev); +} + +static int tspc_send_close(struct t300rs_device_entry *tspc) +{ + int r1, r2; + tspc->send_buffer[0] = 0x01; + tspc->send_buffer[1] = 0x05; + if ((r1 = t300rs_send_int(tspc))) + return r1; + + tspc->send_buffer[0] = 0x01; + tspc->send_buffer[1] = 0x00; + if ((r2 = t300rs_send_int(tspc))) + return r2; + + return 0; +} + +static int tspc_close(void *data, int open_mode) +{ + struct t300rs_device_entry *tspc = data; + + if (!tspc) + return -ENODEV; + + if (open_mode) + tspc_send_close(tspc); + + tspc->close(tspc->input_dev); + return 0; +} + +static int tspc_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) +{ + struct t300rs_device_entry *tspc = kzalloc(sizeof(struct t300rs_device_entry), + GFP_KERNEL); + struct list_head *report_list; + int ret; + + + if (!tspc) { + ret = -ENOMEM; + goto tspc_err; + } + + tspc->hdev = tmff2->hdev; + tspc->input_dev = tmff2->input_dev; + tspc->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); + tspc->buffer_length = TMTSPC_BUFFER_LENGTH; + + tspc->send_buffer = kzalloc(tspc->buffer_length, GFP_KERNEL); + if (!tspc->send_buffer) { + ret = -ENOMEM; + goto send_err; + } + + report_list = &tspc->hdev->report_enum[HID_OUTPUT_REPORT].report_list; + tspc->report = list_entry(report_list->next, struct hid_report, list); + tspc->ff_field = tspc->report->field[0]; + + tspc->open = tspc->input_dev->open; + tspc->close = tspc->input_dev->close; + + if ((ret = tspc_interrupts(tspc))) + goto interrupt_err; + + /* everything went OK */ + tmff2->data = tspc; + tmff2->params = tspc_params; + tmff2->max_effects = TMTSPC_MAX_EFFECTS; + memcpy(tmff2->supported_effects, tspc_effects, sizeof(tspc_effects)); + + if (!open_mode) + tspc_send_open(tspc); + + hid_info(tspc->hdev, "force feedback for TS-PC\n"); + return 0; + +interrupt_err: +send_err: + kfree(tspc); +tspc_err: + hid_err(tmff2->hdev, "failed initializing TS-PC\n"); + return ret; +} + +static __u8 *tspc_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + rdesc = tspc_pc_rdesc_fixed; + *rsize = sizeof(tspc_pc_rdesc_fixed); + return rdesc; +} + +static ssize_t tspc_alt_mode_store(void *data, const char *buf, size_t count) +{ + struct t300rs_device_entry *tspc = data; + if (!tspc) + return -ENODEV; + + /* blindly trusting that this works for now */ + usb_control_msg(tspc->usbdev, + usb_sndctrlpipe(tspc->usbdev, 0), + 83, 0x41, 0xb, 0, 0, 0, + USB_CTRL_SET_TIMEOUT + ); + + return count; +} + +int tspc_populate_api(struct tmff2_device_entry *tmff2) +{ + tmff2->play_effect = t300rs_play_effect; + tmff2->upload_effect = t300rs_upload_effect; + tmff2->update_effect = t300rs_update_effect; + tmff2->stop_effect = t300rs_stop_effect; + + tmff2->set_gain = t300rs_set_gain; + tmff2->set_autocenter = t300rs_set_autocenter; + /* TS-PC has 1080 degree range, like T300RS 1080 */ + tmff2->set_range = tspc_set_range; + tmff2->wheel_fixup = tspc_wheel_fixup; + + tmff2->open = tspc_open; + tmff2->close = tspc_close; + + tmff2->alt_mode_store = tspc_alt_mode_store; + + tmff2->wheel_init = tspc_wheel_init; + tmff2->wheel_destroy = tspc_wheel_destroy; + + return 0; +} diff --git a/drivers/custom/hid-tmff2/src/tmtsxw/hid-tmtsxw.c b/drivers/custom/hid-tmff2/src/tmtsxw/hid-tmtsxw.c new file mode 100644 index 000000000000..b8ee1fdea466 --- /dev/null +++ b/drivers/custom/hid-tmff2/src/tmtsxw/hid-tmtsxw.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include "../hid-tmff2.h" + +#define TMTSXW_MAX_EFFECTS 16 +#define TMTSXW_BUFFER_LENGTH 63 + +static const u8 setup_0[64] = { 0x42, 0x01 }; +static const u8 setup_1[64] = { 0x0a, 0x04, 0x90, 0x03 }; +static const u8 setup_2[64] = { 0x0a, 0x04, 0x00, 0x0c }; +static const u8 setup_3[64] = { 0x0a, 0x04, 0x12, 0x10 }; +static const u8 setup_4[64] = { 0x0a, 0x04, 0x00, 0x06 }; +static const u8 setup_5[64] = { 0x0a, 0x04, 0x00, 0x0e }; +static const u8 setup_6[64] = { 0x0a, 0x04, 0x00, 0x0e, 0x01 }; +static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4, setup_5, setup_6 }; +static const unsigned int setup_arr_sizes[] = { + ARRAY_SIZE(setup_0), + ARRAY_SIZE(setup_1), + ARRAY_SIZE(setup_2), + ARRAY_SIZE(setup_3), + ARRAY_SIZE(setup_4), + ARRAY_SIZE(setup_5), + ARRAY_SIZE(setup_6) +}; + +static const unsigned long tsxw_params = + PARAM_SPRING_LEVEL + | PARAM_DAMPER_LEVEL + | PARAM_FRICTION_LEVEL + | PARAM_RANGE + | PARAM_GAIN + ; + +static const signed short tsxw_effects[] = { + FF_CONSTANT, + FF_RAMP, + FF_SPRING, + FF_DAMPER, + FF_FRICTION, + FF_INERTIA, + FF_PERIODIC, + FF_SINE, + FF_TRIANGLE, + FF_SQUARE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_AUTOCENTER, + FF_GAIN, + -1 +}; + +/* TODO: sort through this stuff */ +static u8 tsxw_pc_rdesc_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) (Brake) */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x32, /* Usage (Z) (Gas) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) (Clutch) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x0d, /* Usage maximum (13) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x0d, /* Report count (13) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x0b, /* Report size (13) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Unit (None) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x7f, /* Logical maximum (32767) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x46, 0xff, 0x7f, /* Physical maximum (32767) */ + 0x36, 0x00, 0x80, /* Physical minimum (-32768) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static int tsxw_interrupts(struct t300rs_device_entry *tsxw) +{ + u8 *send_buf = kmalloc(256, GFP_KERNEL); + struct usb_interface *usbif = to_usb_interface(tsxw->hdev->dev.parent); + struct usb_host_endpoint *ep; + int ret, trans, b_ep, i; + + if (!send_buf) { + hid_err(tsxw->hdev, "failed allocating send buffer\n"); + return -ENOMEM; + } + + ep = &usbif->cur_altsetting->endpoint[1]; + b_ep = ep->desc.bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { + memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); + + ret = usb_interrupt_msg(tsxw->usbdev, + usb_sndintpipe(tsxw->usbdev, b_ep), + send_buf, setup_arr_sizes[i], + &trans, + USB_CTRL_SET_TIMEOUT); + + if (ret) { + hid_err(tsxw->hdev, "setup data couldn't be sent\n"); + goto err; + } + } + +err: + kfree(send_buf); + return ret; +} + +static int tsxw_wheel_destroy(void *data) +{ + struct t300rs_device_entry *t300rs = data; + + if (!t300rs) + return -ENODEV; + + kfree(t300rs->send_buffer); + kfree(t300rs); + return 0; +} + +static int tsxw_set_range(void *data, uint16_t value) +{ + struct t300rs_device_entry *tsxw = data; + + if (value < 140) { + hid_info(tsxw->hdev, "value %i too small, clamping to 140\n", value); + value = 140; + } + + if (value > 1080) { + hid_info(tsxw->hdev, "value %i too large, clamping to 1080\n", value); + value = 1080; + } + + return t300rs_set_range(data, value); +} + +static int tsxw_send_open(struct t300rs_device_entry *tsxw) +{ + int r1, r2; + tsxw->send_buffer[0] = 0x01; + tsxw->send_buffer[1] = 0x04; + if ((r1 = t300rs_send_int(tsxw))) + return r1; + + tsxw->send_buffer[0] = 0x01; + tsxw->send_buffer[1] = 0x05; + if ((r2 = t300rs_send_int(tsxw))) + return r2; + + return 0; +} + +static int tsxw_open(void *data, int open_mode) +{ + struct t300rs_device_entry *tsxw = data; + + if (!tsxw) + return -ENODEV; + + if (open_mode) + tsxw_send_open(tsxw); + + return tsxw->open(tsxw->input_dev); +} + +static int tsxw_send_close(struct t300rs_device_entry *tsxw) +{ + int r1, r2; + tsxw->send_buffer[0] = 0x01; + tsxw->send_buffer[1] = 0x05; + if ((r1 = t300rs_send_int(tsxw))) + return r1; + + tsxw->send_buffer[0] = 0x01; + tsxw->send_buffer[1] = 0x00; + if ((r2 = t300rs_send_int(tsxw))) + return r2; + + return 0; +} + +static int tsxw_close(void *data, int open_mode) +{ + struct t300rs_device_entry *tsxw = data; + + if (!tsxw) + return -ENODEV; + + if (open_mode) + tsxw_send_close(tsxw); + + tsxw->close(tsxw->input_dev); + return 0; +} + +static int tsxw_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) +{ + struct t300rs_device_entry *tsxw = kzalloc(sizeof(struct t300rs_device_entry), + GFP_KERNEL); + struct list_head *report_list; + int ret; + + + if (!tsxw) { + ret = -ENOMEM; + goto tsxw_err; + } + + tsxw->hdev = tmff2->hdev; + tsxw->input_dev = tmff2->input_dev; + tsxw->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); + tsxw->buffer_length = TMTSXW_BUFFER_LENGTH; + + tsxw->send_buffer = kzalloc(tsxw->buffer_length, GFP_KERNEL); + if (!tsxw->send_buffer) { + ret = -ENOMEM; + goto send_err; + } + + report_list = &tsxw->hdev->report_enum[HID_OUTPUT_REPORT].report_list; + tsxw->report = list_entry(report_list->next, struct hid_report, list); + tsxw->ff_field = tsxw->report->field[0]; + + tsxw->open = tsxw->input_dev->open; + tsxw->close = tsxw->input_dev->close; + + if ((ret = tsxw_interrupts(tsxw))) + goto interrupt_err; + + /* everything went OK */ + tmff2->data = tsxw; + tmff2->params = tsxw_params; + tmff2->max_effects = TMTSXW_MAX_EFFECTS; + memcpy(tmff2->supported_effects, tsxw_effects, sizeof(tsxw_effects)); + + if (!open_mode) + tsxw_send_open(tsxw); + + hid_info(tsxw->hdev, "force feedback for TS-XW\n"); + return 0; + +interrupt_err: +send_err: + kfree(tsxw); +tsxw_err: + hid_err(tmff2->hdev, "failed initializing TS-XW\n"); + return ret; +} + +static __u8 *tsxw_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + rdesc = tsxw_pc_rdesc_fixed; + *rsize = sizeof(tsxw_pc_rdesc_fixed); + return rdesc; +} + +int tsxw_populate_api(struct tmff2_device_entry *tmff2) +{ + tmff2->play_effect = t300rs_play_effect; + tmff2->upload_effect = t300rs_upload_effect; + tmff2->update_effect = t300rs_update_effect; + tmff2->stop_effect = t300rs_stop_effect; + + tmff2->set_gain = t300rs_set_gain; + tmff2->set_autocenter = t300rs_set_autocenter; + /* TS-XW has 1080 degree range, just like T300RS 1080 */ + tmff2->set_range = tsxw_set_range; + tmff2->wheel_fixup = tsxw_wheel_fixup; + + tmff2->open = tsxw_open; + tmff2->close = tsxw_close; + + tmff2->wheel_init = tsxw_wheel_init; + tmff2->wheel_destroy = tsxw_wheel_destroy; + + return 0; +} diff --git a/drivers/custom/hid-tmff2/src/tmtx/hid-tmtx.c b/drivers/custom/hid-tmff2/src/tmtx/hid-tmtx.c new file mode 100644 index 000000000000..857886c62152 --- /dev/null +++ b/drivers/custom/hid-tmff2/src/tmtx/hid-tmtx.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include "../hid-tmff2.h" + +#define TMTX_MAX_EFFECTS 16 +#define TMTX_BUFFER_LENGTH 63 + +static const u8 setup_0[64] = { 0x42, 0x01 }; +static const u8 setup_1[64] = { 0x0a, 0x04, 0x90, 0x03 }; +static const u8 setup_2[64] = { 0x0a, 0x04, 0x00, 0x0c }; +static const u8 setup_3[64] = { 0x0a, 0x04, 0x12, 0x10 }; +static const u8 setup_4[64] = { 0x0a, 0x04, 0x00, 0x06 }; +static const u8 setup_5[64] = { 0x0a, 0x04, 0x00, 0x0e }; +static const u8 setup_6[64] = { 0x0a, 0x04, 0x00, 0x0e, 0x01 }; +static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4, setup_5, setup_6 }; +static const unsigned int setup_arr_sizes[] = { + ARRAY_SIZE(setup_0), + ARRAY_SIZE(setup_1), + ARRAY_SIZE(setup_2), + ARRAY_SIZE(setup_3), + ARRAY_SIZE(setup_4), + ARRAY_SIZE(setup_5), + ARRAY_SIZE(setup_6) +}; + +static const unsigned long tx_params = + PARAM_SPRING_LEVEL + | PARAM_DAMPER_LEVEL + | PARAM_FRICTION_LEVEL + | PARAM_RANGE + | PARAM_GAIN + ; + +static const signed short tx_effects[] = { + FF_CONSTANT, + FF_RAMP, + FF_SPRING, + FF_DAMPER, + FF_FRICTION, + FF_INERTIA, + FF_PERIODIC, + FF_SINE, + FF_TRIANGLE, + FF_SQUARE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_AUTOCENTER, + FF_GAIN, + -1 +}; + +/* TODO: sort through this stuff */ +static u8 tx_pc_rdesc_fixed[] = { + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x04, /* Usage (Joystick) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x85, 0x07, /* Report ID (7) */ + 0x09, 0x30, /* Usage (X) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x27, 0xff, 0xff, 0x00, 0x00, /* Logical maximum (65535) */ + 0x35, 0x00, /* Physical minimum (0) */ + 0x47, 0xff, 0xff, 0x00, 0x00, /* Physical maximum (65535) */ + 0x75, 0x10, /* Report size (16) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x35, /* Usage (Rz) (Brake) */ + 0x26, 0xff, 0x03, /* Logical maximum (1023) */ + 0x46, 0xff, 0x03, /* Physical maximum (1023) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x32, /* Usage (Z) (Gas) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x31, /* Usage (Y) (Clutch) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x05, 0x09, /* Usage page (Button) */ + 0x19, 0x01, /* Usage minimum (1) */ + 0x29, 0x0d, /* Usage maximum (13) */ + 0x25, 0x01, /* Logical maximum (1) */ + 0x45, 0x01, /* Physical maximum (1) */ + 0x75, 0x01, /* Report size (1) */ + 0x95, 0x0d, /* Report count (13) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x75, 0x0b, /* Report size (13) */ + 0x95, 0x01, /* Report count (1) */ + 0x81, 0x03, /* Usage (Variable, Absolute, Constant) */ + 0x05, 0x01, /* Usage page (Generic Desktop) */ + 0x09, 0x39, /* Usage (Hat Switch) */ + 0x25, 0x07, /* Logical maximum (7) */ + 0x46, 0x3b, 0x01, /* Physical maximum (315) */ + 0x55, 0x00, /* Unit exponent (0) */ + 0x65, 0x14, /* Unit (Eng Rot, Angular Pos) */ + 0x75, 0x04, /* Report size (4) */ + 0x81, 0x42, /* Input (Variable, Absolute, NullState) */ + 0x65, 0x00, /* Unit (None) */ + 0x81, 0x03, /* Input (Variable, Absolute, Constant) */ + 0x85, 0x60, /* Report ID (96), prev 10 */ + 0x06, 0x00, 0xff, /* Usage page (Vendor 1) */ + 0x09, 0x60, /* Usage (96), prev 10 */ + 0x75, 0x08, /* Report size (8) */ + 0x95, 0x3f, /* Report count (63) */ + 0x26, 0xff, 0x7f, /* Logical maximum (32767) */ + 0x15, 0x00, /* Logical minimum (0) */ + 0x46, 0xff, 0x7f, /* Physical maximum (32767) */ + 0x36, 0x00, 0x80, /* Physical minimum (-32768) */ + 0x91, 0x02, /* Output (Variable, Absolute) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x02, /* Usage (2) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0x09, 0x14, /* Usage (20) */ + 0x85, 0x14, /* Report ID (20) */ + 0x81, 0x02, /* Input (Variable, Absolute) */ + 0xc0, /* End collection */ + 0xc0, /* End collection */ +}; + +static int tx_interrupts(struct t300rs_device_entry *tx) +{ + u8 *send_buf = kmalloc(256, GFP_KERNEL); + struct usb_interface *usbif = to_usb_interface(tx->hdev->dev.parent); + struct usb_host_endpoint *ep; + int ret, trans, b_ep, i; + + if (!send_buf) { + hid_err(tx->hdev, "failed allocating send buffer\n"); + return -ENOMEM; + } + + ep = &usbif->cur_altsetting->endpoint[1]; + b_ep = ep->desc.bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) { + memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]); + + ret = usb_interrupt_msg(tx->usbdev, + usb_sndintpipe(tx->usbdev, b_ep), + send_buf, setup_arr_sizes[i], + &trans, + USB_CTRL_SET_TIMEOUT); + + if (ret) { + hid_err(tx->hdev, "setup data couldn't be sent\n"); + goto err; + } + } + +err: + kfree(send_buf); + return ret; +} + +static int tx_wheel_destroy(void *data) +{ + struct t300rs_device_entry *t300rs = data; + + if (!t300rs) + return -ENODEV; + + kfree(t300rs->send_buffer); + kfree(t300rs); + return 0; +} + +static int tx_set_range(void *data, uint16_t value) +{ + struct t300rs_device_entry *tx = data; + + if (value < 140) { + hid_info(tx->hdev, "value %i too small, clamping to 140\n", value); + value = 140; + } + + if (value > 900) { + hid_info(tx->hdev, "value %i too large, clamping to 900\n", value); + value = 900; + } + + return t300rs_set_range(data, value); +} + +static int tx_send_open(struct t300rs_device_entry *tx) +{ + int r1, r2; + tx->send_buffer[0] = 0x01; + tx->send_buffer[1] = 0x04; + if ((r1 = t300rs_send_int(tx))) + return r1; + + tx->send_buffer[0] = 0x01; + tx->send_buffer[1] = 0x05; + if ((r2 = t300rs_send_int(tx))) + return r2; + + return 0; +} + +static int tx_open(void *data, int open_mode) +{ + struct t300rs_device_entry *tx = data; + + if (!tx) + return -ENODEV; + + if (open_mode) + tx_send_open(tx); + + return tx->open(tx->input_dev); +} + +static int tx_send_close(struct t300rs_device_entry *tx) +{ + int r1, r2; + tx->send_buffer[0] = 0x01; + tx->send_buffer[1] = 0x05; + if ((r1 = t300rs_send_int(tx))) + return r1; + + tx->send_buffer[0] = 0x01; + tx->send_buffer[1] = 0x00; + if ((r2 = t300rs_send_int(tx))) + return r2; + + return 0; +} + +static int tx_close(void *data, int open_mode) +{ + struct t300rs_device_entry *tx = data; + + if (!tx) + return -ENODEV; + + if (open_mode) + tx_send_close(tx); + + tx->close(tx->input_dev); + return 0; +} + +static int tx_wheel_init(struct tmff2_device_entry *tmff2, int open_mode) +{ + struct t300rs_device_entry *tx = kzalloc(sizeof(struct t300rs_device_entry), + GFP_KERNEL); + struct list_head *report_list; + int ret; + + + if (!tx) { + ret = -ENOMEM; + goto tx_err; + } + + tx->hdev = tmff2->hdev; + tx->input_dev = tmff2->input_dev; + tx->usbdev = to_usb_device(tmff2->hdev->dev.parent->parent); + tx->buffer_length = TMTX_BUFFER_LENGTH; + + tx->send_buffer = kzalloc(tx->buffer_length, GFP_KERNEL); + if (!tx->send_buffer) { + ret = -ENOMEM; + goto send_err; + } + + report_list = &tx->hdev->report_enum[HID_OUTPUT_REPORT].report_list; + tx->report = list_entry(report_list->next, struct hid_report, list); + tx->ff_field = tx->report->field[0]; + + tx->open = tx->input_dev->open; + tx->close = tx->input_dev->close; + + if ((ret = tx_interrupts(tx))) + goto interrupt_err; + + /* everything went OK */ + tmff2->data = tx; + tmff2->params = tx_params; + tmff2->max_effects = TMTX_MAX_EFFECTS; + memcpy(tmff2->supported_effects, tx_effects, sizeof(tx_effects)); + + if (!open_mode) + tx_send_open(tx); + + hid_info(tx->hdev, "force feedback for TX\n"); + return 0; + +interrupt_err: +send_err: + kfree(tx); +tx_err: + hid_err(tmff2->hdev, "failed initializing TX\n"); + return ret; +} + +static __u8 *tx_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + rdesc = tx_pc_rdesc_fixed; + *rsize = sizeof(tx_pc_rdesc_fixed); + return rdesc; +} + +int tx_populate_api(struct tmff2_device_entry *tmff2) +{ + tmff2->play_effect = t300rs_play_effect; + tmff2->upload_effect = t300rs_upload_effect; + tmff2->update_effect = t300rs_update_effect; + tmff2->stop_effect = t300rs_stop_effect; + + tmff2->set_gain = t300rs_set_gain; + tmff2->set_autocenter = t300rs_set_autocenter; + /* TX only has 900 degree range, instead of T300RS 1080 */ + tmff2->set_range = tx_set_range; + tmff2->wheel_fixup = tx_wheel_fixup; + + tmff2->open = tx_open; + tmff2->close = tx_close; + + tmff2->wheel_init = tx_wheel_init; + tmff2->wheel_destroy = tx_wheel_destroy; + + return 0; +} # ---------------------------------------- # Module: kvfm # Version: 53bfb6547f2b # ---------------------------------------- diff --git a/drivers/custom/kvfm/module/Makefile b/drivers/custom/kvfm/module/Makefile new file mode 100644 index 000000000000..f0b086238ac7 --- /dev/null +++ b/drivers/custom/kvfm/module/Makefile @@ -0,0 +1,22 @@ +obj-m += kvmfr.o +USER := $(shell whoami) +KVER ?= $(shell uname -r) +KDIR ?= /lib/modules/$(KVER)/build + +all: + make -C $(KDIR) M=$(PWD) modules + +clean: + make -C $(KDIR) M=$(PWD) clean + +test: + gcc test.c -Wall -Werror -g -Og -o test + +load: all + grep -q '^uio' /proc/modules || sudo modprobe uio + grep -q '^kvmfr' /proc/modules && sudo rmmod kvmfr || true + sudo insmod ./kvmfr.ko + sudo chown $(USER) /dev/uio0 + sudo chown $(USER) /dev/kvmfr0 + +.PHONY: test diff --git a/drivers/custom/kvfm/module/kvmfr.c b/drivers/custom/kvfm/module/kvmfr.c new file mode 100644 index 000000000000..9bcff378b485 --- /dev/null +++ b/drivers/custom/kvfm/module/kvmfr.c @@ -0,0 +1,662 @@ +/** + * Looking Glass + * Copyright © 2017-2025 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PCI_KVMFR_VENDOR_ID 0x1af4 //Red Hat Inc, +#define PCI_KVMFR_DEVICE_ID 0x1110 //Inter-VM shared memory + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef CONFIG_AMD_MEM_ENCRYPT +#include +#endif + +#include "kvmfr.h" + +DEFINE_MUTEX(minor_lock); +DEFINE_IDR(kvmfr_idr); + +#define KVMFR_DEV_NAME "kvmfr" +#define KVMFR_MAX_DEVICES 10 + +static int static_size_mb[KVMFR_MAX_DEVICES]; +static int static_count; +module_param_array(static_size_mb, int, &static_count, 0000); +MODULE_PARM_DESC(static_size_mb, "List of static devices to create in MiB"); + +struct kvmfr_info +{ + int major; + struct class * pClass; +}; + +static struct kvmfr_info *kvmfr; + +enum kvmfr_type +{ + KVMFR_TYPE_PCI, + KVMFR_TYPE_STATIC, +}; + +struct kvmfr_dev +{ + unsigned long size; + int minor; + dev_t devNo; + struct device * pDev; + struct dev_pagemap pgmap; + void * addr; + enum kvmfr_type type; +}; + +struct kvmfrbuf +{ + struct kvmfr_dev * kdev; + pgoff_t pagecount; + unsigned long offset; + struct page ** pages; +}; + +static vm_fault_t kvmfr_vm_fault(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; + struct kvmfrbuf *kbuf = (struct kvmfrbuf *)vma->vm_private_data; + pgoff_t pgoff = vmf->pgoff; + + if (pgoff >= kbuf->pagecount) + return VM_FAULT_SIGBUS; + + vmf->page = kbuf->pages[pgoff]; + get_page(vmf->page); + return 0; +} + +static const struct vm_operations_struct kvmfr_vm_ops = +{ + .fault = kvmfr_vm_fault +}; + +static struct sg_table * map_kvmfrbuf(struct dma_buf_attachment *at, + enum dma_data_direction direction) +{ + struct kvmfrbuf *kbuf = at->dmabuf->priv; + struct sg_table *sg; + int ret; + + sg = kzalloc(sizeof(*sg), GFP_KERNEL); + if (!sg) + return ERR_PTR(-ENOMEM); + + ret = sg_alloc_table_from_pages(sg, kbuf->pages, kbuf->pagecount, + 0, kbuf->pagecount << PAGE_SHIFT, GFP_KERNEL); + if (ret < 0) + goto err; + + if (!dma_map_sg(at->dev, sg->sgl, sg->nents, direction)) + { + ret = -EINVAL; + goto err; + } + + return sg; + +err: + sg_free_table(sg); + kfree(sg); + return ERR_PTR(ret); +} + +static void unmap_kvmfrbuf(struct dma_buf_attachment * at, struct sg_table * sg, + enum dma_data_direction direction) +{ + dma_unmap_sg(at->dev, sg->sgl, sg->nents, direction); + sg_free_table(sg); + kfree(sg); +} + +static void release_kvmfrbuf(struct dma_buf * buf) +{ + struct kvmfrbuf *kbuf = (struct kvmfrbuf *)buf->priv; + kfree(kbuf->pages); + kfree(kbuf); +} + +static int mmap_kvmfrbuf(struct dma_buf * buf, struct vm_area_struct * vma) +{ + struct kvmfrbuf * kbuf = (struct kvmfrbuf *)buf->priv; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + + if ((offset + size > (kbuf->pagecount << PAGE_SHIFT)) + || (offset + size < offset)) + return -EINVAL; + + if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) == 0) + return -EINVAL; + + switch (kbuf->kdev->type) + { + case KVMFR_TYPE_PCI: + vma->vm_ops = &kvmfr_vm_ops; + vma->vm_private_data = buf->priv; + return 0; + + case KVMFR_TYPE_STATIC: + return remap_vmalloc_range(vma, kbuf->kdev->addr + kbuf->offset, + vma->vm_pgoff); + + default: + return -EINVAL; + } +} + +static const struct dma_buf_ops kvmfrbuf_ops = +{ + .map_dma_buf = map_kvmfrbuf, + .unmap_dma_buf = unmap_kvmfrbuf, + .release = release_kvmfrbuf, + .mmap = mmap_kvmfrbuf +}; + +static long kvmfr_dmabuf_create(struct kvmfr_dev * kdev, struct file * filp, + unsigned long arg) +{ + struct kvmfr_dmabuf_create create; + DEFINE_DMA_BUF_EXPORT_INFO(exp_kdev); + struct kvmfrbuf * kbuf; + struct dma_buf * buf; + u32 i; + u8 *p; + int ret = -EINVAL; + + if (copy_from_user(&create, (void __user *)arg, + sizeof(create))) + return -EFAULT; + + if (!IS_ALIGNED(create.offset, PAGE_SIZE) || + !IS_ALIGNED(create.size , PAGE_SIZE)) + { + printk("kvmfr: buffer not aligned to 0x%lx bytes", PAGE_SIZE); + return -EINVAL; + } + + if ((create.offset + create.size > kdev->size) || + (create.offset + create.size < create.offset)) + return -EINVAL; + + kbuf = kzalloc(sizeof(struct kvmfrbuf), GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + kbuf->kdev = kdev; + kbuf->pagecount = create.size >> PAGE_SHIFT; + kbuf->offset = create.offset; + kbuf->pages = kmalloc_array(kbuf->pagecount, sizeof(*kbuf->pages), + GFP_KERNEL); + if (!kbuf->pages) + { + ret = -ENOMEM; + goto err; + } + + p = ((u8*)kdev->addr) + create.offset; + + switch (kdev->type) + { + case KVMFR_TYPE_PCI: + for (i = 0; i < kbuf->pagecount; ++i) + { + kbuf->pages[i] = virt_to_page(p); + p += PAGE_SIZE; + } + break; + + case KVMFR_TYPE_STATIC: + for (i = 0; i < kbuf->pagecount; ++i) + { + kbuf->pages[i] = vmalloc_to_page(p); + p += PAGE_SIZE; + } + break; + } + + exp_kdev.ops = &kvmfrbuf_ops; + exp_kdev.size = create.size; + exp_kdev.priv = kbuf; + exp_kdev.flags = O_RDWR; + + buf = dma_buf_export(&exp_kdev); + if (IS_ERR(buf)) + { + ret = PTR_ERR(buf); + goto err; + } + + printk("kvmfr_dmabuf_create with size %llu offset: %llu", + create.size, create.offset); + return dma_buf_fd(buf, create.flags & KVMFR_DMABUF_FLAG_CLOEXEC ? O_CLOEXEC : 0); + +err: + kfree(kbuf->pages); + kfree(kbuf); + return ret; +} + +static long device_ioctl(struct file * filp, unsigned int ioctl, + unsigned long arg) +{ + struct kvmfr_dev * kdev; + long ret; + + kdev = (struct kvmfr_dev *)idr_find(&kvmfr_idr, iminor(filp->f_inode)); + if (!kdev) + return -EINVAL; + + switch(ioctl) + { + case KVMFR_DMABUF_CREATE: + ret = kvmfr_dmabuf_create(kdev, filp, arg); + break; + + case KVMFR_DMABUF_GETSIZE: + ret = kdev->size; + break; + + default: + return -ENOTTY; + } + + return ret; +} + +static vm_fault_t pci_mmap_fault(struct vm_fault *vmf) +{ + struct vm_area_struct * vma = vmf->vma; + struct kvmfr_dev * kdev = (struct kvmfr_dev *)vma->vm_private_data; + + vmf->page = virt_to_page(kdev->addr + (vmf->pgoff << PAGE_SHIFT)); + get_page(vmf->page); + return 0; +} + +static const struct vm_operations_struct pci_mmap_ops = +{ + .fault = pci_mmap_fault +}; + +static int device_mmap(struct file * filp, struct vm_area_struct * vma) +{ + struct kvmfr_dev * kdev; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + + kdev = (struct kvmfr_dev *)idr_find(&kvmfr_idr, iminor(filp->f_inode)); + if (!kdev) + return -EINVAL; + + if ((offset + size > kdev->size) || (offset + size < offset)) + return -EINVAL; + + printk(KERN_INFO "mmap kvmfr%d: %lx-%lx with size %lu offset %lu\n", + kdev->minor, vma->vm_start, vma->vm_end, size, offset); + + switch (kdev->type) + { + case KVMFR_TYPE_PCI: +#ifdef CONFIG_AMD_MEM_ENCRYPT + /* Clear C-bit for ivshmem when mapped + * as normal memory to the userspace + * + * devm_memremap below will "hotplug" the ivshmem as normal mem, + * when sev and/or sev-snp is effective, + * ivshmem will be encrypted and private memory. + * + * However, this is not the intention of ivshmem, as it + * is meant to be shared with other VMs and the hypervisor. + * + * Mapping ivshmem as iomem could resolve the sev/sev-snp issue, + * but it then will not be cached and the performance is low. + * + * To maintain high performance yet make it shared, we should + * clear the C-bit for ivshmem. + */ + vma->vm_page_prot.pgprot &= ~(sme_me_mask); +#endif + vma->vm_ops = &pci_mmap_ops; + vma->vm_private_data = kdev; + return 0; + + case KVMFR_TYPE_STATIC: + return remap_vmalloc_range(vma, kdev->addr, vma->vm_pgoff); + + default: + return -ENODEV; + } +} + +static struct file_operations fops = +{ + .owner = THIS_MODULE, + .unlocked_ioctl = device_ioctl, + .mmap = device_mmap, +}; + +static int kvmfr_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct kvmfr_dev *kdev; + + kdev = kzalloc(sizeof(struct kvmfr_dev), GFP_KERNEL); + if (!kdev) + { + printk(KERN_INFO "kvmfr: kvmfr_pci_probe: failed to allocate memory!\n"); + return -ENOMEM; + } + + if (pci_enable_device(dev)) + { + printk(KERN_INFO "kvmfr: kvmfr_pci_probe: failed to enable device!\n"); + goto out_free; + } + + if (pci_request_regions(dev, KVMFR_DEV_NAME)) + { + printk(KERN_INFO "kvmfr: kvmfr_pci_probe: failed to request regions!\n"); + goto out_disable; + } + + kdev->size = pci_resource_len(dev, 2); + kdev->type = KVMFR_TYPE_PCI; + + mutex_lock(&minor_lock); + kdev->minor = idr_alloc(&kvmfr_idr, kdev, 0, KVMFR_MAX_DEVICES, GFP_KERNEL); + if (kdev->minor < 0) + { + printk(KERN_INFO "kvmfr: kvmfr_pci_probe: failed to allocate ID!\n"); + mutex_unlock(&minor_lock); + goto out_release; + } + mutex_unlock(&minor_lock); + + kdev->devNo = MKDEV(kvmfr->major, kdev->minor); + kdev->pDev = device_create(kvmfr->pClass, NULL, kdev->devNo, NULL, + KVMFR_DEV_NAME "%d", kdev->minor); + if (IS_ERR(kdev->pDev)) + { + printk( + KERN_INFO "kvmfr: kvmfr_pci_probe: failed to create /dev/%s%d device!\n", + KVMFR_DEV_NAME, + kdev->minor); + goto out_unminor; + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) + kdev->pgmap.res.start = pci_resource_start(dev, 2); + kdev->pgmap.res.end = pci_resource_end (dev, 2); + kdev->pgmap.res.flags = pci_resource_flags(dev, 2); +#else + kdev->pgmap.range.start = pci_resource_start(dev, 2); + kdev->pgmap.range.end = pci_resource_end (dev, 2); + kdev->pgmap.nr_range = 1; +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) + kdev->pgmap.type = MEMORY_DEVICE_DEVDAX; +#else + kdev->pgmap.type = MEMORY_DEVICE_GENERIC; +#endif + + kdev->addr = devm_memremap_pages(&dev->dev, &kdev->pgmap); + if (IS_ERR(kdev->addr)) + { + printk( + KERN_INFO "kvmfr: kvmfr_pci_probe: failed to remap pages! ret = %ld\n", + PTR_ERR(kdev->addr)); + goto out_destroy; + } + + pci_set_drvdata(dev, kdev); + printk( + KERN_INFO "kvmfr: kvmfr_pci_probe: /dev/%s%d created\n", + KVMFR_DEV_NAME, + kdev->minor); + return 0; + +out_destroy: + device_destroy(kvmfr->pClass, kdev->devNo); +out_unminor: + mutex_lock(&minor_lock); + idr_remove(&kvmfr_idr, kdev->minor); + mutex_unlock(&minor_lock); +out_release: + pci_release_regions(dev); +out_disable: + pci_disable_device(dev); +out_free: + kfree(kdev); + return -ENODEV; +} + +static void kvmfr_pci_remove(struct pci_dev *dev) +{ + struct kvmfr_dev *kdev = pci_get_drvdata(dev); + + devm_memunmap_pages(&dev->dev, &kdev->pgmap); + device_destroy(kvmfr->pClass, kdev->devNo); + + mutex_lock(&minor_lock); + idr_remove(&kvmfr_idr, kdev->minor); + mutex_unlock(&minor_lock); + + pci_release_regions(dev); + pci_disable_device(dev); + + kfree(kdev); +} + +static struct pci_device_id kvmfr_pci_ids[] = +{ + { + .vendor = PCI_KVMFR_VENDOR_ID, + .device = PCI_KVMFR_DEVICE_ID, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID + }, + { 0, } +}; + +static struct pci_driver kvmfr_pci_driver = +{ + .name = KVMFR_DEV_NAME, + .id_table = kvmfr_pci_ids, + .probe = kvmfr_pci_probe, + .remove = kvmfr_pci_remove +}; + +static int create_static_device_unlocked(int size_mb) +{ + struct kvmfr_dev * kdev; + int ret = -ENODEV; + + kdev = kzalloc(sizeof(struct kvmfr_dev), GFP_KERNEL); + if (!kdev) + return -ENOMEM; + + kdev->size = size_mb * 1024 * 1024; + kdev->type = KVMFR_TYPE_STATIC; + kdev->addr = vmalloc_user(kdev->size); + if (!kdev->addr) + { + printk( + KERN_ERR "kvmfr: failed to allocate memory for static device: %d MiB\n", + size_mb); + ret = -ENOMEM; + goto out_free; + } + + kdev->minor = idr_alloc(&kvmfr_idr, kdev, 0, KVMFR_MAX_DEVICES, GFP_KERNEL); + if (kdev->minor < 0) + goto out_release; + + kdev->devNo = MKDEV(kvmfr->major, kdev->minor); + kdev->pDev = device_create(kvmfr->pClass, NULL, kdev->devNo, NULL, + KVMFR_DEV_NAME "%d", kdev->minor); + if (IS_ERR(kdev->pDev)) + goto out_unminor; + + return 0; + +out_unminor: + idr_remove(&kvmfr_idr, kdev->minor); +out_release: + vfree(kdev->addr); +out_free: + kfree(kdev); + return ret; +} + +static void free_static_device_unlocked(struct kvmfr_dev * kdev) +{ + device_destroy(kvmfr->pClass, kdev->devNo); + idr_remove(&kvmfr_idr, kdev->minor); + vfree(kdev->addr); + kfree(kdev); +} + +static void free_static_devices(void) +{ + int id; + struct kvmfr_dev * kdev; + + mutex_lock(&minor_lock); + idr_for_each_entry(&kvmfr_idr, kdev, id) + free_static_device_unlocked(kdev); + mutex_unlock(&minor_lock); +} + +static int create_static_devices(void) +{ + int i; + int ret = 0; + + mutex_lock(&minor_lock); + printk(KERN_INFO "kvmfr: creating %d static devices\n", static_count); + for (i = 0; i < static_count; ++i) + { + ret = create_static_device_unlocked(static_size_mb[i]); + if (ret < 0) + break; + } + mutex_unlock(&minor_lock); + + if (ret < 0) + free_static_devices(); + return ret; +} + +static int __init kvmfr_module_init(void) +{ + int ret; + + kvmfr = kzalloc(sizeof(struct kvmfr_info), GFP_KERNEL); + if (!kvmfr) { + printk(KERN_INFO "kvmfr: kvmfr_module_init: failed to allocate memory!\n"); + return -ENOMEM; + } + + kvmfr->major = register_chrdev(0, KVMFR_DEV_NAME, &fops); + if (kvmfr->major < 0) { + printk( + KERN_INFO "kvmfr: kvmfr_module_init: failed to register char device!\n"); + goto out_free; + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + kvmfr->pClass = class_create(THIS_MODULE, KVMFR_DEV_NAME); +#else + kvmfr->pClass = class_create(KVMFR_DEV_NAME); +#endif + if (IS_ERR(kvmfr->pClass)) { + printk(KERN_INFO "kvmfr: kvmfr_module_init: failed to create class!\n"); + goto out_unreg; + } + + ret = create_static_devices(); + if (ret < 0) { + printk( + KERN_INFO "kvmfr: kvmfr_module_init: failed to create static devices!\n"); + goto out_class_destroy; + } + + ret = pci_register_driver(&kvmfr_pci_driver); + if (ret < 0) { + printk( + KERN_INFO "kvmfr: kvmfr_module_init: failed to register pci driver!\n"); + goto out_free_static; + } + + printk(KERN_INFO "kvmfr: kvmfr_module_init: module loaded\n"); + return 0; + +out_free_static: + free_static_devices(); +out_class_destroy: + class_destroy(kvmfr->pClass); +out_unreg: + unregister_chrdev(kvmfr->major, KVMFR_DEV_NAME); +out_free: + kfree(kvmfr); + + kvmfr = NULL; + return -ENODEV; +} + +static void __exit kvmfr_module_exit(void) +{ + pci_unregister_driver(&kvmfr_pci_driver); + free_static_devices(); + class_destroy(kvmfr->pClass); + unregister_chrdev(kvmfr->major, KVMFR_DEV_NAME); + kfree(kvmfr); + kvmfr = NULL; +} + +module_init(kvmfr_module_init); +module_exit(kvmfr_module_exit); + +MODULE_DEVICE_TABLE(pci, kvmfr_pci_ids); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Geoffrey McRae "); +MODULE_AUTHOR("Guanzhong Chen "); +MODULE_VERSION("0.0.12"); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,13,0) +MODULE_IMPORT_NS("DMA_BUF"); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5,16,0) +MODULE_IMPORT_NS(DMA_BUF); +#endif diff --git a/drivers/custom/kvfm/module/kvmfr.h b/drivers/custom/kvfm/module/kvmfr.h new file mode 100644 index 000000000000..23c01e359a28 --- /dev/null +++ b/drivers/custom/kvfm/module/kvmfr.h @@ -0,0 +1,38 @@ +/** + * Looking Glass + * Copyright © 2017-2025 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _KVMFR_H +#define _KVMFR_H + +#include +#include + +#define KVMFR_DMABUF_FLAG_CLOEXEC 0x1 + +struct kvmfr_dmabuf_create { + __u8 flags; + __u64 offset; + __u64 size; +}; + +#define KVMFR_DMABUF_GETSIZE _IO('u', 0x44) +#define KVMFR_DMABUF_CREATE _IOW('u', 0x42, struct kvmfr_dmabuf_create) + +#endif \ No newline at end of file diff --git a/drivers/custom/kvfm/module/test.c b/drivers/custom/kvfm/module/test.c new file mode 100644 index 000000000000..8833977edd35 --- /dev/null +++ b/drivers/custom/kvfm/module/test.c @@ -0,0 +1,138 @@ +/** + * Looking Glass + * Copyright © 2017-2025 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kvmfr.h" + +int main(void) +{ + int page_size = getpagesize(); + + int fd = open("/dev/kvmfr0", O_RDWR); + if (fd < 0) + { + perror("open"); + return -1; + } + + unsigned long size = ioctl(fd, KVMFR_DMABUF_GETSIZE , 0); + printf("Size: %lu MiB\n", size / 1024 / 1024); + + // mmaping 0-offset dmabuf with 0 offset + struct kvmfr_dmabuf_create create = + { + .flags = KVMFR_DMABUF_FLAG_CLOEXEC, + .offset = 0x0, + .size = size, + }; + int dmaFd = ioctl(fd, KVMFR_DMABUF_CREATE, &create); + if (dmaFd < 0) + { + perror("ioctl"); + return -1; + } + + void * mem = mmap(NULL, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, dmaFd, 0); + if (mem == MAP_FAILED) + { + perror("mmap on dmabuf with no offset"); + return -1; + } + memset(mem, 0xAA, create.size); + strcpy(mem + page_size, "Hello, world!"); + munmap(mem, create.size); + + // mmaping 0-offset dmabuf with 1 page offset + mem = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, dmaFd, page_size); + if (mem == MAP_FAILED) + { + perror("mmap on dmabuf with offset"); + return -1; + } + printf("Read string: %s\n", (char *) mem); + munmap(mem, page_size); + + close(dmaFd); + + // mmaping page-offset dmabuf with 0 offset + create.offset = page_size; + create.size = 2 * page_size; + dmaFd = ioctl(fd, KVMFR_DMABUF_CREATE, &create); + mem = mmap(NULL, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, dmaFd, 0); + if (mem == MAP_FAILED) + { + perror("mmap on offset dmabuf with no offset"); + return -1; + } + printf("Read string: %s\n", (char *) mem); + munmap(mem, create.size); + + // mmaping page-offset dmabuf with 1 page offset + char *bytes = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, dmaFd, page_size); + if (bytes == MAP_FAILED) + { + perror("mmap on offset dmabuf with offset"); + return -1; + } + for (int i = 0; i < page_size; i++) + if (bytes[i] != (char) 0xAA) + printf("Index: %d: 0x%02x\n", i, (unsigned) bytes[i]); + munmap(mem, page_size); + + close(dmaFd); + + // mmaping device with 0 offset + mem = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, page_size); + if (mem == MAP_FAILED) + { + perror("mmap on file with offset"); + return -1; + } + printf("Read string: %s\n", (char *) mem); + munmap(mem, page_size); + + // mmaping device with 0 offset + uint32_t *data = mmap(NULL, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) + { + perror("mmap on file with no offset"); + return -1; + } + for (size_t i = 0; i < create.size / sizeof(uint32_t); i++) + { + if (data[i] != 0xAAAAAAAA) + printf("Index %lu: 0x%08" PRIx32 "\n", i, data[i]); + } + munmap(data, create.size); + + close(fd); + return 0; +} # ---------------------------------------- # Module: lg4ff # Version: v0.5.0 # ---------------------------------------- diff --git a/drivers/custom/lg4ff/Kbuild b/drivers/custom/lg4ff/Kbuild new file mode 100644 index 000000000000..673c45615fa7 --- /dev/null +++ b/drivers/custom/lg4ff/Kbuild @@ -0,0 +1,6 @@ +obj-m := hid-logitech-new.o +hid-logitech-new-y := hid-lg.o hid-lg4ff.o +hid-logitech-new-$(CONFIG_LOGITECH_FF) += hid-lgff.o +hid-logitech-new-$(CONFIG_LOGIRUMBLEPAD2_FF) += hid-lg2ff.o +hid-logitech-new-$(CONFIG_LOGIG940_FF) += hid-lg3ff.o +ccflags-y := -Idrivers/hid diff --git a/drivers/custom/lg4ff/Makefile b/drivers/custom/lg4ff/Makefile new file mode 100644 index 000000000000..c97e309b3a91 --- /dev/null +++ b/drivers/custom/lg4ff/Makefile @@ -0,0 +1,27 @@ +KVERSION := `uname -r` +KDIR := /lib/modules/${KVERSION}/build + +default: + $(MAKE) -C $(KDIR) M=$$PWD + +install: default + $(MAKE) -C $(KDIR) M=$$PWD modules_install + depmod -A + +remove: + rmmod hid-logitech 2> /dev/null || true + rmmod hid-logitech-new 2> /dev/null || true + +load: install remove + modprobe hid-logitech-new ${OPTIONS} + +load_debug: install remove + modprobe hid-logitech-new dyndbg=+p ${OPTIONS} + +unload: + rmmod hid-logitech-new + modprobe hid-logitech + +clean: + $(MAKE) -C $(KDIR) M=$$PWD clean + diff --git a/drivers/custom/lg4ff/hid-ids.h b/drivers/custom/lg4ff/hid-ids.h new file mode 100644 index 000000000000..45071de0d3dd --- /dev/null +++ b/drivers/custom/lg4ff/hid-ids.h @@ -0,0 +1,1433 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * USB HID quirks support for Linux + * + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik + * Copyright (c) 2005 Michael Haboustak for Concept2, Inc + * Copyright (c) 2006-2007 Jiri Kosina + */ + +/* + */ + +#ifndef HID_IDS_H_FILE +#define HID_IDS_H_FILE + +#define USB_VENDOR_ID_258A 0x258a +#define USB_DEVICE_ID_258A_6A88 0x6a88 + +#define USB_VENDOR_ID_3M 0x0596 +#define USB_DEVICE_ID_3M1968 0x0500 +#define USB_DEVICE_ID_3M2256 0x0502 +#define USB_DEVICE_ID_3M3266 0x0506 + +#define USB_VENDOR_ID_A4TECH 0x09da +#define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006 +#define USB_DEVICE_ID_A4TECH_X5_005D 0x000a +#define USB_DEVICE_ID_A4TECH_RP_649 0x001a +#define USB_DEVICE_ID_A4TECH_NB_95 0x022b + +#define USB_VENDOR_ID_AASHIMA 0x06d6 +#define USB_DEVICE_ID_AASHIMA_GAMEPAD 0x0025 +#define USB_DEVICE_ID_AASHIMA_PREDATOR 0x0026 + +#define USB_VENDOR_ID_ACECAD 0x0460 +#define USB_DEVICE_ID_ACECAD_FLAIR 0x0004 +#define USB_DEVICE_ID_ACECAD_302 0x0008 + +#define USB_VENDOR_ID_ACRUX 0x1a34 + +#define USB_VENDOR_ID_ACTIONSTAR 0x2101 +#define USB_DEVICE_ID_ACTIONSTAR_1011 0x1011 + +#define USB_VENDOR_ID_ADS_TECH 0x06e1 +#define USB_DEVICE_ID_ADS_TECH_RADIO_SI470X 0xa155 + +#define USB_VENDOR_ID_AFATECH 0x15a4 +#define USB_DEVICE_ID_AFATECH_AF9016 0x9016 + +#define USB_VENDOR_ID_AIPTEK 0x08ca +#define USB_DEVICE_ID_AIPTEK_01 0x0001 +#define USB_DEVICE_ID_AIPTEK_10 0x0010 +#define USB_DEVICE_ID_AIPTEK_20 0x0020 +#define USB_DEVICE_ID_AIPTEK_21 0x0021 +#define USB_DEVICE_ID_AIPTEK_22 0x0022 +#define USB_DEVICE_ID_AIPTEK_23 0x0023 +#define USB_DEVICE_ID_AIPTEK_24 0x0024 + +#define USB_VENDOR_ID_AIRCABLE 0x16CA +#define USB_DEVICE_ID_AIRCABLE1 0x1502 + +#define USB_VENDOR_ID_AIREN 0x1a2c +#define USB_DEVICE_ID_AIREN_SLIMPLUS 0x0002 + +#define USB_VENDOR_ID_AKAI 0x2011 +#define USB_DEVICE_ID_AKAI_MPKMINI2 0x0715 + +#define USB_VENDOR_ID_AKAI_09E8 0x09E8 +#define USB_DEVICE_ID_AKAI_09E8_MIDIMIX 0x0031 + +#define USB_VENDOR_ID_ALCOR 0x058f +#define USB_DEVICE_ID_ALCOR_USBRS232 0x9720 +#define USB_DEVICE_ID_ALCOR_MALTRON_KB 0x9410 + +#define USB_VENDOR_ID_ALPS 0x0433 +#define USB_DEVICE_ID_IBM_GAMEPAD 0x1101 + +#define USB_VENDOR_ID_ALPS_JP 0x044E +#define HID_DEVICE_ID_ALPS_U1_DUAL 0x120B +#define HID_DEVICE_ID_ALPS_U1 0x1215 +#define HID_DEVICE_ID_ALPS_U1_UNICORN_LEGACY 0x121E +#define HID_DEVICE_ID_ALPS_T4_BTNLESS 0x120C + +#define USB_VENDOR_ID_AMI 0x046b +#define USB_DEVICE_ID_AMI_VIRT_KEYBOARD_AND_MOUSE 0xff10 + +#define USB_VENDOR_ID_ANTON 0x1130 +#define USB_DEVICE_ID_ANTON_TOUCH_PAD 0x3101 + +#define USB_VENDOR_ID_APPLE 0x05ac +#define BT_VENDOR_ID_APPLE 0x004c +#define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304 +#define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d +#define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269 +#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD 0x030e +#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 0x0265 +#define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI 0x020e +#define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO 0x020f +#define USB_DEVICE_ID_APPLE_GEYSER_ANSI 0x0214 +#define USB_DEVICE_ID_APPLE_GEYSER_ISO 0x0215 +#define USB_DEVICE_ID_APPLE_GEYSER_JIS 0x0216 +#define USB_DEVICE_ID_APPLE_GEYSER3_ANSI 0x0217 +#define USB_DEVICE_ID_APPLE_GEYSER3_ISO 0x0218 +#define USB_DEVICE_ID_APPLE_GEYSER3_JIS 0x0219 +#define USB_DEVICE_ID_APPLE_GEYSER4_ANSI 0x021a +#define USB_DEVICE_ID_APPLE_GEYSER4_ISO 0x021b +#define USB_DEVICE_ID_APPLE_GEYSER4_JIS 0x021c +#define USB_DEVICE_ID_APPLE_ALU_MINI_ANSI 0x021d +#define USB_DEVICE_ID_APPLE_ALU_MINI_ISO 0x021e +#define USB_DEVICE_ID_APPLE_ALU_MINI_JIS 0x021f +#define USB_DEVICE_ID_APPLE_ALU_ANSI 0x0220 +#define USB_DEVICE_ID_APPLE_ALU_ISO 0x0221 +#define USB_DEVICE_ID_APPLE_ALU_JIS 0x0222 +#define USB_DEVICE_ID_APPLE_WELLSPRING_ANSI 0x0223 +#define USB_DEVICE_ID_APPLE_WELLSPRING_ISO 0x0224 +#define USB_DEVICE_ID_APPLE_WELLSPRING_JIS 0x0225 +#define USB_DEVICE_ID_APPLE_GEYSER4_HF_ANSI 0x0229 +#define USB_DEVICE_ID_APPLE_GEYSER4_HF_ISO 0x022a +#define USB_DEVICE_ID_APPLE_GEYSER4_HF_JIS 0x022b +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI 0x022c +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO 0x022d +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS 0x022e +#define USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI 0x0230 +#define USB_DEVICE_ID_APPLE_WELLSPRING2_ISO 0x0231 +#define USB_DEVICE_ID_APPLE_WELLSPRING2_JIS 0x0232 +#define USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI 0x0236 +#define USB_DEVICE_ID_APPLE_WELLSPRING3_ISO 0x0237 +#define USB_DEVICE_ID_APPLE_WELLSPRING3_JIS 0x0238 +#define USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI 0x023f +#define USB_DEVICE_ID_APPLE_WELLSPRING4_ISO 0x0240 +#define USB_DEVICE_ID_APPLE_WELLSPRING4_JIS 0x0241 +#define USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI 0x0242 +#define USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO 0x0243 +#define USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS 0x0244 +#define USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI 0x0245 +#define USB_DEVICE_ID_APPLE_WELLSPRING5_ISO 0x0246 +#define USB_DEVICE_ID_APPLE_WELLSPRING5_JIS 0x0247 +#define USB_DEVICE_ID_APPLE_ALU_REVB_ANSI 0x024f +#define USB_DEVICE_ID_APPLE_ALU_REVB_ISO 0x0250 +#define USB_DEVICE_ID_APPLE_ALU_REVB_JIS 0x0251 +#define USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI 0x0252 +#define USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO 0x0253 +#define USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS 0x0254 +#define USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI 0x0259 +#define USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO 0x025a +#define USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS 0x025b +#define USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI 0x0249 +#define USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO 0x024a +#define USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS 0x024b +#define USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI 0x024c +#define USB_DEVICE_ID_APPLE_WELLSPRING6_ISO 0x024d +#define USB_DEVICE_ID_APPLE_WELLSPRING6_JIS 0x024e +#define USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI 0x0262 +#define USB_DEVICE_ID_APPLE_WELLSPRING7_ISO 0x0263 +#define USB_DEVICE_ID_APPLE_WELLSPRING7_JIS 0x0264 +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI 0x0239 +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO 0x023a +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS 0x023b +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI 0x0255 +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO 0x0256 +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS 0x0257 +#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015 0x0267 +#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015 0x026c +#define USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI 0x0290 +#define USB_DEVICE_ID_APPLE_WELLSPRING8_ISO 0x0291 +#define USB_DEVICE_ID_APPLE_WELLSPRING8_JIS 0x0292 +#define USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI 0x0272 +#define USB_DEVICE_ID_APPLE_WELLSPRING9_ISO 0x0273 +#define USB_DEVICE_ID_APPLE_WELLSPRING9_JIS 0x0274 +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K 0x027a +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132 0x027b +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680 0x027c +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213 0x027d +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K 0x027e +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223 0x027f +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K 0x0280 +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F 0x0340 +#define USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY 0x030a +#define USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY 0x030b +#define USB_DEVICE_ID_APPLE_IRCONTROL 0x8240 +#define USB_DEVICE_ID_APPLE_IRCONTROL2 0x1440 +#define USB_DEVICE_ID_APPLE_IRCONTROL3 0x8241 +#define USB_DEVICE_ID_APPLE_IRCONTROL4 0x8242 +#define USB_DEVICE_ID_APPLE_IRCONTROL5 0x8243 +#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 0x029c +#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 0x029a +#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021 0x029f +#define USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT 0x8102 +#define USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY 0x8302 + +#define USB_VENDOR_ID_ASUS 0x0486 +#define USB_DEVICE_ID_ASUS_T91MT 0x0185 +#define USB_DEVICE_ID_ASUSTEK_MULTITOUCH_YFO 0x0186 + +#define USB_VENDOR_ID_ASUSTEK 0x0b05 +#define USB_DEVICE_ID_ASUSTEK_LCM 0x1726 +#define USB_DEVICE_ID_ASUSTEK_LCM2 0x175b +#define USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD 0x17e0 +#define USB_DEVICE_ID_ASUSTEK_T100TAF_KEYBOARD 0x1807 +#define USB_DEVICE_ID_ASUSTEK_T100CHI_KEYBOARD 0x8502 +#define USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD 0x183d +#define USB_DEVICE_ID_ASUSTEK_T304_KEYBOARD 0x184a +#define USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD 0x8585 +#define USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD 0x0101 +#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854 +#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837 +#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822 +#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD 0x1866 +#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6 +#define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD 0x196b +#define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD 0x1869 + +#define USB_VENDOR_ID_ATEN 0x0557 +#define USB_DEVICE_ID_ATEN_UC100KM 0x2004 +#define USB_DEVICE_ID_ATEN_CS124U 0x2202 +#define USB_DEVICE_ID_ATEN_2PORTKVM 0x2204 +#define USB_DEVICE_ID_ATEN_4PORTKVM 0x2205 +#define USB_DEVICE_ID_ATEN_4PORTKVMC 0x2208 +#define USB_DEVICE_ID_ATEN_CS682 0x2213 +#define USB_DEVICE_ID_ATEN_CS692 0x8021 +#define USB_DEVICE_ID_ATEN_CS1758 0x2220 + +#define USB_VENDOR_ID_ATMEL 0x03eb +#define USB_DEVICE_ID_ATMEL_MULTITOUCH 0x211c +#define USB_DEVICE_ID_ATMEL_MXT_DIGITIZER 0x2118 +#define USB_VENDOR_ID_ATMEL_V_USB 0x16c0 +#define USB_DEVICE_ID_ATMEL_V_USB 0x05df + +#define USB_VENDOR_ID_AUREAL 0x0755 +#define USB_DEVICE_ID_AUREAL_W01RN 0x2626 + +#define USB_VENDOR_ID_AVERMEDIA 0x07ca +#define USB_DEVICE_ID_AVER_FM_MR800 0xb800 + +#define USB_VENDOR_ID_AXENTIA 0x12cf +#define USB_DEVICE_ID_AXENTIA_FM_RADIO 0x7111 + +#define USB_VENDOR_ID_BAANTO 0x2453 +#define USB_DEVICE_ID_BAANTO_MT_190W2 0x0100 + +#define USB_VENDOR_ID_BELKIN 0x050d +#define USB_DEVICE_ID_FLIP_KVM 0x3201 + +#define USB_VENDOR_ID_BERKSHIRE 0x0c98 +#define USB_DEVICE_ID_BERKSHIRE_PCWD 0x1140 + +#define USB_VENDOR_ID_BETOP_2185BFM 0x11c2 +#define USB_VENDOR_ID_BETOP_2185PC 0x11c0 +#define USB_VENDOR_ID_BETOP_2185V2PC 0x8380 +#define USB_VENDOR_ID_BETOP_2185V2BFM 0x20bc + +#define USB_VENDOR_ID_BIGBEN 0x146b +#define USB_DEVICE_ID_BIGBEN_PS3OFMINIPAD 0x0902 + +#define USB_VENDOR_ID_BTC 0x046e +#define USB_DEVICE_ID_BTC_EMPREX_REMOTE 0x5578 +#define USB_DEVICE_ID_BTC_EMPREX_REMOTE_2 0x5577 + +#define USB_VENDOR_ID_CANDO 0x2087 +#define USB_DEVICE_ID_CANDO_PIXCIR_MULTI_TOUCH 0x0703 +#define USB_DEVICE_ID_CANDO_MULTI_TOUCH 0x0a01 +#define USB_DEVICE_ID_CANDO_MULTI_TOUCH_10_1 0x0a02 +#define USB_DEVICE_ID_CANDO_MULTI_TOUCH_11_6 0x0b03 +#define USB_DEVICE_ID_CANDO_MULTI_TOUCH_15_6 0x0f01 + +#define USB_VENDOR_ID_CH 0x068e +#define USB_DEVICE_ID_CH_PRO_THROTTLE 0x00f1 +#define USB_DEVICE_ID_CH_PRO_PEDALS 0x00f2 +#define USB_DEVICE_ID_CH_FIGHTERSTICK 0x00f3 +#define USB_DEVICE_ID_CH_COMBATSTICK 0x00f4 +#define USB_DEVICE_ID_CH_FLIGHT_SIM_ECLIPSE_YOKE 0x0051 +#define USB_DEVICE_ID_CH_FLIGHT_SIM_YOKE 0x00ff +#define USB_DEVICE_ID_CH_3AXIS_5BUTTON_STICK 0x00d3 +#define USB_DEVICE_ID_CH_AXIS_295 0x001c + +#define USB_VENDOR_ID_CHERRY 0x046a +#define USB_DEVICE_ID_CHERRY_CYMOTION 0x0023 +#define USB_DEVICE_ID_CHERRY_CYMOTION_SOLAR 0x0027 + +#define USB_VENDOR_ID_CHIC 0x05fe +#define USB_DEVICE_ID_CHIC_GAMEPAD 0x0014 + +#define USB_VENDOR_ID_CHICONY 0x04f2 +#define USB_DEVICE_ID_CHICONY_TACTICAL_PAD 0x0418 +#define USB_DEVICE_ID_CHICONY_MULTI_TOUCH 0xb19d +#define USB_DEVICE_ID_CHICONY_WIRELESS 0x0618 +#define USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE 0x1053 +#define USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE2 0x0939 +#define USB_DEVICE_ID_CHICONY_WIRELESS2 0x1123 +#define USB_DEVICE_ID_CHICONY_WIRELESS3 0x1236 +#define USB_DEVICE_ID_ASUS_AK1D 0x1125 +#define USB_DEVICE_ID_CHICONY_TOSHIBA_WT10A 0x1408 +#define USB_DEVICE_ID_CHICONY_ACER_SWITCH12 0x1421 + +#define USB_VENDOR_ID_CHUNGHWAT 0x2247 +#define USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH 0x0001 + +#define USB_VENDOR_ID_CIDC 0x1677 + +#define USB_VENDOR_ID_CJTOUCH 0x24b8 +#define USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0020 0x0020 +#define USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0040 0x0040 + +#define USB_VENDOR_ID_CLAY_LOGIC 0x20a0 +#define USB_DEVICE_ID_NITROKEY_U2F 0x4287 + +#define USB_VENDOR_ID_CMEDIA 0x0d8c +#define USB_DEVICE_ID_CM109 0x000e +#define USB_DEVICE_ID_CMEDIA_HS100B 0x0014 +#define USB_DEVICE_ID_CM6533 0x0022 + +#define USB_VENDOR_ID_CODEMERCS 0x07c0 +#define USB_DEVICE_ID_CODEMERCS_IOW_FIRST 0x1500 +#define USB_DEVICE_ID_CODEMERCS_IOW_LAST 0x15ff + +#define USB_VENDOR_ID_CORSAIR 0x1b1c +#define USB_DEVICE_ID_CORSAIR_K90 0x1b02 +#define USB_DEVICE_ID_CORSAIR_K70R 0x1b09 +#define USB_DEVICE_ID_CORSAIR_K95RGB 0x1b11 +#define USB_DEVICE_ID_CORSAIR_M65RGB 0x1b12 +#define USB_DEVICE_ID_CORSAIR_K70RGB 0x1b13 +#define USB_DEVICE_ID_CORSAIR_STRAFE 0x1b15 +#define USB_DEVICE_ID_CORSAIR_K65RGB 0x1b17 +#define USB_DEVICE_ID_CORSAIR_GLAIVE_RGB 0x1b34 +#define USB_DEVICE_ID_CORSAIR_K70RGB_RAPIDFIRE 0x1b38 +#define USB_DEVICE_ID_CORSAIR_K65RGB_RAPIDFIRE 0x1b39 +#define USB_DEVICE_ID_CORSAIR_SCIMITAR_PRO_RGB 0x1b3e + +#define USB_VENDOR_ID_CREATIVELABS 0x041e +#define USB_DEVICE_ID_CREATIVE_SB_OMNI_SURROUND_51 0x322c +#define USB_DEVICE_ID_PRODIKEYS_PCMIDI 0x2801 +#define USB_DEVICE_ID_CREATIVE_SB0540 0x3100 + +#define USB_VENDOR_ID_CVTOUCH 0x1ff7 +#define USB_DEVICE_ID_CVTOUCH_SCREEN 0x0013 + +#define USB_VENDOR_ID_CYGNAL 0x10c4 +#define USB_DEVICE_ID_CYGNAL_RADIO_SI470X 0x818a +#define USB_DEVICE_ID_FOCALTECH_FTXXXX_MULTITOUCH 0x81b9 +#define USB_DEVICE_ID_CYGNAL_CP2112 0xea90 +#define USB_DEVICE_ID_U2F_ZERO 0x8acf + +#define USB_DEVICE_ID_CYGNAL_RADIO_SI4713 0x8244 + +#define USB_VENDOR_ID_CYPRESS 0x04b4 +#define USB_DEVICE_ID_CYPRESS_MOUSE 0x0001 +#define USB_DEVICE_ID_CYPRESS_HIDCOM 0x5500 +#define USB_DEVICE_ID_CYPRESS_ULTRAMOUSE 0x7417 +#define USB_DEVICE_ID_CYPRESS_BARCODE_1 0xde61 +#define USB_DEVICE_ID_CYPRESS_BARCODE_2 0xde64 +#define USB_DEVICE_ID_CYPRESS_BARCODE_3 0xbca1 +#define USB_DEVICE_ID_CYPRESS_BARCODE_4 0xed81 +#define USB_DEVICE_ID_CYPRESS_TRUETOUCH 0xc001 + +#define USB_DEVICE_ID_CYPRESS_VARMILO_VA104M_07B1 0X07b1 + +#define USB_VENDOR_ID_DATA_MODUL 0x7374 +#define USB_VENDOR_ID_DATA_MODUL_EASYMAXTOUCH 0x1201 + +#define USB_VENDOR_ID_DEALEXTREAME 0x10c5 +#define USB_DEVICE_ID_DEALEXTREAME_RADIO_SI4701 0x819a + +#define USB_VENDOR_ID_DELCOM 0x0fc5 +#define USB_DEVICE_ID_DELCOM_VISUAL_IND 0xb080 + +#define USB_VENDOR_ID_DELL 0x413c +#define USB_DEVICE_ID_DELL_PIXART_USB_OPTICAL_MOUSE 0x301a + +#define USB_VENDOR_ID_DELORME 0x1163 +#define USB_DEVICE_ID_DELORME_EARTHMATE 0x0100 +#define USB_DEVICE_ID_DELORME_EM_LT20 0x0200 + +#define USB_VENDOR_ID_DMI 0x0c0b +#define USB_DEVICE_ID_DMI_ENC 0x5fab + +#define USB_VENDOR_ID_DRAGONRISE 0x0079 +#define USB_DEVICE_ID_REDRAGON_SEYMUR2 0x0006 +#define USB_DEVICE_ID_DRAGONRISE_WIIU 0x1800 +#define USB_DEVICE_ID_DRAGONRISE_PS3 0x1801 +#define USB_DEVICE_ID_DRAGONRISE_DOLPHINBAR 0x1803 +#define USB_DEVICE_ID_DRAGONRISE_GAMECUBE1 0x1843 +#define USB_DEVICE_ID_DRAGONRISE_GAMECUBE2 0x1844 +#define USB_DEVICE_ID_DRAGONRISE_GAMECUBE3 0x1846 + +#define USB_VENDOR_ID_DWAV 0x0eef +#define USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER 0x0001 +#define USB_DEVICE_ID_DWAV_TOUCHCONTROLLER 0x0002 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_480D 0x480d +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_480E 0x480e +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7207 0x7207 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_720C 0x720c +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7224 0x7224 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_722A 0x722A +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_725E 0x725e +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7262 0x7262 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_726B 0x726b +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72A1 0x72a1 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72AA 0x72aa +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72C4 0x72c4 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72D0 0x72d0 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72FA 0x72fa +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7302 0x7302 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7349 0x7349 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_73F7 0x73f7 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001 0xa001 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002 0xc002 + +#define USB_VENDOR_ID_ELAN 0x04f3 +#define USB_DEVICE_ID_TOSHIBA_CLICK_L9W 0x0401 +#define USB_DEVICE_ID_HP_X2 0x074d +#define USB_DEVICE_ID_HP_X2_10_COVER 0x0755 +#define I2C_DEVICE_ID_HP_ENVY_X360_15 0x2d05 +#define I2C_DEVICE_ID_HP_ENVY_X360_15T_DR100 0x29CF +#define I2C_DEVICE_ID_HP_ENVY_X360_EU0009NV 0x2CF9 +#define I2C_DEVICE_ID_HP_SPECTRE_X360_15 0x2817 +#define I2C_DEVICE_ID_HP_SPECTRE_X360_13_AW0020NG 0x29DF +#define I2C_DEVICE_ID_ASUS_TP420IA_TOUCHSCREEN 0x2BC8 +#define USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN 0x2544 +#define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706 +#define I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN 0x261A +#define I2C_DEVICE_ID_SURFACE_GO2_TOUCHSCREEN 0x2A1C +#define I2C_DEVICE_ID_LENOVO_YOGA_C630_TOUCHSCREEN 0x279F + +#define USB_VENDOR_ID_ELECOM 0x056e +#define USB_DEVICE_ID_ELECOM_BM084 0x0061 +#define USB_DEVICE_ID_ELECOM_M_XGL20DLBK 0x00e6 +#define USB_DEVICE_ID_ELECOM_M_XT3URBK 0x00fb +#define USB_DEVICE_ID_ELECOM_M_XT3DRBK 0x00fc +#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd +#define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe +#define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff +#define USB_DEVICE_ID_ELECOM_M_HT1URBK 0x010c +#define USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D 0x010d +#define USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C 0x011c + +#define USB_VENDOR_ID_DREAM_CHEEKY 0x1d34 +#define USB_DEVICE_ID_DREAM_CHEEKY_WN 0x0004 +#define USB_DEVICE_ID_DREAM_CHEEKY_FA 0x000a + +#define USB_VENDOR_ID_ELITEGROUP 0x03fc +#define USB_DEVICE_ID_ELITEGROUP_05D8 0x05d8 + +#define USB_VENDOR_ID_ELO 0x04E7 +#define USB_DEVICE_ID_ELO_TS2515 0x0022 +#define USB_DEVICE_ID_ELO_TS2700 0x0020 +#define USB_DEVICE_ID_ELO_ACCUTOUCH_2216 0x0050 + +#define USB_VENDOR_ID_EMS 0x2006 +#define USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II 0x0118 + +#define USB_VENDOR_ID_EVISION 0x320f +#define USB_DEVICE_ID_EVISION_ICL01 0x5041 + +#define USB_VENDOR_ID_FLATFROG 0x25b5 +#define USB_DEVICE_ID_MULTITOUCH_3200 0x0002 + +#define USB_VENDOR_ID_FUTABA 0x0547 +#define USB_DEVICE_ID_LED_DISPLAY 0x7000 + +#define USB_VENDOR_ID_FUTURE_TECHNOLOGY 0x0403 +#define USB_DEVICE_ID_RETRODE2 0x97c1 +#define USB_DEVICE_ID_FT260 0x6030 + +#define USB_VENDOR_ID_ESSENTIAL_REALITY 0x0d7f +#define USB_DEVICE_ID_ESSENTIAL_REALITY_P5 0x0100 + +#define USB_VENDOR_ID_ETT 0x0664 +#define USB_DEVICE_ID_TC5UH 0x0309 +#define USB_DEVICE_ID_TC4UM 0x0306 + +#define USB_VENDOR_ID_ETURBOTOUCH 0x22b9 +#define USB_DEVICE_ID_ETURBOTOUCH 0x0006 +#define USB_DEVICE_ID_ETURBOTOUCH_2968 0x2968 + +#define USB_VENDOR_ID_EZKEY 0x0518 +#define USB_DEVICE_ID_BTC_8193 0x0002 + +#define USB_VENDOR_ID_FORMOSA 0x147a +#define USB_DEVICE_ID_FORMOSA_IR_RECEIVER 0xe03e + +#define USB_VENDOR_ID_FREESCALE 0x15A2 +#define USB_DEVICE_ID_FREESCALE_MX28 0x004F + +#define USB_VENDOR_ID_FRUCTEL 0x25B6 +#define USB_DEVICE_ID_GAMETEL_MT_MODE 0x0002 + +#define USB_VENDOR_ID_GAMEVICE 0x27F8 +#define USB_DEVICE_ID_GAMEVICE_GV186 0x0BBE +#define USB_DEVICE_ID_GAMEVICE_KISHI 0x0BBF + +#define USB_VENDOR_ID_GAMERON 0x0810 +#define USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR 0x0001 +#define USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR 0x0002 + +#define USB_VENDOR_ID_GEMBIRD 0x11ff +#define USB_DEVICE_ID_GEMBIRD_JPD_DUALFORCE2 0x3331 + +#define USB_VENDOR_ID_GENERAL_TOUCH 0x0dfc +#define USB_DEVICE_ID_GENERAL_TOUCH_WIN7_TWOFINGERS 0x0003 +#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PWT_TENFINGERS 0x0100 +#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_0101 0x0101 +#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_0102 0x0102 +#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_0106 0x0106 +#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_010A 0x010a +#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_E100 0xe100 + +#define USB_VENDOR_ID_GLORIOUS 0x258a +#define USB_DEVICE_ID_GLORIOUS_MODEL_D 0x0033 +#define USB_DEVICE_ID_GLORIOUS_MODEL_O 0x0036 + +#define I2C_VENDOR_ID_GOODIX 0x27c6 +#define I2C_DEVICE_ID_GOODIX_01F0 0x01f0 + +#define USB_VENDOR_ID_GOODTOUCH 0x1aad +#define USB_DEVICE_ID_GOODTOUCH_000f 0x000f + +#define USB_VENDOR_ID_GOOGLE 0x18d1 +#define USB_DEVICE_ID_GOOGLE_HAMMER 0x5022 +#define USB_DEVICE_ID_GOOGLE_TOUCH_ROSE 0x5028 +#define USB_DEVICE_ID_GOOGLE_STAFF 0x502b +#define USB_DEVICE_ID_GOOGLE_WAND 0x502d +#define USB_DEVICE_ID_GOOGLE_WHISKERS 0x5030 +#define USB_DEVICE_ID_GOOGLE_MASTERBALL 0x503c +#define USB_DEVICE_ID_GOOGLE_MAGNEMITE 0x503d +#define USB_DEVICE_ID_GOOGLE_MOONBALL 0x5044 +#define USB_DEVICE_ID_GOOGLE_DON 0x5050 +#define USB_DEVICE_ID_GOOGLE_EEL 0x5057 + +#define USB_VENDOR_ID_GOTOP 0x08f2 +#define USB_DEVICE_ID_SUPER_Q2 0x007f +#define USB_DEVICE_ID_GOGOPEN 0x00ce +#define USB_DEVICE_ID_PENPOWER 0x00f4 + +#define USB_VENDOR_ID_GREENASIA 0x0e8f +#define USB_DEVICE_ID_GREENASIA_DUAL_SAT_ADAPTOR 0x3010 +#define USB_DEVICE_ID_GREENASIA_DUAL_USB_JOYPAD 0x3013 + +#define USB_VENDOR_ID_GRETAGMACBETH 0x0971 +#define USB_DEVICE_ID_GRETAGMACBETH_HUEY 0x2005 + +#define USB_VENDOR_ID_GRIFFIN 0x077d +#define USB_DEVICE_ID_POWERMATE 0x0410 +#define USB_DEVICE_ID_SOUNDKNOB 0x04AA +#define USB_DEVICE_ID_RADIOSHARK 0x627a + +#define USB_VENDOR_ID_GTCO 0x078c +#define USB_DEVICE_ID_GTCO_90 0x0090 +#define USB_DEVICE_ID_GTCO_100 0x0100 +#define USB_DEVICE_ID_GTCO_101 0x0101 +#define USB_DEVICE_ID_GTCO_103 0x0103 +#define USB_DEVICE_ID_GTCO_104 0x0104 +#define USB_DEVICE_ID_GTCO_105 0x0105 +#define USB_DEVICE_ID_GTCO_106 0x0106 +#define USB_DEVICE_ID_GTCO_107 0x0107 +#define USB_DEVICE_ID_GTCO_108 0x0108 +#define USB_DEVICE_ID_GTCO_200 0x0200 +#define USB_DEVICE_ID_GTCO_201 0x0201 +#define USB_DEVICE_ID_GTCO_202 0x0202 +#define USB_DEVICE_ID_GTCO_203 0x0203 +#define USB_DEVICE_ID_GTCO_204 0x0204 +#define USB_DEVICE_ID_GTCO_205 0x0205 +#define USB_DEVICE_ID_GTCO_206 0x0206 +#define USB_DEVICE_ID_GTCO_207 0x0207 +#define USB_DEVICE_ID_GTCO_300 0x0300 +#define USB_DEVICE_ID_GTCO_301 0x0301 +#define USB_DEVICE_ID_GTCO_302 0x0302 +#define USB_DEVICE_ID_GTCO_303 0x0303 +#define USB_DEVICE_ID_GTCO_304 0x0304 +#define USB_DEVICE_ID_GTCO_305 0x0305 +#define USB_DEVICE_ID_GTCO_306 0x0306 +#define USB_DEVICE_ID_GTCO_307 0x0307 +#define USB_DEVICE_ID_GTCO_308 0x0308 +#define USB_DEVICE_ID_GTCO_309 0x0309 +#define USB_DEVICE_ID_GTCO_400 0x0400 +#define USB_DEVICE_ID_GTCO_401 0x0401 +#define USB_DEVICE_ID_GTCO_402 0x0402 +#define USB_DEVICE_ID_GTCO_403 0x0403 +#define USB_DEVICE_ID_GTCO_404 0x0404 +#define USB_DEVICE_ID_GTCO_405 0x0405 +#define USB_DEVICE_ID_GTCO_500 0x0500 +#define USB_DEVICE_ID_GTCO_501 0x0501 +#define USB_DEVICE_ID_GTCO_502 0x0502 +#define USB_DEVICE_ID_GTCO_503 0x0503 +#define USB_DEVICE_ID_GTCO_504 0x0504 +#define USB_DEVICE_ID_GTCO_1000 0x1000 +#define USB_DEVICE_ID_GTCO_1001 0x1001 +#define USB_DEVICE_ID_GTCO_1002 0x1002 +#define USB_DEVICE_ID_GTCO_1003 0x1003 +#define USB_DEVICE_ID_GTCO_1004 0x1004 +#define USB_DEVICE_ID_GTCO_1005 0x1005 +#define USB_DEVICE_ID_GTCO_1006 0x1006 +#define USB_DEVICE_ID_GTCO_1007 0x1007 + +#define USB_VENDOR_ID_GYRATION 0x0c16 +#define USB_DEVICE_ID_GYRATION_REMOTE 0x0002 +#define USB_DEVICE_ID_GYRATION_REMOTE_2 0x0003 +#define USB_DEVICE_ID_GYRATION_REMOTE_3 0x0008 + +#define I2C_VENDOR_ID_HANTICK 0x0911 +#define I2C_PRODUCT_ID_HANTICK_5288 0x5288 + +#define USB_VENDOR_ID_HANWANG 0x0b57 +#define USB_DEVICE_ID_HANWANG_TABLET_FIRST 0x5000 +#define USB_DEVICE_ID_HANWANG_TABLET_LAST 0x8fff + +#define USB_VENDOR_ID_HANVON 0x20b3 +#define USB_DEVICE_ID_HANVON_MULTITOUCH 0x0a18 + +#define USB_VENDOR_ID_HANVON_ALT 0x22ed +#define USB_DEVICE_ID_HANVON_ALT_MULTITOUCH 0x1010 + +#define USB_VENDOR_ID_HAPP 0x078b +#define USB_DEVICE_ID_UGCI_DRIVING 0x0010 +#define USB_DEVICE_ID_UGCI_FLYING 0x0020 +#define USB_DEVICE_ID_UGCI_FIGHTING 0x0030 + +#define USB_VENDOR_ID_HP 0x03f0 +#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a +#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A 0x0b4a +#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE 0x134a +#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_094A 0x094a +#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0941 0x0941 +#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641 0x0641 +#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a 0x1f4a + +#define USB_VENDOR_ID_HUION 0x256c +#define USB_DEVICE_ID_HUION_TABLET 0x006e +#define USB_DEVICE_ID_HUION_TABLET2 0x006d + +#define USB_VENDOR_ID_IBM 0x04b3 +#define USB_DEVICE_ID_IBM_SCROLLPOINT_III 0x3100 +#define USB_DEVICE_ID_IBM_SCROLLPOINT_PRO 0x3103 +#define USB_DEVICE_ID_IBM_SCROLLPOINT_OPTICAL 0x3105 +#define USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL 0x3108 +#define USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO 0x3109 + +#define USB_VENDOR_ID_IDEACOM 0x1cb6 +#define USB_DEVICE_ID_IDEACOM_IDC6650 0x6650 +#define USB_DEVICE_ID_IDEACOM_IDC6651 0x6651 +#define USB_DEVICE_ID_IDEACOM_IDC6680 0x6680 + +#define USB_VENDOR_ID_ILITEK 0x222a +#define USB_DEVICE_ID_ILITEK_MULTITOUCH 0x0001 + +#define USB_VENDOR_ID_INTEL_0 0x8086 +#define USB_VENDOR_ID_INTEL_1 0x8087 +#define USB_DEVICE_ID_INTEL_HID_SENSOR_0 0x09fa +#define USB_DEVICE_ID_INTEL_HID_SENSOR_1 0x0a04 + +#define USB_VENDOR_ID_STM_0 0x0483 +#define USB_DEVICE_ID_STM_HID_SENSOR 0x91d1 +#define USB_DEVICE_ID_STM_HID_SENSOR_1 0x9100 + +#define USB_VENDOR_ID_ION 0x15e4 +#define USB_DEVICE_ID_ICADE 0x0132 + +#define USB_VENDOR_ID_HOLTEK 0x1241 +#define USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP 0x5015 + +#define USB_VENDOR_ID_HOLTEK_ALT 0x04d9 +#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD 0xa055 +#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A04A 0xa04a +#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A067 0xa067 +#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070 0xa070 +#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072 0xa072 +#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081 0xa081 +#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A0C2 0xa0c2 +#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096 0xa096 +#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A293 0xa293 + +#define USB_VENDOR_ID_IMATION 0x0718 +#define USB_DEVICE_ID_DISC_STAKKA 0xd000 + +#define USB_VENDOR_ID_IRTOUCHSYSTEMS 0x6615 +#define USB_DEVICE_ID_IRTOUCH_INFRARED_USB 0x0070 + +#define USB_VENDOR_ID_INNOMEDIA 0x1292 +#define USB_DEVICE_ID_INNEX_GENESIS_ATARI 0x4745 + +#define USB_VENDOR_ID_ITE 0x048d +#define I2C_VENDOR_ID_ITE 0x103c +#define I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15 0x184f +#define USB_DEVICE_ID_ITE_LENOVO_YOGA 0x8386 +#define USB_DEVICE_ID_ITE_LENOVO_YOGA2 0x8350 +#define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a +#define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396 +#define USB_DEVICE_ID_ITE8595 0x8595 +#define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 + +#define USB_VENDOR_ID_JABRA 0x0b0e +#define USB_DEVICE_ID_JABRA_SPEAK_410 0x0412 +#define USB_DEVICE_ID_JABRA_SPEAK_510 0x0420 +#define USB_DEVICE_ID_JABRA_GN9350E 0x9350 + +#define USB_VENDOR_ID_JESS 0x0c45 +#define USB_DEVICE_ID_JESS_YUREX 0x1010 +#define USB_DEVICE_ID_ASUS_MD_5112 0x5112 +#define USB_DEVICE_ID_REDRAGON_ASURA 0x760b + +#define USB_VENDOR_ID_JESS2 0x0f30 +#define USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD 0x0111 + +#define USB_VENDOR_ID_KBGEAR 0x084e +#define USB_DEVICE_ID_KBGEAR_JAMSTUDIO 0x1001 + +#define USB_VENDOR_ID_KENSINGTON 0x047d +#define USB_DEVICE_ID_KS_SLIMBLADE 0x2041 + +#define USB_VENDOR_ID_KWORLD 0x1b80 +#define USB_DEVICE_ID_KWORLD_RADIO_FM700 0xd700 + +#define USB_VENDOR_ID_KEYTOUCH 0x0926 +#define USB_DEVICE_ID_KEYTOUCH_IEC 0x3333 + +#define USB_VENDOR_ID_KYE 0x0458 +#define USB_DEVICE_ID_KYE_ERGO_525V 0x0087 +#define USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE 0x0138 +#define USB_DEVICE_ID_GENIUS_MANTICORE 0x0153 +#define USB_DEVICE_ID_GENIUS_GX_IMPERATOR 0x4018 +#define USB_DEVICE_ID_KYE_GPEN_560 0x5003 +#define USB_DEVICE_ID_KYE_EASYPEN_I405X 0x5010 +#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X 0x5011 +#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2 0x501a +#define USB_DEVICE_ID_KYE_EASYPEN_M610X 0x5013 +#define USB_DEVICE_ID_KYE_PENSKETCH_M912 0x5015 +#define USB_DEVICE_ID_KYE_EASYPEN_M406XE 0x5019 + +#define USB_VENDOR_ID_LABTEC 0x1020 +#define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD 0x0006 + +#define USB_VENDOR_ID_LCPOWER 0x1241 +#define USB_DEVICE_ID_LCPOWER_LC1000 0xf767 + +#define USB_VENDOR_ID_LD 0x0f11 +#define USB_DEVICE_ID_LD_CASSY 0x1000 +#define USB_DEVICE_ID_LD_CASSY2 0x1001 +#define USB_DEVICE_ID_LD_POCKETCASSY 0x1010 +#define USB_DEVICE_ID_LD_POCKETCASSY2 0x1011 +#define USB_DEVICE_ID_LD_MOBILECASSY 0x1020 +#define USB_DEVICE_ID_LD_MOBILECASSY2 0x1021 +#define USB_DEVICE_ID_LD_MICROCASSYVOLTAGE 0x1031 +#define USB_DEVICE_ID_LD_MICROCASSYCURRENT 0x1032 +#define USB_DEVICE_ID_LD_MICROCASSYTIME 0x1033 +#define USB_DEVICE_ID_LD_MICROCASSYTEMPERATURE 0x1035 +#define USB_DEVICE_ID_LD_MICROCASSYPH 0x1038 +#define USB_DEVICE_ID_LD_POWERANALYSERCASSY 0x1040 +#define USB_DEVICE_ID_LD_CONVERTERCONTROLLERCASSY 0x1042 +#define USB_DEVICE_ID_LD_MACHINETESTCASSY 0x1043 +#define USB_DEVICE_ID_LD_JWM 0x1080 +#define USB_DEVICE_ID_LD_DMMP 0x1081 +#define USB_DEVICE_ID_LD_UMIP 0x1090 +#define USB_DEVICE_ID_LD_UMIC 0x10A0 +#define USB_DEVICE_ID_LD_UMIB 0x10B0 +#define USB_DEVICE_ID_LD_XRAY 0x1100 +#define USB_DEVICE_ID_LD_XRAY2 0x1101 +#define USB_DEVICE_ID_LD_XRAYCT 0x1110 +#define USB_DEVICE_ID_LD_VIDEOCOM 0x1200 +#define USB_DEVICE_ID_LD_MOTOR 0x1210 +#define USB_DEVICE_ID_LD_COM3LAB 0x2000 +#define USB_DEVICE_ID_LD_TELEPORT 0x2010 +#define USB_DEVICE_ID_LD_NETWORKANALYSER 0x2020 +#define USB_DEVICE_ID_LD_POWERCONTROL 0x2030 +#define USB_DEVICE_ID_LD_MACHINETEST 0x2040 +#define USB_DEVICE_ID_LD_MOSTANALYSER 0x2050 +#define USB_DEVICE_ID_LD_MOSTANALYSER2 0x2051 +#define USB_DEVICE_ID_LD_ABSESP 0x2060 +#define USB_DEVICE_ID_LD_AUTODATABUS 0x2070 +#define USB_DEVICE_ID_LD_MCT 0x2080 +#define USB_DEVICE_ID_LD_HYBRID 0x2090 +#define USB_DEVICE_ID_LD_HEATCONTROL 0x20A0 + +#define USB_VENDOR_ID_LENOVO 0x17ef +#define USB_DEVICE_ID_LENOVO_TPKBD 0x6009 +#define USB_DEVICE_ID_LENOVO_CUSBKBD 0x6047 +#define USB_DEVICE_ID_LENOVO_TPIIUSBKBD 0x60ee +#define USB_DEVICE_ID_LENOVO_CBTKBD 0x6048 +#define USB_DEVICE_ID_LENOVO_TPIIBTKBD 0x60e1 +#define USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL 0x6049 +#define USB_DEVICE_ID_LENOVO_TP10UBKBD 0x6062 +#define USB_DEVICE_ID_LENOVO_TPPRODOCK 0x6067 +#define USB_DEVICE_ID_LENOVO_X1_COVER 0x6085 +#define USB_DEVICE_ID_LENOVO_X1_TAB 0x60a3 +#define USB_DEVICE_ID_LENOVO_X1_TAB3 0x60b5 +#define USB_DEVICE_ID_LENOVO_X12_TAB 0x60fe +#define USB_DEVICE_ID_LENOVO_OPTICAL_USB_MOUSE_600E 0x600e +#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D 0x608d +#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019 0x6019 +#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E 0x602e +#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093 0x6093 + +#define USB_VENDOR_ID_LETSKETCH 0x6161 +#define USB_DEVICE_ID_WP9620N 0x4d15 + +#define USB_VENDOR_ID_LG 0x1fd2 +#define USB_DEVICE_ID_LG_MULTITOUCH 0x0064 +#define USB_DEVICE_ID_LG_MELFAS_MT 0x6007 +#define I2C_DEVICE_ID_LG_8001 0x8001 +#define I2C_DEVICE_ID_LG_7010 0x7010 + +#define USB_VENDOR_ID_LOGITECH 0x046d +#define USB_DEVICE_ID_LOGITECH_Z_10_SPK 0x0a07 +#define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e +#define USB_DEVICE_ID_LOGITECH_T651 0xb00c +#define USB_DEVICE_ID_LOGITECH_DINOVO_EDGE_KBD 0xb309 +#define USB_DEVICE_ID_LOGITECH_C007 0xc007 +#define USB_DEVICE_ID_LOGITECH_C077 0xc077 +#define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101 +#define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST 0xc110 +#define USB_DEVICE_ID_LOGITECH_HARMONY_LAST 0xc14f +#define USB_DEVICE_ID_LOGITECH_HARMONY_PS3 0x0306 +#define USB_DEVICE_ID_LOGITECH_KEYBOARD_G710_PLUS 0xc24d +#define USB_DEVICE_ID_LOGITECH_MOUSE_C01A 0xc01a +#define USB_DEVICE_ID_LOGITECH_MOUSE_C05A 0xc05a +#define USB_DEVICE_ID_LOGITECH_MOUSE_C06A 0xc06a +#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD 0xc20a +#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD 0xc211 +#define USB_DEVICE_ID_LOGITECH_EXTREME_3D 0xc215 +#define USB_DEVICE_ID_LOGITECH_DUAL_ACTION 0xc216 +#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2 0xc218 +#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2 0xc219 +#define USB_DEVICE_ID_LOGITECH_G15_LCD 0xc222 +#define USB_DEVICE_ID_LOGITECH_G11 0xc225 +#define USB_DEVICE_ID_LOGITECH_G15_V2_LCD 0xc227 +#define USB_DEVICE_ID_LOGITECH_G510 0xc22d +#define USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO 0xc22e +#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f +#define USB_DEVICE_ID_LOGITECH_G920_WHEEL 0xc262 +#define USB_DEVICE_ID_LOGITECH_G923_WHEEL 0xc266 +#define USB_DEVICE_ID_LOGITECH_G923_PS_WHEEL 0xc267 +#define USB_DEVICE_ID_LOGITECH_G923_XBOX_WHEEL 0xc26e +#define USB_DEVICE_ID_LOGITECH_WINGMAN_F3D 0xc283 +#define USB_DEVICE_ID_LOGITECH_FORCE3D_PRO 0xc286 +#define USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940 0xc287 +#define USB_DEVICE_ID_LOGITECH_WINGMAN_FG 0xc20e +#define USB_DEVICE_ID_LOGITECH_WINGMAN_FFG 0xc293 +#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294 +#define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL 0xc295 +#define USB_DEVICE_ID_LOGITECH_DFP_WHEEL 0xc298 +#define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299 +#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL 0xc29a +#define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b +#define USB_DEVICE_ID_LOGITECH_WII_WHEEL 0xc29c +#define USB_DEVICE_ID_LOGITECH_ELITE_KBD 0xc30a +#define USB_DEVICE_ID_LOGITECH_GROUP_AUDIO 0x0882 +#define USB_DEVICE_ID_S510_RECEIVER 0xc50c +#define USB_DEVICE_ID_S510_RECEIVER_2 0xc517 +#define USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500 0xc512 +#define USB_DEVICE_ID_MX3000_RECEIVER 0xc513 +#define USB_DEVICE_ID_LOGITECH_27MHZ_MOUSE_RECEIVER 0xc51b +#define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER 0xc52b +#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER 0xc52f +#define USB_DEVICE_ID_LOGITECH_G700_RECEIVER 0xc531 +#define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2 0xc532 +#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_2 0xc534 +#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1 0xc539 +#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1 0xc53f +#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a +#define USB_DEVICE_ID_SPACETRAVELLER 0xc623 +#define USB_DEVICE_ID_SPACENAVIGATOR 0xc626 +#define USB_DEVICE_ID_DINOVO_DESKTOP 0xc704 +#define USB_DEVICE_ID_MX5000_RECEIVER_MOUSE_DEV 0xc70a +#define USB_DEVICE_ID_MX5000_RECEIVER_KBD_DEV 0xc70e +#define USB_DEVICE_ID_DINOVO_EDGE_RECEIVER_KBD_DEV 0xc713 +#define USB_DEVICE_ID_DINOVO_EDGE_RECEIVER_MOUSE_DEV 0xc714 +#define USB_DEVICE_ID_MX5500_RECEIVER_KBD_DEV 0xc71b +#define USB_DEVICE_ID_MX5500_RECEIVER_MOUSE_DEV 0xc71c +#define USB_DEVICE_ID_DINOVO_MINI_RECEIVER_KBD_DEV 0xc71e +#define USB_DEVICE_ID_DINOVO_MINI_RECEIVER_MOUSE_DEV 0xc71f +#define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2 0xca03 +#define USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL 0xca04 + +#define USB_VENDOR_ID_LUMIO 0x202e +#define USB_DEVICE_ID_CRYSTALTOUCH 0x0006 +#define USB_DEVICE_ID_CRYSTALTOUCH_DUAL 0x0007 + +#define USB_VENDOR_ID_MADCATZ 0x0738 +#define USB_DEVICE_ID_MADCATZ_BEATPAD 0x4540 +#define USB_DEVICE_ID_MADCATZ_RAT5 0x1705 +#define USB_DEVICE_ID_MADCATZ_RAT9 0x1709 +#define USB_DEVICE_ID_MADCATZ_MMO7 0x1713 + +#define USB_VENDOR_ID_MCC 0x09db +#define USB_DEVICE_ID_MCC_PMD1024LS 0x0076 +#define USB_DEVICE_ID_MCC_PMD1208LS 0x007a + +#define USB_VENDOR_ID_MCS 0x16d0 +#define USB_DEVICE_ID_MCS_GAMEPADBLOCK 0x0bcc + +#define USB_VENDOR_MEGAWORLD 0x07b5 +#define USB_DEVICE_ID_MEGAWORLD_GAMEPAD 0x0312 + +#define USB_VENDOR_ID_MGE 0x0463 +#define USB_DEVICE_ID_MGE_UPS 0xffff +#define USB_DEVICE_ID_MGE_UPS1 0x0001 + +#define USB_VENDOR_ID_MICROCHIP 0x04d8 +#define USB_DEVICE_ID_PICKIT1 0x0032 +#define USB_DEVICE_ID_PICKIT2 0x0033 +#define USB_DEVICE_ID_PICOLCD 0xc002 +#define USB_DEVICE_ID_PICOLCD_BOOTLOADER 0xf002 +#define USB_DEVICE_ID_PICK16F1454 0x0042 +#define USB_DEVICE_ID_PICK16F1454_V2 0xf2f7 +#define USB_DEVICE_ID_LUXAFOR 0xf372 +#define USB_DEVICE_ID_MCP2221 0x00dd + +#define USB_VENDOR_ID_MICROSOFT 0x045e +#define USB_DEVICE_ID_SIDEWINDER_GV 0x003b +#define USB_DEVICE_ID_MS_OFFICE_KB 0x0048 +#define USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0 0x009d +#define USB_DEVICE_ID_MS_DIGITAL_MEDIA_7K 0x00b4 +#define USB_DEVICE_ID_MS_NE4K 0x00db +#define USB_DEVICE_ID_MS_NE4K_JP 0x00dc +#define USB_DEVICE_ID_MS_LK6K 0x00f9 +#define USB_DEVICE_ID_MS_PRESENTER_8K_BT 0x0701 +#define USB_DEVICE_ID_MS_PRESENTER_8K_USB 0x0713 +#define USB_DEVICE_ID_MS_NE7K 0x071d +#define USB_DEVICE_ID_MS_DIGITAL_MEDIA_3K 0x0730 +#define USB_DEVICE_ID_MS_DIGITAL_MEDIA_3KV1 0x0732 +#define USB_DEVICE_ID_MS_DIGITAL_MEDIA_600 0x0750 +#define USB_DEVICE_ID_MS_COMFORT_MOUSE_4500 0x076c +#define USB_DEVICE_ID_MS_COMFORT_KEYBOARD 0x00e3 +#define USB_DEVICE_ID_MS_SURFACE_PRO_2 0x0799 +#define USB_DEVICE_ID_MS_TOUCH_COVER_2 0x07a7 +#define USB_DEVICE_ID_MS_TYPE_COVER_2 0x07a9 +#define USB_DEVICE_ID_MS_POWER_COVER 0x07da +#define USB_DEVICE_ID_MS_SURFACE3_COVER 0x07de +#define USB_DEVICE_ID_MS_XBOX_ONE_S_CONTROLLER 0x02fd +#define USB_DEVICE_ID_MS_PIXART_MOUSE 0x00cb +#define USB_DEVICE_ID_8BITDO_SN30_PRO_PLUS 0x02e0 +#define USB_DEVICE_ID_MS_MOUSE_0783 0x0783 + +#define USB_VENDOR_ID_MOJO 0x8282 +#define USB_DEVICE_ID_RETRO_ADAPTER 0x3201 + +#define USB_VENDOR_ID_MONTEREY 0x0566 +#define USB_DEVICE_ID_GENIUS_KB29E 0x3004 + +#define USB_VENDOR_ID_MSI 0x1770 +#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00 + +#define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400 +#define USB_DEVICE_ID_N_S_HARMONY 0xc359 + +#define USB_VENDOR_ID_NATSU 0x08b7 +#define USB_DEVICE_ID_NATSU_GAMEPAD 0x0001 + +#define USB_VENDOR_ID_NCR 0x0404 +#define USB_DEVICE_ID_NCR_FIRST 0x0300 +#define USB_DEVICE_ID_NCR_LAST 0x03ff + +#define USB_VENDOR_ID_NEC 0x073e +#define USB_DEVICE_ID_NEC_USB_GAME_PAD 0x0301 + +#define USB_VENDOR_ID_NEXIO 0x1870 +#define USB_DEVICE_ID_NEXIO_MULTITOUCH_420 0x010d +#define USB_DEVICE_ID_NEXIO_MULTITOUCH_PTI0750 0x0110 + +#define USB_VENDOR_ID_NEXTWINDOW 0x1926 +#define USB_DEVICE_ID_NEXTWINDOW_TOUCHSCREEN 0x0003 + +#define USB_VENDOR_ID_NINTENDO 0x057e +#define USB_DEVICE_ID_NINTENDO_WIIMOTE 0x0306 +#define USB_DEVICE_ID_NINTENDO_WIIMOTE2 0x0330 +#define USB_DEVICE_ID_NINTENDO_JOYCONL 0x2006 +#define USB_DEVICE_ID_NINTENDO_JOYCONR 0x2007 +#define USB_DEVICE_ID_NINTENDO_PROCON 0x2009 +#define USB_DEVICE_ID_NINTENDO_CHRGGRIP 0x200E + +#define USB_VENDOR_ID_NOVATEK 0x0603 +#define USB_DEVICE_ID_NOVATEK_PCT 0x0600 +#define USB_DEVICE_ID_NOVATEK_MOUSE 0x1602 + +#define USB_VENDOR_ID_NTI 0x0757 +#define USB_DEVICE_ID_USB_SUN 0x0a00 + +#define USB_VENDOR_ID_NTRIG 0x1b96 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN 0x0001 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1 0x0003 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_2 0x0004 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_3 0x0005 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_4 0x0006 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_5 0x0007 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_6 0x0008 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_7 0x0009 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_8 0x000A +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_9 0x000B +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_10 0x000C +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_11 0x000D +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_12 0x000E +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_13 0x000F +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_14 0x0010 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_15 0x0011 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_16 0x0012 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_17 0x0013 +#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_18 0x0014 +#define USB_DEVICE_ID_NTRIG_DUOSENSE 0x1500 + +#define USB_VENDOR_ID_ONTRAK 0x0a07 +#define USB_DEVICE_ID_ONTRAK_ADU100 0x0064 + +#define USB_VENDOR_ID_ORTEK 0x05a4 +#define USB_DEVICE_ID_ORTEK_PKB1700 0x1700 +#define USB_DEVICE_ID_ORTEK_WKB2000 0x2000 +#define USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S 0x8003 + +#define USB_VENDOR_ID_PLANTRONICS 0x047f +#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3210_SERIES 0xc055 +#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES 0xc056 +#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3215_SERIES 0xc057 +#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3225_SERIES 0xc058 + +#define USB_VENDOR_ID_PANASONIC 0x04da +#define USB_DEVICE_ID_PANABOARD_UBT780 0x1044 +#define USB_DEVICE_ID_PANABOARD_UBT880 0x104d + +#define USB_VENDOR_ID_PANJIT 0x134c + +#define USB_VENDOR_ID_PANTHERLORD 0x0810 +#define USB_DEVICE_ID_PANTHERLORD_TWIN_USB_JOYSTICK 0x0001 + +#define USB_VENDOR_ID_PENMOUNT 0x14e1 +#define USB_DEVICE_ID_PENMOUNT_PCI 0x3500 +#define USB_DEVICE_ID_PENMOUNT_1610 0x1610 +#define USB_DEVICE_ID_PENMOUNT_1640 0x1640 +#define USB_DEVICE_ID_PENMOUNT_6000 0x6000 + +#define USB_VENDOR_ID_PETALYNX 0x18b1 +#define USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE 0x0037 + +#define USB_VENDOR_ID_PETZL 0x2122 +#define USB_DEVICE_ID_PETZL_HEADLAMP 0x1234 + +#define USB_VENDOR_ID_PHILIPS 0x0471 +#define USB_DEVICE_ID_PHILIPS_IEEE802154_DONGLE 0x0617 + +#define USB_VENDOR_ID_PI_ENGINEERING 0x05f3 +#define USB_DEVICE_ID_PI_ENGINEERING_VEC_USB_FOOTPEDAL 0xff + +#define USB_VENDOR_ID_PIXART 0x093a +#define USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2 0x0137 +#define USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE 0x2510 +#define USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN 0x8001 +#define USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1 0x8002 +#define USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2 0x8003 + +#define USB_VENDOR_ID_PLAYDOTCOM 0x0b43 +#define USB_DEVICE_ID_PLAYDOTCOM_EMS_USBII 0x0003 + +#define USB_VENDOR_ID_POWERCOM 0x0d9f +#define USB_DEVICE_ID_POWERCOM_UPS 0x0002 + +#define USB_VENDOR_ID_PRODIGE 0x05af +#define USB_DEVICE_ID_PRODIGE_CORDLESS 0x3062 + +#define USB_VENDOR_ID_QUANTA 0x0408 +#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH 0x3000 +#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001 0x3001 +#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3003 0x3003 +#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008 0x3008 + +#define I2C_VENDOR_ID_RAYDIUM 0x2386 +#define I2C_PRODUCT_ID_RAYDIUM_4B33 0x4b33 +#define I2C_PRODUCT_ID_RAYDIUM_3118 0x3118 + +#define USB_VENDOR_ID_RAZER 0x1532 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE 0x010D +#define USB_DEVICE_ID_RAZER_BLACKWIDOW 0x010e +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_CLASSIC 0x011b +#define USB_DEVICE_ID_RAZER_BLADE_14 0x011D + +#define USB_VENDOR_ID_REALTEK 0x0bda +#define USB_DEVICE_ID_REALTEK_READER 0x0152 + +#define USB_VENDOR_ID_REDOCTANE 0x1430 +#define USB_DEVICE_ID_REDOCTANE_GUITAR_DONGLE 0x474c +#define USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE 0x07bb + +#define USB_VENDOR_ID_RETROUSB 0xf000 +#define USB_DEVICE_ID_RETROUSB_SNES_RETROPAD 0x0003 +#define USB_DEVICE_ID_RETROUSB_SNES_RETROPORT 0x00f1 + +#define USB_VENDOR_ID_ROCCAT 0x1e7d +#define USB_DEVICE_ID_ROCCAT_ARVO 0x30d4 +#define USB_DEVICE_ID_ROCCAT_ISKU 0x319c +#define USB_DEVICE_ID_ROCCAT_ISKUFX 0x3264 +#define USB_DEVICE_ID_ROCCAT_KONE 0x2ced +#define USB_DEVICE_ID_ROCCAT_KONEPLUS 0x2d51 +#define USB_DEVICE_ID_ROCCAT_KONEPURE 0x2dbe +#define USB_DEVICE_ID_ROCCAT_KONEPURE_OPTICAL 0x2db4 +#define USB_DEVICE_ID_ROCCAT_KONEXTD 0x2e22 +#define USB_DEVICE_ID_ROCCAT_KOVAPLUS 0x2d50 +#define USB_DEVICE_ID_ROCCAT_LUA 0x2c2e +#define USB_DEVICE_ID_ROCCAT_PYRA_WIRED 0x2c24 +#define USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS 0x2cf6 +#define USB_DEVICE_ID_ROCCAT_RYOS_MK 0x3138 +#define USB_DEVICE_ID_ROCCAT_RYOS_MK_GLOW 0x31ce +#define USB_DEVICE_ID_ROCCAT_RYOS_MK_PRO 0x3232 +#define USB_DEVICE_ID_ROCCAT_SAVU 0x2d5a + +#define USB_VENDOR_ID_SAI 0x17dd + +#define USB_VENDOR_ID_SAITEK 0x06a3 +#define USB_DEVICE_ID_SAITEK_RUMBLEPAD 0xff17 +#define USB_DEVICE_ID_SAITEK_PS1000 0x0621 +#define USB_DEVICE_ID_SAITEK_RAT7_OLD 0x0ccb +#define USB_DEVICE_ID_SAITEK_RAT7_CONTAGION 0x0ccd +#define USB_DEVICE_ID_SAITEK_RAT7 0x0cd7 +#define USB_DEVICE_ID_SAITEK_RAT9 0x0cfa +#define USB_DEVICE_ID_SAITEK_MMO7 0x0cd0 +#define USB_DEVICE_ID_SAITEK_X52 0x075c +#define USB_DEVICE_ID_SAITEK_X52_2 0x0255 +#define USB_DEVICE_ID_SAITEK_X52_PRO 0x0762 +#define USB_DEVICE_ID_SAITEK_X65 0x0b6a + +#define USB_VENDOR_ID_SAMSUNG 0x0419 +#define USB_DEVICE_ID_SAMSUNG_IR_REMOTE 0x0001 +#define USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE 0x0600 + +#define USB_VENDOR_ID_SEMICO 0x1a2c +#define USB_DEVICE_ID_SEMICO_USB_KEYKOARD 0x0023 +#define USB_DEVICE_ID_SEMICO_USB_KEYKOARD2 0x0027 + +#define USB_VENDOR_ID_SEMITEK 0x1ea7 +#define USB_DEVICE_ID_SEMITEK_KEYBOARD 0x0907 + +#define USB_VENDOR_ID_SENNHEISER 0x1395 +#define USB_DEVICE_ID_SENNHEISER_BTD500USB 0x002c + +#define USB_VENDOR_ID_SIGMA_MICRO 0x1c4f +#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD 0x0002 +#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD2 0x0059 + +#define USB_VENDOR_ID_SIGMATEL 0x066F +#define USB_DEVICE_ID_SIGMATEL_STMP3780 0x3780 + +#define USB_VENDOR_ID_SIS_TOUCH 0x0457 +#define USB_DEVICE_ID_SIS9200_TOUCH 0x9200 +#define USB_DEVICE_ID_SIS817_TOUCH 0x0817 +#define USB_DEVICE_ID_SIS_TS 0x1013 +#define USB_DEVICE_ID_SIS1030_TOUCH 0x1030 + +#define USB_VENDOR_ID_SKYCABLE 0x1223 +#define USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER 0x3F07 + +#define USB_VENDOR_ID_SMK 0x0609 +#define USB_DEVICE_ID_SMK_PS3_BDREMOTE 0x0306 +#define USB_DEVICE_ID_SMK_NSG_MR5U_REMOTE 0x0368 +#define USB_DEVICE_ID_SMK_NSG_MR7U_REMOTE 0x0369 + + +#define USB_VENDOR_ID_SONY 0x054c +#define USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE 0x024b +#define USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE 0x0374 +#define USB_DEVICE_ID_SONY_PS3_BDREMOTE 0x0306 +#define USB_DEVICE_ID_SONY_PS3_CONTROLLER 0x0268 +#define USB_DEVICE_ID_SONY_PS4_CONTROLLER 0x05c4 +#define USB_DEVICE_ID_SONY_PS4_CONTROLLER_2 0x09cc +#define USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE 0x0ba0 +#define USB_DEVICE_ID_SONY_PS5_CONTROLLER 0x0ce6 +#define USB_DEVICE_ID_SONY_PS5_CONTROLLER_2 0x0df2 +#define USB_DEVICE_ID_SONY_MOTION_CONTROLLER 0x03d5 +#define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER 0x042f +#define USB_DEVICE_ID_SONY_BUZZ_CONTROLLER 0x0002 +#define USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER 0x1000 + +#define USB_VENDOR_ID_SONY_RHYTHM 0x12ba +#define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE_DONGLE 0x074b +#define USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE 0x0100 + +#define USB_VENDOR_ID_SINO_LITE 0x1345 +#define USB_DEVICE_ID_SINO_LITE_CONTROLLER 0x3008 + +#define USB_VENDOR_ID_SOLID_YEAR 0x060b +#define USB_DEVICE_ID_MACALLY_IKEY_KEYBOARD 0x0001 +#define USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD 0x500a +#define USB_DEVICE_ID_COUGAR_700K_GAMING_KEYBOARD 0x700a + +#define USB_VENDOR_ID_SOUNDGRAPH 0x15c2 +#define USB_DEVICE_ID_SOUNDGRAPH_IMON_FIRST 0x0034 +#define USB_DEVICE_ID_SOUNDGRAPH_IMON_LAST 0x0046 + +#define USB_VENDOR_ID_STANTUM 0x1f87 +#define USB_DEVICE_ID_MTP 0x0002 + +#define USB_VENDOR_ID_STANTUM_STM 0x0483 +#define USB_DEVICE_ID_MTP_STM 0x3261 + +#define USB_VENDOR_ID_STANTUM_SITRONIX 0x1403 +#define USB_DEVICE_ID_MTP_SITRONIX 0x5001 + +#define USB_VENDOR_ID_VALVE 0x28de +#define USB_DEVICE_ID_STEAM_CONTROLLER 0x1102 +#define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS 0x1142 +#define USB_DEVICE_ID_STEAM_DECK 0x1205 + +#define USB_VENDOR_ID_STEELSERIES 0x1038 +#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 + +#define USB_VENDOR_ID_SUN 0x0430 +#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab + +#define USB_VENDOR_ID_SUNPLUS 0x04fc +#define USB_DEVICE_ID_SUNPLUS_WDESKTOP 0x05d8 + +#define USB_VENDOR_ID_SYMBOL 0x05e0 +#define USB_DEVICE_ID_SYMBOL_SCANNER_1 0x0800 +#define USB_DEVICE_ID_SYMBOL_SCANNER_2 0x1300 +#define USB_DEVICE_ID_SYMBOL_SCANNER_3 0x1200 + +#define I2C_VENDOR_ID_SYNAPTICS 0x06cb +#define I2C_PRODUCT_ID_SYNAPTICS_SYNA2393 0x7a13 + +#define USB_VENDOR_ID_SYNAPTICS 0x06cb +#define USB_DEVICE_ID_SYNAPTICS_TP 0x0001 +#define USB_DEVICE_ID_SYNAPTICS_INT_TP 0x0002 +#define USB_DEVICE_ID_SYNAPTICS_CPAD 0x0003 +#define USB_DEVICE_ID_SYNAPTICS_TS 0x0006 +#define USB_DEVICE_ID_SYNAPTICS_STICK 0x0007 +#define USB_DEVICE_ID_SYNAPTICS_WP 0x0008 +#define USB_DEVICE_ID_SYNAPTICS_COMP_TP 0x0009 +#define USB_DEVICE_ID_SYNAPTICS_WTP 0x0010 +#define USB_DEVICE_ID_SYNAPTICS_DPAD 0x0013 +#define USB_DEVICE_ID_SYNAPTICS_LTS1 0x0af8 +#define USB_DEVICE_ID_SYNAPTICS_LTS2 0x1d10 +#define USB_DEVICE_ID_SYNAPTICS_HD 0x0ac3 +#define USB_DEVICE_ID_SYNAPTICS_QUAD_HD 0x1ac3 +#define USB_DEVICE_ID_SYNAPTICS_DELL_K12A 0x2819 +#define USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5_012 0x2968 +#define USB_DEVICE_ID_SYNAPTICS_TP_V103 0x5710 +#define USB_DEVICE_ID_SYNAPTICS_DELL_K15A 0x6e21 +#define USB_DEVICE_ID_SYNAPTICS_ACER_ONE_S1002 0x73f4 +#define USB_DEVICE_ID_SYNAPTICS_ACER_ONE_S1003 0x73f5 +#define USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5_017 0x73f6 +#define USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5 0x81a7 + +#define USB_VENDOR_ID_TEXAS_INSTRUMENTS 0x2047 +#define USB_DEVICE_ID_TEXAS_INSTRUMENTS_LENOVO_YOGA 0x0855 + +#define USB_VENDOR_ID_THINGM 0x27b8 +#define USB_DEVICE_ID_BLINK1 0x01ed + +#define USB_VENDOR_ID_THQ 0x20d6 +#define USB_DEVICE_ID_THQ_PS3_UDRAW 0xcb17 + +#define USB_VENDOR_ID_THRUSTMASTER 0x044f + +#define USB_VENDOR_ID_TIVO 0x150a +#define USB_DEVICE_ID_TIVO_SLIDE_BT 0x1200 +#define USB_DEVICE_ID_TIVO_SLIDE 0x1201 +#define USB_DEVICE_ID_TIVO_SLIDE_PRO 0x1203 + +#define USB_VENDOR_ID_TOPRE 0x0853 +#define USB_DEVICE_ID_TOPRE_REALFORCE_R2_108 0x0148 + +#define USB_VENDOR_ID_TOPSEED 0x0766 +#define USB_DEVICE_ID_TOPSEED_CYBERLINK 0x0204 + +#define USB_VENDOR_ID_TOPSEED2 0x1784 +#define USB_DEVICE_ID_TOPSEED2_RF_COMBO 0x0004 +#define USB_DEVICE_ID_TOPSEED2_PERIPAD_701 0x0016 + +#define USB_VENDOR_ID_TOPMAX 0x0663 +#define USB_DEVICE_ID_TOPMAX_COBRAPAD 0x0103 + +#define USB_VENDOR_ID_TOUCH_INTL 0x1e5e +#define USB_DEVICE_ID_TOUCH_INTL_MULTI_TOUCH 0x0313 + +#define USB_VENDOR_ID_TOUCHPACK 0x1bfd +#define USB_DEVICE_ID_TOUCHPACK_RTS 0x1688 + +#define USB_VENDOR_ID_TPV 0x25aa +#define USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN_8882 0x8882 +#define USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN_8883 0x8883 + +#define USB_VENDOR_ID_TRUST 0x145f +#define USB_DEVICE_ID_TRUST_PANORA_TABLET 0x0212 + +#define USB_VENDOR_ID_TURBOX 0x062a +#define USB_DEVICE_ID_TURBOX_KEYBOARD 0x0201 +#define USB_DEVICE_ID_ASUS_MD_5110 0x5110 +#define USB_DEVICE_ID_TURBOX_TOUCHSCREEN_MOSART 0x7100 + +#define USB_VENDOR_ID_TWINHAN 0x6253 +#define USB_DEVICE_ID_TWINHAN_IR_REMOTE 0x0100 + +#define USB_VENDOR_ID_UCLOGIC 0x5543 +#define USB_DEVICE_ID_UCLOGIC_TABLET_PF1209 0x0042 +#define USB_DEVICE_ID_UCLOGIC_TABLET_KNA5 0x6001 +#define USB_DEVICE_ID_UCLOGIC_TABLET_TWA60 0x0064 +#define USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U 0x0003 +#define USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U 0x0004 +#define USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U 0x0005 +#define USB_DEVICE_ID_UCLOGIC_TABLET_WP1062 0x0064 +#define USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850 0x0522 +#define USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60 0x0781 +#define USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3 0x3031 +#define USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81 0x0081 +#define USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45 0x0045 +#define USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_47 0x0047 +#define USB_DEVICE_ID_YIYNOVA_TABLET 0x004d + +#define USB_VENDOR_ID_UGEE 0x28bd +#define USB_DEVICE_ID_UGEE_PARBLO_A610_PRO 0x1903 +#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540 0x0075 +#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640 0x0094 +#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01 0x0042 +#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01_V2 0x0905 +#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L 0x0935 +#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW 0x0934 +#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S 0x0909 +#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW 0x0933 +#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078 +#define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074 +#define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071 +#define USB_DEVICE_ID_UGEE_TABLET_RAINBOW_CV720 0x0055 + +#define USB_VENDOR_ID_UNITEC 0x227d +#define USB_DEVICE_ID_UNITEC_USB_TOUCH_0709 0x0709 +#define USB_DEVICE_ID_UNITEC_USB_TOUCH_0A19 0x0a19 + +#define USB_VENDOR_ID_VELLEMAN 0x10cf +#define USB_DEVICE_ID_VELLEMAN_K8055_FIRST 0x5500 +#define USB_DEVICE_ID_VELLEMAN_K8055_LAST 0x5503 +#define USB_DEVICE_ID_VELLEMAN_K8061_FIRST 0x8061 +#define USB_DEVICE_ID_VELLEMAN_K8061_LAST 0x8068 + +#define USB_VENDOR_ID_VTL 0x0306 +#define USB_DEVICE_ID_VTL_MULTITOUCH_FF3F 0xff3f + +#define USB_VENDOR_ID_WACOM 0x056a +#define USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH 0x81 +#define USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH 0x00BD + +#define USB_VENDOR_ID_WALTOP 0x172f +#define USB_DEVICE_ID_WALTOP_SLIM_TABLET_5_8_INCH 0x0032 +#define USB_DEVICE_ID_WALTOP_SLIM_TABLET_12_1_INCH 0x0034 +#define USB_DEVICE_ID_WALTOP_Q_PAD 0x0037 +#define USB_DEVICE_ID_WALTOP_PID_0038 0x0038 +#define USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH 0x0501 +#define USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH 0x0500 +#define USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET 0x0502 + +#define USB_VENDOR_ID_WEIDA 0x2575 +#define USB_DEVICE_ID_WEIDA_8752 0xC300 +#define USB_DEVICE_ID_WEIDA_8755 0xC301 + +#define USB_VENDOR_ID_WINBOND 0x0416 +#define USB_DEVICE_ID_TSTP_MTOUCH 0xc168 + +#define USB_VENDOR_ID_WISEGROUP 0x0925 +#define USB_DEVICE_ID_SMARTJOY_PLUS 0x0005 +#define USB_DEVICE_ID_SUPER_JOY_BOX_3 0x8888 +#define USB_DEVICE_ID_QUAD_USB_JOYPAD 0x8800 +#define USB_DEVICE_ID_DUAL_USB_JOYPAD 0x8866 + +#define USB_VENDOR_ID_WISEGROUP_LTD 0x6666 +#define USB_VENDOR_ID_WISEGROUP_LTD2 0x6677 +#define USB_DEVICE_ID_SMARTJOY_DUAL_PLUS 0x8802 +#define USB_DEVICE_ID_SUPER_JOY_BOX_3_PRO 0x8801 +#define USB_DEVICE_ID_SUPER_DUAL_BOX_PRO 0x8802 +#define USB_DEVICE_ID_SUPER_JOY_BOX_5_PRO 0x8804 + +#define USB_VENDOR_ID_WISTRON 0x0fb8 +#define USB_DEVICE_ID_WISTRON_OPTICAL_TOUCH 0x1109 + +#define USB_VENDOR_ID_X_TENSIONS 0x1ae7 +#define USB_DEVICE_ID_SPEEDLINK_VAD_CEZANNE 0x9001 + +#define USB_VENDOR_ID_XAT 0x2505 +#define USB_DEVICE_ID_XAT_CSR 0x0220 + +#define USB_VENDOR_ID_XIAOMI 0x2717 +#define USB_DEVICE_ID_MI_SILENT_MOUSE 0x5014 + +#define USB_VENDOR_ID_XIN_MO 0x16c0 +#define USB_DEVICE_ID_XIN_MO_DUAL_ARCADE 0x05e1 +#define USB_DEVICE_ID_THT_2P_ARCADE 0x75e1 + +#define USB_VENDOR_ID_XIROKU 0x1477 +#define USB_DEVICE_ID_XIROKU_SPX 0x1006 +#define USB_DEVICE_ID_XIROKU_MPX 0x1007 +#define USB_DEVICE_ID_XIROKU_CSR 0x100e +#define USB_DEVICE_ID_XIROKU_SPX1 0x1021 +#define USB_DEVICE_ID_XIROKU_CSR1 0x1022 +#define USB_DEVICE_ID_XIROKU_MPX1 0x1023 +#define USB_DEVICE_ID_XIROKU_SPX2 0x1024 +#define USB_DEVICE_ID_XIROKU_CSR2 0x1025 +#define USB_DEVICE_ID_XIROKU_MPX2 0x1026 + +#define USB_VENDOR_ID_YEALINK 0x6993 +#define USB_DEVICE_ID_YEALINK_P1K_P4K_B2K 0xb001 + +#define USB_VENDOR_ID_ZEROPLUS 0x0c12 + +#define USB_VENDOR_ID_ZYDACRON 0x13EC +#define USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL 0x0006 + +#define USB_VENDOR_ID_ZYTRONIC 0x14c8 +#define USB_DEVICE_ID_ZYTRONIC_ZXY100 0x0005 + +#define USB_VENDOR_ID_PRIMAX 0x0461 +#define USB_DEVICE_ID_PRIMAX_MOUSE_4D22 0x4d22 +#define USB_DEVICE_ID_PRIMAX_MOUSE_4E2A 0x4e2a +#define USB_DEVICE_ID_PRIMAX_KEYBOARD 0x4e05 +#define USB_DEVICE_ID_PRIMAX_REZEL 0x4e72 +#define USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D0F 0x4d0f +#define USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D65 0x4d65 +#define USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4E22 0x4e22 + + +#define USB_VENDOR_ID_RISO_KAGAKU 0x1294 /* Riso Kagaku Corp. */ +#define USB_DEVICE_ID_RI_KA_WEBMAIL 0x1320 /* Webmail Notifier */ + +#define USB_VENDOR_ID_MULTIPLE_1781 0x1781 +#define USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD 0x0a9d +#define USB_DEVICE_ID_PHOENIXRC 0x0898 + +#define USB_VENDOR_ID_DRACAL_RAPHNET 0x289b +#define USB_DEVICE_ID_RAPHNET_2NES2SNES 0x0002 +#define USB_DEVICE_ID_RAPHNET_4NES4SNES 0x0003 + +#define USB_VENDOR_ID_UGTIZER 0x2179 +#define USB_DEVICE_ID_UGTIZER_TABLET_GP0610 0x0053 +#define USB_DEVICE_ID_UGTIZER_TABLET_GT5040 0x0077 +#define USB_DEVICE_ID_UGTIZER_TABLET_WP5540 0x0004 + +#define USB_VENDOR_ID_VIEWSONIC 0x0543 +#define USB_DEVICE_ID_VIEWSONIC_PD1011 0xe621 + +#define USB_VENDOR_ID_SIGNOTEC 0x2133 +#define USB_DEVICE_ID_SIGNOTEC_VIEWSONIC_PD1011 0x0018 + +#endif diff --git a/drivers/custom/lg4ff/hid-lg.c b/drivers/custom/lg4ff/hid-lg.c new file mode 100644 index 000000000000..ad114214d85e --- /dev/null +++ b/drivers/custom/lg4ff/hid-lg.c @@ -0,0 +1,958 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for some logitech "special" devices + * + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik + * Copyright (c) 2005 Michael Haboustak for Concept2, Inc + * Copyright (c) 2006-2007 Jiri Kosina + * Copyright (c) 2008 Jiri Slaby + * Copyright (c) 2010 Hendrik Iben + */ + +/* + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbhid/usbhid.h" +#include "hid-ids.h" +#include "hid-lg.h" +#include "hid-lg4ff.h" + +#define LG_RDESC 0x001 +#define LG_BAD_RELATIVE_KEYS 0x002 +#define LG_DUPLICATE_USAGES 0x004 +#define LG_EXPANDED_KEYMAP 0x010 +#define LG_IGNORE_DOUBLED_WHEEL 0x020 +#define LG_WIRELESS 0x040 +#define LG_INVERT_HWHEEL 0x080 +#define LG_NOGET 0x100 +#define LG_FF 0x200 +#define LG_FF2 0x400 +#define LG_RDESC_REL_ABS 0x800 +#define LG_FF3 0x1000 +#define LG_FF4 0x2000 + +/* Size of the original descriptors of the Driving Force (and Pro) wheels */ +#define DF_RDESC_ORIG_SIZE 130 +#define DFP_RDESC_ORIG_SIZE 97 +#define FV_RDESC_ORIG_SIZE 130 +#define MOMO_RDESC_ORIG_SIZE 87 +#define MOMO2_RDESC_ORIG_SIZE 87 +#define FFG_RDESC_ORIG_SIZE 85 +#define FG_RDESC_ORIG_SIZE 82 + +/* Fixed report descriptors for Logitech Driving Force (and Pro) + * wheel controllers + * + * The original descriptors hide the separate throttle and brake axes in + * a custom vendor usage page, providing only a combined value as + * GenericDesktop.Y. + * These descriptors remove the combined Y axis and instead report + * separate throttle (Y) and brake (RZ). + */ +static __u8 df_rdesc_fixed[] = { +0x05, 0x01, /* Usage Page (Desktop), */ +0x09, 0x04, /* Usage (Joystick), */ +0xA1, 0x01, /* Collection (Application), */ +0xA1, 0x02, /* Collection (Logical), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x0A, /* Report Size (10), */ +0x14, /* Logical Minimum (0), */ +0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ +0x34, /* Physical Minimum (0), */ +0x46, 0xFF, 0x03, /* Physical Maximum (1023), */ +0x09, 0x30, /* Usage (X), */ +0x81, 0x02, /* Input (Variable), */ +0x95, 0x0C, /* Report Count (12), */ +0x75, 0x01, /* Report Size (1), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x45, 0x01, /* Physical Maximum (1), */ +0x05, 0x09, /* Usage (Buttons), */ +0x19, 0x01, /* Usage Minimum (1), */ +0x29, 0x0c, /* Usage Maximum (12), */ +0x81, 0x02, /* Input (Variable), */ +0x95, 0x02, /* Report Count (2), */ +0x06, 0x00, 0xFF, /* Usage Page (Vendor: 65280), */ +0x09, 0x01, /* Usage (?: 1), */ +0x81, 0x02, /* Input (Variable), */ +0x05, 0x01, /* Usage Page (Desktop), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x08, /* Report Size (8), */ +0x81, 0x02, /* Input (Variable), */ +0x25, 0x07, /* Logical Maximum (7), */ +0x46, 0x3B, 0x01, /* Physical Maximum (315), */ +0x75, 0x04, /* Report Size (4), */ +0x65, 0x14, /* Unit (Degrees), */ +0x09, 0x39, /* Usage (Hat Switch), */ +0x81, 0x42, /* Input (Variable, Null State), */ +0x75, 0x01, /* Report Size (1), */ +0x95, 0x04, /* Report Count (4), */ +0x65, 0x00, /* Unit (none), */ +0x06, 0x00, 0xFF, /* Usage Page (Vendor: 65280), */ +0x09, 0x01, /* Usage (?: 1), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x45, 0x01, /* Physical Maximum (1), */ +0x81, 0x02, /* Input (Variable), */ +0x05, 0x01, /* Usage Page (Desktop), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x08, /* Report Size (8), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x09, 0x31, /* Usage (Y), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x35, /* Usage (Rz), */ +0x81, 0x02, /* Input (Variable), */ +0xC0, /* End Collection, */ +0xA1, 0x02, /* Collection (Logical), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x95, 0x07, /* Report Count (7), */ +0x75, 0x08, /* Report Size (8), */ +0x09, 0x03, /* Usage (?: 3), */ +0x91, 0x02, /* Output (Variable), */ +0xC0, /* End Collection, */ +0xC0 /* End Collection */ +}; + +static __u8 dfp_rdesc_fixed[] = { +0x05, 0x01, /* Usage Page (Desktop), */ +0x09, 0x04, /* Usage (Joystick), */ +0xA1, 0x01, /* Collection (Application), */ +0xA1, 0x02, /* Collection (Logical), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x0E, /* Report Size (14), */ +0x14, /* Logical Minimum (0), */ +0x26, 0xFF, 0x3F, /* Logical Maximum (16383), */ +0x34, /* Physical Minimum (0), */ +0x46, 0xFF, 0x3F, /* Physical Maximum (16383), */ +0x09, 0x30, /* Usage (X), */ +0x81, 0x02, /* Input (Variable), */ +0x95, 0x0E, /* Report Count (14), */ +0x75, 0x01, /* Report Size (1), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x45, 0x01, /* Physical Maximum (1), */ +0x05, 0x09, /* Usage Page (Button), */ +0x19, 0x01, /* Usage Minimum (01h), */ +0x29, 0x0E, /* Usage Maximum (0Eh), */ +0x81, 0x02, /* Input (Variable), */ +0x05, 0x01, /* Usage Page (Desktop), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x04, /* Report Size (4), */ +0x25, 0x07, /* Logical Maximum (7), */ +0x46, 0x3B, 0x01, /* Physical Maximum (315), */ +0x65, 0x14, /* Unit (Degrees), */ +0x09, 0x39, /* Usage (Hat Switch), */ +0x81, 0x42, /* Input (Variable, Nullstate), */ +0x65, 0x00, /* Unit, */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x75, 0x08, /* Report Size (8), */ +0x81, 0x01, /* Input (Constant), */ +0x09, 0x31, /* Usage (Y), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x35, /* Usage (Rz), */ +0x81, 0x02, /* Input (Variable), */ +0x81, 0x01, /* Input (Constant), */ +0xC0, /* End Collection, */ +0xA1, 0x02, /* Collection (Logical), */ +0x09, 0x02, /* Usage (02h), */ +0x95, 0x07, /* Report Count (7), */ +0x91, 0x02, /* Output (Variable), */ +0xC0, /* End Collection, */ +0xC0 /* End Collection */ +}; + +static __u8 fv_rdesc_fixed[] = { +0x05, 0x01, /* Usage Page (Desktop), */ +0x09, 0x04, /* Usage (Joystick), */ +0xA1, 0x01, /* Collection (Application), */ +0xA1, 0x02, /* Collection (Logical), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x0A, /* Report Size (10), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x46, 0xFF, 0x03, /* Physical Maximum (1023), */ +0x09, 0x30, /* Usage (X), */ +0x81, 0x02, /* Input (Variable), */ +0x95, 0x0C, /* Report Count (12), */ +0x75, 0x01, /* Report Size (1), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x45, 0x01, /* Physical Maximum (1), */ +0x05, 0x09, /* Usage Page (Button), */ +0x19, 0x01, /* Usage Minimum (01h), */ +0x29, 0x0C, /* Usage Maximum (0Ch), */ +0x81, 0x02, /* Input (Variable), */ +0x95, 0x02, /* Report Count (2), */ +0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ +0x09, 0x01, /* Usage (01h), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x02, /* Usage (02h), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x08, /* Report Size (8), */ +0x81, 0x02, /* Input (Variable), */ +0x05, 0x01, /* Usage Page (Desktop), */ +0x25, 0x07, /* Logical Maximum (7), */ +0x46, 0x3B, 0x01, /* Physical Maximum (315), */ +0x75, 0x04, /* Report Size (4), */ +0x65, 0x14, /* Unit (Degrees), */ +0x09, 0x39, /* Usage (Hat Switch), */ +0x81, 0x42, /* Input (Variable, Null State), */ +0x75, 0x01, /* Report Size (1), */ +0x95, 0x04, /* Report Count (4), */ +0x65, 0x00, /* Unit, */ +0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ +0x09, 0x01, /* Usage (01h), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x45, 0x01, /* Physical Maximum (1), */ +0x81, 0x02, /* Input (Variable), */ +0x05, 0x01, /* Usage Page (Desktop), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x08, /* Report Size (8), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x09, 0x31, /* Usage (Y), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x32, /* Usage (Z), */ +0x81, 0x02, /* Input (Variable), */ +0xC0, /* End Collection, */ +0xA1, 0x02, /* Collection (Logical), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x95, 0x07, /* Report Count (7), */ +0x75, 0x08, /* Report Size (8), */ +0x09, 0x03, /* Usage (03h), */ +0x91, 0x02, /* Output (Variable), */ +0xC0, /* End Collection, */ +0xC0 /* End Collection */ +}; + +static __u8 momo_rdesc_fixed[] = { +0x05, 0x01, /* Usage Page (Desktop), */ +0x09, 0x04, /* Usage (Joystick), */ +0xA1, 0x01, /* Collection (Application), */ +0xA1, 0x02, /* Collection (Logical), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x0A, /* Report Size (10), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x46, 0xFF, 0x03, /* Physical Maximum (1023), */ +0x09, 0x30, /* Usage (X), */ +0x81, 0x02, /* Input (Variable), */ +0x95, 0x08, /* Report Count (8), */ +0x75, 0x01, /* Report Size (1), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x45, 0x01, /* Physical Maximum (1), */ +0x05, 0x09, /* Usage Page (Button), */ +0x19, 0x01, /* Usage Minimum (01h), */ +0x29, 0x08, /* Usage Maximum (08h), */ +0x81, 0x02, /* Input (Variable), */ +0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ +0x75, 0x0E, /* Report Size (14), */ +0x95, 0x01, /* Report Count (1), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x09, 0x00, /* Usage (00h), */ +0x81, 0x02, /* Input (Variable), */ +0x05, 0x01, /* Usage Page (Desktop), */ +0x75, 0x08, /* Report Size (8), */ +0x09, 0x31, /* Usage (Y), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x32, /* Usage (Z), */ +0x81, 0x02, /* Input (Variable), */ +0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ +0x09, 0x01, /* Usage (01h), */ +0x81, 0x02, /* Input (Variable), */ +0xC0, /* End Collection, */ +0xA1, 0x02, /* Collection (Logical), */ +0x09, 0x02, /* Usage (02h), */ +0x95, 0x07, /* Report Count (7), */ +0x91, 0x02, /* Output (Variable), */ +0xC0, /* End Collection, */ +0xC0 /* End Collection */ +}; + +static __u8 momo2_rdesc_fixed[] = { +0x05, 0x01, /* Usage Page (Desktop), */ +0x09, 0x04, /* Usage (Joystick), */ +0xA1, 0x01, /* Collection (Application), */ +0xA1, 0x02, /* Collection (Logical), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x0A, /* Report Size (10), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x46, 0xFF, 0x03, /* Physical Maximum (1023), */ +0x09, 0x30, /* Usage (X), */ +0x81, 0x02, /* Input (Variable), */ +0x95, 0x0A, /* Report Count (10), */ +0x75, 0x01, /* Report Size (1), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x45, 0x01, /* Physical Maximum (1), */ +0x05, 0x09, /* Usage Page (Button), */ +0x19, 0x01, /* Usage Minimum (01h), */ +0x29, 0x0A, /* Usage Maximum (0Ah), */ +0x81, 0x02, /* Input (Variable), */ +0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ +0x09, 0x00, /* Usage (00h), */ +0x95, 0x04, /* Report Count (4), */ +0x81, 0x02, /* Input (Variable), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x08, /* Report Size (8), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x09, 0x01, /* Usage (01h), */ +0x81, 0x02, /* Input (Variable), */ +0x05, 0x01, /* Usage Page (Desktop), */ +0x09, 0x31, /* Usage (Y), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x32, /* Usage (Z), */ +0x81, 0x02, /* Input (Variable), */ +0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ +0x09, 0x00, /* Usage (00h), */ +0x81, 0x02, /* Input (Variable), */ +0xC0, /* End Collection, */ +0xA1, 0x02, /* Collection (Logical), */ +0x09, 0x02, /* Usage (02h), */ +0x95, 0x07, /* Report Count (7), */ +0x91, 0x02, /* Output (Variable), */ +0xC0, /* End Collection, */ +0xC0 /* End Collection */ +}; + +static __u8 ffg_rdesc_fixed[] = { +0x05, 0x01, /* Usage Page (Desktop), */ +0x09, 0x04, /* Usage (Joystik), */ +0xA1, 0x01, /* Collection (Application), */ +0xA1, 0x02, /* Collection (Logical), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x0A, /* Report Size (10), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x46, 0xFF, 0x03, /* Physical Maximum (1023), */ +0x09, 0x30, /* Usage (X), */ +0x81, 0x02, /* Input (Variable), */ +0x95, 0x06, /* Report Count (6), */ +0x75, 0x01, /* Report Size (1), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x45, 0x01, /* Physical Maximum (1), */ +0x05, 0x09, /* Usage Page (Button), */ +0x19, 0x01, /* Usage Minimum (01h), */ +0x29, 0x06, /* Usage Maximum (06h), */ +0x81, 0x02, /* Input (Variable), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x08, /* Report Size (8), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ +0x09, 0x01, /* Usage (01h), */ +0x81, 0x02, /* Input (Variable), */ +0x05, 0x01, /* Usage Page (Desktop), */ +0x81, 0x01, /* Input (Constant), */ +0x09, 0x31, /* Usage (Y), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x32, /* Usage (Z), */ +0x81, 0x02, /* Input (Variable), */ +0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ +0x09, 0x01, /* Usage (01h), */ +0x81, 0x02, /* Input (Variable), */ +0xC0, /* End Collection, */ +0xA1, 0x02, /* Collection (Logical), */ +0x09, 0x02, /* Usage (02h), */ +0x95, 0x07, /* Report Count (7), */ +0x91, 0x02, /* Output (Variable), */ +0xC0, /* End Collection, */ +0xC0 /* End Collection */ +}; + +static __u8 fg_rdesc_fixed[] = { +0x05, 0x01, /* Usage Page (Desktop), */ +0x09, 0x04, /* Usage (Joystik), */ +0xA1, 0x01, /* Collection (Application), */ +0xA1, 0x02, /* Collection (Logical), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x01, /* Report Count (1), */ +0x09, 0x30, /* Usage (X), */ +0x81, 0x02, /* Input (Variable), */ +0xA4, /* Push, */ +0x25, 0x01, /* Logical Maximum (1), */ +0x45, 0x01, /* Physical Maximum (1), */ +0x75, 0x01, /* Report Size (1), */ +0x95, 0x02, /* Report Count (2), */ +0x81, 0x01, /* Input (Constant), */ +0x95, 0x06, /* Report Count (6), */ +0x05, 0x09, /* Usage Page (Button), */ +0x19, 0x01, /* Usage Minimum (01h), */ +0x29, 0x06, /* Usage Maximum (06h), */ +0x81, 0x02, /* Input (Variable), */ +0x05, 0x01, /* Usage Page (Desktop), */ +0xB4, /* Pop, */ +0x81, 0x02, /* Input (Constant), */ +0x09, 0x31, /* Usage (Y), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x32, /* Usage (Z), */ +0x81, 0x02, /* Input (Variable), */ +0xC0, /* End Collection, */ +0xA1, 0x02, /* Collection (Logical), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x04, /* Report Count (4), */ +0x09, 0x02, /* Usage (02h), */ +0xB1, 0x02, /* Feature (Variable), */ +0xC0, /* End Collection, */ +0xC0 /* End Collection, */ +}; + +/* + * Certain Logitech keyboards send in report #3 keys which are far + * above the logical maximum described in descriptor. This extends + * the original value of 0x28c of logical maximum to 0x104d + */ +static +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,12,0) +const +#endif +__u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + struct lg_drv_data *drv_data = hid_get_drvdata(hdev); + + if ((drv_data->quirks & LG_RDESC) && *rsize >= 91 && rdesc[83] == 0x26 && + rdesc[84] == 0x8c && rdesc[85] == 0x02) { + hid_info(hdev, + "fixing up Logitech keyboard report descriptor\n"); + rdesc[84] = rdesc[89] = 0x4d; + rdesc[85] = rdesc[90] = 0x10; + } + if ((drv_data->quirks & LG_RDESC_REL_ABS) && *rsize >= 51 && + rdesc[32] == 0x81 && rdesc[33] == 0x06 && + rdesc[49] == 0x81 && rdesc[50] == 0x06) { + hid_info(hdev, + "fixing up rel/abs in Logitech report descriptor\n"); + rdesc[33] = rdesc[50] = 0x02; + } + + switch (hdev->product) { + + case USB_DEVICE_ID_LOGITECH_WINGMAN_FG: + if (*rsize == FG_RDESC_ORIG_SIZE) { + hid_info(hdev, + "fixing up Logitech Wingman Formula GP report descriptor\n"); + rdesc = fg_rdesc_fixed; + *rsize = sizeof(fg_rdesc_fixed); + } else { + hid_info(hdev, + "rdesc size test failed for formula gp\n"); + } + break; + + + case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG: + if (*rsize == FFG_RDESC_ORIG_SIZE) { + hid_info(hdev, + "fixing up Logitech Wingman Formula Force GP report descriptor\n"); + rdesc = ffg_rdesc_fixed; + *rsize = sizeof(ffg_rdesc_fixed); + } + break; + + /* Several wheels report as this id when operating in emulation mode. */ + case USB_DEVICE_ID_LOGITECH_WHEEL: + if (*rsize == DF_RDESC_ORIG_SIZE) { + hid_info(hdev, + "fixing up Logitech Driving Force report descriptor\n"); + rdesc = df_rdesc_fixed; + *rsize = sizeof(df_rdesc_fixed); + } + break; + + case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL: + if (*rsize == MOMO_RDESC_ORIG_SIZE) { + hid_info(hdev, + "fixing up Logitech Momo Force (Red) report descriptor\n"); + rdesc = momo_rdesc_fixed; + *rsize = sizeof(momo_rdesc_fixed); + } + break; + + case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2: + if (*rsize == MOMO2_RDESC_ORIG_SIZE) { + hid_info(hdev, + "fixing up Logitech Momo Racing Force (Black) report descriptor\n"); + rdesc = momo2_rdesc_fixed; + *rsize = sizeof(momo2_rdesc_fixed); + } + break; + + case USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL: + if (*rsize == FV_RDESC_ORIG_SIZE) { + hid_info(hdev, + "fixing up Logitech Formula Vibration report descriptor\n"); + rdesc = fv_rdesc_fixed; + *rsize = sizeof(fv_rdesc_fixed); + } + break; + + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + if (*rsize == DFP_RDESC_ORIG_SIZE) { + hid_info(hdev, + "fixing up Logitech Driving Force Pro report descriptor\n"); + rdesc = dfp_rdesc_fixed; + *rsize = sizeof(dfp_rdesc_fixed); + } + break; + + case USB_DEVICE_ID_LOGITECH_WII_WHEEL: + if (*rsize >= 101 && rdesc[41] == 0x95 && rdesc[42] == 0x0B && + rdesc[47] == 0x05 && rdesc[48] == 0x09) { + hid_info(hdev, "fixing up Logitech Speed Force Wireless report descriptor\n"); + rdesc[41] = 0x05; + rdesc[42] = 0x09; + rdesc[47] = 0x95; + rdesc[48] = 0x0B; + } + break; + } + + return rdesc; +} + +#define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \ + EV_KEY, (c)) + +static int lg_ultrax_remote_mapping(struct hid_input *hi, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR) + return 0; + + set_bit(EV_REP, hi->input->evbit); + switch (usage->hid & HID_USAGE) { + /* Reported on Logitech Ultra X Media Remote */ + case 0x004: lg_map_key_clear(KEY_AGAIN); break; + case 0x00d: lg_map_key_clear(KEY_HOME); break; + case 0x024: lg_map_key_clear(KEY_SHUFFLE); break; + case 0x025: lg_map_key_clear(KEY_TV); break; + case 0x026: lg_map_key_clear(KEY_MENU); break; + case 0x031: lg_map_key_clear(KEY_AUDIO); break; + case 0x032: lg_map_key_clear(KEY_TEXT); break; + case 0x033: lg_map_key_clear(KEY_LAST); break; + case 0x047: lg_map_key_clear(KEY_MP3); break; + case 0x048: lg_map_key_clear(KEY_DVD); break; + case 0x049: lg_map_key_clear(KEY_MEDIA); break; + case 0x04a: lg_map_key_clear(KEY_VIDEO); break; + case 0x04b: lg_map_key_clear(KEY_ANGLE); break; + case 0x04c: lg_map_key_clear(KEY_LANGUAGE); break; + case 0x04d: lg_map_key_clear(KEY_SUBTITLE); break; + case 0x051: lg_map_key_clear(KEY_RED); break; + case 0x052: lg_map_key_clear(KEY_CLOSE); break; + + default: + return 0; + } + return 1; +} + +static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER) + return 0; + + switch (usage->hid & HID_USAGE) { + case 0x1001: lg_map_key_clear(KEY_MESSENGER); break; + case 0x1003: lg_map_key_clear(KEY_SOUND); break; + case 0x1004: lg_map_key_clear(KEY_VIDEO); break; + case 0x1005: lg_map_key_clear(KEY_AUDIO); break; + case 0x100a: lg_map_key_clear(KEY_DOCUMENTS); break; + /* The following two entries are Playlist 1 and 2 on the MX3200 */ + case 0x100f: lg_map_key_clear(KEY_FN_1); break; + case 0x1010: lg_map_key_clear(KEY_FN_2); break; + case 0x1011: lg_map_key_clear(KEY_PREVIOUSSONG); break; + case 0x1012: lg_map_key_clear(KEY_NEXTSONG); break; + case 0x1013: lg_map_key_clear(KEY_CAMERA); break; + case 0x1014: lg_map_key_clear(KEY_MESSENGER); break; + case 0x1015: lg_map_key_clear(KEY_RECORD); break; + case 0x1016: lg_map_key_clear(KEY_PLAYER); break; + case 0x1017: lg_map_key_clear(KEY_EJECTCD); break; + case 0x1018: lg_map_key_clear(KEY_MEDIA); break; + case 0x1019: lg_map_key_clear(KEY_PROG1); break; + case 0x101a: lg_map_key_clear(KEY_PROG2); break; + case 0x101b: lg_map_key_clear(KEY_PROG3); break; + case 0x101c: lg_map_key_clear(KEY_CYCLEWINDOWS); break; + case 0x101f: lg_map_key_clear(KEY_ZOOMIN); break; + case 0x1020: lg_map_key_clear(KEY_ZOOMOUT); break; + case 0x1021: lg_map_key_clear(KEY_ZOOMRESET); break; + case 0x1023: lg_map_key_clear(KEY_CLOSE); break; + case 0x1027: lg_map_key_clear(KEY_MENU); break; + /* this one is marked as 'Rotate' */ + case 0x1028: lg_map_key_clear(KEY_ANGLE); break; + case 0x1029: lg_map_key_clear(KEY_SHUFFLE); break; + case 0x102a: lg_map_key_clear(KEY_BACK); break; + case 0x102b: lg_map_key_clear(KEY_CYCLEWINDOWS); break; + case 0x102d: lg_map_key_clear(KEY_WWW); break; + /* The following two are 'Start/answer call' and 'End/reject call' + on the MX3200 */ + case 0x1031: lg_map_key_clear(KEY_OK); break; + case 0x1032: lg_map_key_clear(KEY_CANCEL); break; + case 0x1041: lg_map_key_clear(KEY_BATTERY); break; + case 0x1042: lg_map_key_clear(KEY_WORDPROCESSOR); break; + case 0x1043: lg_map_key_clear(KEY_SPREADSHEET); break; + case 0x1044: lg_map_key_clear(KEY_PRESENTATION); break; + case 0x1045: lg_map_key_clear(KEY_UNDO); break; + case 0x1046: lg_map_key_clear(KEY_REDO); break; + case 0x1047: lg_map_key_clear(KEY_PRINT); break; + case 0x1048: lg_map_key_clear(KEY_SAVE); break; + case 0x1049: lg_map_key_clear(KEY_PROG1); break; + case 0x104a: lg_map_key_clear(KEY_PROG2); break; + case 0x104b: lg_map_key_clear(KEY_PROG3); break; + case 0x104c: lg_map_key_clear(KEY_PROG4); break; + + default: + return 0; + } + return 1; +} + +static int lg_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + /* extended mapping for certain Logitech hardware (Logitech cordless + desktop LX500) */ + static const u8 e_keymap[] = { + 0,216, 0,213,175,156, 0, 0, 0, 0, + 144, 0, 0, 0, 0, 0, 0, 0, 0,212, + 174,167,152,161,112, 0, 0, 0,154, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,183,184,185,186,187, + 188,189,190,191,192,193,194, 0, 0, 0 + }; + struct lg_drv_data *drv_data = hid_get_drvdata(hdev); + unsigned int hid = usage->hid; + + if (hdev->product == USB_DEVICE_ID_LOGITECH_RECEIVER && + lg_ultrax_remote_mapping(hi, usage, bit, max)) + return 1; + + if ((drv_data->quirks & LG_WIRELESS) && lg_wireless_mapping(hi, usage, bit, max)) + return 1; + + if ((hid & HID_USAGE_PAGE) != HID_UP_BUTTON) + return 0; + + hid &= HID_USAGE; + + /* Special handling for Logitech Cordless Desktop */ + if (field->application == HID_GD_MOUSE) { + if ((drv_data->quirks & LG_IGNORE_DOUBLED_WHEEL) && + (hid == 7 || hid == 8)) + return -1; + } else { + if ((drv_data->quirks & LG_EXPANDED_KEYMAP) && + hid < ARRAY_SIZE(e_keymap) && + e_keymap[hid] != 0) { + hid_map_usage(hi, usage, bit, max, EV_KEY, + e_keymap[hid]); + return 1; + } + } + + return 0; +} + +static int lg_input_mapped(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + struct lg_drv_data *drv_data = hid_get_drvdata(hdev); + + if ((drv_data->quirks & LG_BAD_RELATIVE_KEYS) && usage->type == EV_KEY && + (field->flags & HID_MAIN_ITEM_RELATIVE)) + field->flags &= ~HID_MAIN_ITEM_RELATIVE; + + if ((drv_data->quirks & LG_DUPLICATE_USAGES) && (usage->type == EV_KEY || + usage->type == EV_REL || usage->type == EV_ABS)) + clear_bit(usage->code, *bit); + + /* Ensure that Logitech wheels are not given a default fuzz/flat value */ + if (usage->type == EV_ABS && (usage->code == ABS_X || + usage->code == ABS_Y || usage->code == ABS_Z || + usage->code == ABS_RZ)) { + switch (hdev->product) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G923_WHEEL: + case USB_DEVICE_ID_LOGITECH_WINGMAN_FG: + case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG: + case USB_DEVICE_ID_LOGITECH_WHEEL: + case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_WII_WHEEL: + case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2: + case USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL: + field->application = HID_GD_MULTIAXIS; + break; + default: + break; + } + } + + return 0; +} + +static int lg_event(struct hid_device *hdev, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + struct lg_drv_data *drv_data = hid_get_drvdata(hdev); + + if ((drv_data->quirks & LG_INVERT_HWHEEL) && usage->code == REL_HWHEEL) { + input_event(field->hidinput->input, usage->type, usage->code, + -value); + return 1; + } + if (drv_data->quirks & LG_FF4) { + return lg4ff_adjust_input_event(hdev, field, usage, value, drv_data); + } + + return 0; +} + +static int lg_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *rd, int size) +{ + struct lg_drv_data *drv_data = hid_get_drvdata(hdev); + + if (drv_data->quirks & LG_FF4) + return lg4ff_raw_event(hdev, report, rd, size, drv_data); + + return 0; +} + +static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct usb_interface *iface; + __u8 iface_num; + unsigned int connect_mask = HID_CONNECT_DEFAULT; + struct lg_drv_data *drv_data; + int ret; + + if (!hid_is_usb(hdev)) + return -EINVAL; + + iface = to_usb_interface(hdev->dev.parent); + iface_num = iface->cur_altsetting->desc.bInterfaceNumber; + + /* G29 and G923 only work with the 1st interface */ + if ((hdev->product == USB_DEVICE_ID_LOGITECH_G29_WHEEL || + hdev->product == USB_DEVICE_ID_LOGITECH_G923_WHEEL || + hdev->product == USB_DEVICE_ID_LOGITECH_G923_PS_WHEEL) && + (iface_num != 0)) { + dbg_hid("%s: ignoring ifnum %d\n", __func__, iface_num); + return -ENODEV; + } + + drv_data = kzalloc(sizeof(struct lg_drv_data), GFP_KERNEL); + if (!drv_data) { + hid_err(hdev, "Insufficient memory, cannot allocate driver data\n"); + return -ENOMEM; + } + drv_data->quirks = id->driver_data; + + hid_set_drvdata(hdev, (void *)drv_data); + + if (drv_data->quirks & LG_NOGET) + hdev->quirks |= HID_QUIRK_NOGET; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + goto err_free; + } + + if (drv_data->quirks & (LG_FF | LG_FF2 | LG_FF3 | LG_FF4)) + connect_mask &= ~HID_CONNECT_FF; + + ret = hid_hw_start(hdev, connect_mask); + if (ret) { + hid_err(hdev, "hw start failed\n"); + goto err_free; + } + + /* Setup wireless link with Logitech Wii wheel */ + if (hdev->product == USB_DEVICE_ID_LOGITECH_WII_WHEEL) { + static const unsigned char cbuf[] = { + 0x00, 0xAF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + u8 *buf = kmemdup(cbuf, sizeof(cbuf), GFP_KERNEL); + + if (!buf) { + ret = -ENOMEM; + goto err_stop; + } + + ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(cbuf), + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (ret >= 0) { + /* insert a little delay of 10 jiffies ~ 40ms */ + wait_queue_head_t wait; + init_waitqueue_head (&wait); + wait_event_interruptible_timeout(wait, 0, + msecs_to_jiffies(40)); + + /* Select random Address */ + buf[1] = 0xB2; + get_random_bytes(&buf[2], 2); + + ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(cbuf), + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + } + kfree(buf); + } + + if (drv_data->quirks & LG_FF) + ret = lgff_init(hdev); + else if (drv_data->quirks & LG_FF2) + ret = lg2ff_init(hdev); + else if (drv_data->quirks & LG_FF3) + ret = lg3ff_init(hdev); + else if (drv_data->quirks & LG_FF4) + ret = lg4ff_init(hdev); + + if (ret) + goto err_stop; + + return 0; + +err_stop: + hid_hw_stop(hdev); +err_free: + kfree(drv_data); + return ret; +} + +static void lg_remove(struct hid_device *hdev) +{ + struct lg_drv_data *drv_data = hid_get_drvdata(hdev); + if (drv_data->quirks & LG_FF4) + lg4ff_deinit(hdev); + hid_hw_stop(hdev); + kfree(drv_data); +} + +static const struct hid_device_id lg_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER), + .driver_data = LG_RDESC | LG_WIRELESS }, + + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER), + .driver_data = LG_BAD_RELATIVE_KEYS }, + + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP), + .driver_data = LG_DUPLICATE_USAGES }, + + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD), + .driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500), + .driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP }, + + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D), + .driver_data = LG_NOGET }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DUAL_ACTION), + .driver_data = LG_NOGET }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL), + .driver_data = LG_NOGET | LG_FF4 }, + + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD), + .driver_data = LG_FF2 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD), + .driver_data = LG_FF }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2), + .driver_data = LG_FF }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G29_WHEEL), + .driver_data = LG_FF4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G923_WHEEL), + .driver_data = LG_FF4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G923_PS_WHEEL), + .driver_data = LG_FF4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_F3D), + .driver_data = LG_FF }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FORCE3D_PRO), + .driver_data = LG_FF }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL), + .driver_data = LG_NOGET | LG_FF4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2), + .driver_data = LG_FF4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL), + .driver_data = LG_FF2 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL), + .driver_data = LG_FF4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFGT_WHEEL), + .driver_data = LG_FF4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G27_WHEEL), + .driver_data = LG_FF4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFP_WHEEL), + .driver_data = LG_NOGET | LG_FF4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL), + .driver_data = LG_FF4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FG), + .driver_data = LG_NOGET }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG), + .driver_data = LG_NOGET | LG_FF4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2), + .driver_data = LG_NOGET | LG_FF2 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940), + .driver_data = LG_FF3 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR), + .driver_data = LG_RDESC_REL_ABS }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER), + .driver_data = LG_RDESC_REL_ABS }, + { } +}; + +MODULE_DEVICE_TABLE(hid, lg_devices); + +static struct hid_driver lg_driver = { + .name = "logitech", + .id_table = lg_devices, + .report_fixup = lg_report_fixup, + .input_mapping = lg_input_mapping, + .input_mapped = lg_input_mapped, + .event = lg_event, + .raw_event = lg_raw_event, + .probe = lg_probe, + .remove = lg_remove, +}; +module_hid_driver(lg_driver); + +#ifdef CONFIG_LOGIWHEELS_FF +int lg4ff_no_autoswitch = 0; +module_param_named(lg4ff_no_autoswitch, lg4ff_no_autoswitch, int, S_IRUGO); +MODULE_PARM_DESC(lg4ff_no_autoswitch, "Do not switch multimode wheels to their native mode automatically"); +#endif + +MODULE_LICENSE("GPL"); +MODULE_VERSION(VERSION); diff --git a/drivers/custom/lg4ff/hid-lg.h b/drivers/custom/lg4ff/hid-lg.h new file mode 100644 index 000000000000..df1f83ea9783 --- /dev/null +++ b/drivers/custom/lg4ff/hid-lg.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __HID_LG_H +#define __HID_LG_H + +#define VERSION "0.5.0" + +struct lg_drv_data { + unsigned long quirks; + void *device_props; /* Device specific properties */ +}; + +#ifdef CONFIG_LOGITECH_FF +int lgff_init(struct hid_device *hdev); +#else +static inline int lgff_init(struct hid_device *hdev) { return -1; } +#endif + +#ifdef CONFIG_LOGIRUMBLEPAD2_FF +int lg2ff_init(struct hid_device *hdev); +#else +static inline int lg2ff_init(struct hid_device *hdev) { return -1; } +#endif + +#ifdef CONFIG_LOGIG940_FF +int lg3ff_init(struct hid_device *hdev); +#else +static inline int lg3ff_init(struct hid_device *hdev) { return -1; } +#endif + +#endif diff --git a/drivers/custom/lg4ff/hid-lg2ff.c b/drivers/custom/lg4ff/hid-lg2ff.c new file mode 100644 index 000000000000..73d07e35f12a --- /dev/null +++ b/drivers/custom/lg4ff/hid-lg2ff.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Force feedback support for Logitech RumblePad and Rumblepad 2 + * + * Copyright (c) 2008 Anssi Hannula + */ + +/* + */ + + +#include +#include +#include + +#include "hid-lg.h" + +struct lg2ff_device { + struct hid_report *report; +}; + +static int play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct lg2ff_device *lg2ff = data; + int weak, strong; + + strong = effect->u.rumble.strong_magnitude; + weak = effect->u.rumble.weak_magnitude; + + if (weak || strong) { + weak = weak * 0xff / 0xffff; + strong = strong * 0xff / 0xffff; + + lg2ff->report->field[0]->value[0] = 0x51; + lg2ff->report->field[0]->value[2] = weak; + lg2ff->report->field[0]->value[4] = strong; + } else { + lg2ff->report->field[0]->value[0] = 0xf3; + lg2ff->report->field[0]->value[2] = 0x00; + lg2ff->report->field[0]->value[4] = 0x00; + } + + hid_hw_request(hid, lg2ff->report, HID_REQ_SET_REPORT); + return 0; +} + +int lg2ff_init(struct hid_device *hid) +{ + struct lg2ff_device *lg2ff; + struct hid_report *report; + struct hid_input *hidinput; + struct input_dev *dev; + int error; + + if (list_empty(&hid->inputs)) { + hid_err(hid, "no inputs found\n"); + return -ENODEV; + } + hidinput = list_entry(hid->inputs.next, struct hid_input, list); + dev = hidinput->input; + + /* Check that the report looks ok */ + report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7); + if (!report) + return -ENODEV; + + lg2ff = kmalloc(sizeof(struct lg2ff_device), GFP_KERNEL); + if (!lg2ff) + return -ENOMEM; + + set_bit(FF_RUMBLE, dev->ffbit); + + error = input_ff_create_memless(dev, lg2ff, play_effect); + if (error) { + kfree(lg2ff); + return error; + } + + lg2ff->report = report; + report->field[0]->value[0] = 0xf3; + report->field[0]->value[1] = 0x00; + report->field[0]->value[2] = 0x00; + report->field[0]->value[3] = 0x00; + report->field[0]->value[4] = 0x00; + report->field[0]->value[5] = 0x00; + report->field[0]->value[6] = 0x00; + + hid_hw_request(hid, report, HID_REQ_SET_REPORT); + + hid_info(hid, "Force feedback for Logitech variant 2 rumble devices by Anssi Hannula \n"); + + return 0; +} diff --git a/drivers/custom/lg4ff/hid-lg3ff.c b/drivers/custom/lg4ff/hid-lg3ff.c new file mode 100644 index 000000000000..b7e1949f3cf7 --- /dev/null +++ b/drivers/custom/lg4ff/hid-lg3ff.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Force feedback support for Logitech Flight System G940 + * + * Copyright (c) 2009 Gary Stein + */ + +/* + */ + + +#include +#include + +#include "hid-lg.h" + +/* + * G940 Theory of Operation (from experimentation) + * + * There are 63 fields (only 3 of them currently used) + * 0 - seems to be command field + * 1 - 30 deal with the x axis + * 31 -60 deal with the y axis + * + * Field 1 is x axis constant force + * Field 31 is y axis constant force + * + * other interesting fields 1,2,3,4 on x axis + * (same for 31,32,33,34 on y axis) + * + * 0 0 127 127 makes the joystick autocenter hard + * + * 127 0 127 127 makes the joystick loose on the right, + * but stops all movemnt left + * + * -127 0 -127 -127 makes the joystick loose on the left, + * but stops all movement right + * + * 0 0 -127 -127 makes the joystick rattle very hard + * + * I'm sure these are effects that I don't know enough about them + */ + +struct lg3ff_device { + struct hid_report *report; +}; + +static int hid_lg3ff_play(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + int x, y; + +/* + * Available values in the field should always be 63, but we only use up to + * 35. Instead, clear the entire area, however big it is. + */ + memset(report->field[0]->value, 0, + sizeof(__s32) * report->field[0]->report_count); + + switch (effect->type) { + case FF_CONSTANT: +/* + * Already clamped in ff_memless + * 0 is center (different then other logitech) + */ + x = effect->u.ramp.start_level; + y = effect->u.ramp.end_level; + + /* send command byte */ + report->field[0]->value[0] = 0x51; + +/* + * Sign backwards from other Force3d pro + * which get recast here in two's complement 8 bits + */ + report->field[0]->value[1] = (unsigned char)(-x); + report->field[0]->value[31] = (unsigned char)(-y); + + hid_hw_request(hid, report, HID_REQ_SET_REPORT); + break; + } + return 0; +} +static void hid_lg3ff_set_autocenter(struct input_dev *dev, u16 magnitude) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + +/* + * Auto Centering probed from device + * NOTE: deadman's switch on G940 must be covered + * for effects to work + */ + report->field[0]->value[0] = 0x51; + report->field[0]->value[1] = 0x00; + report->field[0]->value[2] = 0x00; + report->field[0]->value[3] = 0x7F; + report->field[0]->value[4] = 0x7F; + report->field[0]->value[31] = 0x00; + report->field[0]->value[32] = 0x00; + report->field[0]->value[33] = 0x7F; + report->field[0]->value[34] = 0x7F; + + hid_hw_request(hid, report, HID_REQ_SET_REPORT); +} + + +static const signed short ff3_joystick_ac[] = { + FF_CONSTANT, + FF_AUTOCENTER, + -1 +}; + +int lg3ff_init(struct hid_device *hid) +{ + struct hid_input *hidinput; + struct input_dev *dev; + const signed short *ff_bits = ff3_joystick_ac; + int error; + int i; + + if (list_empty(&hid->inputs)) { + hid_err(hid, "no inputs found\n"); + return -ENODEV; + } + hidinput = list_entry(hid->inputs.next, struct hid_input, list); + dev = hidinput->input; + + /* Check that the report looks ok */ + if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 35)) + return -ENODEV; + + /* Assume single fixed device G940 */ + for (i = 0; ff_bits[i] >= 0; i++) + set_bit(ff_bits[i], dev->ffbit); + + error = input_ff_create_memless(dev, NULL, hid_lg3ff_play); + if (error) + return error; + + if (test_bit(FF_AUTOCENTER, dev->ffbit)) + dev->ff->set_autocenter = hid_lg3ff_set_autocenter; + + hid_info(hid, "Force feedback for Logitech Flight System G940 by Gary Stein \n"); + return 0; +} + diff --git a/drivers/custom/lg4ff/hid-lg4ff.c b/drivers/custom/lg4ff/hid-lg4ff.c new file mode 100644 index 000000000000..b7721fbcb34c --- /dev/null +++ b/drivers/custom/lg4ff/hid-lg4ff.c @@ -0,0 +1,2571 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Force feedback support for Logitech Gaming Wheels + * + * Including G27, G25, DFP, DFGT, FFEX, Momo, Momo2 & + * Speed Force Wireless (WiiWheel) + * + * Copyright (c) 2010 Simon Wood + * Copyright (c) 2019 Bernat Arlandis + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbhid/usbhid.h" +#include "hid-lg.h" +#include "hid-lg4ff.h" +#include "hid-ids.h" + +#define LG4FF_MMODE_IS_MULTIMODE 0 +#define LG4FF_MMODE_SWITCHED 1 +#define LG4FF_MMODE_NOT_MULTIMODE 2 + +/* Device has a "friction" effect in firmware */ +#define LG4FF_CAP_FRICTION 1 + +#define LG4FF_MODE_NATIVE_IDX 0 +#define LG4FF_MODE_DFEX_IDX 1 +#define LG4FF_MODE_DFP_IDX 2 +#define LG4FF_MODE_G25_IDX 3 +#define LG4FF_MODE_DFGT_IDX 4 +#define LG4FF_MODE_G27_IDX 5 +#define LG4FF_MODE_G29_IDX 6 +#define LG4FF_MODE_G923_PS_IDX 7 +#define LG4FF_MODE_G923_IDX 8 +#define LG4FF_MODE_MAX_IDX 9 + +#define LG4FF_MODE_NATIVE BIT(LG4FF_MODE_NATIVE_IDX) +#define LG4FF_MODE_DFEX BIT(LG4FF_MODE_DFEX_IDX) +#define LG4FF_MODE_DFP BIT(LG4FF_MODE_DFP_IDX) +#define LG4FF_MODE_G25 BIT(LG4FF_MODE_G25_IDX) +#define LG4FF_MODE_DFGT BIT(LG4FF_MODE_DFGT_IDX) +#define LG4FF_MODE_G27 BIT(LG4FF_MODE_G27_IDX) +#define LG4FF_MODE_G29 BIT(LG4FF_MODE_G29_IDX) +#define LG4FF_MODE_G923_PS BIT(LG4FF_MODE_G923_PS_IDX) +#define LG4FF_MODE_G923 BIT(LG4FF_MODE_G923_IDX) + +#define LG4FF_DFEX_TAG "DF-EX" +#define LG4FF_DFEX_NAME "Driving Force / Formula EX" +#define LG4FF_DFP_TAG "DFP" +#define LG4FF_DFP_NAME "Driving Force Pro" +#define LG4FF_G25_TAG "G25" +#define LG4FF_G25_NAME "G25 Racing Wheel" +#define LG4FF_G27_TAG "G27" +#define LG4FF_G27_NAME "G27 Racing Wheel" +#define LG4FF_G29_TAG "G29" +#define LG4FF_G29_NAME "G29 Racing Wheel" +#define LG4FF_G923_TAG "G923" +#define LG4FF_G923_NAME "G923 Racing Wheel" +#define LG4FF_G923_PS_TAG "G923" +#define LG4FF_G923_PS_NAME "G923 Racing Wheel (Playstation mode)" +#define LG4FF_DFGT_TAG "DFGT" +#define LG4FF_DFGT_NAME "Driving Force GT" + +#define LG4FF_FFEX_REV_MAJ 0x21 +#define LG4FF_FFEX_REV_MIN 0x00 + +#define DEBUG(...) pr_debug("lg4ff: " __VA_ARGS__) +#define time_diff(a,b) ({ \ + typecheck(unsigned long, a); \ + typecheck(unsigned long, b); \ + ((a) - (long)(b)); }) +#define CLAMP_VALUE_U16(x) ((unsigned short)((x) > 0xffff ? 0xffff : (x))) +#define CLAMP_VALUE_S16(x) ((unsigned short)((x) <= -0x8000 ? -0x8000 : ((x) > 0x7fff ? 0x7fff : (x)))) +#define SCALE_VALUE_U16(x, bits) (CLAMP_VALUE_U16(x) >> (16 - bits)) +#define SCALE_COEFF(x, bits) SCALE_VALUE_U16(abs(x) * 2, bits) +#define TRANSLATE_FORCE(x) ((CLAMP_VALUE_S16(x) + 0x8000) >> 8) +#define STOP_EFFECT(state) ((state)->flags = 0) +#define JIFFIES2MS(jiffies) ((jiffies) * 1000 / HZ) +#undef fixp_sin16 +#define fixp_sin16(v) (((v % 360) > 180)? -(fixp_sin32((v % 360) - 180) >> 16) : fixp_sin32(v) >> 16) + +#define DEFAULT_TIMER_PERIOD 2 +#define LG4FF_MAX_EFFECTS 16 + +#define FF_EFFECT_STARTED 0 +#define FF_EFFECT_ALLSET 1 +#define FF_EFFECT_PLAYING 2 +#define FF_EFFECT_UPDATING 3 + +struct lg4ff_effect_state { + struct ff_effect effect; + struct ff_envelope *envelope; + unsigned long start_at; + unsigned long play_at; + unsigned long stop_at; + unsigned long flags; + unsigned long time_playing; + unsigned long updated_at; + unsigned int phase; + unsigned int phase_adj; + unsigned int count; + unsigned int cmd; + unsigned int cmd_start_time; + unsigned int cmd_start_count; + int direction_gain; + int slope; + unsigned int slot; +}; + +struct lg4ff_effect_parameters { + int level; + int d1; + int d2; + int k1; + int k2; + unsigned int clip; +}; + +struct lg4ff_slot { + int id; + struct lg4ff_effect_parameters parameters; + u8 current_cmd[7]; + int cmd_op; + int is_updated; + int effect_type; +}; + +struct lg4ff_wheel_data { + const u32 product_id; + u16 combine; + u16 range; + u16 autocenter; + u16 master_gain; + u16 gain; + const u16 min_range; + const u16 max_range; +#ifdef CONFIG_LEDS_CLASS + u8 led_state; + struct led_classdev *led[5]; +#endif + const u32 alternate_modes; + const char * const real_tag; + const char * const real_name; + const u16 real_product_id; + const u16 capabilities; + + void (*set_range)(struct hid_device *hid, u16 range); +}; + +struct lg4ff_device_entry { + spinlock_t report_lock; /* Protect output HID report */ + spinlock_t timer_lock; + struct hid_report *report; + struct lg4ff_wheel_data wdata; + struct hid_device *hid; + struct timer_list timer; + struct hrtimer hrtimer; + struct lg4ff_slot slots[4]; + struct lg4ff_effect_state states[LG4FF_MAX_EFFECTS]; + unsigned peak_ffb_level; + int effects_used; +#ifdef CONFIG_LEDS_CLASS + int has_leds; +#endif +}; + +static const signed short lg4ff_wheel_effects[] = { + FF_CONSTANT, + FF_SPRING, + FF_DAMPER, + FF_AUTOCENTER, + FF_PERIODIC, + FF_SINE, + FF_SQUARE, + FF_TRIANGLE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_RAMP, + FF_FRICTION, + FF_INERTIA, + -1 +}; + +static const signed short no_wheel_effects[] = { + -1 +}; + +struct lg4ff_wheel { + const u32 product_id; + const signed short *ff_effects; + const u16 min_range; + const u16 max_range; + const u16 capabilities; + void (*set_range)(struct hid_device *hid, u16 range); +}; + +struct lg4ff_compat_mode_switch { + const u8 cmd_count; /* Number of commands to send */ + const u8 cmd[]; +}; + +struct lg4ff_wheel_ident_info { + const u32 modes; + const u16 mask; + const u16 result; + const u16 real_product_id; +}; + +struct lg4ff_multimode_wheel { + const u16 product_id; + const u32 alternate_modes; + const char *real_tag; + const char *real_name; +}; + +struct lg4ff_alternate_mode { + const u16 product_id; + const char *tag; + const char *name; +}; + +static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range); +static void lg4ff_set_range_g25(struct hid_device *hid, u16 range); +#ifdef CONFIG_LEDS_CLASS +static void lg4ff_set_leds(struct hid_device *hid, u8 leds); +#endif + +static const struct lg4ff_wheel lg4ff_devices[] = { + {USB_DEVICE_ID_LOGITECH_WINGMAN_FG, + no_wheel_effects, 40, 180, 0, NULL}, + {USB_DEVICE_ID_LOGITECH_WINGMAN_FFG, + lg4ff_wheel_effects, 40, 180, 0, NULL}, + {USB_DEVICE_ID_LOGITECH_WHEEL, + lg4ff_wheel_effects, 40, 270, 0, NULL}, + {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, + lg4ff_wheel_effects, 40, 270, 0, NULL}, + {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, + lg4ff_wheel_effects, 40, 900, LG4FF_CAP_FRICTION, lg4ff_set_range_dfp}, + {USB_DEVICE_ID_LOGITECH_G25_WHEEL, + lg4ff_wheel_effects, 40, 900, LG4FF_CAP_FRICTION, lg4ff_set_range_g25}, + {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, + lg4ff_wheel_effects, 40, 900, LG4FF_CAP_FRICTION, lg4ff_set_range_g25}, + {USB_DEVICE_ID_LOGITECH_G27_WHEEL, + lg4ff_wheel_effects, 40, 900, LG4FF_CAP_FRICTION, lg4ff_set_range_g25}, + {USB_DEVICE_ID_LOGITECH_G29_WHEEL, + lg4ff_wheel_effects, 40, 900, 0, lg4ff_set_range_g25}, + {USB_DEVICE_ID_LOGITECH_G923_WHEEL, + lg4ff_wheel_effects, 40, 900, 0, lg4ff_set_range_g25}, + {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, + lg4ff_wheel_effects, 40, 270, 0, NULL}, + {USB_DEVICE_ID_LOGITECH_WII_WHEEL, + lg4ff_wheel_effects, 40, 270, 0, NULL} +}; + +static const struct lg4ff_multimode_wheel lg4ff_multimode_wheels[] = { + {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, + LG4FF_MODE_NATIVE | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, + LG4FF_DFP_TAG, LG4FF_DFP_NAME}, + {USB_DEVICE_ID_LOGITECH_G25_WHEEL, + LG4FF_MODE_NATIVE | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, + LG4FF_G25_TAG, LG4FF_G25_NAME}, + {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, + LG4FF_MODE_NATIVE | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, + LG4FF_DFGT_TAG, LG4FF_DFGT_NAME}, + {USB_DEVICE_ID_LOGITECH_G27_WHEEL, + LG4FF_MODE_NATIVE | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, + LG4FF_G27_TAG, LG4FF_G27_NAME}, + {USB_DEVICE_ID_LOGITECH_G29_WHEEL, + LG4FF_MODE_NATIVE | LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, + LG4FF_G29_TAG, LG4FF_G29_NAME}, + {USB_DEVICE_ID_LOGITECH_G923_PS_WHEEL, + LG4FF_MODE_G923_PS | LG4FF_MODE_G923, + LG4FF_G923_PS_TAG, LG4FF_G923_PS_NAME}, + {USB_DEVICE_ID_LOGITECH_G923_WHEEL, + LG4FF_MODE_G923, + LG4FF_G923_TAG, LG4FF_G923_NAME}, +}; + +static const struct lg4ff_alternate_mode lg4ff_alternate_modes[] = { + [LG4FF_MODE_NATIVE_IDX] = {0, "native", ""}, + [LG4FF_MODE_DFEX_IDX] = {USB_DEVICE_ID_LOGITECH_WHEEL, LG4FF_DFEX_TAG, LG4FF_DFEX_NAME}, + [LG4FF_MODE_DFP_IDX] = {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, LG4FF_DFP_TAG, LG4FF_DFP_NAME}, + [LG4FF_MODE_G25_IDX] = {USB_DEVICE_ID_LOGITECH_G25_WHEEL, LG4FF_G25_TAG, LG4FF_G25_NAME}, + [LG4FF_MODE_DFGT_IDX] = {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, LG4FF_DFGT_TAG, LG4FF_DFGT_NAME}, + [LG4FF_MODE_G27_IDX] = {USB_DEVICE_ID_LOGITECH_G27_WHEEL, LG4FF_G27_TAG, LG4FF_G27_NAME}, + [LG4FF_MODE_G29_IDX] = {USB_DEVICE_ID_LOGITECH_G29_WHEEL, LG4FF_G29_TAG, LG4FF_G29_NAME}, + [LG4FF_MODE_G923_PS_IDX] = {USB_DEVICE_ID_LOGITECH_G923_PS_WHEEL, LG4FF_G923_PS_TAG, LG4FF_G923_PS_NAME}, + [LG4FF_MODE_G923_IDX] = {USB_DEVICE_ID_LOGITECH_G923_WHEEL, LG4FF_G923_TAG, LG4FF_G923_NAME}, +}; + +/* Multimode wheel identificators */ +static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = { + LG4FF_MODE_DFP | LG4FF_MODE_DFEX, + 0xf000, + 0x1000, + USB_DEVICE_ID_LOGITECH_DFP_WHEEL +}; + +static const struct lg4ff_wheel_ident_info lg4ff_g25_ident_info = { + LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, + 0xff00, + 0x1200, + USB_DEVICE_ID_LOGITECH_G25_WHEEL +}; + +static const struct lg4ff_wheel_ident_info lg4ff_g27_ident_info = { + LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, + 0xfff0, + 0x1230, + USB_DEVICE_ID_LOGITECH_G27_WHEEL +}; + +static const struct lg4ff_wheel_ident_info lg4ff_dfgt_ident_info = { + LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, + 0xff00, + 0x1300, + USB_DEVICE_ID_LOGITECH_DFGT_WHEEL +}; + +static const struct lg4ff_wheel_ident_info lg4ff_g29_ident_info = { + LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, + 0xfff8, + 0x1350, + USB_DEVICE_ID_LOGITECH_G29_WHEEL +}; + +static const struct lg4ff_wheel_ident_info lg4ff_g29_ident_info2 = { + LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, + 0xff00, + 0x8900, + USB_DEVICE_ID_LOGITECH_G29_WHEEL +}; + +static const struct lg4ff_wheel_ident_info lg4ff_g923_ident_info = { + LG4FF_MODE_G923_PS | LG4FF_MODE_G923, + 0xff00, + 0x3800, + USB_DEVICE_ID_LOGITECH_G923_WHEEL +}; + + +/* Multimode wheel identification checklists */ +static const struct lg4ff_wheel_ident_info *lg4ff_main_checklist[] = { + &lg4ff_g29_ident_info, + &lg4ff_g29_ident_info2, + &lg4ff_g923_ident_info, + &lg4ff_dfgt_ident_info, + &lg4ff_g27_ident_info, + &lg4ff_g25_ident_info, + &lg4ff_dfp_ident_info +}; + +/* Compatibility mode switching commands */ +/* EXT_CMD9 - Understood by G27 and DFGT */ +static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfex = { + 2, + {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */ + 0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DF-EX with detach */ +}; + +static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfp = { + 2, + {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */ + 0xf8, 0x09, 0x01, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFP with detach */ +}; + +static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g25 = { + 2, + {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */ + 0xf8, 0x09, 0x02, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G25 with detach */ +}; + +static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfgt = { + 2, + {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */ + 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFGT with detach */ +}; + +static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g27 = { + 2, + {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */ + 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G27 with detach */ +}; + +static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g29 = { + 2, + {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */ + 0xf8, 0x09, 0x05, 0x01, 0x01, 0x00, 0x00} /* Switch mode to G29 with detach */ +}; + +static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g923 = { + 2, + {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */ + 0xf8, 0x09, 0x07, 0x01, 0x01, 0x00, 0x00} /* Switch mode to G923 with detach */ +}; + +/* EXT_CMD1 - Understood by DFP, G25, G27 and DFGT */ +static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext01_dfp = { + 1, + {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +/* EXT_CMD16 - Understood by G25 and G27 */ +static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext16_g25 = { + 1, + {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +/* 0x30 - PS mode - Understood by G923 PS */ +// Report ID must be 0x30 +static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_30_g923 = { + 1, + {0xf8, 0x09, 0x07, 0x01, 0x01, 0x00, 0x00} /* Switch mode to G923 with detach */ +}; + +static int timer_msecs = DEFAULT_TIMER_PERIOD; +module_param(timer_msecs, int, 0660); +MODULE_PARM_DESC(timer_msecs, "Timer resolution in msecs."); + +static int fixed_loop = 0; +module_param(fixed_loop, int, 0); +MODULE_PARM_DESC(fixed_loop, "Put the device into fixed loop mode."); + +static int timer_mode = 2; +module_param(timer_mode, int, 0660); +MODULE_PARM_DESC(timer_mode, "Timer mode: 0) fixed, 1) static, 2) dynamic (default)."); + +static int profile = 0; +module_param(profile, int, 0660); +MODULE_PARM_DESC(profile, "Enable profile debug messages."); + +#ifdef CONFIG_LEDS_CLASS +static int ffb_leds = 0; +module_param(ffb_leds, int, 0); +MODULE_PARM_DESC(ffb_leds, "Use leds to display FFB levels for calibration."); +#endif + +static int spring_level = 30; +module_param(spring_level, int, 0); +MODULE_PARM_DESC(spring_level, "Level of spring force (0-100)."); + +static int damper_level = 30; +module_param(damper_level, int, 0); +MODULE_PARM_DESC(damper_level, "Level of damper force (0-100)."); + +static int friction_level = 30; +module_param(friction_level, int, 0); +MODULE_PARM_DESC(friction_level, "Level of friction force (0-100)."); + +static struct lg4ff_device_entry *lg4ff_get_device_entry(struct hid_device *hid) +{ + struct lg_drv_data *drv_data; + struct lg4ff_device_entry *entry; + if (!hid) { + hid_err(hid, "HID not found!\n"); + return NULL; + } + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return NULL; + } + + entry = drv_data->device_props; + if (!entry) { + hid_err(hid, "Device properties not found!\n"); + return NULL; + } + + return entry; +} + +static void lg4ff_send_cmd_with_id(struct lg4ff_device_entry *entry, u8 *cmd, u8 id) { + unsigned long flags; + s32 *value = entry->report->field[0]->value; + + spin_lock_irqsave(&entry->report_lock, flags); + entry->report->id = id; + value[0] = cmd[0]; + value[1] = cmd[1]; + value[2] = cmd[2]; + value[3] = cmd[3]; + value[4] = cmd[4]; + value[5] = cmd[5]; + value[6] = cmd[6]; + hid_hw_request(entry->hid, entry->report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&entry->report_lock, flags); + DEBUG("send_cmd: %02X %02X %02X %02X %02X %02X %02X %02X\n", id, cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]); +} + +static void lg4ff_send_cmd(struct lg4ff_device_entry *entry, u8 *cmd) +{ + unsigned long flags; + s32 *value = entry->report->field[0]->value; + + spin_lock_irqsave(&entry->report_lock, flags); + value[0] = cmd[0]; + value[1] = cmd[1]; + value[2] = cmd[2]; + value[3] = cmd[3]; + value[4] = cmd[4]; + value[5] = cmd[5]; + value[6] = cmd[6]; + hid_hw_request(entry->hid, entry->report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&entry->report_lock, flags); + DEBUG("send_cmd: %02X %02X %02X %02X %02X %02X %02X", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]); +} + +static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_parameters *parameters) +{ + u8 original_cmd[7]; + int d1; + int d2; + int k1; + int k2; + int s1; + int s2; + + memcpy(original_cmd, slot->current_cmd, sizeof(original_cmd)); + + if ((original_cmd[0] & 0xf) == 1) { + original_cmd[0] = (original_cmd[0] & 0xf0) + 0xc; + } + + if (slot->effect_type == FF_CONSTANT) { + if (slot->cmd_op == 0) { + slot->cmd_op = 1; + } else { + slot->cmd_op = 0xc; + } + } else { + if (parameters->clip == 0 || slot->effect_type == 0) { + slot->cmd_op = 3; + } else if (slot->cmd_op == 3) { + slot->cmd_op = 1; + } else { + slot->cmd_op = 0xc; + } + } + + slot->current_cmd[0] = (0x10 << slot->id) + slot->cmd_op; + + if (slot->cmd_op == 3) { + slot->current_cmd[1] = 0; + slot->current_cmd[2] = 0; + slot->current_cmd[3] = 0; + slot->current_cmd[4] = 0; + slot->current_cmd[5] = 0; + slot->current_cmd[6] = 0; + } else { + switch (slot->effect_type) { + case FF_CONSTANT: + slot->current_cmd[1] = 0x00; + slot->current_cmd[2] = 0; + slot->current_cmd[3] = 0; + slot->current_cmd[4] = 0; + slot->current_cmd[5] = 0; + slot->current_cmd[6] = 0; + slot->current_cmd[2 + slot->id] = TRANSLATE_FORCE(parameters->level); + break; + case FF_SPRING: + d1 = SCALE_VALUE_U16(((parameters->d1) + 0x8000) & 0xffff, 11); + d2 = SCALE_VALUE_U16(((parameters->d2) + 0x8000) & 0xffff, 11); + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + k1 = abs(parameters->k1); + k2 = abs(parameters->k2); + if (k1 < 2048) { + d1 = 0; + } else { + k1 -= 2048; + } + if (k2 < 2048) { + d2 = 2047; + } else { + k2 -= 2048; + } + slot->current_cmd[1] = 0x0b; + slot->current_cmd[2] = d1 >> 3; + slot->current_cmd[3] = d2 >> 3; + slot->current_cmd[4] = (SCALE_COEFF(k2, 4) << 4) + SCALE_COEFF(k1, 4); + slot->current_cmd[5] = ((d2 & 7) << 5) + ((d1 & 7) << 1) + (s2 << 4) + s1; + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + break; + case FF_DAMPER: + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0c; + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 4); + slot->current_cmd[3] = s1; + slot->current_cmd[4] = SCALE_COEFF(parameters->k2, 4); + slot->current_cmd[5] = s2; + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + break; + case FF_FRICTION: + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0e; + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 8); + slot->current_cmd[3] = SCALE_COEFF(parameters->k2, 8); + slot->current_cmd[4] = SCALE_VALUE_U16(parameters->clip, 8); + slot->current_cmd[5] = (s2 << 4) + s1; + slot->current_cmd[6] = 0; + break; + } + } + + if (memcmp(original_cmd, slot->current_cmd, sizeof(original_cmd))) { + slot->is_updated = 1; + } +} + +static __always_inline int lg4ff_calculate_constant(struct lg4ff_effect_state *state) +{ + int level_sign; + int level = state->effect.u.constant.level; + int d, t; + + if (state->time_playing < state->envelope->attack_length) { + level_sign = level < 0 ? -1 : 1; + d = level - level_sign * state->envelope->attack_level; + level = level_sign * state->envelope->attack_level + d * state->time_playing / state->envelope->attack_length; + } else if (state->effect.replay.length) { + t = state->time_playing - state->effect.replay.length + state->envelope->fade_length; + if (t > 0) { + level_sign = level < 0 ? -1 : 1; + d = level - level_sign * state->envelope->fade_level; + level = level - d * t / state->envelope->fade_length; + } + } + + return state->direction_gain * level / 0x7fff; +} + +static __always_inline int lg4ff_calculate_ramp(struct lg4ff_effect_state *state) +{ + struct ff_ramp_effect *ramp = &state->effect.u.ramp; + int level_sign; + int level = INT_MAX; + int d, t; + + if (state->time_playing < state->envelope->attack_length) { + level = ramp->start_level; + level_sign = level < 0 ? -1 : 1; + t = state->envelope->attack_length - state->time_playing; + d = level - level_sign * state->envelope->attack_level; + level = level_sign * state->envelope->attack_level + d * t / state->envelope->attack_length; + } else if (state->effect.replay.length && state->time_playing >= state->effect.replay.length - state->envelope->fade_length) { + level = ramp->end_level; + level_sign = level < 0 ? -1 : 1; + t = state->time_playing - state->effect.replay.length + state->envelope->fade_length; + d = level_sign * state->envelope->fade_level - level; + level = level - d * t / state->envelope->fade_length; + } else { + t = state->time_playing - state->envelope->attack_length; + level = ramp->start_level + ((t * state->slope) >> 16); + } + + return state->direction_gain * level / 0x7fff; +} + +static __always_inline int lg4ff_calculate_periodic(struct lg4ff_effect_state *state) +{ + struct ff_periodic_effect *periodic = &state->effect.u.periodic; + int magnitude = periodic->magnitude; + int magnitude_sign = magnitude < 0 ? -1 : 1; + int level = periodic->offset; + int d, t; + + if (state->time_playing < state->envelope->attack_length) { + d = magnitude - magnitude_sign * state->envelope->attack_level; + magnitude = magnitude_sign * state->envelope->attack_level + d * state->time_playing / state->envelope->attack_length; + } else if (state->effect.replay.length) { + t = state->time_playing - state->effect.replay.length + state->envelope->fade_length; + if (t > 0) { + d = magnitude - magnitude_sign * state->envelope->fade_level; + magnitude = magnitude - d * t / state->envelope->fade_length; + } + } + + switch (periodic->waveform) { + case FF_SINE: + level += fixp_sin16(state->phase) * magnitude / 0x7fff; + break; + case FF_SQUARE: + level += (state->phase < 180 ? 1 : -1) * magnitude; + break; + case FF_TRIANGLE: + level += abs(state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude; + break; + case FF_SAW_UP: + level += state->phase * magnitude * 2 / 360 - magnitude; + break; + case FF_SAW_DOWN: + level += magnitude - state->phase * magnitude * 2 / 360; + break; + } + + return state->direction_gain * level / 0x7fff; +} + +static __always_inline void lg4ff_calculate_spring(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters) +{ + struct ff_condition_effect *condition = &state->effect.u.condition[0]; + + parameters->d1 = ((int)condition->center) - condition->deadband / 2; + parameters->d2 = ((int)condition->center) + condition->deadband / 2; + parameters->k1 = condition->left_coeff; + parameters->k2 = condition->right_coeff; + parameters->clip = (unsigned)condition->right_saturation; +} + +static __always_inline void lg4ff_calculate_resistance(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters) +{ + struct ff_condition_effect *condition = &state->effect.u.condition[0]; + + parameters->k1 = condition->left_coeff; + parameters->k2 = condition->right_coeff; + parameters->clip = (unsigned)condition->right_saturation; +} + +static __always_inline struct ff_envelope *lg4ff_effect_envelope(struct ff_effect *effect) +{ + switch (effect->type) { + case FF_CONSTANT: + return &effect->u.constant.envelope; + case FF_RAMP: + return &effect->u.ramp.envelope; + case FF_PERIODIC: + return &effect->u.periodic.envelope; + } + + return NULL; +} + +static __always_inline void lg4ff_update_state(struct lg4ff_effect_state *state, const unsigned long now) +{ + struct ff_effect *effect = &state->effect; + unsigned long phase_time; + + if (!__test_and_set_bit(FF_EFFECT_ALLSET, &state->flags)) { + state->play_at = state->start_at + effect->replay.delay; + if (!test_bit(FF_EFFECT_UPDATING, &state->flags)) { + state->updated_at = state->play_at; + } + state->direction_gain = fixp_sin16(effect->direction * 360 / 0x10000); + if (effect->type == FF_PERIODIC) { + state->phase_adj = effect->u.periodic.phase * 360 / effect->u.periodic.period; + } + if (effect->replay.length) { + state->stop_at = state->play_at + effect->replay.length; + } + } + + if (__test_and_clear_bit(FF_EFFECT_UPDATING, &state->flags)) { + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + state->play_at = state->updated_at + effect->replay.delay; + state->direction_gain = fixp_sin16(effect->direction * 360 / 0x10000); + if (effect->replay.length) { + state->stop_at = state->updated_at + effect->replay.length; + } + if (effect->type == FF_PERIODIC) { + state->phase_adj = state->phase; + } + } + + state->envelope = lg4ff_effect_envelope(effect); + + state->slope = 0; + if (effect->type == FF_RAMP && effect->replay.length) { + state->slope = ((effect->u.ramp.end_level - effect->u.ramp.start_level) << 16) / (effect->replay.length - state->envelope->attack_length - state->envelope->fade_length); + } + + if (!test_bit(FF_EFFECT_PLAYING, &state->flags) && time_after_eq(now, + state->play_at) && (effect->replay.length == 0 || + time_before(now, state->stop_at))) { + __set_bit(FF_EFFECT_PLAYING, &state->flags); + } + + if (test_bit(FF_EFFECT_PLAYING, &state->flags)) { + state->time_playing = time_diff(now, state->play_at); + if (effect->type == FF_PERIODIC) { + phase_time = time_diff(now, state->updated_at); + state->phase = (phase_time % effect->u.periodic.period) * 360 / effect->u.periodic.period; + state->phase += state->phase_adj % 360; + } + } +} + +static __always_inline int lg4ff_timer(struct lg4ff_device_entry *entry) +{ + struct usbhid_device *usbhid = entry->hid->driver_data; + struct lg4ff_slot *slot; + struct lg4ff_effect_state *state; + struct lg4ff_effect_parameters parameters[4]; + unsigned long jiffies_now = jiffies; + unsigned long now = JIFFIES2MS(jiffies_now); + unsigned long flags; + unsigned gain; + int current_period; + int count; + int effect_id; + int i; + int ffb_level; +#ifdef CONFIG_LEDS_CLASS + static int leds_timer = 0; + static int leds_level = 0; + u8 led_states; +#endif + + if (timer_mode > 0 && usbhid->outhead != usbhid->outtail) { + current_period = timer_msecs; + if (timer_mode == 1) { + timer_msecs *= 2; + hid_info(entry->hid, "Commands stacking up, increasing timer period to %d ms.", timer_msecs); + } else { + DEBUG("Commands stacking up, delaying timer."); + } + return current_period; + } + + memset(parameters, 0, sizeof(parameters)); + + gain = (unsigned)entry->wdata.master_gain * entry->wdata.gain / 0xffff; + + spin_lock_irqsave(&entry->timer_lock, flags); + + count = entry->effects_used; + + for (effect_id = 0; effect_id < LG4FF_MAX_EFFECTS; effect_id++) { + + if (!count) { + break; + } + + state = &entry->states[effect_id]; + + if (!test_bit(FF_EFFECT_STARTED, &state->flags)) { + continue; + } + + count--; + + if (test_bit(FF_EFFECT_ALLSET, &state->flags)) { + if (state->effect.replay.length && time_after_eq(now, state->stop_at)) { + STOP_EFFECT(state); + if (!--state->count) { + entry->effects_used--; + continue; + } + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->start_at = state->stop_at; + } + } + + lg4ff_update_state(state, now); + + if (!test_bit(FF_EFFECT_PLAYING, &state->flags)) { + continue; + } + + switch (state->effect.type) { + case FF_CONSTANT: + parameters[0].level += lg4ff_calculate_constant(state); + break; + case FF_RAMP: + parameters[0].level += lg4ff_calculate_ramp(state); + break; + case FF_PERIODIC: + parameters[0].level += lg4ff_calculate_periodic(state); + break; + case FF_SPRING: + if (state->slot != 0) { + lg4ff_calculate_spring(state, ¶meters[state->slot]); + } + break; + case FF_DAMPER: + case FF_FRICTION: + case FF_INERTIA: + if (state->slot != 0) { + lg4ff_calculate_resistance(state, ¶meters[state->slot]); + } + } + } + + spin_unlock_irqrestore(&entry->timer_lock, flags); + + parameters[0].level = (long)parameters[0].level * gain / 0xffff; + + ffb_level = abs(parameters[0].level); + for (i = 1; i < 4; i++) { + parameters[i].k1 = (long)parameters[i].k1 * gain / 0xffff; + parameters[i].k2 = (long)parameters[i].k2 * gain / 0xffff; + switch (entry->slots[i].effect_type) { + case FF_SPRING: + parameters[i].clip = parameters[i].clip * spring_level / 100; + break; + case FF_DAMPER: + parameters[i].clip = parameters[i].clip * damper_level / 100; + break; + case FF_FRICTION: + parameters[i].clip = parameters[i].clip * friction_level / 100; + break; + } + parameters[i].clip = parameters[i].clip * gain / 0xffff; + ffb_level += parameters[i].clip * 0x7fff / 0xffff; + } + if (ffb_level > entry->peak_ffb_level) { + entry->peak_ffb_level = ffb_level; + } + + for (i = 0; i < 4; i++) { + slot = &entry->slots[i]; + lg4ff_update_slot(slot, ¶meters[i]); + if (slot->is_updated) { + lg4ff_send_cmd(entry, slot->current_cmd); + slot->is_updated = 0; + } + } + +#ifdef CONFIG_LEDS_CLASS + if (ffb_leds || leds_level > 0) { + if (ffb_level > leds_level) { + leds_level = ffb_level; + } + if (!ffb_leds || entry->effects_used == 0) { + leds_timer = 0; + leds_level = 0; + } + if (leds_timer == 0) { + leds_timer = 480 / timer_msecs; + if (leds_level < 2458) { // < 7.5% + led_states = 0; + } else if (leds_level < 8192) { // < 25% + led_states = 1; + } else if (leds_level < 16384) { // < 50% + led_states = 3; + } else if (leds_level < 24576) { // < 75% + led_states = 7; + } else if (leds_level < 29491) { // < 90% + led_states = 15; + } else if (leds_level <= 32768) { // <= 100% + led_states = 31; + } else if (leds_level < 36045) { // < 110% + led_states = 30; + } else if (leds_level < 40960) { // < 125% + led_states = 28; + } else if (leds_level < 49152) { // < 150% + led_states = 24; + } else { + led_states = 16; + } + lg4ff_set_leds(entry->hid, led_states); + leds_level = 0; + } + leds_timer--; + } +#endif + + return 0; +} + +static enum hrtimer_restart lg4ff_timer_hires(struct hrtimer *t) +{ + struct lg4ff_device_entry *entry = container_of(t, struct lg4ff_device_entry, hrtimer); + int delay_timer; + int overruns; + + delay_timer = lg4ff_timer(entry); + + if (delay_timer) { + hrtimer_forward_now(&entry->hrtimer, ms_to_ktime(delay_timer)); + return HRTIMER_RESTART; + } + + if (entry->effects_used) { + overruns = hrtimer_forward_now(&entry->hrtimer, ms_to_ktime(timer_msecs)); + overruns--; + if (unlikely(profile && overruns > 0)) + DEBUG("Overruns: %d", overruns); + return HRTIMER_RESTART; + } else { + if (unlikely(profile)) + DEBUG("Stop timer."); + return HRTIMER_NORESTART; + } +} + +static void lg4ff_init_slots(struct lg4ff_device_entry *entry) +{ + struct lg4ff_effect_parameters parameters; + u8 cmd[8] = {0}; + int i; + + // Set/unset fixed loop mode + cmd[0] = 0x0d; + cmd[1] = fixed_loop ? 1 : 0; + lg4ff_send_cmd(entry, cmd); + + memset(&entry->states, 0, sizeof(entry->states)); + memset(&entry->slots, 0, sizeof(entry->slots)); + memset(¶meters, 0, sizeof(parameters)); + + entry->slots[0].effect_type = FF_CONSTANT; + + for (i = 0; i < 4; i++) { + entry->slots[i].id = i; + lg4ff_update_slot(&entry->slots[i], ¶meters); + lg4ff_send_cmd(entry, entry->slots[i].current_cmd); + entry->slots[i].is_updated = 0; + } +} + +static void lg4ff_stop_effects(struct lg4ff_device_entry *entry) +{ + u8 cmd[7] = {0}; + + cmd[0] = 0xf3; + lg4ff_send_cmd(entry, cmd); +} + +static int lg4ff_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct lg4ff_device_entry *entry; + struct lg4ff_effect_state *state; + unsigned long now = JIFFIES2MS(jiffies); + unsigned long flags; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + if (effect->type == FF_PERIODIC && effect->u.periodic.period == 0) { + return -EINVAL; + } + + state = &entry->states[effect->id]; + + if (test_bit(FF_EFFECT_STARTED, &state->flags) && effect->type != state->effect.type) { + return -EINVAL; + } + + spin_lock_irqsave(&entry->timer_lock, flags); + + state->effect = *effect; + + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + __set_bit(FF_EFFECT_UPDATING, &state->flags); + state->updated_at = now; + } + + spin_unlock_irqrestore(&entry->timer_lock, flags); + + return 0; +} + +static int lg4ff_play_effect(struct input_dev *dev, int effect_id, int value) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct lg4ff_device_entry *entry; + struct lg4ff_effect_state *state; + unsigned long now = JIFFIES2MS(jiffies); + unsigned long flags; + int i; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + state = &entry->states[effect_id]; + + spin_lock_irqsave(&entry->timer_lock, flags); + + if (value > 0) { + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + STOP_EFFECT(state); + } else { + entry->effects_used++; + if (!hrtimer_active(&entry->hrtimer)) { + hrtimer_start(&entry->hrtimer, ms_to_ktime(timer_msecs), HRTIMER_MODE_REL); + if (unlikely(profile)) + DEBUG("Start timer."); + } + if ((state->effect.type == FF_SPRING || state->effect.type == FF_DAMPER + || state->effect.type == FF_FRICTION || state->effect.type == FF_INERTIA) + && state->slot == 0) { + /* Find a free slot */ + for (i = 1; i < 4 && entry->slots[i].effect_type != 0; i++); + if (i < 4) { + state->slot = i; + entry->slots[i].effect_type = state->effect.type; + + /* Cast unsupported effect types to "damper": this is what the Windows + * driver does. + * This is not physically plausible, but we are working with toy-strength + * wheels that won't let you feel more than "big value = wheel stuck" */ + if (state->effect.type == FF_INERTIA + || (state->effect.type == FF_FRICTION && !(entry->wdata.capabilities & LG4FF_CAP_FRICTION))) { + entry->slots[i].effect_type = FF_DAMPER; + } + } + } + } + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->start_at = now; + state->count = value; + } else { + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + STOP_EFFECT(state); + entry->effects_used--; + if (state->slot) { + entry->slots[state->slot].effect_type = 0; + state->slot = 0; + } + } + } + + spin_unlock_irqrestore(&entry->timer_lock, flags); + + return 0; +} + +/* Recalculates X axis value accordingly to currently selected range */ +static s32 lg4ff_adjust_dfp_x_axis(s32 value, u16 range) +{ + u16 max_range; + s32 new_value; + + if (range == 900) + return value; + else if (range == 200) + return value; + else if (range < 200) + max_range = 200; + else + max_range = 900; + + new_value = 8192 + mult_frac(value - 8192, max_range, range); + if (new_value < 0) + return 0; + else if (new_value > 16383) + return 16383; + else + return new_value; +} + +int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field, + struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data) +{ + struct lg4ff_device_entry *entry = drv_data->device_props; + s32 new_value = 0; + + if (!entry) { + hid_err(hid, "Device properties not found"); + return 0; + } + + switch (entry->wdata.product_id) { + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + switch (usage->code) { + case ABS_X: + new_value = lg4ff_adjust_dfp_x_axis(value, entry->wdata.range); + input_event(field->hidinput->input, usage->type, usage->code, new_value); + return 1; + default: + return 0; + } + default: + return 0; + } +} + +int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *rd, int size, struct lg_drv_data *drv_data) +{ + int offset; + struct lg4ff_device_entry *entry = drv_data->device_props; + + if (!entry) + return 0; + + /* adjust HID report present combined pedals data */ + if (entry->wdata.combine == 1) { + switch (entry->wdata.product_id) { + case USB_DEVICE_ID_LOGITECH_WHEEL: + rd[5] = rd[3]; + rd[6] = 0x7F; + return 1; + case USB_DEVICE_ID_LOGITECH_WINGMAN_FG: + case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG: + case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL: + case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2: + rd[4] = rd[3]; + rd[5] = 0x7F; + return 1; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + rd[5] = rd[4]; + rd[6] = 0x7F; + return 1; + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + offset = 5; + break; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G923_WHEEL: + offset = 6; + break; + case USB_DEVICE_ID_LOGITECH_WII_WHEEL: + offset = 3; + break; + default: + return 0; + } + + /* Compute a combined axis when wheel does not supply it */ + rd[offset] = (0xFF + rd[offset] - rd[offset+1]) >> 1; + rd[offset+1] = 0x7F; + return 1; + } + + if (entry->wdata.combine == 2) { + switch (entry->wdata.product_id) { + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + offset = 5; + break; + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G923_WHEEL: + offset = 6; + break; + default: + return 0; + } + + /* Compute a combined axis when wheel does not supply it */ + rd[offset] = (0xFF + rd[offset] - rd[offset+2]) >> 1; + rd[offset+2] = 0x7F; + return 1; + } + + return 0; +} + +static void lg4ff_init_wheel_data(struct lg4ff_wheel_data * const wdata, const struct lg4ff_wheel *wheel, + const struct lg4ff_multimode_wheel *mmode_wheel, + const u16 real_product_id) +{ + u32 alternate_modes = 0; + const char *real_tag = NULL; + const char *real_name = NULL; + + if (mmode_wheel) { + alternate_modes = mmode_wheel->alternate_modes; + real_tag = mmode_wheel->real_tag; + real_name = mmode_wheel->real_name; + } + + { + struct lg4ff_wheel_data t_wdata = { .product_id = wheel->product_id, + .real_product_id = real_product_id, + .combine = 0, + .min_range = wheel->min_range, + .max_range = wheel->max_range, + .set_range = wheel->set_range, + .alternate_modes = alternate_modes, + .real_tag = real_tag, + .real_name = real_name, + .capabilities = wheel->capabilities }; + + memcpy(wdata, &t_wdata, sizeof(t_wdata)); + } +} + +/* Sends default autocentering command compatible with + * all wheels except Formula Force EX */ +static void lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude) +{ + struct hid_device *hid = input_get_drvdata(dev); + u8 cmd[7]; + u32 expand_a, expand_b; + struct lg4ff_device_entry *entry; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return; + } + + entry->wdata.autocenter = magnitude; + + /* De-activate Auto-Center */ + if (magnitude == 0) { + cmd[0] = 0xf5; + cmd[1] = 0x00; + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + lg4ff_send_cmd(entry, cmd); + return; + } + + if (magnitude <= 0xaaaa) { + expand_a = 0x0c * magnitude; + expand_b = 0x80 * magnitude; + } else { + expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa); + expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa); + } + + /* Adjust for non-MOMO wheels */ + switch (entry->wdata.product_id) { + case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL: + case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2: + break; + default: + expand_a = expand_a >> 1; + break; + } + + cmd[0] = 0xfe; + cmd[1] = 0x0d; + cmd[2] = expand_a / 0xaaaa; + cmd[3] = expand_a / 0xaaaa; + cmd[4] = expand_b / 0xaaaa; + cmd[5] = 0x00; + cmd[6] = 0x00; + lg4ff_send_cmd(entry, cmd); + + /* Activate Auto-Center */ + cmd[0] = 0x14; + cmd[1] = 0x00; + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + lg4ff_send_cmd(entry, cmd); +} + +/* Sends autocentering command compatible with Formula Force EX */ +static void lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct lg4ff_device_entry *entry; + u8 cmd[7]; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return; + } + + entry->wdata.autocenter = magnitude; + + magnitude = magnitude * 90 / 65535; + + cmd[0] = 0xfe; + cmd[1] = 0x03; + cmd[2] = magnitude >> 14; + cmd[3] = magnitude >> 14; + cmd[4] = magnitude; + cmd[5] = 0x00; + cmd[6] = 0x00; + lg4ff_send_cmd(entry, cmd); +} + +/* Sends command to set range compatible with G25/G27/Driving Force GT */ +static void lg4ff_set_range_g25(struct hid_device *hid, u16 range) +{ + struct lg4ff_device_entry *entry; + struct lg_drv_data *drv_data; + u8 cmd[7]; + + drv_data = hid_get_drvdata(hid); + entry = drv_data->device_props; + + dbg_hid("G25/G27/DFGT: setting range to %u\n", range); + + cmd[0] = 0xf8; + cmd[1] = 0x81; + cmd[2] = range & 0x00ff; + cmd[3] = (range & 0xff00) >> 8; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + lg4ff_send_cmd(entry, cmd); +} + +/* Sends commands to set range compatible with Driving Force Pro wheel */ +static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range) +{ + struct lg4ff_device_entry *entry; + struct lg_drv_data *drv_data; + int start_left, start_right, full_range; + u8 cmd[7]; + + drv_data = hid_get_drvdata(hid); + entry = drv_data->device_props; + + dbg_hid("Driving Force Pro: setting range to %u\n", range); + + /* Prepare "coarse" limit command */ + cmd[0] = 0xf8; + cmd[1] = 0x00; /* Set later */ + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + + if (range > 200) { + cmd[1] = 0x03; + full_range = 900; + } else { + cmd[1] = 0x02; + full_range = 200; + } + lg4ff_send_cmd(entry, cmd); + + /* Prepare "fine" limit command */ + cmd[0] = 0x81; + cmd[1] = 0x0b; + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + + if (range == 200 || range == 900) { /* Do not apply any fine limit */ + lg4ff_send_cmd(entry, cmd); + return; + } + + /* Construct fine limit command */ + start_left = (((full_range - range + 1) * 2047) / full_range); + start_right = 0xfff - start_left; + + cmd[2] = start_left >> 4; + cmd[3] = start_right >> 4; + cmd[4] = 0xff; + cmd[5] = (start_right & 0xe) << 4 | (start_left & 0xe); + cmd[6] = 0xff; + lg4ff_send_cmd(entry, cmd); +} + +static void lg4ff_set_gain(struct input_dev *dev, u16 gain) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct lg4ff_device_entry *entry; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return; + } + + entry->wdata.gain = gain; +} + +static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id) +{ + switch (real_product_id) { + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + switch (target_product_id) { + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + return &lg4ff_mode_switch_ext01_dfp; + /* DFP can only be switched to its native mode */ + default: + return NULL; + } + break; + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + switch (target_product_id) { + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + return &lg4ff_mode_switch_ext01_dfp; + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + return &lg4ff_mode_switch_ext16_g25; + /* G25 can only be switched to DFP mode or its native mode */ + default: + return NULL; + } + break; + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + switch (target_product_id) { + case USB_DEVICE_ID_LOGITECH_WHEEL: + return &lg4ff_mode_switch_ext09_dfex; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + return &lg4ff_mode_switch_ext09_dfp; + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + return &lg4ff_mode_switch_ext09_g25; + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + return &lg4ff_mode_switch_ext09_g27; + /* G27 can only be switched to DF-EX, DFP, G25 or its native mode */ + default: + return NULL; + } + break; + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + switch (target_product_id) { + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + return &lg4ff_mode_switch_ext09_dfp; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + return &lg4ff_mode_switch_ext09_dfgt; + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + return &lg4ff_mode_switch_ext09_g25; + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + return &lg4ff_mode_switch_ext09_g27; + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + return &lg4ff_mode_switch_ext09_g29; + /* G29 can only be switched to DF-EX, DFP, DFGT, G25, G27 or its native mode */ + default: + return NULL; + } + break; + case USB_DEVICE_ID_LOGITECH_G923_PS_WHEEL: + switch (target_product_id) { + case USB_DEVICE_ID_LOGITECH_G923_WHEEL: + return &lg4ff_mode_switch_30_g923; + /* We can only switch from PS mode to Classic. */ + default: + return NULL; + } + break; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + switch (target_product_id) { + case USB_DEVICE_ID_LOGITECH_WHEEL: + return &lg4ff_mode_switch_ext09_dfex; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + return &lg4ff_mode_switch_ext09_dfp; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + return &lg4ff_mode_switch_ext09_dfgt; + /* DFGT can only be switched to DF-EX, DFP or its native mode */ + default: + return NULL; + } + break; + /* No other wheels have multiple modes */ + default: + return NULL; + } +} + +static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s) +{ + struct lg4ff_device_entry *entry; + u8 cmd[7]; + u8 i; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + for (i = 0; i < s->cmd_count; i++) { + u8 j; + + for (j = 0; j < 7; j++) + cmd[j] = s->cmd[j + (7*i)]; + + lg4ff_send_cmd(entry, cmd); + } + hid_hw_wait(hid); + return 0; +} + +/* For switching from PS mode we need to set report id to 0x30 */ +static int lg4ff_switch_from_ps_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s) +{ + struct lg4ff_device_entry *entry; + u8 cmd[7]; + u8 i; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + for (i = 0; i < s->cmd_count; i++) { + u8 j; + + for (j = 0; j < 7; j++) + cmd[j] = s->cmd[j + (7*i)]; + + lg4ff_send_cmd_with_id(entry, cmd, 0x30); + } + hid_hw_wait(hid); + return 0; +} + +static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + ssize_t count = 0; + int i; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + if (!entry->wdata.real_name) { + hid_err(hid, "NULL pointer to string\n"); + return 0; + } + + for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) { + if (entry->wdata.alternate_modes & BIT(i)) { + /* Print tag and full name */ + count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s", + lg4ff_alternate_modes[i].tag, + !lg4ff_alternate_modes[i].product_id ? entry->wdata.real_name : lg4ff_alternate_modes[i].name); + if (count >= PAGE_SIZE - 1) + return count; + + /* Mark the currently active mode with an asterisk */ + if (lg4ff_alternate_modes[i].product_id == entry->wdata.product_id || + (lg4ff_alternate_modes[i].product_id == 0 && entry->wdata.product_id == entry->wdata.real_product_id)) + count += scnprintf(buf + count, PAGE_SIZE - count, " *\n"); + else + count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); + + if (count >= PAGE_SIZE - 1) + return count; + } + } + + return count; +} + +static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + const struct lg4ff_compat_mode_switch *s; + u16 target_product_id = 0; + int i, ret; + char *lbuf; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + /* Allow \n at the end of the input parameter */ + lbuf = kasprintf(GFP_KERNEL, "%s", buf); + if (!lbuf) + return -ENOMEM; + + i = strlen(lbuf); + + if (i == 0) { + kfree(lbuf); + return -EINVAL; + } + + if (lbuf[i-1] == '\n') { + if (i == 1) { + kfree(lbuf); + return -EINVAL; + } + lbuf[i-1] = '\0'; + } + + for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) { + const u16 mode_product_id = lg4ff_alternate_modes[i].product_id; + const char *tag = lg4ff_alternate_modes[i].tag; + + if (entry->wdata.alternate_modes & BIT(i)) { + if (!strcmp(tag, lbuf)) { + if (!mode_product_id) + target_product_id = entry->wdata.real_product_id; + else + target_product_id = mode_product_id; + break; + } + } + } + + if (i == LG4FF_MODE_MAX_IDX) { + hid_info(hid, "Requested mode \"%s\" is not supported by the device\n", lbuf); + kfree(lbuf); + return -EINVAL; + } + kfree(lbuf); /* Not needed anymore */ + + if (target_product_id == entry->wdata.product_id) /* Nothing to do */ + return count; + + /* Automatic switching has to be disabled for the switch to DF-EX mode to work correctly */ + if (target_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && !lg4ff_no_autoswitch) { + hid_info(hid, "\"%s\" cannot be switched to \"DF-EX\" mode. Load the \"hid_logitech\" module with \"lg4ff_no_autoswitch=1\" parameter set and try again\n", + entry->wdata.real_name); + return -EINVAL; + } + + /* Take care of hardware limitations */ + if ((entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) && + entry->wdata.product_id > target_product_id) { + hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode\n", entry->wdata.real_name, lg4ff_alternate_modes[i].name); + return -EINVAL; + } + + s = lg4ff_get_mode_switch_command(entry->wdata.real_product_id, target_product_id); + if (!s) { + hid_err(hid, "Invalid target product ID %X\n", target_product_id); + return -EINVAL; + } + + ret = lg4ff_switch_compatibility_mode(hid, s); + return (ret == 0 ? count : ret); +} +static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store); + +static ssize_t lg4ff_combine_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + size_t count; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.combine); + return count; +} + +static ssize_t lg4ff_combine_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + u16 combine = simple_strtoul(buf, NULL, 10); + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + if (combine > 2) + combine = 2; + + entry->wdata.combine = combine; + return count; +} +static DEVICE_ATTR(combine_pedals, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_combine_show, lg4ff_combine_store); + +/* Export the currently set range of the wheel */ +static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + size_t count; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.range); + return count; +} + +/* Set range to user specified value, call appropriate function + * according to the type of the wheel */ +static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + u16 range = simple_strtoul(buf, NULL, 10); + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + if (range == 0) + range = entry->wdata.max_range; + + /* Check if the wheel supports range setting + * and that the range is within limits for the wheel */ + if (entry->wdata.set_range && range >= entry->wdata.min_range && range <= entry->wdata.max_range) { + entry->wdata.set_range(hid, range); + entry->wdata.range = range; + } + + return count; +} +static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_range_show, lg4ff_range_store); + +static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + size_t count; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + if (!entry->wdata.real_tag || !entry->wdata.real_name) { + hid_err(hid, "NULL pointer to string\n"); + return 0; + } + + count = scnprintf(buf, PAGE_SIZE, "%s: %s\n", entry->wdata.real_tag, entry->wdata.real_name); + return count; +} + +static ssize_t lg4ff_real_id_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + /* Real ID is a read-only value */ + return -EPERM; +} +static DEVICE_ATTR(real_id, S_IRUGO, lg4ff_real_id_show, lg4ff_real_id_store); + +/* Export the currently set gain of the wheel */ +static ssize_t lg4ff_gain_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + size_t count; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.master_gain); + return count; +} + +/* Set gain to user specified value, call appropriate function + * according to the type of the wheel */ +static ssize_t lg4ff_gain_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + u16 gain = simple_strtoul(buf, NULL, 10); + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + if (gain > 0xffff) { + gain = 0xffff; + } + + entry->wdata.master_gain = gain; + + return count; +} +static DEVICE_ATTR(gain, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_gain_show, lg4ff_gain_store); + +/* Export the currently set autocenter of the wheel */ +static ssize_t lg4ff_autocenter_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + size_t count; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.autocenter); + return count; +} + +/* Set autocenter to user specified value, call appropriate function + * according to the type of the wheel */ +static ssize_t lg4ff_autocenter_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); + struct input_dev *inputdev = hidinput->input; + u16 autocenter = simple_strtoul(buf, NULL, 10); + + if (autocenter > 0xffff) { + autocenter = 0xffff; + } + + inputdev->ff->set_autocenter(inputdev, autocenter); + + return count; +} +static DEVICE_ATTR(autocenter, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_autocenter_show, lg4ff_autocenter_store); + +static ssize_t lg4ff_spring_level_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + size_t count; + + count = scnprintf(buf, PAGE_SIZE, "%u\n", spring_level); + + return count; +} + +static ssize_t lg4ff_spring_level_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned value = simple_strtoul(buf, NULL, 10); + + if (value > 100) { + value = 100; + } + + spring_level = value; + + return count; +} +static DEVICE_ATTR(spring_level, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_spring_level_show, lg4ff_spring_level_store); + +static ssize_t lg4ff_damper_level_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + size_t count; + + count = scnprintf(buf, PAGE_SIZE, "%u\n", damper_level); + + return count; +} + +static ssize_t lg4ff_damper_level_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned value = simple_strtoul(buf, NULL, 10); + + if (value > 100) { + value = 100; + } + + damper_level = value; + + return count; +} +static DEVICE_ATTR(damper_level, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_damper_level_show, lg4ff_damper_level_store); + +static ssize_t lg4ff_friction_level_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + size_t count; + + count = scnprintf(buf, PAGE_SIZE, "%u\n", friction_level); + + return count; +} + +static ssize_t lg4ff_friction_level_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned value = simple_strtoul(buf, NULL, 10); + + if (value > 100) { + value = 100; + } + + friction_level = value; + + return count; +} +static DEVICE_ATTR(friction_level, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_friction_level_show, lg4ff_friction_level_store); + +static ssize_t lg4ff_peak_ffb_level_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + size_t count; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->peak_ffb_level); + + return count; +} + +static ssize_t lg4ff_peak_ffb_level_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + unsigned long value = simple_strtoul(buf, NULL, 10); + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + entry->peak_ffb_level = value; + + return count; +} +static DEVICE_ATTR(peak_ffb_level, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_peak_ffb_level_show, lg4ff_peak_ffb_level_store); + +#ifdef CONFIG_LEDS_CLASS + +static ssize_t lg4ff_ffb_leds_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + size_t count; + + count = scnprintf(buf, PAGE_SIZE, "%d\n", ffb_leds); + + return count; +} + +static ssize_t lg4ff_ffb_leds_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long value = simple_strtoul(buf, NULL, 10); + + ffb_leds = value; + + return count; +} +static DEVICE_ATTR(ffb_leds, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_ffb_leds_show, lg4ff_ffb_leds_store); + +static void lg4ff_set_leds(struct hid_device *hid, u8 leds) +{ + struct lg4ff_device_entry *entry; + u8 cmd[7]; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return; + } + + cmd[0] = 0xf8; + cmd[1] = 0x12; + cmd[2] = leds; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + lg4ff_send_cmd(entry, cmd); +} + +static void lg4ff_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct device *dev = led_cdev->dev->parent; + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + int i, state = 0; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return; + } + + for (i = 0; i < 5; i++) { + if (led_cdev != entry->wdata.led[i]) + continue; + state = (entry->wdata.led_state >> i) & 1; + if (value == LED_OFF && state) { + entry->wdata.led_state &= ~(1 << i); + if (!ffb_leds) { + lg4ff_set_leds(hid, entry->wdata.led_state); + } + } else if (value != LED_OFF && !state) { + entry->wdata.led_state |= 1 << i; + if (!ffb_leds) { + lg4ff_set_leds(hid, entry->wdata.led_state); + } + } + break; + } +} + +static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cdev) +{ + struct device *dev = led_cdev->dev->parent; + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + int i, value = 0; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + for (i = 0; i < 5; i++) + if (led_cdev == entry->wdata.led[i]) { + value = (entry->wdata.led_state >> i) & 1; + break; + } + + return value ? LED_FULL : LED_OFF; +} + +static void lg4ff_init_leds(struct hid_device *hid, struct lg4ff_device_entry *entry, int i) +{ + int error, j; + + /* register led subsystem - G27/G29/G923 only */ + entry->wdata.led_state = 0; + for (j = 0; j < 5; j++) + entry->wdata.led[j] = NULL; + + { + struct led_classdev *led; + size_t name_sz; + char *name; + + lg4ff_set_leds(hid, 0); + + name_sz = strlen(dev_name(&hid->dev)) + 8; + + for (j = 0; j < 5; j++) { + led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); + if (!led) { + hid_err(hid, "can't allocate memory for LED %d\n", j); + goto err_leds; + } + + name = (void *)(&led[1]); + snprintf(name, name_sz, "%s::RPM%d", dev_name(&hid->dev), j+1); + led->name = name; + led->brightness = 0; + led->max_brightness = 1; + led->brightness_get = lg4ff_led_get_brightness; + led->brightness_set = lg4ff_led_set_brightness; + + entry->wdata.led[j] = led; + error = led_classdev_register(&hid->dev, led); + + if (error) { + hid_err(hid, "failed to register LED %d. Aborting.\n", j); +err_leds: + /* Deregister LEDs (if any) */ + for (j = 0; j < 5; j++) { + led = entry->wdata.led[j]; + entry->wdata.led[j] = NULL; + if (!led) + continue; + led_classdev_unregister(led); + kfree(led); + } + goto out; /* Let the driver continue without LEDs */ + } + } + } +out: + return; +} +#endif + +static u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_product_id, const u16 bcdDevice) +{ + u32 current_mode; + int i; + + /* identify current mode from USB PID */ + for (i = 1; i < ARRAY_SIZE(lg4ff_alternate_modes); i++) { + dbg_hid("Testing whether PID is %X\n", lg4ff_alternate_modes[i].product_id); + if (reported_product_id == lg4ff_alternate_modes[i].product_id) + break; + } + + if (i == ARRAY_SIZE(lg4ff_alternate_modes)) + return 0; + + current_mode = BIT(i); + + for (i = 0; i < ARRAY_SIZE(lg4ff_main_checklist); i++) { + const u16 mask = lg4ff_main_checklist[i]->mask; + const u16 result = lg4ff_main_checklist[i]->result; + const u16 real_product_id = lg4ff_main_checklist[i]->real_product_id; + + if ((current_mode & lg4ff_main_checklist[i]->modes) && \ + (bcdDevice & mask) == result) { + dbg_hid("Found wheel with real PID %X whose reported PID is %X\n", real_product_id, reported_product_id); + return real_product_id; + } + } + + /* No match found. This is either Driving Force or an unknown + * wheel model, do not touch it */ + dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice); + return 0; +} + +static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, const u16 bcdDevice) +{ + const u16 reported_product_id = hid->product; + int ret; + + *real_product_id = lg4ff_identify_multimode_wheel(hid, reported_product_id, bcdDevice); + /* Probed wheel is not a multimode wheel */ + if (!*real_product_id) { + *real_product_id = reported_product_id; + dbg_hid("Wheel is not a multimode wheel\n"); + return LG4FF_MMODE_NOT_MULTIMODE; + } + + /* Switch from "Driving Force" mode to native mode automatically. + * Otherwise keep the wheel in its current mode */ + if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && + reported_product_id != *real_product_id && + !lg4ff_no_autoswitch) { + const struct lg4ff_compat_mode_switch *s = lg4ff_get_mode_switch_command(*real_product_id, *real_product_id); + + if (!s) { + hid_err(hid, "Invalid product id %X\n", *real_product_id); + return LG4FF_MMODE_NOT_MULTIMODE; + } + + ret = lg4ff_switch_compatibility_mode(hid, s); + if (ret) { + /* Wheel could not have been switched to native mode, + * leave it in "Driving Force" mode and continue */ + hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret); + return LG4FF_MMODE_IS_MULTIMODE; + } + return LG4FF_MMODE_SWITCHED; + } + + /* Switch from "G923 PS" mode to native mode automatically. */ + /* Users could use lg4ff_no_autoswitch option if they want to manually change modes */ + if ((reported_product_id == USB_DEVICE_ID_LOGITECH_G923_PS_WHEEL) && + reported_product_id != *real_product_id) { + const struct lg4ff_compat_mode_switch *s = &lg4ff_mode_switch_30_g923; + if (lg4ff_no_autoswitch) { + hid_err(hid, "This device should switch mode. Load the \"hid_logitech\" module with \"lg4ff_no_autoswitch=0\" parameter set and try again.\n"); + return -EINVAL; + } + if (!s) { + hid_err(hid, "Invalid product id %X\n", *real_product_id); + return LG4FF_MMODE_NOT_MULTIMODE; + } + + ret = lg4ff_switch_from_ps_mode(hid, s); + if (ret) { + /* Wheel could not have been switched to Classic mode, + * leave it in "PS" mode and continue */ + hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret); + return LG4FF_MMODE_IS_MULTIMODE; + } + return LG4FF_MMODE_SWITCHED; + } + + return LG4FF_MMODE_IS_MULTIMODE; +} + +static void lg4ff_destroy(struct ff_device *ff) +{ +} + +int lg4ff_init(struct hid_device *hid) +{ + struct hid_input *hidinput; + struct input_dev *dev; + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor); + const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice); + const struct lg4ff_multimode_wheel *mmode_wheel = NULL; + struct lg4ff_device_entry *entry; + struct lg_drv_data *drv_data; + int error, i, j; + int mmode_ret, mmode_idx = -1; + u16 real_product_id; + struct ff_device *ff; + + if (list_empty(&hid->inputs)) { + hid_err(hid, "no inputs found\n"); + return -ENODEV; + } + hidinput = list_entry(hid->inputs.next, struct hid_input, list); + dev = hidinput->input; + + /* Check that the report looks ok */ + if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7)) + return -1; + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Cannot add device, private driver data not allocated\n"); + return -1; + } + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + spin_lock_init(&entry->report_lock); + entry->hid = hid; + entry->report = report; + drv_data->device_props = entry; + + /* Check if a multimode wheel has been connected and + * handle it appropriately */ + mmode_ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice); + + /* Wheel has been told to switch to native mode. There is no point in going on + * with the initialization as the wheel will do a USB reset when it switches mode + */ + if (mmode_ret == LG4FF_MMODE_SWITCHED) { + error = 0; + goto err_init; + } else if (mmode_ret < 0) { + hid_err(hid, "Unable to switch device mode during initialization, errno %d\n", mmode_ret); + error = mmode_ret; + goto err_init; + } + + /* Check what wheel has been connected */ + for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) { + if (hid->product == lg4ff_devices[i].product_id) { + dbg_hid("Found compatible device, product ID %04X\n", lg4ff_devices[i].product_id); + break; + } + } + + if (i == ARRAY_SIZE(lg4ff_devices)) { + hid_err(hid, "This device is flagged to be handled by the lg4ff module but this module does not know how to handle it. " + "Please report this as a bug to LKML, Simon Wood or " + "Michal Maly \n"); + error = -1; + goto err_init; + } + + if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) { + for (mmode_idx = 0; mmode_idx < ARRAY_SIZE(lg4ff_multimode_wheels); mmode_idx++) { + if (real_product_id == lg4ff_multimode_wheels[mmode_idx].product_id) + break; + } + + if (mmode_idx == ARRAY_SIZE(lg4ff_multimode_wheels)) { + hid_err(hid, "Device product ID %X is not listed as a multimode wheel", real_product_id); + error = -1; + goto err_init; + } + } + + /* Set supported force feedback capabilities */ + for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++) + set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit); + + error = input_ff_create(dev, LG4FF_MAX_EFFECTS); + + //__clear_bit(FF_RUMBLE, dev->ffbit); + + if (error) + goto err_init; + + ff = dev->ff; + ff->upload = lg4ff_upload_effect; + ff->playback = lg4ff_play_effect; + ff->set_gain = lg4ff_set_gain; + ff->destroy = lg4ff_destroy; + + /* Initialize device properties */ + if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) { + BUG_ON(mmode_idx == -1); + mmode_wheel = &lg4ff_multimode_wheels[mmode_idx]; + } + lg4ff_init_wheel_data(&entry->wdata, &lg4ff_devices[i], mmode_wheel, real_product_id); + + set_bit(FF_GAIN, dev->ffbit); + + /* Check if autocentering is available and + * set the centering force to zero by default */ + if (test_bit(FF_AUTOCENTER, dev->ffbit)) { + /* Formula Force EX expects different autocentering command */ + if ((bcdDevice >> 8) == LG4FF_FFEX_REV_MAJ && + (bcdDevice & 0xff) == LG4FF_FFEX_REV_MIN) + dev->ff->set_autocenter = lg4ff_set_autocenter_ffex; + else + dev->ff->set_autocenter = lg4ff_set_autocenter_default; + + dev->ff->set_autocenter(dev, 0); + } + +#ifdef CONFIG_LEDS_CLASS + if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL || + lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G29_WHEEL || + lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G923_WHEEL) { + entry->has_leds = 1; + lg4ff_init_leds(hid, entry, i); + } else { + ffb_leds = 0; + } +#endif + + /* Create sysfs interface */ + error = device_create_file(&hid->dev, &dev_attr_combine_pedals); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"combine\", errno %d\n", error); + error = device_create_file(&hid->dev, &dev_attr_range); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"range\", errno %d\n", error); + if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) { + error = device_create_file(&hid->dev, &dev_attr_real_id); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"real_id\", errno %d\n", error); + error = device_create_file(&hid->dev, &dev_attr_alternate_modes); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"alternate_modes\", errno %d\n", error); + } + + if (test_bit(FF_CONSTANT, dev->ffbit)) { + error = device_create_file(&hid->dev, &dev_attr_gain); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"gain\", errno %d\n", error); + if (test_bit(FF_AUTOCENTER, dev->ffbit)) { + error = device_create_file(&hid->dev, &dev_attr_autocenter); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"autocenter\", errno %d\n", error); + } + error = device_create_file(&hid->dev, &dev_attr_peak_ffb_level); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"peak_ffb_level\", errno %d\n", error); + if (test_bit(FF_SPRING, dev->ffbit)) { + error = device_create_file(&hid->dev, &dev_attr_spring_level); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"spring_level\", errno %d\n", error); + } + if (test_bit(FF_DAMPER, dev->ffbit)) { + error = device_create_file(&hid->dev, &dev_attr_damper_level); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"damper_level\", errno %d\n", error); + } + if (test_bit(FF_FRICTION, dev->ffbit) && (entry->wdata.capabilities & LG4FF_CAP_FRICTION)) { + error = device_create_file(&hid->dev, &dev_attr_friction_level); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"friction_level\", errno %d\n", error); + } + } + +#ifdef CONFIG_LEDS_CLASS + if (entry->has_leds) { + error = device_create_file(&hid->dev, &dev_attr_ffb_leds); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"ffb_leds\", errno %d\n", error); + } +#endif + + dbg_hid("sysfs interface created\n"); + + /* Set the maximum range to start with */ + entry->wdata.range = entry->wdata.max_range; + if (entry->wdata.set_range) + entry->wdata.set_range(hid, entry->wdata.range); + + lg4ff_init_slots(entry); + + entry->effects_used = 0; + entry->wdata.master_gain = 0xffff; + entry->wdata.gain = 0xffff; + + spin_lock_init(&entry->timer_lock); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 15, 0) + hrtimer_init(&entry->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + entry->hrtimer.function = lg4ff_timer_hires; +#else + hrtimer_setup(&entry->hrtimer, lg4ff_timer_hires, CLOCK_MONOTONIC, HRTIMER_MODE_REL); +#endif + + hid_info(hid, "Force feedback support for Logitech Gaming Wheels (%s)\n", VERSION); + + hid_info(hid, "Hires timer: period = %d ms", timer_msecs); + + return 0; + +err_init: + drv_data->device_props = NULL; + kfree(entry); + return error; +} + +int lg4ff_deinit(struct hid_device *hid) +{ + struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); + struct input_dev *dev = hidinput->input; + struct lg4ff_device_entry *entry; + struct lg_drv_data *drv_data; + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Error while deinitializing device, no private driver data.\n"); + return -1; + } + entry = drv_data->device_props; + if (!entry) + goto out; /* Nothing more to do */ + + hrtimer_cancel(&entry->hrtimer); + + /* Multimode devices will have at least the "MODE_NATIVE" bit set */ + if (entry->wdata.alternate_modes) { + device_remove_file(&hid->dev, &dev_attr_real_id); + device_remove_file(&hid->dev, &dev_attr_alternate_modes); + } + + device_remove_file(&hid->dev, &dev_attr_combine_pedals); + device_remove_file(&hid->dev, &dev_attr_range); + + if (test_bit(FF_CONSTANT, dev->ffbit)) { + device_remove_file(&hid->dev, &dev_attr_gain); + if (test_bit(FF_AUTOCENTER, dev->ffbit)) { + device_remove_file(&hid->dev, &dev_attr_autocenter); + } + device_remove_file(&hid->dev, &dev_attr_peak_ffb_level); + if (test_bit(FF_SPRING, dev->ffbit)) { + device_remove_file(&hid->dev, &dev_attr_spring_level); + } + if (test_bit(FF_DAMPER, dev->ffbit)) { + device_remove_file(&hid->dev, &dev_attr_damper_level); + } + if (test_bit(FF_FRICTION, dev->ffbit) + && (entry->wdata.capabilities & LG4FF_CAP_FRICTION)) { + device_remove_file(&hid->dev, &dev_attr_friction_level); + } + } + + lg4ff_stop_effects(entry); + +#ifdef CONFIG_LEDS_CLASS + if (entry->has_leds) { + int j; + struct led_classdev *led; + + device_remove_file(&hid->dev, &dev_attr_ffb_leds); + + /* Deregister LEDs (if any) */ + for (j = 0; j < 5; j++) { + + led = entry->wdata.led[j]; + entry->wdata.led[j] = NULL; + if (!led) + continue; + led_classdev_unregister(led); + kfree(led); + } + } +#endif + + drv_data->device_props = NULL; + + kfree(entry); +out: + dbg_hid("Device successfully unregistered\n"); + return 0; +} diff --git a/drivers/custom/lg4ff/hid-lg4ff.h b/drivers/custom/lg4ff/hid-lg4ff.h new file mode 100644 index 000000000000..e5c55d515ac2 --- /dev/null +++ b/drivers/custom/lg4ff/hid-lg4ff.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __HID_LG4FF_H +#define __HID_LG4FF_H + +#ifdef CONFIG_LOGIWHEELS_FF +extern int lg4ff_no_autoswitch; /* From hid-lg.c */ + +int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field, + struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data); +int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *rd, int size, struct lg_drv_data *drv_data); +int lg4ff_init(struct hid_device *hdev); +int lg4ff_deinit(struct hid_device *hdev); +#else +static inline int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field, + struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data) { return 0; } +static inline int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *rd, int size, struct lg_drv_data *drv_data) { return 0; } +static inline int lg4ff_init(struct hid_device *hdev) { return -1; } +static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; } +#endif + +#endif diff --git a/drivers/custom/lg4ff/hid-lgff.c b/drivers/custom/lg4ff/hid-lgff.c new file mode 100644 index 000000000000..aed4ddc397a9 --- /dev/null +++ b/drivers/custom/lg4ff/hid-lgff.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Force feedback support for hid-compliant for some of the devices from + * Logitech, namely: + * - WingMan Cordless RumblePad + * - WingMan Force 3D + * + * Copyright (c) 2002-2004 Johann Deneux + * Copyright (c) 2006 Anssi Hannula + */ + +/* + * + * Should you need to contact me, the author, you can do so by + * e-mail - mail your message to + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +#include "hid-lg.h" + +struct dev_type { + u16 idVendor; + u16 idProduct; + const signed short *ff; +}; + +static const signed short ff_rumble[] = { + FF_RUMBLE, + -1 +}; + +static const signed short ff_joystick[] = { + FF_CONSTANT, + -1 +}; + +static const signed short ff_joystick_ac[] = { + FF_CONSTANT, + FF_AUTOCENTER, + -1 +}; + +static const struct dev_type devices[] = { + { 0x046d, 0xc211, ff_rumble }, + { 0x046d, 0xc219, ff_rumble }, + { 0x046d, 0xc283, ff_joystick }, + { 0x046d, 0xc286, ff_joystick_ac }, + { 0x046d, 0xc287, ff_joystick_ac }, + { 0x046d, 0xc293, ff_joystick }, + { 0x046d, 0xc295, ff_joystick }, +}; + +static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + int x, y; + unsigned int left, right; + +#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff + + switch (effect->type) { + case FF_CONSTANT: + x = effect->u.ramp.start_level + 0x7f; /* 0x7f is center */ + y = effect->u.ramp.end_level + 0x7f; + CLAMP(x); + CLAMP(y); + report->field[0]->value[0] = 0x51; + report->field[0]->value[1] = 0x08; + report->field[0]->value[2] = x; + report->field[0]->value[3] = y; + dbg_hid("(x, y)=(%04x, %04x)\n", x, y); + hid_hw_request(hid, report, HID_REQ_SET_REPORT); + break; + + case FF_RUMBLE: + right = effect->u.rumble.strong_magnitude; + left = effect->u.rumble.weak_magnitude; + right = right * 0xff / 0xffff; + left = left * 0xff / 0xffff; + CLAMP(left); + CLAMP(right); + report->field[0]->value[0] = 0x42; + report->field[0]->value[1] = 0x00; + report->field[0]->value[2] = left; + report->field[0]->value[3] = right; + dbg_hid("(left, right)=(%04x, %04x)\n", left, right); + hid_hw_request(hid, report, HID_REQ_SET_REPORT); + break; + } + return 0; +} + +static void hid_lgff_set_autocenter(struct input_dev *dev, u16 magnitude) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + __s32 *value = report->field[0]->value; + magnitude = (magnitude >> 12) & 0xf; + *value++ = 0xfe; + *value++ = 0x0d; + *value++ = magnitude; /* clockwise strength */ + *value++ = magnitude; /* counter-clockwise strength */ + *value++ = 0x80; + *value++ = 0x00; + *value = 0x00; + hid_hw_request(hid, report, HID_REQ_SET_REPORT); +} + +int lgff_init(struct hid_device* hid) +{ + struct hid_input *hidinput; + struct input_dev *dev; + const signed short *ff_bits = ff_joystick; + int error; + int i; + + if (list_empty(&hid->inputs)) { + hid_err(hid, "no inputs found\n"); + return -ENODEV; + } + hidinput = list_entry(hid->inputs.next, struct hid_input, list); + dev = hidinput->input; + + /* Check that the report looks ok */ + if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7)) + return -ENODEV; + + for (i = 0; i < ARRAY_SIZE(devices); i++) { + if (dev->id.vendor == devices[i].idVendor && + dev->id.product == devices[i].idProduct) { + ff_bits = devices[i].ff; + break; + } + } + + for (i = 0; ff_bits[i] >= 0; i++) + set_bit(ff_bits[i], dev->ffbit); + + error = input_ff_create_memless(dev, NULL, hid_lgff_play); + if (error) + return error; + + if ( test_bit(FF_AUTOCENTER, dev->ffbit) ) + dev->ff->set_autocenter = hid_lgff_set_autocenter; + + pr_info("Force feedback for Logitech force feedback devices by Johann Deneux \n"); + + return 0; +} diff --git a/drivers/custom/lg4ff/usbhid/usbhid.h b/drivers/custom/lg4ff/usbhid/usbhid.h new file mode 100644 index 000000000000..8620408bd7af --- /dev/null +++ b/drivers/custom/lg4ff/usbhid/usbhid.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __USBHID_H +#define __USBHID_H + +/* + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2001 Vojtech Pavlik + * Copyright (c) 2006 Jiri Kosina + */ + +/* + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* API provided by hid-core.c for USB HID drivers */ +void usbhid_init_reports(struct hid_device *hid); +struct usb_interface *usbhid_find_interface(int minor); + +/* iofl flags */ +#define HID_CTRL_RUNNING 1 +#define HID_OUT_RUNNING 2 +#define HID_IN_RUNNING 3 +#define HID_RESET_PENDING 4 +#define HID_SUSPENDED 5 +#define HID_CLEAR_HALT 6 +#define HID_DISCONNECTED 7 +#define HID_STARTED 8 +#define HID_KEYS_PRESSED 10 +#define HID_NO_BANDWIDTH 11 +#define HID_RESUME_RUNNING 12 +/* + * The device is opened, meaning there is a client that is interested + * in data coming from the device. + */ +#define HID_OPENED 13 +/* + * We are polling input endpoint by [re]submitting IN URB, because + * either HID device is opened or ALWAYS POLL quirk is set for the + * device. + */ +#define HID_IN_POLLING 14 + +/* + * USB-specific HID struct, to be pointed to + * from struct hid_device->driver_data + */ + +struct usbhid_device { + struct hid_device *hid; /* pointer to corresponding HID dev */ + + struct usb_interface *intf; /* USB interface */ + int ifnum; /* USB interface number */ + + unsigned int bufsize; /* URB buffer size */ + + struct urb *urbin; /* Input URB */ + char *inbuf; /* Input buffer */ + dma_addr_t inbuf_dma; /* Input buffer dma */ + + struct urb *urbctrl; /* Control URB */ + struct usb_ctrlrequest *cr; /* Control request struct */ + struct hid_control_fifo ctrl[HID_CONTROL_FIFO_SIZE]; /* Control fifo */ + unsigned char ctrlhead, ctrltail; /* Control fifo head & tail */ + char *ctrlbuf; /* Control buffer */ + dma_addr_t ctrlbuf_dma; /* Control buffer dma */ + unsigned long last_ctrl; /* record of last output for timeouts */ + + struct urb *urbout; /* Output URB */ + struct hid_output_fifo out[HID_CONTROL_FIFO_SIZE]; /* Output pipe fifo */ + unsigned char outhead, outtail; /* Output pipe fifo head & tail */ + char *outbuf; /* Output buffer */ + dma_addr_t outbuf_dma; /* Output buffer dma */ + unsigned long last_out; /* record of last output for timeouts */ + + spinlock_t lock; /* fifo spinlock */ + unsigned long iofl; /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */ + struct timer_list io_retry; /* Retry timer */ + unsigned long stop_retry; /* Time to give up, in jiffies */ + unsigned int retry_delay; /* Delay length in ms */ + struct work_struct reset_work; /* Task context for resets */ + wait_queue_head_t wait; /* For sleeping */ +}; + +#define hid_to_usb_dev(hid_dev) \ + to_usb_device(hid_dev->dev.parent->parent) + +#endif + # ---------------------------------------- # Module: nct6687d # Version: 80dc037bb5e1 # ---------------------------------------- diff --git a/drivers/custom/nct6687d/Makefile b/drivers/custom/nct6687d/Makefile new file mode 100644 index 000000000000..33d58a2cdaef --- /dev/null +++ b/drivers/custom/nct6687d/Makefile @@ -0,0 +1,10 @@ +obj-m += nct6687.o + +ccflags-y := -std=gnu99 -Wno-declaration-after-statement +KERNEL_SOURCE_DIR := /lib/modules/$(shell uname -r)/build + +all: + make -C "$(KERNEL_SOURCE_DIR)" M="$(PWD)" modules + +clean: + make -C "$(KERNEL_SOURCE_DIR)" M="$(PWD)" clean \ No newline at end of file diff --git a/drivers/custom/nct6687d/nct6687.c b/drivers/custom/nct6687d/nct6687.c new file mode 100644 index 000000000000..ab43554631b1 --- /dev/null +++ b/drivers/custom/nct6687d/nct6687.c @@ -0,0 +1,1374 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * nct6687 - Driver for the hardware monitoring functionality of + * Nuvoton NCT6687 Super-I/O chips + * + * Copyright (C) 2020 Frederic Boltz + * + * Derived from nct6683 driver + * Copyright (C) 2013 Guenter Roeck + * + * Inspired of LibreHardwareMonitor + * https://github.com/LibreHardwareMonitor/LibreHardwareMonitor + * + * Supports the following chips: + * + * Chip #voltage #fan #pwm #temp chip ID + * nct6683 14(1) 8 8 7 0xc732 (partial support) + * nct6686d 21(1) 16 8 32(1) 0xd440 + * nct6687 14(1) 8 8 7 0xd592 + * + * Notes: + * (1) Total number of voltage and 9 displayed. + */ +// #define DEBUG 1 +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +#ifndef MAX +#define MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +enum kinds +{ + nct6683, + nct6686, + nct6687 +}; + +enum pwm_enable +{ + manual_mode = 1, + // There are multiple automatic modes, none of which is configurable by this module yet. + firmware_mode = 99, +}; + +static bool force; +static bool manual; + +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Set to one to enable support for unknown vendors"); + +module_param(manual, bool, 0); +MODULE_PARM_DESC(manual, "Set voltage input and voltage label configured with external sensors file"); + +static const char *const nct6687_device_names[] = { + "nct6683", + "nct6686", + "nct6687", +}; + +static const char *const nct6687_chip_names[] = { + "NCT6683D", + "NCT6686D", + "NCT6687D", +}; + +#define DRVNAME "nct6687" + +/* + * Super-I/O constants and functions + */ + +#define NCT6687_LD_ACPI 0x0a +#define NCT6687_LD_HWM 0x0b +#define NCT6687_LD_VID 0x0d + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_DEVREVISION 0x21 /* Device ID (2 bytes) */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ + +#define SIO_NCT6681_ID 0xb270 /* for later */ +#define SIO_NCT6683_ID 0xc730 +#define SIO_NCT6686_ID 0xd440 +#define SIO_NCT6687D_ID 0xd450 /* NCT6687 ???*/ +#define SIO_NCT6687_ID 0xd590 +#define SIO_ID_MASK 0xFFF0 + +static inline void superio_outb(int ioreg, int reg, int val) +{ + outb(reg, ioreg); + outb(val, ioreg + 1); +} + +static inline int superio_inb(int ioreg, int reg) +{ + outb(reg, ioreg); + + return inb(ioreg + 1); +} + +static inline void superio_select(int ioreg, int ld) +{ + outb(SIO_REG_LDSEL, ioreg); + outb(ld, ioreg + 1); +} + +static inline int superio_enter(int ioreg) +{ + /* + * Try to reserve and for exclusive access. + */ + if (!request_muxed_region(ioreg, 2, DRVNAME)) + return -EBUSY; + + outb(0x87, ioreg); + outb(0x87, ioreg); + + return 0; +} + +static inline void superio_exit(int ioreg) +{ + outb(0xaa, ioreg); + outb(0x02, ioreg); + outb(0x02, ioreg + 1); + + release_region(ioreg, 2); +} + +/* + * ISA constants + */ + +#define IOREGION_OFFSET 0 /* Use EC port 1 */ +#define IOREGION_LENGTH 4 + +/* Common and NCT6687 specific data */ + +#define NCT6687_NUM_REG_VOLTAGE (sizeof(nct6687_voltage_definition) / sizeof(struct voltage_reg)) +#define NCT6687_NUM_REG_TEMP 7 +#define NCT6687_NUM_REG_FAN 8 +#define NCT6687_NUM_REG_PWM 8 + +#define NCT6687_REG_TEMP(x) (0x100 + (x)*2) +#define NCT6687_REG_VOLTAGE(x) (0x120 + (x)*2) +#define NCT6687_REG_FAN_RPM(x) (0x140 + (x)*2) +#define NCT6687_REG_PWM(x) (0x160 + (x)) +#define NCT6687_REG_PWM_WRITE(x) (0xa28 + (x)) + +#define NCT6687_HWM_CFG 0x180 + +#define NCT6687_REG_MON_CFG(x) (0x1a0 + (x)) +#define NCT6687_REG_FANIN_CFG(x) (0xA00 + (x)) +#define NCT6687_REG_FANOUT_CFG(x) (0x1d0 + (x)) + +#define NCT6687_REG_TEMP_HYST(x) (0x330 + (x)) /* 8 bit */ +#define NCT6687_REG_TEMP_MAX(x) (0x350 + (x)) /* 8 bit */ +#define NCT6687_REG_MON_HIGH(x) (0x370 + (x)*2) /* 8 bit */ +#define NCT6687_REG_MON_LOW(x) (0x371 + (x)*2) /* 8 bit */ + +#define NCT6687_REG_FAN_MIN(x) (0x3b8 + (x)*2) /* 16 bit */ + +#define NCT6687_REG_FAN_CTRL_MODE(x) 0xA00 +#define NCT6687_REG_FAN_PWM_COMMAND(x) 0xA01 +#define NCT6687_FAN_CFG_REQ 0x80 +//#define NCT6687_FAN_CFG_DONE 0x40 //! for 6683 returns auto mode and clears 0xA00, 0xA28-0xA2F registers +#define NCT6687_FAN_CFG_DONE 0x00 //! tested on 6683 6687 + +#define NCT6687_REG_BUILD_YEAR 0x604 +#define NCT6687_REG_BUILD_MONTH 0x605 +#define NCT6687_REG_BUILD_DAY 0x606 +#define NCT6687_REG_SERIAL 0x607 +#define NCT6687_REG_VERSION_HI 0x608 +#define NCT6687_REG_VERSION_LO 0x609 + +#define NCT6687_REG_CR_CASEOPEN 0xe8 +#define NCT6687_CR_CASEOPEN_MASK (1 << 7) + +#define NCT6687_REG_CR_BEEP 0xe0 +#define NCT6687_CR_BEEP_MASK (1 << 6) + +#define EC_SPACE_PAGE_REGISTER_OFFSET 0x04 +#define EC_SPACE_INDEX_REGISTER_OFFSET 0x05 +#define EC_SPACE_DATA_REGISTER_OFFSET 0x06 +#define EC_SPACE_PAGE_SELECT 0xFF + +struct voltage_reg +{ + u16 reg; + u16 multiplier; + const char *label; +}; + +static struct voltage_reg nct6687_voltage_definition[] = { + // +12V + { + .reg = 0, + .multiplier = 12, + .label = "+12V", + }, + // + 5V + { + .reg = 1, + .multiplier = 5, + .label = "+5V", + }, + // +3.3V + { + .reg = 11, + .multiplier = 1, + .label = "+3.3V", + }, + // CPU SOC + { + .reg = 2, + .multiplier = 1, + .label = "CPU Soc", + }, + // CPU Vcore + { + .reg = 4, + .multiplier = 1, + .label = "CPU Vcore", + }, + // CPU 1P8 + { + .reg = 9, + .multiplier = 1, + .label = "CPU 1P8", + }, + // CPU VDDP + { + .reg = 10, + .multiplier = 1, + .label = "CPU VDDP", + }, + // DRAM + { + .reg = 3, + .multiplier = 2, + .label = "DRAM", + }, + // Chipset + { + .reg = 5, + .multiplier = 1, + .label = "Chipset", + }, + + // CPU SA + { + .reg = 6, + .multiplier = 1, + .label = "CPU SA", + }, + // Voltage #2 + { + .reg = 7, + .multiplier = 1, + .label = "Voltage #2", + }, + // AVCC3 + { + .reg = 8, + .multiplier = 1, + .label = "AVCC3", + }, + // AVSB + { + .reg = 12, + .multiplier = 1, + .label = "AVSB", + }, + // VBAT + { + .reg = 13, + .multiplier = 1, + .label = "VBat", + }, + +}; + +static const char *const nct6687_temp_label[] = { + "CPU", + "System", + "VRM MOS", + "PCH", + "CPU Socket", + "PCIe x1", + "M2_1", + NULL, +}; + +static const char *const nct6687_fan_label[] = { + "CPU Fan", + "Pump Fan", + "System Fan #1", + "System Fan #2", + "System Fan #3", + "System Fan #4", + "System Fan #5", + "System Fan #6", + NULL, +}; + +/* ------------------------------------------------------- */ +struct nct6687_data +{ + int addr; /* IO base of EC space */ + int sioreg; /* SIO register */ + enum kinds kind; + + struct device *hwmon_dev; + const struct attribute_group *groups[6]; + + struct mutex update_lock; /* used to protect sensor updates */ + struct mutex EC_io_lock; /* used to protect EC io */ + bool valid; /* true if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* Voltage values */ + s16 voltage[3][NCT6687_NUM_REG_VOLTAGE]; // 0 = current 1 = min 2 = max + + /* Temperature values */ + s32 temperature[3][NCT6687_NUM_REG_TEMP]; // 0 = current 1 = min 2 = max + + /* Fan attribute values */ + u16 rpm[3][NCT6687_NUM_REG_FAN]; // 0 = current 1 = min 2 = max + u8 _initialFanControlMode[NCT6687_NUM_REG_FAN]; + u8 _initialFanPwmCommand[NCT6687_NUM_REG_FAN]; + bool _restoreDefaultFanControlRequired[NCT6687_NUM_REG_FAN]; + + u8 pwm[NCT6687_NUM_REG_PWM]; + enum pwm_enable pwm_enable[NCT6687_NUM_REG_PWM]; + + /* Remember extra register values over suspend/resume */ + u8 hwm_cfg; +}; + +struct nct6687_sio_data +{ + int sioreg; + enum kinds kind; +}; + +struct sensor_device_template +{ + struct device_attribute dev_attr; + union + { + struct + { + u8 nr; + u8 index; + } s; + int index; + } u; + bool s2; /* true if both index and nr are used */ +}; + +struct sensor_device_attr_u +{ + union + { + struct sensor_device_attribute a1; + struct sensor_device_attribute_2 a2; + } u; + char name[32]; +}; + +#define __TEMPLATE_ATTR(_template, _mode, _show, _store) \ + { \ + .attr = {.name = _template, .mode = _mode}, \ + .show = _show, \ + .store = _store, \ + } + +#define SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, _index) \ + { \ + .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ + .u.index = _index, \ + .s2 = false \ + } + +#define SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ + _nr, _index) \ + { \ + .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ + .u.s.index = _index, \ + .u.s.nr = _nr, \ + .s2 = true \ + } + +#define SENSOR_TEMPLATE(_name, _template, _mode, _show, _store, _index) \ + static struct sensor_device_template sensor_dev_template_##_name = SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, \ + _index) + +#define SENSOR_TEMPLATE_2(_name, _template, _mode, _show, _store, \ + _nr, _index) \ + static struct sensor_device_template sensor_dev_template_##_name = SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ + _nr, _index) + +struct sensor_template_group +{ + struct sensor_device_template **templates; + umode_t (*is_visible)(struct kobject *, struct attribute *, int); + int base; +}; + +static void nct6687_save_fan_control(struct nct6687_data *data, int index); + +static const char* nct6687_voltage_label(char* buf, int index) +{ + if (manual) + sprintf(buf, "in%d", index); + else + strcpy(buf, nct6687_voltage_definition[index].label); + + return buf; +} + +static struct attribute_group *nct6687_create_attr_group(struct device *dev, const struct sensor_template_group *tg, int repeat) +{ + struct sensor_device_attribute_2 *a2; + struct sensor_device_attribute *a; + struct sensor_device_template **t; + struct sensor_device_attr_u *su; + struct attribute_group *group; + struct attribute **attrs; + int i, j, count; + + if (repeat <= 0) + return ERR_PTR(-EINVAL); + + t = tg->templates; + for (count = 0; *t; t++, count++) + ; + + if (count == 0) + return ERR_PTR(-EINVAL); + + group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); + if (group == NULL) + return ERR_PTR(-ENOMEM); + + attrs = devm_kcalloc(dev, repeat * count + 1, sizeof(*attrs), GFP_KERNEL); + if (attrs == NULL) + return ERR_PTR(-ENOMEM); + + su = devm_kzalloc(dev, array3_size(repeat, count, sizeof(*su)), GFP_KERNEL); + if (su == NULL) + return ERR_PTR(-ENOMEM); + + group->attrs = attrs; + group->is_visible = tg->is_visible; + + for (i = 0; i < repeat; i++) + { + t = tg->templates; + + for (j = 0; *t != NULL; j++) + { + snprintf(su->name, sizeof(su->name), (*t)->dev_attr.attr.name, tg->base + i); + + if ((*t)->s2) + { + a2 = &su->u.a2; + sysfs_attr_init(&a2->dev_attr.attr); + a2->dev_attr.attr.name = su->name; + a2->nr = (*t)->u.s.nr + i; + a2->index = (*t)->u.s.index; + a2->dev_attr.attr.mode = (*t)->dev_attr.attr.mode; + a2->dev_attr.show = (*t)->dev_attr.show; + a2->dev_attr.store = (*t)->dev_attr.store; + *attrs = &a2->dev_attr.attr; + } + else + { + a = &su->u.a1; + sysfs_attr_init(&a->dev_attr.attr); + a->dev_attr.attr.name = su->name; + a->index = (*t)->u.index + i; + a->dev_attr.attr.mode = (*t)->dev_attr.attr.mode; + a->dev_attr.show = (*t)->dev_attr.show; + a->dev_attr.store = (*t)->dev_attr.store; + *attrs = &a->dev_attr.attr; + } + attrs++; + su++; + t++; + } + } + + return group; +} + +static u16 nct6687_read(struct nct6687_data *data, u16 address) +{ + u8 page = (u8)(address >> 8); + u8 index = (u8)(address & 0xFF); + int res; + mutex_lock(&data->EC_io_lock); + outb_p(EC_SPACE_PAGE_SELECT, data->addr + EC_SPACE_PAGE_REGISTER_OFFSET); + outb_p(page, data->addr + EC_SPACE_PAGE_REGISTER_OFFSET); + outb_p(index, data->addr + EC_SPACE_INDEX_REGISTER_OFFSET); + mutex_unlock(&data->EC_io_lock); + res = inb_p(data->addr + EC_SPACE_DATA_REGISTER_OFFSET); + + return res; +} + +static u16 nct6687_read16(struct nct6687_data *data, u16 reg) +{ + return (nct6687_read(data, reg) << 8) | nct6687_read(data, reg + 1); +} + +static void nct6687_write(struct nct6687_data *data, u16 address, u16 value) +{ + u8 page = (u8)(address >> 8); + u8 index = (u8)(address & 0xFF); + mutex_lock(&data->EC_io_lock); + outb_p(EC_SPACE_PAGE_SELECT, data->addr + EC_SPACE_PAGE_REGISTER_OFFSET); + outb_p(page, data->addr + EC_SPACE_PAGE_REGISTER_OFFSET); + outb_p(index, data->addr + EC_SPACE_INDEX_REGISTER_OFFSET); + outb_p(value, data->addr + EC_SPACE_DATA_REGISTER_OFFSET); + mutex_unlock(&data->EC_io_lock); +} + +static void nct6687_update_temperatures(struct nct6687_data *data) +{ + int i; + + for (i = 0; i < NCT6687_NUM_REG_TEMP; i++) + { + s32 value = (char)nct6687_read(data, NCT6687_REG_TEMP(i)); + s32 half = (nct6687_read(data, NCT6687_REG_TEMP(i) + 1) >> 7) & 0x1; + s32 temperature = (value * 1000) + (500 * half); + + data->temperature[0][i] = temperature; + data->temperature[1][i] = MIN(temperature, data->temperature[1][i]); + data->temperature[2][i] = MAX(temperature, data->temperature[2][i]); + + pr_debug("nct6687_update_temperatures[%d]], addr=%04X, value=%d, half=%d, temperature=%d\n", i, NCT6687_REG_TEMP(i), value, half, temperature); + } +} + +static void nct6687_update_voltage(struct nct6687_data *data) +{ + int index; + char buf[128]; + + /* Measured voltages and limits */ + for (index = 0; index < NCT6687_NUM_REG_VOLTAGE; index++) + { + s16 reg = manual ? index : nct6687_voltage_definition[index].reg; + s16 high = nct6687_read(data, NCT6687_REG_VOLTAGE(reg)) * 16; + s16 low = ((u16)nct6687_read(data, NCT6687_REG_VOLTAGE(reg) + 1)) >> 4; + s16 value = low + high; + s16 voltage = manual ? value : value * nct6687_voltage_definition[index].multiplier; + + data->voltage[0][index] = voltage; + data->voltage[1][index] = MIN(voltage, data->voltage[1][index]); + data->voltage[2][index] = MAX(voltage, data->voltage[2][index]); + + pr_debug("nct6687_update_voltage[%d], %s, reg=%d, addr=0x%04x, value=%d, voltage=%d\n", index, nct6687_voltage_label(buf, index), reg, NCT6687_REG_VOLTAGE(index), value, voltage); + } + + pr_debug("nct6687_update_voltage\n"); +} + +static enum pwm_enable nct6687_get_pwm_enable(struct nct6687_data *data, int index) +{ + u16 bitMask = 0x01 << index; + if (nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(index)) & bitMask) + { + return manual_mode; + } + return firmware_mode; +} + +static void nct6687_update_fans(struct nct6687_data *data) +{ + int i; + + for (i = 0; i < NCT6687_NUM_REG_FAN; i++) + { + s16 rmp = nct6687_read16(data, NCT6687_REG_FAN_RPM(i)); + + data->rpm[0][i] = rmp; + data->rpm[1][i] = MIN(rmp, data->rpm[1][i]); + data->rpm[2][i] = MAX(rmp, data->rpm[2][i]); + + pr_debug("nct6687_update_fans[%d], rpm=%d min=%d, max=%d", i, rmp, data->rpm[1][i], data->rpm[2][i]); + } + + for (i = 0; i < NCT6687_NUM_REG_PWM; i++) + { + data->pwm[i] = nct6687_read(data, NCT6687_REG_PWM(i)); + data->pwm_enable[i] = nct6687_get_pwm_enable(data, i); + + pr_debug("nct6687_update_fans[%d], pwm=%d", i, data->pwm[i]); + } +} + +static struct nct6687_data *nct6687_update_device(struct device *dev) +{ + struct nct6687_data *data = dev_get_drvdata(dev); + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) + { + /* Measured voltages and limits */ + nct6687_update_voltage(data); + + /* Measured temperatures and limits */ + nct6687_update_temperatures(data); + + /* Measured fan speeds and limits */ + nct6687_update_fans(data); + + data->last_updated = jiffies; + data->valid = true; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Sysfs callback functions + */ +static ssize_t show_voltage_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + if (manual) + return sprintf(buf, "in%d\n", sattr->index); + else + return sprintf(buf, "%s\n", nct6687_voltage_definition[sattr->index].label); +} + +static ssize_t show_voltage_value(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct6687_data *data = nct6687_update_device(dev); + + return sprintf(buf, "%d\n", data->voltage[sattr->index][sattr->nr]); +} + +static umode_t nct6687_voltage_is_visible(struct kobject *kobj, struct attribute *attr, int index) +{ + pr_debug("nct6687_voltage_is_visible[%d], attr=0x%04X\n", index, attr->mode); + return attr->mode; +} + +SENSOR_TEMPLATE(voltage_label, "in%d_label", S_IRUGO, show_voltage_label, NULL, 0); +SENSOR_TEMPLATE_2(voltage_input, "in%d_input", S_IRUGO, show_voltage_value, NULL, 0, 0); +SENSOR_TEMPLATE_2(voltage_min, "in%d_min", S_IRUGO, show_voltage_value, NULL, 0, 1); +SENSOR_TEMPLATE_2(voltage_max, "in%d_max", S_IRUGO, show_voltage_value, NULL, 0, 2); + +static struct sensor_device_template *nct6687_attributes_voltage_template[] = { + &sensor_dev_template_voltage_label, + &sensor_dev_template_voltage_input, + &sensor_dev_template_voltage_min, + &sensor_dev_template_voltage_max, + NULL, +}; + +static const struct sensor_template_group nct6687_voltage_template_group = { + .templates = nct6687_attributes_voltage_template, + .is_visible = nct6687_voltage_is_visible, +}; + +static ssize_t show_fan_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sprintf(buf, "%s\n", nct6687_fan_label[sattr->index]); +} + +static ssize_t show_fan_value(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct6687_data *data = nct6687_update_device(dev); + + return sprintf(buf, "%d\n", data->rpm[sattr->index][sattr->nr]); +} + +static umode_t nct6687_fan_is_visible(struct kobject *kobj, struct attribute *attr, int index) +{ + return attr->mode; +} + +SENSOR_TEMPLATE(fan_label, "fan%d_label", S_IRUGO, show_fan_label, NULL, 0); +SENSOR_TEMPLATE_2(fan_input, "fan%d_input", S_IRUGO, show_fan_value, NULL, 0, 0); +SENSOR_TEMPLATE_2(fan_min, "fan%d_min", S_IRUGO, show_fan_value, NULL, 0, 1); +SENSOR_TEMPLATE_2(fan_max, "fan%d_max", S_IRUGO, show_fan_value, NULL, 0, 2); + +/* + * nct6687_fan_is_visible uses the index into the following array + * to determine if attributes should be created or not. + * Any change in order or content must be matched. + */ +static struct sensor_device_template *nct6687_attributes_fan_template[] = { + &sensor_dev_template_fan_label, + &sensor_dev_template_fan_input, + &sensor_dev_template_fan_min, + &sensor_dev_template_fan_max, + NULL, +}; + +static const struct sensor_template_group nct6687_fan_template_group = { + .templates = nct6687_attributes_fan_template, + .is_visible = nct6687_fan_is_visible, + .base = 1, +}; + +static ssize_t show_temperature_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sprintf(buf, "%s\n", nct6687_temp_label[sattr->index]); +} + +static ssize_t show_temperature_value(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct6687_data *data = nct6687_update_device(dev); + + return sprintf(buf, "%d\n", data->temperature[sattr->index][sattr->nr]); +} + +static umode_t nct6687_temp_is_visible(struct kobject *kobj, struct attribute *attr, int index) +{ + return attr->mode; +} + +SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temperature_label, NULL, 0); +SENSOR_TEMPLATE_2(temp_input, "temp%d_input", S_IRUGO, show_temperature_value, NULL, 0, 0); +SENSOR_TEMPLATE_2(temp_min, "temp%d_min", S_IRUGO, show_temperature_value, NULL, 0, 1); +SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO, show_temperature_value, NULL, 0, 2); + +/* + * nct6687_temp_is_visible uses the index into the following array + * to determine if attributes should be created or not. + * Any change in order or content must be matched. + */ +static struct sensor_device_template *nct6687_attributes_temp_template[] = { + &sensor_dev_template_temp_input, + &sensor_dev_template_temp_label, + &sensor_dev_template_temp_min, + &sensor_dev_template_temp_max, + NULL, +}; + +static const struct sensor_template_group nct6687_temp_template_group = { + .templates = nct6687_attributes_temp_template, + .is_visible = nct6687_temp_is_visible, + .base = 1, +}; + +static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6687_data *data = nct6687_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int index = sattr->index; + + return sprintf(buf, "%d\n", data->pwm[index]); +} + +static ssize_t store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct6687_data *data = dev_get_drvdata(dev); + int index = sattr->index; + unsigned long val; + int retry; + u16 readback; + u16 mode; + u8 bitMask; + + if (kstrtoul(buf, 10, &val) || val > 255 || index >= NCT6687_NUM_REG_FAN) + return -EINVAL; + + mutex_lock(&data->update_lock); + + nct6687_save_fan_control(data, index); + + mode = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(index)); + bitMask = (u8)(0x01 << index); + + mode = (u8)(mode | bitMask); + nct6687_write(data, NCT6687_REG_FAN_CTRL_MODE(index), mode); + + nct6687_write(data, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_FAN_CFG_REQ); + msleep(50); + nct6687_write(data, NCT6687_REG_PWM_WRITE(index), val); + nct6687_write(data, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_FAN_CFG_DONE); + + for (retry = 0; retry < 20; retry++) { + msleep(50); + + readback = nct6687_read(data, NCT6687_REG_PWM(index)); + if (readback == val) + break; + } + data->pwm[index] = readback; + data->pwm_enable[index] = nct6687_get_pwm_enable(data, index); + + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_enable(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6687_data *data = nct6687_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + return sprintf(buf, "%d\n", data->pwm_enable[sattr->nr]); +} + +static ssize_t store_pwm_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct6687_data *data = dev_get_drvdata(dev); + int index = sattr->nr; + unsigned long val; + u16 mode; + u8 bitMask; + + if (index >= NCT6687_NUM_REG_FAN || kstrtoul(buf, 10, &val)) + return -EINVAL; + if (val != manual_mode && val != firmware_mode) + return -EINVAL; + + mutex_lock(&data->update_lock); + + nct6687_save_fan_control(data, index); + + mode = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(index)); + + bitMask = (u8)(0x01 << index); + if (val == manual_mode) + { + mode = (u8)(mode | bitMask); + } + else if (val == firmware_mode) + { + mode = (u8)(mode & ~bitMask); + } + + nct6687_write(data, NCT6687_REG_FAN_CTRL_MODE(index), mode); + + mutex_unlock(&data->update_lock); + + return count; +} + +SENSOR_TEMPLATE(pwm, "pwm%d", S_IRUGO, show_pwm, store_pwm, 0); +SENSOR_TEMPLATE_2(pwm_enable, "pwm%d_enable", S_IRUGO, show_pwm_enable, store_pwm_enable, 0, 0); + +static void nct6687_save_fan_control(struct nct6687_data *data, int index) +{ + if (data->_restoreDefaultFanControlRequired[index] == false) + { + u16 reg = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(index)); + u16 bitMask = 0x01 << index; + u8 pwm = nct6687_read(data, NCT6687_REG_FAN_PWM_COMMAND(index)); + + data->_initialFanControlMode[index] = (u8)(reg & bitMask); + data->_initialFanPwmCommand[index] = pwm; + + data->_restoreDefaultFanControlRequired[index] = true; + } +} + +static void nct6687_restore_fan_control(struct nct6687_data *data, int index) +{ + if (data->_restoreDefaultFanControlRequired[index]) + { + u8 mode = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(index)); + u8 bitMask = 0x01 << index; + mode = (u8)((mode & ~bitMask) | data->_initialFanControlMode[index]); + + nct6687_write(data, NCT6687_REG_FAN_CTRL_MODE(index), mode); + + nct6687_write(data, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_FAN_CFG_REQ); + msleep(50); + nct6687_write(data, NCT6687_REG_PWM_WRITE(index), data->_initialFanPwmCommand[index]); + nct6687_write(data, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_FAN_CFG_DONE); + msleep(50); + + data->_restoreDefaultFanControlRequired[index] = false; + + pr_debug("nct6687_restore_fan_control[%d], addr=%04X, ctrl=%04X, _initialFanPwmCommand=%d\n", index, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_REG_PWM_WRITE(index), data->_initialFanPwmCommand[index]); + } +} + +static umode_t nct6687_pwm_is_visible(struct kobject *kobj, struct attribute *attr, int index) +{ + return attr->mode | S_IWUSR; +} + +static struct sensor_device_template *nct6687_attributes_pwm_template[] = { + &sensor_dev_template_pwm, + &sensor_dev_template_pwm_enable, + NULL, +}; + +static const struct sensor_template_group nct6687_pwm_template_group = { + .templates = nct6687_attributes_pwm_template, + .is_visible = nct6687_pwm_is_visible, + .base = 1, +}; + +/* Get the monitoring functions started */ +static inline void nct6687_init_device(struct nct6687_data *data) +{ + u8 tmp; + + pr_debug("nct6687_init_device\n"); + + /* Start hardware monitoring if needed */ + tmp = nct6687_read(data, NCT6687_HWM_CFG); + if (!(tmp & 0x80)) + { + pr_debug("nct6687_init_device: 0x%04x\n", tmp); + nct6687_write(data, NCT6687_HWM_CFG, tmp | 0x80); + } + + // enable SIO voltage + nct6687_write(data, 0x1BB, 0x61); + nct6687_write(data, 0x1BC, 0x62); + nct6687_write(data, 0x1BD, 0x63); + nct6687_write(data, 0x1BE, 0x64); + nct6687_write(data, 0x1BF, 0x65); +} + +/* + * There are a total of 8 fan inputs. + */ +static void nct6687_setup_fans(struct nct6687_data *data) +{ + int i; + + for (i = 0; i < NCT6687_NUM_REG_FAN; i++) + { + u16 reg = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(i)); + u16 bitMask = 0x01 << i; + u16 rpm = nct6687_read16(data, NCT6687_REG_FAN_RPM(i)); + + data->rpm[0][i] = rpm; + data->rpm[1][i] = rpm; + data->rpm[2][i] = rpm; + data->_initialFanControlMode[i] = (u8)(reg & bitMask); + data->_restoreDefaultFanControlRequired[i] = false; + + pr_debug("nct6687_setup_fans[%d], %s - addr=%04X, ctrl=%04X, rpm=%d, _initialFanControlMode=%d\n", i, nct6687_fan_label[i], NCT6687_REG_FAN_CTRL_MODE(i), reg, rpm, data->_initialFanControlMode[i]); + } +} + +static void nct6687_setup_voltages(struct nct6687_data *data) +{ + int index; + char buf[64]; + + /* Measured voltages and limits */ + for (index = 0; index < NCT6687_NUM_REG_VOLTAGE; index++) + { + s16 reg = manual ? index : nct6687_voltage_definition[index].reg; + s16 high = nct6687_read(data, NCT6687_REG_VOLTAGE(reg)) * 16; + s16 low = ((u16)nct6687_read(data, NCT6687_REG_VOLTAGE(reg) + 1)) >> 4; + s16 value = low + high; + s16 voltage = manual ? value : value * nct6687_voltage_definition[index].multiplier; + + data->voltage[0][index] = voltage; + data->voltage[1][index] = voltage; + data->voltage[2][index] = voltage; + + pr_debug("nct6687_setup_voltages[%d], %s, addr=0x%04x, value=%d, voltage=%d\n", index, nct6687_voltage_label(buf, index), NCT6687_REG_VOLTAGE(index), value, voltage); + } +} + +static void nct6687_setup_temperatures(struct nct6687_data *data) +{ + int i; + + for (i = 0; i < NCT6687_NUM_REG_TEMP; i++) + { + s32 value = (char)nct6687_read(data, NCT6687_REG_TEMP(i)); + s32 half = (nct6687_read(data, NCT6687_REG_TEMP(i) + 1) >> 7) & 0x1; + s32 temperature = (value * 1000) + (5 * half); + + data->temperature[0][i] = temperature; + data->temperature[1][i] = temperature; + data->temperature[2][i] = temperature; + + pr_debug("nct6687_setup_temperatures[%d]], addr=%04X, value=%d, half=%d, temperature=%d\n", i, NCT6687_REG_TEMP(i), value, half, temperature); + } +} + +static void nct6687_setup_pwm(struct nct6687_data *data) +{ + int i; + + for (i = 0; i < NCT6687_NUM_REG_PWM; i++) + { + data->_initialFanPwmCommand[i] = nct6687_read(data, NCT6687_REG_FAN_PWM_COMMAND(i)); + data->pwm[i] = nct6687_read(data, NCT6687_REG_PWM(i)); + data->pwm_enable[i] = nct6687_get_pwm_enable(data, i); + + pr_debug("nct6687_setup_pwm[%d], addr=%04X, pwm=%d, pwm_enable=%d, _initialFanPwmCommand=%d\n", + i, + NCT6687_REG_FAN_PWM_COMMAND(i), + data->pwm[i], + data->pwm_enable[i], + data->_initialFanPwmCommand[i]); + } +} + +static int nct6687_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nct6687_data *data = dev_get_drvdata(dev); + int i; + + mutex_lock(&data->update_lock); + + for (i = 0; i < NCT6687_NUM_REG_FAN; i++) + { + nct6687_restore_fan_control(data, i); + } + + mutex_unlock(&data->update_lock); + + return 0; +} + +static int nct6687_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nct6687_sio_data *sio_data = dev->platform_data; + struct attribute_group *group; + struct nct6687_data *data; + struct device *hwmon_dev; + struct resource *res; + int groups = 0; + char build[16]; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(dev, res->start, IOREGION_LENGTH, DRVNAME)) + return -EBUSY; + + data = devm_kzalloc(dev, sizeof(struct nct6687_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->kind = sio_data->kind; + data->sioreg = sio_data->sioreg; + data->addr = res->start; + + pr_debug("nct6687_probe addr=0x%04X, sioreg=0x%04X\n", data->addr, data->sioreg); + + mutex_init(&data->update_lock); + mutex_init(&data->EC_io_lock); + platform_set_drvdata(pdev, data); + + nct6687_init_device(data); + nct6687_setup_fans(data); + nct6687_setup_pwm(data); + nct6687_setup_temperatures(data); + nct6687_setup_voltages(data); + + /* Register sysfs hooks */ + + group = nct6687_create_attr_group(dev, &nct6687_pwm_template_group, NCT6687_NUM_REG_FAN); + + if (IS_ERR(group)) + return PTR_ERR(group); + + data->groups[groups++] = group; + + group = nct6687_create_attr_group(dev, &nct6687_voltage_template_group, NCT6687_NUM_REG_VOLTAGE); + + if (IS_ERR(group)) + return PTR_ERR(group); + + data->groups[groups++] = group; + + group = nct6687_create_attr_group(dev, &nct6687_fan_template_group, NCT6687_NUM_REG_FAN); + + if (IS_ERR(group)) + return PTR_ERR(group); + + data->groups[groups++] = group; + + group = nct6687_create_attr_group(dev, &nct6687_temp_template_group, NCT6687_NUM_REG_TEMP); + + if (IS_ERR(group)) + return PTR_ERR(group); + + data->groups[groups++] = group; + + scnprintf(build, sizeof(build), "%02d/%02d/%02d", nct6687_read(data, NCT6687_REG_BUILD_MONTH), nct6687_read(data, NCT6687_REG_BUILD_DAY), nct6687_read(data, NCT6687_REG_BUILD_YEAR)); + + dev_info(dev, "%s EC firmware version %d.%d build %s\n", nct6687_chip_names[data->kind], nct6687_read(data, NCT6687_REG_VERSION_HI), nct6687_read(data, NCT6687_REG_VERSION_LO), build); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, nct6687_device_names[data->kind], data, data->groups); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static int nct6687_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct nct6687_data *data = nct6687_update_device(&pdev->dev); + + mutex_lock(&data->update_lock); + data->hwm_cfg = nct6687_read(data, NCT6687_HWM_CFG); + mutex_unlock(&data->update_lock); + + return 0; +} + +static int nct6687_resume(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nct6687_data *data = dev_get_drvdata(dev); + + mutex_lock(&data->update_lock); + + nct6687_write(data, NCT6687_HWM_CFG, data->hwm_cfg); + + /* Force re-reading all values */ + data->valid = false; + mutex_unlock(&data->update_lock); + + return 0; +} + +// static const struct dev_pm_ops nct6687_dev_pm_ops = { +// .suspend = nct6687_suspend, +// .resume = nct6687_resume, +// .freeze = nct6687_suspend, +// .restore = nct6687_resume, +// }; + +#define NCT6687_DEV_PM_OPS NULL + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" +static struct platform_driver nct6687_driver = { + .driver = { + .name = DRVNAME, + .pm = NCT6687_DEV_PM_OPS, + }, + .probe = nct6687_probe, + .remove = nct6687_remove, + .suspend = nct6687_suspend, + .resume = nct6687_resume, +}; +#pragma GCC diagnostic pop + +static int __init nct6687_find(int sioaddr, struct nct6687_sio_data *sio_data) +{ + u16 address; + u16 verify; + u16 val; + int err; + + err = superio_enter(sioaddr); + if (err) + return err; + + val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) | superio_inb(sioaddr, SIO_REG_DEVREVISION); + + pr_debug("found chip ID: 0x%04x\n", val); + + switch (val & SIO_ID_MASK) { + case SIO_NCT6683_ID: + sio_data->kind = nct6683; + break; + case SIO_NCT6686_ID: + sio_data->kind = nct6686; + break; + case SIO_NCT6687D_ID: + case SIO_NCT6687_ID: + sio_data->kind = nct6687; + break; + default: + if (force){ + sio_data->kind = nct6687; + break; + } + if (val != 0xffff) + pr_debug("unsupported chip ID: 0x%04x\n", val); + goto fail; + } + + /* We have a known chip, find the HWM I/O address */ + superio_select(sioaddr, NCT6687_LD_HWM); + address = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) | superio_inb(sioaddr, SIO_REG_ADDR + 1); + ssleep(1); + verify = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) | superio_inb(sioaddr, SIO_REG_ADDR + 1); + + if (address == 0 || address != verify) + { + pr_err("EC base I/O port unconfigured\n"); + goto fail; + } + + if ((address & 0x07) == 0x05) + address &= 0xFFF8; + + if (address < 0x100 || (address & 0xF007) != 0) + { + pr_err("EC Invalid address: 0x%04X\n", address); + goto fail; + } + + /* Activate logical device if needed */ + val = superio_inb(sioaddr, SIO_REG_ENABLE); + if (!(val & 0x01)) + { + pr_warn("Forcibly enabling EC access. Data may be unusable.\n"); + superio_outb(sioaddr, SIO_REG_ENABLE, val | 0x01); + } + + superio_exit(sioaddr); + pr_info("Found %s or compatible chip at 0x%04x:0x%04x\n", nct6687_chip_names[sio_data->kind], sioaddr, address); + sio_data->sioreg = sioaddr; + + return address; + +fail: + superio_exit(sioaddr); + return -ENODEV; +} + +/* + * when Super-I/O functions move to a separate file, the Super-I/O + * bus will manage the lifetime of the device and this module will only keep + * track of the nct6687 driver. But since we use platform_device_alloc(), we + * must keep track of the device + */ +static struct platform_device *pdev[2]; + +static int __init sensors_nct6687_init(void) +{ + struct nct6687_sio_data sio_data; + int sioaddr[2] = {0x2e, 0x4e}; + struct resource res; + bool found = false; + int address; + int i, err; + + err = platform_driver_register(&nct6687_driver); + if (err) + return err; + + /* + * initialize sio_data->kind and sio_data->sioreg. + * + * when Super-I/O functions move to a separate file, the Super-I/O + * driver will probe 0x2e and 0x4e and auto-detect the presence of a + * nct6687 hardware monitor, and call probe() + */ + for (i = 0; i < ARRAY_SIZE(pdev); i++) + { + address = nct6687_find(sioaddr[i], &sio_data); + if (address <= 0) + continue; + + found = true; + + pdev[i] = platform_device_alloc(DRVNAME, address); + if (!pdev[i]) + { + err = -ENOMEM; + goto exit_device_unregister; + } + + err = platform_device_add_data(pdev[i], &sio_data, sizeof(struct nct6687_sio_data)); + if (err) + goto exit_device_put; + + memset(&res, 0, sizeof(res)); + + res.name = DRVNAME; + res.start = address + IOREGION_OFFSET; + res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; + res.flags = IORESOURCE_IO; + + err = acpi_check_resource_conflict(&res); + if (err) + { + platform_device_put(pdev[i]); + pdev[i] = NULL; + continue; + } + + err = platform_device_add_resources(pdev[i], &res, 1); + if (err) + goto exit_device_put; + + /* platform_device_add calls probe() */ + err = platform_device_add(pdev[i]); + if (err) + goto exit_device_put; + } + + if (!found) + { + err = -ENODEV; + goto exit_unregister; + } + + return 0; + +exit_device_put: + platform_device_put(pdev[i]); + +exit_device_unregister: + while (--i >= 0) + { + if (pdev[i]) + platform_device_unregister(pdev[i]); + } + +exit_unregister: + platform_driver_unregister(&nct6687_driver); + + return err; +} + +static void __exit sensors_nct6687_exit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pdev); i++) + { + if (pdev[i]) + platform_device_unregister(pdev[i]); + } + + platform_driver_unregister(&nct6687_driver); +} + +MODULE_AUTHOR("Frederic Boltz "); +MODULE_DESCRIPTION("Driver for NCT6687D"); +MODULE_LICENSE("GPL"); + +module_init(sensors_nct6687_init); +module_exit(sensors_nct6687_exit); # ---------------------------------------- # Module: razer # Version: 0663c556e527 # ---------------------------------------- diff --git a/drivers/custom/razer/driver/Makefile b/drivers/custom/razer/driver/Makefile new file mode 100644 index 000000000000..5d7c515f232a --- /dev/null +++ b/drivers/custom/razer/driver/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +obj-m := razerkbd.o razermouse.o razerkraken.o razeraccessory.o + +razerkbd-y := razerkbd_driver.o razercommon.o razerchromacommon.o +razermouse-y := razermouse_driver.o razercommon.o razerchromacommon.o +razerkraken-y := razerkraken_driver.o razercommon.o +razeraccessory-y := razeraccessory_driver.o razercommon.o razerchromacommon.o + +ccflags-y := -std=gnu99 -Wno-declaration-after-statement +KERNEL_SOURCE_DIR := /lib/modules/$(shell uname -r)/build + +all: + make -C "$(KERNEL_SOURCE_DIR)" M="$(PWD)" modules + +clean: + make -C "$(KERNEL_SOURCE_DIR)" M="$(PWD)" clean diff --git a/drivers/custom/razer/driver/razeraccessory_driver.c b/drivers/custom/razer/driver/razeraccessory_driver.c new file mode 100644 index 000000000000..6120bdccd07f --- /dev/null +++ b/drivers/custom/razer/driver/razeraccessory_driver.c @@ -0,0 +1,2574 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2015 Terri Cain + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "razeraccessory_driver.h" +#include "razercommon.h" +#include "razerchromacommon.h" + +/* + * Version Information + */ +#define DRIVER_DESC "Razer Accessory Device Driver" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE(DRIVER_LICENSE); + +/** + * Send report to the device + */ +static int razer_get_report(struct usb_device *usb_dev, struct razer_report *request, struct razer_report *response) +{ + switch (usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + return razer_get_usb_response(usb_dev, 0x00, request, 0x00, response, RAZER_NEW_DEVICE_WAIT_MIN_US, RAZER_NEW_DEVICE_WAIT_MAX_US); + break; + + default: + return razer_get_usb_response(usb_dev, 0x00, request, 0x00, response, RAZER_ACCESSORY_WAIT_MIN_US, RAZER_ACCESSORY_WAIT_MAX_US); + } +} + +/** + * Function to send to device, get response, and actually check the response + */ +static int razer_send_payload(struct razer_accessory_device *device, struct razer_report *request, struct razer_report *response) +{ + int err; + + request->crc = razer_calculate_crc(request); + + mutex_lock(&device->lock); + err = razer_get_report(device->usb_dev, request, response); + mutex_unlock(&device->lock); + if (err) { + print_erroneous_report(response, "razeraccessory", "Invalid Report Length"); + return err; + } + + /* Check the packet number, class and command are the same */ + if (response->remaining_packets != request->remaining_packets || + response->command_class != request->command_class || + response->command_id.id != request->command_id.id) { + print_erroneous_report(response, "razeraccessory", "Response doesn't match request"); + return -EIO; + } + + switch (response->status) { + case RAZER_CMD_BUSY: + // TODO: Check if this should be an error. + // print_erroneous_report(&response, "razeraccessory", "Device is busy"); + break; + case RAZER_CMD_FAILURE: + print_erroneous_report(response, "razeraccessory", "Command failed"); + return -EIO; + case RAZER_CMD_NOT_SUPPORTED: + print_erroneous_report(response, "razeraccessory", "Command not supported"); + return -EIO; + case RAZER_CMD_TIMEOUT: + print_erroneous_report(response, "razeraccessory", "Command timed out"); + return -EIO; + } + + return 0; +} + +/** + * Device mode function + */ +static void razer_set_device_mode(struct razer_accessory_device *device, unsigned char mode, unsigned char param) +{ + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_set_device_mode(mode, param); + request.transaction_id.id = 0x3F; + + razer_send_payload(device, &request, &response); +} + +/** + * Read device file "version" + * + * Returns a string + */ +static ssize_t razer_attr_read_version(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", DRIVER_VERSION); +} + +/** + * Read device file "device_type" + * + * Returns friendly string of device type + */ +static ssize_t razer_attr_read_device_type(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + + char *device_type; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_FIREFLY: + device_type = "Razer Firefly\n"; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + device_type = "Razer Firefly V2\n"; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + device_type = "Razer Firefly Hyperflux\n"; + break; + + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + device_type = "Razer Goliathus\n"; + break; + + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + device_type = "Razer Goliathus Extended\n"; + break; + + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + device_type = "Razer Goliathus Chroma 3XL\n"; + break; + + case USB_DEVICE_ID_RAZER_CORE: + device_type = "Razer Core\n"; + break; + + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + device_type = "Razer Core X Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + device_type = "Razer Laptop Stand Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + device_type = "Razer Chroma Mug Holder\n"; + break; + + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + device_type = "Razer Chroma HDK\n"; + break; + + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + device_type = "Razer Base Station Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + device_type = "Razer Base Station V2 Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + device_type = "Razer Nommo Pro\n"; + break; + + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + device_type = "Razer Nommo Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + device_type = "Razer Kraken Kitty Edition\n"; + break; + + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + device_type = "Razer Mouse Bungee V3 Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + device_type = "Razer Charging Pad Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + device_type = "Razer Mouse Dock\n"; + break; + + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + device_type = "Razer Thunderbolt 4 Dock Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_RAPTOR_27: + device_type = "Razer Raptor 27\n"; + break; + + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + device_type = "Razer Chroma Addressable RGB Controller\n"; + break; + + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + device_type = "Razer Laptop Stand Chroma V2\n"; + break; + + default: + device_type = "Unknown Device\n"; + break; + } + + return sprintf(buf, device_type); +} + +/** + * Write device file "test" + * + * Does nothing + */ +static ssize_t razer_attr_write_test(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return count; +} + +/** + * Read device file "test" + * + * Returns a string + */ +static ssize_t razer_attr_read_test(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "\n"); +} + +/** + * Write device file "mode_spectrum" + * + * Spectrum effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_matrix_effect_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + request = razer_chroma_standard_matrix_effect_spectrum(); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + request = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, ZERO_LED); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + request = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, ZERO_LED); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + // Must be in normal mode for hardware effects + razer_set_device_mode(device, 0x00, 0x00); + request = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, ZERO_LED); + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_reactive" + * + * Sets reactive mode when this file is written to. A speed byte and 3 RGB bytes should be written + */ +static ssize_t razer_attr_write_matrix_effect_reactive(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned char speed; + + if (count != 4) { + printk(KERN_WARNING "razeraccessory: Reactive only accepts Speed, RGB (4byte)\n"); + return -EINVAL; + } + + speed = (unsigned char)buf[0]; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + request = razer_chroma_extended_matrix_effect_reactive(VARSTORE, ZERO_LED, speed, (struct razer_rgb *)&buf[1]); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + request = razer_chroma_standard_matrix_effect_reactive(speed, (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0xFF; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "matrix_reactive_trigger" + * + * It triggers the mouse pad when written to + */ +static ssize_t razer_attr_write_matrix_reactive_trigger(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + struct razer_rgb rgb = {0}; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + // TODO: Fix reactive trigger for Goliathus + request = razer_chroma_extended_matrix_effect_reactive(VARSTORE, ZERO_LED, 0, &rgb); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY: + // TODO: Issue zeroed out razer_chroma_standard_matrix_effect_reactive report + request = razer_chroma_misc_matrix_reactive_trigger(); + request.transaction_id.id = 0xFF; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_none" + * + * None effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_matrix_effect_none(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + request = razer_chroma_standard_matrix_effect_none(); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + request = razer_chroma_extended_matrix_effect_none(VARSTORE, ZERO_LED); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + request = razer_chroma_extended_matrix_effect_none(VARSTORE, ZERO_LED); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + // Must be in normal mode for hardware effects + razer_set_device_mode(device, 0x00, 0x00); + request = razer_chroma_extended_matrix_effect_none(VARSTORE, ZERO_LED); + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_blinking" + * + * Blinking effect mode is activated whenever the file is written to with 3 bytes + */ +static ssize_t razer_attr_write_matrix_effect_blinking(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count != 3) { + printk(KERN_WARNING "razeraccessory: Blinking mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + + request = razer_chroma_standard_set_led_rgb(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x3F; + + razer_send_payload(device, &request, &response); + + msleep(5); + + request = razer_chroma_standard_set_led_effect(VARSTORE, BACKLIGHT_LED, CLASSIC_EFFECT_BLINKING); + request.transaction_id.id = 0x3F; + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_custom" + * + * Sets the device to custom mode whenever the file is written to + */ +static ssize_t razer_attr_write_matrix_effect_custom(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + request = razer_chroma_standard_matrix_effect_custom_frame(NOSTORE); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + request = razer_chroma_extended_matrix_effect_custom_frame(); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + request = razer_chroma_extended_matrix_effect_custom_frame(); + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_static" + * + * Static effect mode is activated whenever the file is written to with 3 bytes + */ +static ssize_t razer_attr_write_matrix_effect_static(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count != 3) { + printk(KERN_WARNING "razeraccessory: Static mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + request = razer_chroma_standard_matrix_effect_static((struct razer_rgb*) & buf[0]); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + request = razer_chroma_extended_matrix_effect_static(VARSTORE, ZERO_LED, (struct razer_rgb*) & buf[0]); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + request = razer_chroma_extended_matrix_effect_static(VARSTORE, ZERO_LED, (struct razer_rgb*) & buf[0]); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + // Must be in normal mode for hardware effects + razer_set_device_mode(device, 0x00, 0x00); + /** + * Mode switcher required after setting static color effect once and before setting a second time. + * Similar to Naga Trinity? + * + * If the color is not set twice with the mode switch in-between, each subsequent + * setting of the static effect actually sets the previous color... + */ + request = razer_chroma_extended_matrix_effect_static(VARSTORE, ZERO_LED, (struct razer_rgb*) & buf[0]); + request.transaction_id.id = 0x1F; + + razer_send_payload(device, &request, &response); + + request = get_razer_report(0x0f, 0x02, 0x06); + request.arguments[0] = 0x00; + request.arguments[1] = 0x00; + request.arguments[2] = 0x08; + request.arguments[3] = 0x00; + request.arguments[4] = 0x00; + request.arguments[5] = 0x00; + request.transaction_id.id = 0x1F; + + razer_send_payload(device, &request, &response); + + request = razer_chroma_extended_matrix_effect_static(VARSTORE, ZERO_LED, (struct razer_rgb*) & buf[0]); + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_wave" + * + * When 1 is written (as a character, 0x31) the wave effect is displayed moving anti clockwise + * if 2 is written (0x32) then the wave effect goes clockwise + */ +static ssize_t razer_attr_write_matrix_effect_wave(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + unsigned char direction = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + request = razer_chroma_standard_matrix_effect_wave(direction); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + request = razer_chroma_extended_matrix_effect_wave(VARSTORE, ZERO_LED, direction); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + request = razer_chroma_extended_matrix_effect_wave(VARSTORE, ZERO_LED, direction); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + // Must be in normal mode for hardware effects + razer_set_device_mode(device, 0x00, 0x00); + fallthrough; + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + // Direction values are flipped compared to other devices + direction ^= ((1<<0) | (1<<1)); + request = razer_chroma_extended_matrix_effect_wave(VARSTORE, ZERO_LED, direction); + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_breath" + * + * Breathing effect mode is activated whenever the file is written to with 1, 3, or 6 bytes + */ +static ssize_t razer_attr_write_matrix_effect_breath(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_extended_matrix_effect_breathing_single(VARSTORE, ZERO_LED, (struct razer_rgb *)&buf[0]); + break; + + case 6: // Dual colour mode + request = razer_chroma_extended_matrix_effect_breathing_dual(VARSTORE, ZERO_LED, (struct razer_rgb *)&buf[0], (struct razer_rgb *)&buf[3]); + break; + + default: // "Random" colour mode + request = razer_chroma_extended_matrix_effect_breathing_random(VARSTORE, ZERO_LED); + break; + } + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_extended_matrix_effect_breathing_single(VARSTORE, ZERO_LED, (struct razer_rgb *)&buf[0]); + break; + + case 6: // Dual colour mode + request = razer_chroma_extended_matrix_effect_breathing_dual(VARSTORE, ZERO_LED, (struct razer_rgb *)&buf[0], (struct razer_rgb *)&buf[3]); + break; + + default: // "Random" colour mode + request = razer_chroma_extended_matrix_effect_breathing_random(VARSTORE, ZERO_LED); + break; + } + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + // Must be in normal mode for hardware effects + razer_set_device_mode(device, 0x00, 0x00); + switch(count) { + case 3: // Single colour mode + request = razer_chroma_extended_matrix_effect_breathing_single(VARSTORE, ZERO_LED, (struct razer_rgb *)&buf[0]); + break; + + case 6: // Dual colour mode + request = razer_chroma_extended_matrix_effect_breathing_dual(VARSTORE, ZERO_LED, (struct razer_rgb *)&buf[0], (struct razer_rgb *)&buf[3]); + break; + + default: // "Random" colour mode + request = razer_chroma_extended_matrix_effect_breathing_random(VARSTORE, ZERO_LED); + break; + } + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_standard_matrix_effect_breathing_single((struct razer_rgb*)&buf[0]); + break; + + case 6: // Dual colour mode + request = razer_chroma_standard_matrix_effect_breathing_dual((struct razer_rgb*)&buf[0], (struct razer_rgb*)&buf[3]); + break; + + default: // "Random" colour mode + request = razer_chroma_standard_matrix_effect_breathing_random(); + break; + } + request.transaction_id.id = 0x3F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +static ssize_t razer_attr_write_matrix_effect_starlight(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned char speed = 0; + + if (count != 1 && count != 4 && count != 7) { + printk(KERN_WARNING "razeraccessory: Starlight accepts only 1, 4 or 7 bytes input (speed, [RGB], [RGB])\n"); + return -EINVAL; + } + speed = buf[0]; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + switch(count) { + case 4: // Single colour mode + request = razer_chroma_extended_matrix_effect_starlight_single(VARSTORE, ZERO_LED, speed, (struct razer_rgb *)&buf[1]); + break; + + case 7: // Dual colour mode + request = razer_chroma_extended_matrix_effect_starlight_dual(VARSTORE, ZERO_LED, speed, (struct razer_rgb *)&buf[1], (struct razer_rgb *)&buf[4]); + break; + + default: // "Random" colour mode + request = razer_chroma_extended_matrix_effect_starlight_random(VARSTORE, ZERO_LED, speed); + break; + } + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "matrix_custom_frame" + * + * Format + * ROW_ID START_COL STOP_COL RGB... + */ +static ssize_t razer_attr_write_matrix_custom_frame(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + size_t offset = 0; + unsigned char row_id, start_col, stop_col; + size_t row_length; + + while(offset < count) { + if(offset + 3 > count) { + printk(KERN_ALERT "razeraccessory: Wrong Amount of data provided: Should be ROW_ID, START_COL, STOP_COL, N_RGB\n"); + return -EINVAL; + } + + row_id = buf[offset++]; + start_col = buf[offset++]; + stop_col = buf[offset++]; + + // Validate parameters + if(start_col > stop_col) { + printk(KERN_ALERT "razeraccessory: Start column (%u) is greater than end column (%u)\n", start_col, stop_col); + return -EINVAL; + } + + row_length = ((stop_col + 1) - start_col) * 3; + + if(count < offset + row_length) { + printk(KERN_ALERT "razeraccessory: Not enough RGB to fill row (expecting %lu bytes of RGB data, got %lu)\n", row_length, (count - 3)); + return -EINVAL; + } + + // printk(KERN_INFO "razeraccessory: Row ID: %u, Start: %u, Stop: %u, row length: %lu\n", row_id, start_col, stop_col, row_length); + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CORE: + request = razer_chroma_standard_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + request = razer_chroma_misc_one_row_set_custom_frame(start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + request = razer_chroma_extended_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + request = razer_chroma_extended_matrix_set_custom_frame2(row_id, start_col, stop_col, (unsigned char*)&buf[offset], 0); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + // Must be in driver mode for custom effects + razer_set_device_mode(device, 0x03, 0x00); + request = razer_chroma_extended_matrix_set_custom_frame2(row_id, start_col, stop_col, (unsigned char*)&buf[offset], 0); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + razer_send_argb_msg(device->usb_dev, row_id, (stop_col - start_col) + 1, (unsigned char*)&buf[offset]); + return count; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + // *3 as its 3 bytes per col (RGB) + offset += row_length; + } + + return count; +} + +/** + * Read device file "serial", doesn't have a proper one so one is generated + * + * Returns a string + */ +static ssize_t razer_attr_read_device_serial(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + char serial_string[51]; + + request = razer_chroma_standard_get_serial(); + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + strncpy(&serial_string[0], &device->serial[0], sizeof(serial_string)); + break; + + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + strncpy(&serial_string[0], &response.arguments[0], 22); + serial_string[22] = '\0'; + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + request.transaction_id.id = 0x1F; + razer_send_payload(device, &request, &response); + strncpy(&serial_string[0], &response.arguments[0], 22); + serial_string[22] = '\0'; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + return sprintf(buf, "%s\n", &serial_string[0]); +} + +/** + * Read device file "get_firmware_version" + * + * Returns a string + */ +static ssize_t razer_attr_read_firmware_version(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_get_firmware_version(); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + request.transaction_id.id = 0x1F; + break; + + default: + request.transaction_id.id = 0x3F; + break; + } + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "v%u.%u\n", response.arguments[0], response.arguments[1]); +} + +/** + * Write device file "device_mode" + */ +static ssize_t razer_attr_write_device_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count != 2) { + printk(KERN_WARNING "razeraccessory: Device mode only takes 2 bytes.\n"); + return -EINVAL; + } + + request = razer_chroma_standard_set_device_mode(buf[0], buf[1]); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + request.transaction_id.id = 0x1F; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +static ssize_t razer_attr_read_is_mug_present(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = get_razer_report(0x02, 0x81, 0x02); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%u\n", response.arguments[1]); +} + +/** + * Read device file "device_mode" + * + * Returns a string + */ +static ssize_t razer_attr_read_device_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_get_device_mode(); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + request.transaction_id.id = 0x1F; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + buf[0] = response.arguments[0]; + buf[1] = response.arguments[1]; + + return 2; +} + +/** + * Write device file "set_brightness" + * + * Sets the brightness to the ASCII number written to this file. + */ +static ssize_t razer_attr_write_matrix_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + unsigned char brightness = 0; + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count < 1) { + printk(KERN_WARNING "razeraccessory: Brightness takes an ascii number\n"); + return -EINVAL; + } + + brightness = (unsigned char)simple_strtoul(buf, NULL, 10); + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + request = razer_chroma_extended_matrix_brightness(VARSTORE, ZERO_LED, brightness); + request.transaction_id.id = 0x3F; + device->saved_brightness = brightness; + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + request = razer_chroma_extended_matrix_brightness(VARSTORE, ZERO_LED, brightness); + request.transaction_id.id = 0x1F; + device->saved_brightness = brightness; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + request = razer_chroma_standard_set_led_brightness(VARSTORE, BACKLIGHT_LED, brightness); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + request = razer_chroma_extended_matrix_brightness(VARSTORE, ZERO_LED, brightness); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + /* Set the brightness for all channels to the requested value */ + request = razer_chroma_extended_matrix_brightness(VARSTORE, ARGB_CH_1_LED, brightness); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_extended_matrix_brightness(VARSTORE, ARGB_CH_2_LED, brightness); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_extended_matrix_brightness(VARSTORE, ARGB_CH_3_LED, brightness); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_extended_matrix_brightness(VARSTORE, ARGB_CH_4_LED, brightness); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_extended_matrix_brightness(VARSTORE, ARGB_CH_5_LED, brightness); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_extended_matrix_brightness(VARSTORE, ARGB_CH_6_LED, brightness); + request.transaction_id.id = 0x3F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "set_brightness" + * + * Returns brightness or -1 if the initial brightness is not known + */ +static ssize_t razer_attr_read_matrix_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report response = {0}; + struct razer_report request = {0}; + unsigned char brightness = 0; + size_t sum = 0; + size_t i; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + brightness = device->saved_brightness; + break; + + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + /* Get the average brightness of all channels */ + for (i = ARGB_CH_1_LED; i <= ARGB_CH_6_LED; i++) { + request = razer_chroma_extended_matrix_get_brightness(VARSTORE, i); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + sum += response.arguments[2]; + } + brightness = sum / 6; + break; + + default: + request = razer_chroma_standard_get_led_brightness(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + brightness = response.arguments[2]; + break; + } + + return sprintf(buf, "%d\n", brightness); +} + +/** + * Write charge brightness device files + * + * Sets the brightness to the ASCII number written to this file. + */ +static ssize_t razer_attr_write_set_charge_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, int led) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + unsigned char brightness = 0; + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count < 1) { + printk(KERN_WARNING "razeraccessory: Brightness takes an ascii number\n"); + return -EINVAL; + } + + brightness = (unsigned char)simple_strtoul(buf, NULL, 10); + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + request = razer_chroma_extended_matrix_brightness(VARSTORE, led, brightness); + request.transaction_id.id = 0x1F; + device->saved_brightness = brightness; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read charge brightness device files + * + * Returns brightness or -1 if the initial brightness is not known + */ +static ssize_t razer_attr_read_set_charge_brightness(struct device *dev, struct device_attribute *attr, char *buf, int led) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned char brightness = 0; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + brightness = device->saved_brightness; + break; + + default: + request = razer_chroma_standard_get_led_brightness(VARSTORE, led); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + brightness = response.arguments[2]; + break; + } + + return sprintf(buf, "%d\n", brightness); +} + +/** + * Read device file "charging_led_brightness" + */ +static ssize_t razer_attr_read_charging_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_set_charge_brightness(dev, attr, buf, CHARGING_LED); +} + +/** + * Write device file "charging_led_brightness" + */ +static ssize_t razer_attr_write_charging_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_set_charge_brightness(dev, attr, buf, count, CHARGING_LED); +} + +/** + * Read device file "fast_charging_led_brightness" + */ +static ssize_t razer_attr_read_fast_charging_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_set_charge_brightness(dev, attr, buf, FAST_CHARGING_LED); +} + +/** + * Write device file "fast_charging_led_brightness" + */ +static ssize_t razer_attr_write_fast_charging_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_set_charge_brightness(dev, attr, buf, count, FAST_CHARGING_LED); +} + +/** + * Read device file "fully_charged_led_brightness" + */ +static ssize_t razer_attr_read_fully_charged_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_set_charge_brightness(dev, attr, buf, FULLY_CHARGED_LED); +} + +/** + * Write device file "fully_charged_led_brightness" + */ +static ssize_t razer_attr_write_fully_charged_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_set_charge_brightness(dev, attr, buf, count, FULLY_CHARGED_LED); +} + +/** + * Write device file "mode_spectrum" + * + * Spectrum effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_charge_mode_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, int led) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + request = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, led); + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "charging_mode_spectrum" + */ +static ssize_t razer_attr_write_charging_matrix_effect_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_charge_mode_spectrum(dev, attr, buf, count, CHARGING_LED); +} + +/** + * Write device file "fast_charging_mode_spectrum" + */ +static ssize_t razer_attr_write_fast_charging_matrix_effect_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_charge_mode_spectrum(dev, attr, buf, count, FAST_CHARGING_LED); +} + +/** + * Write device file "fully_charged_mode_spectrum" + */ +static ssize_t razer_attr_write_fully_charged_matrix_effect_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_charge_mode_spectrum(dev, attr, buf, count, FULLY_CHARGED_LED); +} + +static ssize_t razer_attr_write_matrix_effect_none_common(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, int led) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + request = razer_chroma_extended_matrix_effect_none(VARSTORE, led); + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "charging_mode_none" + */ +static ssize_t razer_attr_write_charging_matrix_effect_none(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_none_common(dev, attr, buf, count, CHARGING_LED); +} + +/** + * Write device file "fast_charging_mode_none" + */ +static ssize_t razer_attr_write_fast_charging_matrix_effect_none(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_none_common(dev, attr, buf, count, FAST_CHARGING_LED); +} + +/** + * Write device file "fully_charged_mode_none" + */ +static ssize_t razer_attr_write_fully_charged_matrix_effect_none(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_none_common(dev, attr, buf, count, FULLY_CHARGED_LED); +} + +static ssize_t razer_attr_write_matrix_effect_static_common(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, int led) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count != 3) { + printk(KERN_WARNING "razeraccessory: Static mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + request = razer_chroma_extended_matrix_effect_static(VARSTORE, led, (struct razer_rgb*) & buf[0]); + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "charging_mode_static" + */ +static ssize_t razer_attr_write_charging_matrix_effect_static(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_static_common(dev, attr, buf, count, CHARGING_LED); +} + +/** + * Write device file "fast_charging_mode_static" + */ +static ssize_t razer_attr_write_fast_charging_matrix_effect_static(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_static_common(dev, attr, buf, count, FAST_CHARGING_LED); +} + +/** + * Write device file "fully_charged_mode_static" + */ +static ssize_t razer_attr_write_fully_charged_matrix_effect_static(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_static_common(dev, attr, buf, count, FULLY_CHARGED_LED); +} + +static ssize_t razer_attr_write_matrix_effect_wave_common(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, int led) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + unsigned char direction = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + // Direction values are flipped compared to other devices + direction ^= ((1<<0) | (1<<1)); + request = razer_chroma_extended_matrix_effect_wave(VARSTORE, led, direction); + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "charging_mode_wave" + */ +static ssize_t razer_attr_write_charging_matrix_effect_wave(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_wave_common(dev, attr, buf, count, CHARGING_LED); +} + +/** + * Write device file "fast_charging_mode_wave" + */ +static ssize_t razer_attr_write_fast_charging_matrix_effect_wave(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_wave_common(dev, attr, buf, count, FAST_CHARGING_LED); +} + +/** + * Write device file "fully_charged_mode_wave" + */ +static ssize_t razer_attr_write_fully_charged_matrix_effect_wave(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_wave_common(dev, attr, buf, count, FULLY_CHARGED_LED); +} + +static ssize_t razer_attr_write_matrix_effect_breath_common(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, int led) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_extended_matrix_effect_breathing_single(VARSTORE, led, (struct razer_rgb *)&buf[0]); + break; + + case 6: // Dual colour mode + request = razer_chroma_extended_matrix_effect_breathing_dual(VARSTORE, led, (struct razer_rgb *)&buf[0], (struct razer_rgb *)&buf[3]); + break; + + default: // "Random" colour mode + request = razer_chroma_extended_matrix_effect_breathing_random(VARSTORE, led); + break; + } + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razeraccessory: Unknown device\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "charging_mode_breath" + */ +static ssize_t razer_attr_write_charging_matrix_effect_breath(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_breath_common(dev, attr, buf, count, CHARGING_LED); +} + +/** + * Write device file "fast_charging_mode_breath" + */ +static ssize_t razer_attr_write_fast_charging_matrix_effect_breath(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_breath_common(dev, attr, buf, count, FAST_CHARGING_LED); +} + +/** + * Write device file "fully_charged_mode_breath" + */ +static ssize_t razer_attr_write_fully_charged_matrix_effect_breath(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_breath_common(dev, attr, buf, count, FULLY_CHARGED_LED); +} + +/** + * Sets the brightness to the ASCII number + */ +static ssize_t razer_attr_write_channel_led_brightness(unsigned char led, struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + unsigned char brightness = 0; + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count < 1) { + printk(KERN_WARNING "razeraccessory: Brightness takes an ascii number\n"); + return -EINVAL; + } + + brightness = (unsigned char)simple_strtoul(buf, NULL, 10); + + request = razer_chroma_extended_matrix_brightness(VARSTORE, led, brightness); + request.transaction_id.id = 0x3F; + + razer_send_payload(device, &request, &response); + + return count; +} + +static ssize_t razer_attr_write_channel1_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_channel_led_brightness(ARGB_CH_1_LED, dev, attr, buf, count); +} + +static ssize_t razer_attr_write_channel2_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_channel_led_brightness(ARGB_CH_2_LED, dev, attr, buf, count); +} + +static ssize_t razer_attr_write_channel3_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_channel_led_brightness(ARGB_CH_3_LED, dev, attr, buf, count); +} + +static ssize_t razer_attr_write_channel4_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_channel_led_brightness(ARGB_CH_4_LED, dev, attr, buf, count); +} + +static ssize_t razer_attr_write_channel5_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_channel_led_brightness(ARGB_CH_5_LED, dev, attr, buf, count); +} + +static ssize_t razer_attr_write_channel6_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_channel_led_brightness(ARGB_CH_6_LED, dev, attr, buf, count); +} + +/** + * Read device file "channelX_size" + */ +static ssize_t razer_attr_read_channel_size(unsigned int channel, struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = get_razer_report(0x0f, 0x88, 0x0d); + request.transaction_id.id = 0x1F; + request.arguments[0] = 0x06; + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[channel * 2]); +} + +static ssize_t razer_attr_read_channel1_size(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_channel_size(1, dev, attr, buf); +} + +static ssize_t razer_attr_read_channel2_size(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_channel_size(2, dev, attr, buf); +} + +static ssize_t razer_attr_read_channel3_size(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_channel_size(3, dev, attr, buf); +} + +static ssize_t razer_attr_read_channel4_size(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_channel_size(4, dev, attr, buf); +} + +static ssize_t razer_attr_read_channel5_size(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_channel_size(5, dev, attr, buf); +} + +static ssize_t razer_attr_read_channel6_size(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_channel_size(6, dev, attr, buf); +} + +/** + * Write device file "channelX_size" + */ +static ssize_t razer_attr_write_channel_size(unsigned int channel, struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned char sz; + struct razer_report request = {0}; + struct razer_report response = {0}; + struct razer_accessory_device *device; + + if (count < 1) { + printk(KERN_WARNING "razeraccessory: Size takes an ascii number\n"); + return -EINVAL; + } + + device = dev_get_drvdata(dev); + + /* Get existing sizes */ + request = get_razer_report(0x0f, 0x88, 0x0d); + request.transaction_id.id = 0x1F; + request.arguments[0] = 0x06; + + razer_send_payload(device, &request, &response); + + /* Set new sizes */ + sz = (unsigned char)simple_strtoul(buf, NULL, 10); + + request = get_razer_report(0x0f, 0x08, 0x0d); + request.transaction_id.id = 0xFF; + request.arguments[0] = 0x06; + request.arguments[1] = 0x01; + request.arguments[2] = channel == 1 ? sz : response.arguments[2]; + request.arguments[3] = 0x02; + request.arguments[4] = channel == 2 ? sz : response.arguments[4]; + request.arguments[5] = 0x03; + request.arguments[6] = channel == 3 ? sz : response.arguments[6]; + request.arguments[7] = 0x04; + request.arguments[8] = channel == 4 ? sz : response.arguments[8]; + request.arguments[9] = 0x05; + request.arguments[10] = channel == 5 ? sz : response.arguments[10]; + request.arguments[11] = 0x06; + request.arguments[12] = channel == 6 ? sz : response.arguments[12]; + + razer_send_payload(device, &request, &response); + + return count; +} + +static ssize_t razer_attr_write_channel1_size(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_channel_size(1, dev, attr, buf, count); +} + +static ssize_t razer_attr_write_channel2_size(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_channel_size(2, dev, attr, buf, count); +} + +static ssize_t razer_attr_write_channel3_size(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_channel_size(3, dev, attr, buf, count); +} + +static ssize_t razer_attr_write_channel4_size(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_channel_size(4, dev, attr, buf, count); +} + +static ssize_t razer_attr_write_channel5_size(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_channel_size(5, dev, attr, buf, count); +} + +static ssize_t razer_attr_write_channel6_size(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_channel_size(6, dev, attr, buf, count); +} + +static ssize_t razer_attr_write_reset_channels(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + + /* Get existing sizes */ + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned int i; + + for (i = 0; i < 6; i++) { + request = get_razer_report(0x0f, 0x04, 0x03); + request.transaction_id.id = 0x1F; + request.arguments[0] = 0x01; + request.arguments[1] = ARGB_CH_1_LED + i; + request.arguments[2] = 0xff; + + razer_send_payload(device, &request, &response); + } + + request = get_razer_report(0x00, 0xb7, 0x01); + request.transaction_id.id = 0x1F; + request.arguments[0] = 0x00; + razer_send_payload(device, &request, &response); + + request = get_razer_report(0x00, 0x36, 0x01); + request.transaction_id.id = 0x1F; + request.arguments[0] = 0x01; + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "channelX_led_brightness" + * + * Returns brightness or -1 if the initial brightness is not known + */ +static ssize_t razer_attr_read_channel_led_brightness(unsigned char led, struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_accessory_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned char brightness = 0; + + request = razer_chroma_extended_matrix_get_brightness(VARSTORE, led); + request.transaction_id.id = 0x3F; + + razer_send_payload(device, &request, &response); + brightness = response.arguments[2]; + + return sprintf(buf, "%d\n", brightness); +} + +static ssize_t razer_attr_read_channel1_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_channel_led_brightness(ARGB_CH_1_LED, dev, attr, buf); +} + +static ssize_t razer_attr_read_channel2_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_channel_led_brightness(ARGB_CH_2_LED, dev, attr, buf); +} + +static ssize_t razer_attr_read_channel3_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_channel_led_brightness(ARGB_CH_3_LED, dev, attr, buf); +} + +static ssize_t razer_attr_read_channel4_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_channel_led_brightness(ARGB_CH_4_LED, dev, attr, buf); +} + +static ssize_t razer_attr_read_channel5_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_channel_led_brightness(ARGB_CH_5_LED, dev, attr, buf); +} + +static ssize_t razer_attr_read_channel6_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_channel_led_brightness(ARGB_CH_6_LED, dev, attr, buf); +} + +/** + * Set up the device driver files + + * + * Read only is 0444 + * Write only is 0220 + * Read and write is 0664 + */ + +static DEVICE_ATTR(test, 0660, razer_attr_read_test, razer_attr_write_test); +static DEVICE_ATTR(version, 0440, razer_attr_read_version, NULL); +static DEVICE_ATTR(device_type, 0440, razer_attr_read_device_type, NULL); +static DEVICE_ATTR(device_mode, 0660, razer_attr_read_device_mode, razer_attr_write_device_mode); +static DEVICE_ATTR(device_serial, 0440, razer_attr_read_device_serial, NULL); +static DEVICE_ATTR(firmware_version, 0440, razer_attr_read_firmware_version, NULL); + +static DEVICE_ATTR(matrix_effect_none, 0220, NULL, razer_attr_write_matrix_effect_none); +static DEVICE_ATTR(matrix_effect_spectrum, 0220, NULL, razer_attr_write_matrix_effect_spectrum); +static DEVICE_ATTR(matrix_effect_static, 0220, NULL, razer_attr_write_matrix_effect_static); +static DEVICE_ATTR(matrix_effect_reactive, 0220, NULL, razer_attr_write_matrix_effect_reactive); +static DEVICE_ATTR(matrix_effect_breath, 0220, NULL, razer_attr_write_matrix_effect_breath); +static DEVICE_ATTR(matrix_effect_custom, 0220, NULL, razer_attr_write_matrix_effect_custom); +static DEVICE_ATTR(matrix_effect_wave, 0220, NULL, razer_attr_write_matrix_effect_wave); +static DEVICE_ATTR(matrix_effect_blinking, 0220, NULL, razer_attr_write_matrix_effect_blinking); +static DEVICE_ATTR(matrix_effect_starlight, 0220, NULL, razer_attr_write_matrix_effect_starlight); +static DEVICE_ATTR(matrix_brightness, 0660, razer_attr_read_matrix_brightness, razer_attr_write_matrix_brightness); +static DEVICE_ATTR(matrix_custom_frame, 0220, NULL, razer_attr_write_matrix_custom_frame); +static DEVICE_ATTR(matrix_reactive_trigger, 0220, NULL, razer_attr_write_matrix_reactive_trigger); + +static DEVICE_ATTR(charging_led_brightness, 0660, razer_attr_read_charging_led_brightness, razer_attr_write_charging_led_brightness); +static DEVICE_ATTR(charging_matrix_effect_wave, 0220, NULL, razer_attr_write_charging_matrix_effect_wave); +static DEVICE_ATTR(charging_matrix_effect_spectrum, 0220, NULL, razer_attr_write_charging_matrix_effect_spectrum); +static DEVICE_ATTR(charging_matrix_effect_breath, 0220, NULL, razer_attr_write_charging_matrix_effect_breath); +static DEVICE_ATTR(charging_matrix_effect_static, 0220, NULL, razer_attr_write_charging_matrix_effect_static); +static DEVICE_ATTR(charging_matrix_effect_none, 0220, NULL, razer_attr_write_charging_matrix_effect_none); + +static DEVICE_ATTR(fast_charging_led_brightness, 0660, razer_attr_read_fast_charging_led_brightness, razer_attr_write_fast_charging_led_brightness); +static DEVICE_ATTR(fast_charging_matrix_effect_wave, 0220, NULL, razer_attr_write_fast_charging_matrix_effect_wave); +static DEVICE_ATTR(fast_charging_matrix_effect_spectrum, 0220, NULL, razer_attr_write_fast_charging_matrix_effect_spectrum); +static DEVICE_ATTR(fast_charging_matrix_effect_breath, 0220, NULL, razer_attr_write_fast_charging_matrix_effect_breath); +static DEVICE_ATTR(fast_charging_matrix_effect_static, 0220, NULL, razer_attr_write_fast_charging_matrix_effect_static); +static DEVICE_ATTR(fast_charging_matrix_effect_none, 0220, NULL, razer_attr_write_fast_charging_matrix_effect_none); + +static DEVICE_ATTR(fully_charged_led_brightness, 0660, razer_attr_read_fully_charged_led_brightness, razer_attr_write_fully_charged_led_brightness); +static DEVICE_ATTR(fully_charged_matrix_effect_wave, 0220, NULL, razer_attr_write_fully_charged_matrix_effect_wave); +static DEVICE_ATTR(fully_charged_matrix_effect_spectrum, 0220, NULL, razer_attr_write_fully_charged_matrix_effect_spectrum); +static DEVICE_ATTR(fully_charged_matrix_effect_breath, 0220, NULL, razer_attr_write_fully_charged_matrix_effect_breath); +static DEVICE_ATTR(fully_charged_matrix_effect_static, 0220, NULL, razer_attr_write_fully_charged_matrix_effect_static); +static DEVICE_ATTR(fully_charged_matrix_effect_none, 0220, NULL, razer_attr_write_fully_charged_matrix_effect_none); + +static DEVICE_ATTR(reset_channels, 0220, NULL, razer_attr_write_reset_channels); +static DEVICE_ATTR(channel1_size, 0660, razer_attr_read_channel1_size, razer_attr_write_channel1_size); +static DEVICE_ATTR(channel2_size, 0660, razer_attr_read_channel2_size, razer_attr_write_channel2_size); +static DEVICE_ATTR(channel3_size, 0660, razer_attr_read_channel3_size, razer_attr_write_channel3_size); +static DEVICE_ATTR(channel4_size, 0660, razer_attr_read_channel4_size, razer_attr_write_channel4_size); +static DEVICE_ATTR(channel5_size, 0660, razer_attr_read_channel5_size, razer_attr_write_channel5_size); +static DEVICE_ATTR(channel6_size, 0660, razer_attr_read_channel6_size, razer_attr_write_channel6_size); +static DEVICE_ATTR(channel1_led_brightness, 0660, razer_attr_read_channel1_led_brightness, razer_attr_write_channel1_led_brightness); +static DEVICE_ATTR(channel2_led_brightness, 0660, razer_attr_read_channel2_led_brightness, razer_attr_write_channel2_led_brightness); +static DEVICE_ATTR(channel3_led_brightness, 0660, razer_attr_read_channel3_led_brightness, razer_attr_write_channel3_led_brightness); +static DEVICE_ATTR(channel4_led_brightness, 0660, razer_attr_read_channel4_led_brightness, razer_attr_write_channel4_led_brightness); +static DEVICE_ATTR(channel5_led_brightness, 0660, razer_attr_read_channel5_led_brightness, razer_attr_write_channel5_led_brightness); +static DEVICE_ATTR(channel6_led_brightness, 0660, razer_attr_read_channel6_led_brightness, razer_attr_write_channel6_led_brightness); + +static DEVICE_ATTR(is_mug_present, 0440, razer_attr_read_is_mug_present, NULL); + +static void razer_accessory_init(struct razer_accessory_device *dev, struct usb_interface *intf, struct hid_device *hdev) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + unsigned int rand_serial = 0; + + // Initialise mutex + mutex_init(&dev->lock); + // Setup values + dev->usb_dev = usb_dev; + dev->usb_vid = usb_dev->descriptor.idVendor; + dev->usb_pid = usb_dev->descriptor.idProduct; + dev->usb_interface_protocol = intf->cur_altsetting->desc.bInterfaceProtocol; + + // Get a "random" integer + get_random_bytes(&rand_serial, sizeof(unsigned int)); + sprintf(&dev->serial[0], "MUG%012u", rand_serial); +} + +/** + * Say that we want to allow EV_KEY events and that we want to allow KEY_PROG1 events specifically + */ +static int razer_setup_input(struct input_dev *input, struct hid_device *hdev) +{ + __set_bit(EV_KEY, input->evbit); + __set_bit(KEY_PROG1, input->keybit); + + return 0; +} + +/** + * Setup the input device now that its been added to our struct + */ +static int razer_input_configured(struct hid_device *hdev, struct hid_input *hi) +{ + struct razer_accessory_device *device = hid_get_drvdata(hdev); + int ret; + + ret = razer_setup_input(device->input, hdev); + if (ret) { + hid_err(hdev, "magicmouse setup input failed (%d)\n", ret); + /* clean msc->input to notify probe() of the failure */ + device->input = NULL; + return ret; + } + + return 0; +} + +/** + * Basically map a hid input to our structure + */ +static int razer_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) +{ + struct razer_accessory_device *device = hid_get_drvdata(hdev); + + if (!device->input) + device->input = hi->input; + + return 0; +} + +/** + * Match method checks whether this driver should be used for a given HID device + */ +static bool razer_accessory_match(struct hid_device *hdev, bool ignore_special_driver) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + + switch (usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + if (intf->cur_altsetting->desc.bInterfaceNumber != 0) { + dev_info(&intf->dev, "skipping secondary interface\n"); + return false; + } + } + + return true; +} + +/** + * Probe method is ran whenever a device is binded to the driver + */ +static int razer_accessory_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int retval = 0; + unsigned char expected_protocol = USB_INTERFACE_PROTOCOL_MOUSE; + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct razer_accessory_device *dev = NULL; + + dev = kzalloc(sizeof(struct razer_accessory_device), GFP_KERNEL); + if(dev == NULL) { + dev_err(&intf->dev, "out of memory\n"); + return -ENOMEM; + } + + // Init data + razer_accessory_init(dev, intf, hdev); + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + expected_protocol = 0; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + expected_protocol = USB_INTERFACE_PROTOCOL_MOUSE; + break; + + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + expected_protocol = USB_INTERFACE_PROTOCOL_KEYBOARD; + break; + } + + if(dev->usb_interface_protocol == expected_protocol) { + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_version); // Get driver version + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_test); // Test mode + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_type); // Get string of device type + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_mode); // Get string of device mode + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_serial); // Get string of device serial + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_firmware_version); // Get string of device fw version + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Custom effect frame + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_brightness); // Brightness + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + // Razer has also added a "Fast Wave" effect for at least this device + // which uses the same effect command but a speed parameter of 0x10. + // It has not been implemented. + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charging_led_brightness); // Charging effects + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charging_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charging_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charging_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charging_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charging_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fast_charging_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fast_charging_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fast_charging_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fast_charging_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fast_charging_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fast_charging_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fully_charged_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fully_charged_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fully_charged_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fully_charged_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fully_charged_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fully_charged_matrix_effect_none); + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + // Device initial brightness is always 100% anyway + dev->saved_brightness = 0xFF; + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_reactive_trigger); // Reactive trigger + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_is_mug_present); // Is cup present + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_blinking); // Blinking effect + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_reset_channels); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_channel1_size); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_channel2_size); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_channel3_size); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_channel4_size); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_channel5_size); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_channel6_size); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_channel1_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_channel2_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_channel3_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_channel4_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_channel5_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_channel6_led_brightness); + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + // Needs to be in "Normal" mode for idle effects to function properly + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + break; + + default: + // Needs to be in "Driver" mode just to function + razer_set_device_mode(dev, 0x03, 0x00); + break; + } + } + + hid_set_drvdata(hdev, dev); + dev_set_drvdata(&hdev->dev, dev); + + if(hid_parse(hdev)) { + hid_err(hdev, "parse failed\n"); + goto exit_free; + } + + if (hid_hw_start(hdev, HID_CONNECT_DEFAULT)) { + hid_err(hdev, "hw start failed\n"); + goto exit_free; + } + + usb_disable_autosuspend(usb_dev); + + return 0; + +exit_free: + kfree(dev); + return retval; +} + +/** + * Unbind function + */ +static void razer_accessory_disconnect(struct hid_device *hdev) +{ + unsigned char expected_protocol = USB_INTERFACE_PROTOCOL_MOUSE; + struct razer_accessory_device *dev; + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + + dev = hid_get_drvdata(hdev); + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + expected_protocol = 0; + break; + + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + expected_protocol = USB_INTERFACE_PROTOCOL_MOUSE; + break; + + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + expected_protocol = USB_INTERFACE_PROTOCOL_KEYBOARD; + break; + } + + if(dev->usb_interface_protocol == expected_protocol) { + device_remove_file(&hdev->dev, &dev_attr_version); // Get driver version + device_remove_file(&hdev->dev, &dev_attr_test); // Test mode + device_remove_file(&hdev->dev, &dev_attr_device_type); // Get string of device type + device_remove_file(&hdev->dev, &dev_attr_device_mode); // Get string of device mode + device_remove_file(&hdev->dev, &dev_attr_device_serial); // Get string of device serial + device_remove_file(&hdev->dev, &dev_attr_firmware_version); // Get string of device fw version + + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Custom effect frame + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_brightness); // Brightness + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + device_remove_file(&hdev->dev, &dev_attr_charging_led_brightness); // Charging effects + device_remove_file(&hdev->dev, &dev_attr_charging_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_charging_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_charging_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_charging_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_charging_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_fast_charging_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_fast_charging_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_fast_charging_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_fast_charging_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_fast_charging_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_fast_charging_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_fully_charged_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_fully_charged_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_fully_charged_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_fully_charged_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_fully_charged_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_fully_charged_matrix_effect_none); + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_MOUSE_DOCK: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + case USB_DEVICE_ID_RAZER_CHROMA_HDK: + case USB_DEVICE_ID_RAZER_CHROMA_BASE: + case USB_DEVICE_ID_RAZER_NOMMO_PRO: + case USB_DEVICE_ID_RAZER_NOMMO_CHROMA: + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + case USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA: + case USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA: + case USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA: + case USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA: + case USB_DEVICE_ID_RAZER_RAPTOR_27: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX: + case USB_DEVICE_ID_RAZER_FIREFLY_V2: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED: + case USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL: + case USB_DEVICE_ID_RAZER_FIREFLY: + case USB_DEVICE_ID_RAZER_CORE: + case USB_DEVICE_ID_RAZER_CORE_X_CHROMA: + case USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive + device_remove_file(&hdev->dev, &dev_attr_matrix_reactive_trigger); // Reactive trigger + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHROMA_MUG: + device_remove_file(&hdev->dev, &dev_attr_is_mug_present); // Is cup present + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_blinking); // Blinking effect + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); + break; + } + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER: + device_remove_file(&hdev->dev, &dev_attr_reset_channels); + device_remove_file(&hdev->dev, &dev_attr_channel1_size); + device_remove_file(&hdev->dev, &dev_attr_channel2_size); + device_remove_file(&hdev->dev, &dev_attr_channel3_size); + device_remove_file(&hdev->dev, &dev_attr_channel4_size); + device_remove_file(&hdev->dev, &dev_attr_channel5_size); + device_remove_file(&hdev->dev, &dev_attr_channel6_size); + device_remove_file(&hdev->dev, &dev_attr_channel1_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_channel2_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_channel3_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_channel4_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_channel5_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_channel6_led_brightness); + break; + } + } + + hid_hw_stop(hdev); + + kfree(dev); + dev_info(&intf->dev, "Razer Device disconnected\n"); +} + +/** + * Converts interrupt event into PROG1 keypress + * + * Checks if we get the event were after. + * Creates a keypress event of KEY_PROG1 + * + * input_report_key generates an event + * input_sync says were finished, all events are complete. Is useful when setting up other events as they might take multiple statements to complete an event like relative events + * + * data[1] == 0xa0 if mug is present + */ +static int razer_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) +{ + struct razer_accessory_device *device = hid_get_drvdata(hdev); + + if(size == 16 && data[0] == 0x04) { + input_report_key(device->input, KEY_PROG1, 0x01); + input_report_key(device->input, KEY_PROG1, 0x00); + input_sync(device->input); + return 1; + } + + return 0; +} + +/** + * Device ID mapping table + */ +static const struct hid_device_id razer_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_FIREFLY) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_FIREFLY_V2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_CORE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_CORE_X_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_CHROMA_MUG) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_CHROMA_HDK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_CHROMA_BASE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NOMMO_PRO) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NOMMO_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_MOUSE_DOCK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_RAPTOR_27) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(hid, razer_devices); + +/** + * Describes the contents of the driver + */ +static struct hid_driver razer_accessory_driver = { + .name = "razeraccessory", + .id_table = razer_devices, + .match = razer_accessory_match, + .probe = razer_accessory_probe, + .remove = razer_accessory_disconnect, + .raw_event = razer_raw_event, + .input_mapping = razer_input_mapping, + .input_configured = razer_input_configured +}; + +module_hid_driver(razer_accessory_driver); diff --git a/drivers/custom/razer/driver/razeraccessory_driver.h b/drivers/custom/razer/driver/razeraccessory_driver.h new file mode 100644 index 000000000000..55d339692de7 --- /dev/null +++ b/drivers/custom/razer/driver/razeraccessory_driver.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2015 Terri Cain + */ + +#ifndef __HID_RAZER_ACCESSORY_H +#define __HID_RAZER_ACCESSORY_H + +#define USB_DEVICE_ID_RAZER_FIREFLY_HYPERFLUX 0x0068 +#define USB_DEVICE_ID_RAZER_MOUSE_DOCK 0x007E +#define USB_DEVICE_ID_RAZER_CORE 0x0215 +#define USB_DEVICE_ID_RAZER_NOMMO_CHROMA 0x0517 +#define USB_DEVICE_ID_RAZER_NOMMO_PRO 0x0518 +#define USB_DEVICE_ID_RAZER_FIREFLY 0x0C00 +#define USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA 0x0C01 +#define USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_EXTENDED 0x0C02 +#define USB_DEVICE_ID_RAZER_FIREFLY_V2 0x0C04 +#define USB_DEVICE_ID_RAZER_GOLIATHUS_CHROMA_3XL 0x0C06 +#define USB_DEVICE_ID_RAZER_CHROMA_MUG 0x0F07 +#define USB_DEVICE_ID_RAZER_CHROMA_BASE 0x0F08 +#define USB_DEVICE_ID_RAZER_CHROMA_HDK 0x0F09 +#define USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA 0x0F0D +#define USB_DEVICE_ID_RAZER_RAPTOR_27 0x0F12 +#define USB_DEVICE_ID_RAZER_KRAKEN_KITTY_EDITION 0x0F19 +#define USB_DEVICE_ID_RAZER_CORE_X_CHROMA 0x0F1A +#define USB_DEVICE_ID_RAZER_MOUSE_BUNGEE_V3_CHROMA 0x0F1D +#define USB_DEVICE_ID_RAZER_CHROMA_ADDRESSABLE_RGB_CONTROLLER 0x0F1F +#define USB_DEVICE_ID_RAZER_BASE_STATION_V2_CHROMA 0x0F20 +#define USB_DEVICE_ID_RAZER_THUNDERBOLT_4_DOCK_CHROMA 0x0F21 +#define USB_DEVICE_ID_RAZER_CHARGING_PAD_CHROMA 0x0F26 +#define USB_DEVICE_ID_RAZER_LAPTOP_STAND_CHROMA_V2 0x0F2B + +#define RAZER_ACCESSORY_WAIT_MIN_US 600 +#define RAZER_ACCESSORY_WAIT_MAX_US 1000 + +#define RAZER_NEW_DEVICE_WAIT_MIN_US 31000 +#define RAZER_NEW_DEVICE_WAIT_MAX_US 31100 + +struct razer_accessory_device { + struct usb_device *usb_dev; + struct input_dev *input; + struct mutex lock; + unsigned char usb_interface_protocol; + + unsigned short usb_vid; + unsigned short usb_pid; + + unsigned char saved_brightness; + + char serial[23]; +}; + +/* + * USB INTERRUPT + * + * */ + +#endif diff --git a/drivers/custom/razer/driver/razerchromacommon.c b/drivers/custom/razer/driver/razerchromacommon.c new file mode 100644 index 000000000000..ad8dabbf60fa --- /dev/null +++ b/drivers/custom/razer/driver/razerchromacommon.c @@ -0,0 +1,1590 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2015 Terri Cain + */ + +#include "razerchromacommon.h" + +static unsigned char orochi2011_led[] = { 0x01, 0x00, 0x00, 0x06, 0x48, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x03, 0x05, 0x06, 0x06, 0x10, 0x10, 0x10, 0x10, 0x24, 0x24, 0x4c, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x03, 0x03, 0x04, 0x01, 0x04, 0x04, 0x01, 0x01, 0x05, 0x05, 0x01, 0x01, 0x06, 0x31, 0x88, 0x00, 0x07, 0x31, 0x87, 0x00, 0x08, 0x08, 0x01, 0x01, 0x09, 0x09, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x01 }; +static unsigned char orochi2011_dpi[] = { 0x01, 0x00, 0x00, 0x05, 0x05, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x4c, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; + +/* + * Standard Device Functions + */ + +/** + * Set what mode the device will operate in. + * + * Currently known modes + * 0x00, 0x00: Normal Mode + * 0x02, 0x00: Unknown Mode + * 0x03, 0x00: Driver Mode + * + * 0x02, 0x00 Will make M1-5 and FN emit normal keystrokes. Some sort of factory test mode. Not recommended to be used. + */ +struct razer_report razer_chroma_standard_set_device_mode(unsigned char mode, unsigned char param) +{ + struct razer_report report = get_razer_report(0x00, 0x04, 0x02); + + if(mode != 0x00 && mode != 0x03) { // Explicitly blocking the 0x02 mode + mode = 0x00; + } + // Only allow 0x00 as param + param = 0x00; + + report.arguments[0] = mode; + report.arguments[1] = param; + + return report; +} + +/** + * Get what mode the device is operating in. + * + * Currently known modes + * 0x00, 0x00: Normal Mode + * 0x02, 0x00: Unknown Mode + * 0x03, 0x00: Driver Mode + * + * 0x02, 0x00 Will make M1-5 and FN emit normal keystrokes. Some sort of factory test mode. Not recommended to be used. + */ +struct razer_report razer_chroma_standard_get_device_mode(void) +{ + return get_razer_report(0x00, 0x84, 0x02); +} + +/** + * Get serial from device + */ +struct razer_report razer_chroma_standard_get_serial(void) +{ + return get_razer_report(0x00, 0x82, 0x16); +} + +/** + * Get firmware version from device + */ +struct razer_report razer_chroma_standard_get_firmware_version(void) +{ + return get_razer_report(0x00, 0x81, 0x02); +} + +/* + * Standard Functions + */ + +/** + * Set the state of an LED on the device + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 03 03 00 010801 | SET LED STATE (VARSTR, GAMEMODE, ON) + * 00 3f 0000 00 03 03 00 010800 | SET LED STATE (VARSTR, GAMEMODE, OFF) + */ +struct razer_report razer_chroma_standard_set_led_state(unsigned char variable_storage, unsigned char led_id, unsigned char led_state) +{ + struct razer_report report = get_razer_report(0x03, 0x00, 0x03); + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + report.arguments[2] = clamp_u8(led_state, 0x00, 0x01); + + return report; +} + +struct razer_report razer_chroma_standard_set_led_blinking(unsigned char variable_storage, unsigned char led_id) +{ + struct razer_report report = get_razer_report(0x03, 0x04, 0x04); + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + report.arguments[2] = 0x05; + report.arguments[3] = 0x05; + + return report; +} + +/** + * Get the state of an LED on the device + */ +struct razer_report razer_chroma_standard_get_led_state(unsigned char variable_storage, unsigned char led_id) +{ + struct razer_report report = get_razer_report(0x03, 0x80, 0x03); + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + + return report; +} + +/** + * Set LED RGB parameters + */ +struct razer_report razer_chroma_standard_set_led_rgb(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb1) +{ + struct razer_report report = get_razer_report(0x03, 0x01, 0x05); + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + report.arguments[2] = rgb1->r; + report.arguments[3] = rgb1->g; + report.arguments[4] = rgb1->b; + + return report; +} + +/** + * Get LED RGB parameters + */ +struct razer_report razer_chroma_standard_get_led_rgb(unsigned char variable_storage, unsigned char led_id) +{ + struct razer_report report = get_razer_report(0x03, 0x81, 0x05); + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + return report; +} + +/** + * Set the effect of an LED on the device + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_standard_set_led_effect(unsigned char variable_storage, unsigned char led_id, unsigned char led_effect) +{ + struct razer_report report = get_razer_report(0x03, 0x02, 0x03); + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + report.arguments[2] = clamp_u8(led_effect, 0x00, 0x05); + + return report; +} + +/** + * Get the effect of an LED on the device + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_standard_get_led_effect(unsigned char variable_storage, unsigned char led_id) +{ + struct razer_report report = get_razer_report(0x03, 0x82, 0x03); + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + + return report; +} + +/** + * Set the brightness of an LED on the device + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_standard_set_led_brightness(unsigned char variable_storage, unsigned char led_id, unsigned char brightness) +{ + struct razer_report report = get_razer_report(0x03, 0x03, 0x03); + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + report.arguments[2] = brightness; + + return report; +} + +/** + * Get the brightness of an LED on the device + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_standard_get_led_brightness(unsigned char variable_storage, unsigned char led_id) +{ + struct razer_report report = get_razer_report(0x03, 0x83, 0x03); + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + + return report; +} + +/* + * Standard Matrix Effects Functions + */ + +static struct razer_report razer_chroma_standard_matrix_effect_base(unsigned char arg_size, unsigned char effect_id) +{ + struct razer_report report = get_razer_report(0x03, 0x0A, arg_size); + report.arguments[0] = effect_id; + return report; +} + +/** + * Set the effect of the LED matrix to None + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_standard_matrix_effect_none(void) +{ + return razer_chroma_standard_matrix_effect_base(0x01, MATRIX_EFFECT_OFF); +} + +/** + * Set the effect of the LED matrix to Wave + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_standard_matrix_effect_wave(unsigned char wave_direction) +{ + struct razer_report report = razer_chroma_standard_matrix_effect_base(0x02, MATRIX_EFFECT_WAVE); + report.arguments[1] = clamp_u8(wave_direction, 0x01, 0x02); + + return report; +} + +/** + * Set the effect of the LED matrix to Spectrum + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_standard_matrix_effect_spectrum(void) +{ + return razer_chroma_standard_matrix_effect_base(0x01, MATRIX_EFFECT_SPECTRUM); +} + +/** + * Set the effect of the LED matrix to Reactive + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_standard_matrix_effect_reactive(unsigned char speed, struct razer_rgb *rgb1) +{ + struct razer_report report = razer_chroma_standard_matrix_effect_base(0x05, MATRIX_EFFECT_REACTIVE); + report.arguments[1] = clamp_u8(speed, 0x01, 0x04); // Time + report.arguments[2] = rgb1->r; /*rgb color definition*/ + report.arguments[3] = rgb1->g; + report.arguments[4] = rgb1->b; + + return report; +} + +/** + * Set the effect of the LED matrix to Static + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_standard_matrix_effect_static(struct razer_rgb *rgb1) +{ + struct razer_report report = razer_chroma_standard_matrix_effect_base(0x04, MATRIX_EFFECT_STATIC); + report.arguments[1] = rgb1->r; /*rgb color definition*/ + report.arguments[2] = rgb1->g; + report.arguments[3] = rgb1->b; + + return report; +} + +/** + * Set the effect of the LED matrix to Starlight + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_standard_matrix_effect_starlight_single(unsigned char speed, struct razer_rgb *rgb1) +{ + struct razer_report report = razer_chroma_standard_matrix_effect_base(0x01, MATRIX_EFFECT_STARLIGHT); + + report.arguments[1] = 0x01; // Type one color + report.arguments[2] = clamp_u8(speed, 0x01, 0x03); // Speed + + report.arguments[3] = rgb1->r; // Red 1 + report.arguments[4] = rgb1->g; // Green 1 + report.arguments[5] = rgb1->b; // Blue 1 + + // For now haven't seen any chroma using this, seen the extended version + report.arguments[6] = 0x00; // Red 2 + report.arguments[7] = 0x00; // Green 2 + report.arguments[8] = 0x00; // Blue 2 + + return report; +} + +/** + * Set the effect of the LED matrix to Starlight + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_standard_matrix_effect_starlight_dual(unsigned char speed, struct razer_rgb *rgb1, struct razer_rgb *rgb2) +{ + struct razer_report report = razer_chroma_standard_matrix_effect_base(0x01, MATRIX_EFFECT_STARLIGHT); + + report.arguments[1] = 0x02; // Type two color + report.arguments[2] = clamp_u8(speed, 0x01, 0x03); // Speed + + report.arguments[3] = rgb1->r; // Red 1 + report.arguments[4] = rgb1->g; // Green 1 + report.arguments[5] = rgb1->b; // Blue 1 + + report.arguments[6] = rgb2->r; // Red 2 + report.arguments[7] = rgb2->g; // Green 2 + report.arguments[8] = rgb2->b; // Blue 2 + + return report; +} + +struct razer_report razer_chroma_standard_matrix_effect_starlight_random(unsigned char speed) +{ + struct razer_report report = razer_chroma_standard_matrix_effect_base(0x01, MATRIX_EFFECT_STARLIGHT); + + report.arguments[1] = 0x03; // Type random color + report.arguments[2] = clamp_u8(speed, 0x01, 0x03); // Speed + + return report; +} + +/** + * Set the device to "Breathing" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * ?? + * ?? + * ?? + */ +struct razer_report razer_chroma_standard_matrix_effect_breathing_random(void) +{ + struct razer_report report = razer_chroma_standard_matrix_effect_base(0x08, MATRIX_EFFECT_BREATHING); + report.arguments[1] = 0x03; // Breathing type + + return report; +} +struct razer_report razer_chroma_standard_matrix_effect_breathing_single(struct razer_rgb *rgb1) +{ + struct razer_report report = razer_chroma_standard_matrix_effect_base(0x08, MATRIX_EFFECT_BREATHING); + report.arguments[1] = 0x01; // Breathing type + report.arguments[2] = rgb1->r; + report.arguments[3] = rgb1->g; + report.arguments[4] = rgb1->b; + + return report; +} +struct razer_report razer_chroma_standard_matrix_effect_breathing_dual(struct razer_rgb *rgb1, struct razer_rgb *rgb2) +{ + struct razer_report report = razer_chroma_standard_matrix_effect_base(0x08, MATRIX_EFFECT_BREATHING); + report.arguments[1] = 0x02; // Breathing type + report.arguments[2] = rgb1->r; + report.arguments[3] = rgb1->g; + report.arguments[4] = rgb1->b; + report.arguments[5] = rgb2->r; + report.arguments[6] = rgb2->g; + report.arguments[7] = rgb2->b; + + return report; +} + +/** + * Set the device to "Custom" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * ?? + * + * Apparently Ultimate2016, Stealth and Stealth2016 need frame id to be 0x00, I don't think it's needed (depending on set_custom_frame) + */ +struct razer_report razer_chroma_standard_matrix_effect_custom_frame(unsigned char variable_storage) +{ + struct razer_report report = razer_chroma_standard_matrix_effect_base(0x02, MATRIX_EFFECT_CUSTOMFRAME); + report.arguments[1] = variable_storage; // Data frame ID + + return report; +} + +/** + * Set the RGB or a row + * + * Start and stop columns are inclusive + * + * This sets the colour of a row on the keyboard. Takes in an array of RGB bytes. + * The mappings below are correct for the BlackWidow Chroma. The BlackWidow Ultimate 2016 + * contains LEDs under the spacebar and the FN key so there will be changes once I get the + * hardware. + * + * Row 0: + * 0 Unused + * 1 ESC + * 2 Unused + * 3-14 F1-F12 + * 15-17 PrtScr, ScrLk, Pause + * 18-19 Unused + * 20 Razer Logo + * 21 Unused + * + * Row 1: + * 0-21 M1 -> NP Minus + * + * Row 2: + * 0-13 M2 -> Right Square Bracket ] + * 14 Unused + * 15-21 Delete -> NP Plus + * + * Row 3: + * 0-14 M3 -> Return + * 15-17 Unused + * 18-20 NP4 -> NP6 + * + * Row 4: + * 0-12 M4 -> Forward Slash / + * 13 Unused + * 14 Right Shift + * 15 Unused + * 16 Up Arrow Key + * 17 Unused + * 18-21 NP1 -> NP Enter + * + * Row 5: + * 0-3 M5 -> Alt + * 4-10 Unused + * 11 Alt GR + * 12 Unused + * 13-17 Context Menu Key -> Right Arrow Key + * 18 Unused + * 19-20 NP0 -> NP. + * 21 Unused + */ +struct razer_report razer_chroma_standard_matrix_set_custom_frame(unsigned char row_index, unsigned char start_col, unsigned char stop_col, unsigned char *rgb_data) +{ + const size_t start_arg_offset = 4; + struct razer_report report = {0}; + size_t row_length = (size_t) (((stop_col + 1) - start_col) * 3); + + if (row_length > sizeof(report.arguments) - start_arg_offset) { + printk(KERN_ALERT "razerchroma: RGB data too long\n"); + row_length = 0; + } + + report = get_razer_report(0x03, 0x0B, 0x46); // In theory should be able to leave data size at max as we have start/stop + + // printk(KERN_ALERT "razerkbd: Row ID: %d, Start: %d, Stop: %d, row length: %d\n", row_index, start_col, stop_col, (unsigned char)row_length); + + report.arguments[0] = 0xFF; // Frame ID + report.arguments[1] = row_index; + report.arguments[2] = start_col; + report.arguments[3] = stop_col; + memcpy(&report.arguments[4], rgb_data, row_length); + + return report; +} + +/* + * Extended Matrix Effects + */ + +/** + * Sets up the extended matrix effect payload + */ +static struct razer_report razer_chroma_extended_matrix_effect_base(unsigned char arg_size, unsigned char variable_storage, unsigned char led_id, unsigned char effect_id) +{ + struct razer_report report = get_razer_report(0x0F, 0x02, arg_size); + + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + report.arguments[2] = effect_id; // Effect ID + + return report; +} + +/** + * Set the device to "None" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 06 0f 02 010500000000 | SET LED MATRIX Effect (VARSTR, Backlight, None 0x00, 0x000000) + */ +struct razer_report razer_chroma_extended_matrix_effect_none(unsigned char variable_storage, unsigned char led_id) +{ + return razer_chroma_extended_matrix_effect_base(0x06, variable_storage, led_id, 0x00); +} + +/** + * Set the device to "Static" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 09 0f 02 010501000001ff0000 | SET LED MATRIX Effect (VARSTR, Backlight, Static 0x01, ? 0x000001, RGB 0xFF0000) + * 00 3f 0000 00 09 0f 02 01050100000100ff00 | SET LED MATRIX Effect (VARSTR, Backlight, Static 0x01, ? 0x000001, RGB 0x00FF00) + * 00 3f 0000 00 09 0f 02 010501000001008000 | SET LED MATRIX Effect (VARSTR, Backlight, Static 0x01, ? 0x000001, RGB 0x008000) + */ +struct razer_report razer_chroma_extended_matrix_effect_static(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb) +{ + struct razer_report report = razer_chroma_extended_matrix_effect_base(0x09, variable_storage, led_id, 0x01); + + report.arguments[5] = 0x01; + report.arguments[6] = rgb->r; + report.arguments[7] = rgb->g; + report.arguments[8] = rgb->b; + return report; +} + +/** + * Set the device to "Wave" effect + * + * Seems like direction is now 0x00, 0x01 for Left/Right, used to be 0x01, 0x02 + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 06 0f 02 010504002800 | SET LED MATRIX Effect (VARSTR, Backlight, Wave 0x04, Dir 0x00, ? 0x2800) + * 00 3f 0000 00 06 0f 02 010504012800 | SET LED MATRIX Effect (VARSTR, Backlight, Wave 0x04, Dir 0x01, ? 0x2800) + */ +struct razer_report razer_chroma_extended_matrix_effect_wave(unsigned char variable_storage, unsigned char led_id, unsigned char direction) +{ + struct razer_report report = razer_chroma_extended_matrix_effect_base(0x06, variable_storage, led_id, 0x04); + + // Some devices use values 0x00, 0x01 + // Others use values 0x01, 0x02 + direction = clamp_u8(direction, 0x00, 0x02); + + // Razer has also added a "Fast Wave" effect for at least one device + // which uses the same effect command but a speed parameter of 0x10 + report.arguments[3] = direction; + report.arguments[4] = 0x28; // Speed, lower values are faster + return report; +} + +/** + * Set the device to "Starlight" effect + * + * Speed is 0x01 - 0x03 + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 06 0f 02 010507000100 | SET LED MATRIX Effect (VARSTR, Backlight, Starlight 0x07, ? 0x00, Speed 0x01, Colours 0x00) + * 00 3f 0000 00 06 0f 02 010507000200 | SET LED MATRIX Effect (VARSTR, Backlight, Starlight 0x07, ? 0x00, Speed 0x02, Colours 0x00) + * 00 3f 0000 00 06 0f 02 010507000300 | SET LED MATRIX Effect (VARSTR, Backlight, Starlight 0x07, ? 0x00, Speed 0x03, Colours 0x00) + * 00 3f 0000 00 09 0f 02 010507000301ff0000 | SET LED MATRIX Effect (VARSTR, Backlight, Starlight 0x07, ? 0x00, Speed 0x03, Colours 0x01, RGB 0xFF0000) + * 00 3f 0000 00 0c 0f 02 010507000302ff000000ff00 | SET LED MATRIX Effect (VARSTR, Backlight, Starlight 0x07, ? 0x00, Speed 0x03, Colours 0x02, RGB 0xFF0000, RGB 0x00FF00) + */ +struct razer_report razer_chroma_extended_matrix_effect_starlight_random(unsigned char variable_storage, unsigned char led_id, unsigned char speed) +{ + struct razer_report report = razer_chroma_extended_matrix_effect_base(0x06, variable_storage, led_id, 0x07); + + speed = clamp_u8(speed, 0x01, 0x03); + + report.arguments[4] = speed; + return report; +} +struct razer_report razer_chroma_extended_matrix_effect_starlight_single(unsigned char variable_storage, unsigned char led_id, unsigned char speed, struct razer_rgb *rgb1) +{ + struct razer_report report = razer_chroma_extended_matrix_effect_base(0x09, variable_storage, led_id, 0x07); + + speed = clamp_u8(speed, 0x01, 0x03); + + report.arguments[4] = speed; + report.arguments[5] = 0x01; + report.arguments[6] = rgb1->r; + report.arguments[7] = rgb1->g; + report.arguments[8] = rgb1->b; + + return report; +} +struct razer_report razer_chroma_extended_matrix_effect_starlight_dual(unsigned char variable_storage, unsigned char led_id, unsigned char speed, struct razer_rgb *rgb1, struct razer_rgb *rgb2) +{ + struct razer_report report = razer_chroma_extended_matrix_effect_base(0x0C, variable_storage, led_id, 0x07); + + speed = clamp_u8(speed, 0x01, 0x03); + + report.arguments[4] = speed; + report.arguments[5] = 0x02; + report.arguments[6] = rgb1->r; + report.arguments[7] = rgb1->g; + report.arguments[8] = rgb1->b; + report.arguments[9] = rgb2->r; + report.arguments[10] = rgb2->g; + report.arguments[11] = rgb2->b; + + return report; +} + +/** + * Set the device to "Spectrum" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 06 0f 02 010503000000 | SET LED MATRIX Effect (VARSTR, Backlight, Spectrum 0x03, 0x000000) + */ +struct razer_report razer_chroma_extended_matrix_effect_spectrum(unsigned char variable_storage, unsigned char led_id) +{ + return razer_chroma_extended_matrix_effect_base(0x06, variable_storage, led_id, 0x03); +} + +/** + * Set the device to "Wheel" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 1f 0000 00 06 0f 02 01050a022800 | SET LED MATRIX Effect (VARSTR, Backlight, Wheel 0x0A, Dir 0x02, Speed 0x28, ? 0x00) + */ +struct razer_report razer_chroma_extended_matrix_effect_wheel(unsigned char variable_storage, unsigned char led_id, unsigned char direction) +{ + struct razer_report report = razer_chroma_extended_matrix_effect_base(0x06, variable_storage, led_id, 0x0a); + + // BlackWidow V4 Pro uses 0x01 and 0x02 for directions + // Commands with direction 0x00 seem to be ignored + direction = clamp_u8(direction, 0x01, 0x02); + + report.arguments[3] = direction; + report.arguments[4] = 0x28; // Speed, lower values are faster + + return report; +} + +/** + * Set the device to "Reactive" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 09 0f 02 010505000101ffff00 | SET LED MATRIX Effect (VARSTR, Backlight, Reactive 0x05, ? 0x00, Speed 0x01, Colours 0x01, RGB 0xFFFF00) + * 00 3f 0000 00 09 0f 02 010505000101ff0000 | SET LED MATRIX Effect (VARSTR, Backlight, Reactive 0x05, ? 0x00, Speed 0x02, Colours 0x01, RGB 0xFF0000) + * 00 3f 0000 00 09 0f 02 010505000301ff0000 | SET LED MATRIX Effect (VARSTR, Backlight, Reactive 0x05, ? 0x00, Speed 0x03, Colours 0x01, RGB 0xFF0000) + * 00 3f 0000 00 09 0f 02 010505000401ff0000 | SET LED MATRIX Effect (VARSTR, Backlight, Reactive 0x05, ? 0x00, Speed 0x04, Colours 0x01, RGB 0xFF0000) + */ +struct razer_report razer_chroma_extended_matrix_effect_reactive(unsigned char variable_storage, unsigned char led_id, unsigned char speed, struct razer_rgb *rgb) +{ + struct razer_report report = razer_chroma_extended_matrix_effect_base(0x09, variable_storage, led_id, 0x05); + + speed = clamp_u8(speed, 0x01, 0x04); + + report.arguments[4] = speed; + report.arguments[5] = 0x01; + report.arguments[6] = rgb->r; + report.arguments[7] = rgb->g; + report.arguments[8] = rgb->b; + + return report; +} + +/** + * Set the device to "Breathing" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 09 0f 02 01050201000100ff00 | SET LED MATRIX Effect (VARSTR, Backlight, Breathing 0x02, Colours 0x01, ? 0x00, Colours 0x01, RGB 0x00FF00) + * 00 3f 0000 00 0c 0f 02 01050202000200ff00ff0000 | SET LED MATRIX Effect (VARSTR, Backlight, Breathing 0x02, Colours 0x02, ? 0x00, Colours 0x02, RGB 0x00FF00, RGB 0xFF0000) + * 00 3f 0000 00 06 0f 02 010502000000 | SET LED MATRIX Effect (VARSTR, Backlight, Breathing 0x02, Colours 0x00, ? 0x0000) + */ +struct razer_report razer_chroma_extended_matrix_effect_breathing_random(unsigned char variable_storage, unsigned char led_id) +{ + struct razer_report report = razer_chroma_extended_matrix_effect_base(0x06, variable_storage, led_id, 0x02); + return report; +} +struct razer_report razer_chroma_extended_matrix_effect_breathing_single(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb1) +{ + struct razer_report report = razer_chroma_extended_matrix_effect_base(0x09, variable_storage, led_id, 0x02); + + report.arguments[3] = 0x01; + report.arguments[5] = 0x01; + + report.arguments[6] = rgb1->r; + report.arguments[7] = rgb1->g; + report.arguments[8] = rgb1->b; + + return report; +} +struct razer_report razer_chroma_extended_matrix_effect_breathing_dual(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb1, struct razer_rgb *rgb2) +{ + struct razer_report report = razer_chroma_extended_matrix_effect_base(0x0C, variable_storage, led_id, 0x02); + + report.arguments[3] = 0x02; + report.arguments[5] = 0x02; + + report.arguments[6] = rgb1->r; + report.arguments[7] = rgb1->g; + report.arguments[8] = rgb1->b; + report.arguments[9] = rgb2->r; + report.arguments[10] = rgb2->g; + report.arguments[11] = rgb2->b; + + return report; +} + +/** + * Set the device to "Custom" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 0c 0f 02 000008000000000000000000 | DRAW LED MATRIX Frame + */ +struct razer_report razer_chroma_extended_matrix_effect_custom_frame(void) +{ + return razer_chroma_extended_matrix_effect_base(0x0C, 0x00, 0x00, 0x08); +} + +/** + * Set the device brightness + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 03 0f 04 0104b7 + */ +struct razer_report razer_chroma_extended_matrix_brightness(unsigned char variable_storage, unsigned char led_id, unsigned char brightness) +{ + struct razer_report report = get_razer_report(0x0F, 0x04, 0x03); + + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + report.arguments[2] = brightness; + + return report; +} + +/** + * Get the device brightness + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 03 0f 84 0104 + */ +struct razer_report razer_chroma_extended_matrix_get_brightness(unsigned char variable_storage, unsigned char led_id) +{ + struct razer_report report = get_razer_report(0x0F, 0x84, 0x03); + + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + + return report; +} + +/** + * Set the RGB or a row + * + * Start and stop columns are inclusive + */ +struct razer_report razer_chroma_extended_matrix_set_custom_frame(unsigned char row_index, unsigned char start_col, unsigned char stop_col, unsigned char *rgb_data) +{ + return razer_chroma_extended_matrix_set_custom_frame2(row_index, start_col, stop_col, rgb_data, 0x47); +} + +struct razer_report razer_chroma_extended_matrix_set_custom_frame2(unsigned char row_index, unsigned char start_col, unsigned char stop_col, unsigned char *rgb_data, size_t packetLength) +{ + const size_t start_arg_offset = 5; + size_t data_length = 0; + struct razer_report report = {0}; + size_t row_length = (size_t) (((stop_col + 1) - start_col) * 3); + + if (row_length > sizeof(report.arguments) - start_arg_offset) { + printk(KERN_ALERT "razerchroma: RGB data too long\n"); + row_length = 0; + } + + // Some devices need a specific packet length, most devices are happy with 0x47 + // e.g. the Mamba Elite needs a "row_length + 5" packet length + data_length = (packetLength != 0) ? packetLength : row_length + 5; + report = get_razer_report(0x0F, 0x03, data_length); + + // printk(KERN_ALERT "razerkbd: Row ID: %d, Start: %d, Stop: %d, row length: %d\n", row_index, start_col, stop_col, (unsigned char)row_length); + + report.arguments[2] = row_index; + report.arguments[3] = start_col; + report.arguments[4] = stop_col; + memcpy(&report.arguments[5], rgb_data, row_length); + + return report; +} + +/* + * Extended Matrix Effects (Mouse) + */ +/** + * Sets up the extended matrix effect payload for mouse devices + */ +static struct razer_report razer_chroma_mouse_extended_matrix_effect_base(unsigned char arg_size, unsigned char variable_storage, unsigned char led_id, unsigned char effect_id) +{ + struct razer_report report = get_razer_report(0x03, 0x0D, arg_size); + + report.arguments[0] = variable_storage; + report.arguments[1] = led_id; + report.arguments[2] = effect_id; // Effect ID + + return report; +} + +/** + * Set the device to "None" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * * 00 3f 0000 00 03 03 0d 010100 | SET Extended Matrix Effect (VARSTORE, LOGO_LED, OFF) + */ +struct razer_report razer_chroma_mouse_extended_matrix_effect_none(unsigned char variable_storage, unsigned char led_id) +{ + return razer_chroma_mouse_extended_matrix_effect_base(0x03, variable_storage, led_id, 0x00); +} + +/** + * Set the device to "Static" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 06 03 0d 010106 00ff00 | SET Extended Matrix Effect (VARSTORE, SCROLL_WHEEL, STATIC, RGB) + */ +struct razer_report razer_chroma_mouse_extended_matrix_effect_static(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb) +{ + struct razer_report report = razer_chroma_mouse_extended_matrix_effect_base(0x06, variable_storage, led_id, 0x06); + + report.arguments[3] = rgb->r; + report.arguments[4] = rgb->g; + report.arguments[5] = rgb->b; + return report; +} + +/** + * Set the device to "Spectrum" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 03 03 0d 010104 | SET Extended Matrix Effect (VARSTORE, LOGO_LED, SPECTRUM) + */ +struct razer_report razer_chroma_mouse_extended_matrix_effect_spectrum(unsigned char variable_storage, unsigned char led_id) +{ + return razer_chroma_mouse_extended_matrix_effect_base(0x03, variable_storage, led_id, 0x04); +} + +/** + * Set the device to "Reactive" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 07 03 0d 010102 0300ff00 | SET Extended Matrix Effect (VARSTORE, SCROLL_WHEEL, REACTIVE, TIME, RGB) + * 00 3f 0000 00 07 03 0d 010102 0200ff00 | SET Extended Matrix Effect (VARSTORE, SCROLL_WHEEL, REACTIVE, TIME, RGB) + * 00 3f 0000 00 07 03 0d 010102 0100ff00 | SET Extended Matrix Effect (VARSTORE, SCROLL_WHEEL, REACTIVE, TIME, RGB) + */ +struct razer_report razer_chroma_mouse_extended_matrix_effect_reactive(unsigned char variable_storage, unsigned char led_id, unsigned char speed, struct razer_rgb *rgb) +{ + struct razer_report report = razer_chroma_mouse_extended_matrix_effect_base(0x07, variable_storage, led_id, 0x02); + + speed = clamp_u8(speed, 0x01, 0x04); + + report.arguments[3] = speed; + report.arguments[4] = rgb->r; + report.arguments[5] = rgb->g; + report.arguments[6] = rgb->b; + + return report; +} + +/** + * Set the device to "Breathing" effect + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 3f 0000 00 0a 03 0d 010103 0100ff00000000 | SET Extended Matrix Effect (VARSTORE, SCROLL_WHEEL, BREATHING, single, RGB, RGB-none) + * 00 3f 0000 00 0a 03 0d 010103 0200ff00ff0000 | SET Extended Matrix Effect (VARSTORE, SCROLL_WHEEL, BREATHING, dual, RGB, RGB) + * 00 3f 0000 00 0a 03 0d 010103 03000000000000 | SET Extended Matrix Effect (VARSTORE, SCROLL_WHEEL, BREATHING, random, RGB-none, RGB-none) + */ +struct razer_report razer_chroma_mouse_extended_matrix_effect_breathing_random(unsigned char variable_storage, unsigned char led_id) +{ + struct razer_report report = razer_chroma_mouse_extended_matrix_effect_base(0x0A, variable_storage, led_id, 0x03); + + report.arguments[3] = 0x03; + + return report; +} +struct razer_report razer_chroma_mouse_extended_matrix_effect_breathing_single(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb1) +{ + struct razer_report report = razer_chroma_mouse_extended_matrix_effect_base(0x0A, variable_storage, led_id, 0x03); + + report.arguments[3] = 0x01; + + report.arguments[4] = rgb1->r; + report.arguments[5] = rgb1->g; + report.arguments[6] = rgb1->b; + + return report; +} +struct razer_report razer_chroma_mouse_extended_matrix_effect_breathing_dual(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb1, struct razer_rgb *rgb2) +{ + struct razer_report report = razer_chroma_mouse_extended_matrix_effect_base(0x0A, variable_storage, led_id, 0x03); + + report.arguments[3] = 0x02; + + report.arguments[4] = rgb1->r; + report.arguments[5] = rgb1->g; + report.arguments[6] = rgb1->b; + + report.arguments[7] = rgb2->r; + report.arguments[8] = rgb2->g; + report.arguments[9] = rgb2->b; + + return report; +} + +/* + * Misc Functions + */ +/** + * Toggled whether F1-12 act as F1-12 or if they act as the function options (without Fn pressed) + * + * If 0 should mean that the F-keys work as normal F-keys + * If 1 should mean that the F-keys act as if the FN key is held + */ +struct razer_report razer_chroma_misc_fn_key_toggle(unsigned char state) +{ + struct razer_report report = get_razer_report(0x02, 0x06, 0x02); + report.arguments[0] = 0x00; // ?? Variable storage maybe + report.arguments[1] = clamp_u8(state, 0x00, 0x01); // State + + return report; +} + +/** + * Set the keyswitch optimization on the device (first of two commands) + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 1f 0000 00 04 02 02 001400280000 | SET KEY OPTIMIZATION STATE (TYPING SET) + * 00 1f 0000 00 04 02 02 000000000000 | SET KEY OPTIMIZATION STATE (GAMING SET) + */ +struct razer_report razer_chroma_misc_set_keyswitch_optimization_command1(unsigned char optimization_mode) +{ + struct razer_report report = get_razer_report(0x02, 0x02, 0x04); // class, id, data size + + // 0x00 -> Typing (Set) + // 0x01 -> Gaming (Set) - Same report, doesn't include any arguments + if(optimization_mode == 0x00) { + report.arguments[0] = 0x00; + report.arguments[1] = 0x14; + report.arguments[2] = 0x00; + report.arguments[3] = 0x28; + report.arguments[4] = 0x00; + } + + return report; +} + +/** +* Set the keyswitch optimization on the device (second of two commands) + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 1f 0000 00 05 02 15 010014002800 | SET KEY OPTIMIZATION STATE (TYPING VARSTORE) ???? + * 00 1f 0000 00 05 02 15 010000000000 | SET KEY OPTIMIZATION STATE (GAMING VARSTORE) ???? + */ +struct razer_report razer_chroma_misc_set_keyswitch_optimization_command2(unsigned char optimization_mode) +{ + struct razer_report report = get_razer_report(0x02, 0x15, 0x05); // class, id, data size + + // 0x00 -> Typing (Store) + // 0x01 -> Gaming (Store) + switch(optimization_mode) { + case 0x00: + report.arguments[0] = 0x01; + report.arguments[1] = 0x00; + report.arguments[2] = 0x14; + report.arguments[3] = 0x00; + report.arguments[4] = 0x28; + report.arguments[5] = 0x00; + break; + case 0x01: + report.arguments[0] = 0x01; + break; + } + + return report; +} + +/** + * Get the keyswitch optimization on the device + * + * Identifiers in arg[1] and arg[3] + * + * 0x00<->0x14 is in arg[1] + * 0x00<->0x28 is in arg[3] + */ +struct razer_report razer_chroma_misc_get_keyswitch_optimization(void) +{ + struct razer_report report = get_razer_report(0x02, 0x82, 0x04); // class, id, data size + + return report; +} + +/** + * Set the brightness of an LED on the device + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_misc_set_blade_brightness(unsigned char brightness) +{ + struct razer_report report = get_razer_report(0x0E, 0x04, 0x02); + report.arguments[0] = 0x01; + report.arguments[1] = brightness; + + return report; +} + +/** + * Get the brightness of an LED on the device + * + * Status Trans Packet Proto DataSize Class CMD Args + * ? TODO fill this + */ +struct razer_report razer_chroma_misc_get_blade_brightness(void) +{ + struct razer_report report = get_razer_report(0x0E, 0x84, 0x02); + report.arguments[0] = 0x01; + + return report; +} + +/** + * Sets custom frame for the firefly + */ +struct razer_report razer_chroma_misc_one_row_set_custom_frame(unsigned char start_col, unsigned char stop_col, unsigned char *rgb_data) // TODO recheck custom frame hex +{ + const size_t start_arg_offset = 2; + struct razer_report report = get_razer_report(0x03, 0x0C, 0x32); + size_t row_length = (size_t) (((stop_col + 1) - start_col) * 3); + + if (row_length > sizeof(report.arguments) - start_arg_offset) { + printk(KERN_ALERT "razerchroma: RGB data too long\n"); + row_length = 0; + } + + report.arguments[0] = start_col; + report.arguments[1] = stop_col; + + memcpy(&report.arguments[2], rgb_data, row_length); + + return report; +} + +/** + * Trigger reactive on Firefly + */ +struct razer_report razer_chroma_misc_matrix_reactive_trigger(void) +{ + struct razer_report report = razer_chroma_standard_matrix_effect_base(0x05, MATRIX_EFFECT_REACTIVE); + report.arguments[1] = 0; // this speed triggers reactive + report.arguments[2] = 0; + report.arguments[3] = 0; + report.arguments[4] = 0; + + return report; +} + +/** + * Gets battery level + * + * 0->255 is in arg[1] + */ +struct razer_report razer_chroma_misc_get_battery_level(void) +{ + return get_razer_report(0x07, 0x80, 0x02); +} + +/** + * Gets charging status + * + * 0->1 is in arg[1] + */ +struct razer_report razer_chroma_misc_get_charging_status(void) +{ + return get_razer_report(0x07, 0x84, 0x02); +} + +/** + * Set the charging effect, think if I remember correctly, it's either static colour, or "whatever the mouse was last on" + */ +struct razer_report razer_chroma_misc_set_dock_charge_type(unsigned char charge_type) +{ + struct razer_report report = get_razer_report(0x03, 0x10, 0x01); + report.arguments[0] = clamp_u8(charge_type, 0x00, 0x01); + + return report; +} + +/** + * Get the polling rate from the device + * + * Identifier is in arg[0] + * + * 0x01 = 1000Hz + * 0x02 = 500Hz + * 0x08 = 125Hz + */ +struct razer_report razer_chroma_misc_get_polling_rate(void) +{ + return get_razer_report(0x00, 0x85, 0x01); +} + +/** + * Set the polling rate of the device + * + * 0x01 = 1000Hz + * 0x02 = 500Hz + * 0x08 = 125Hz + */ +struct razer_report razer_chroma_misc_set_polling_rate(unsigned short polling_rate) +{ + struct razer_report report = get_razer_report(0x00, 0x05, 0x01); + + switch(polling_rate) { + case 1000: + report.arguments[0] = 0x01; + break; + case 500: + report.arguments[0] = 0x02; + break; + case 125: + report.arguments[0] = 0x08; + break; + default: // 500Hz + report.arguments[0] = 0x02; + break; + } + + return report; +} + +/** + * Get the polling rate from the device + * + * Identifier is in arg[1] + * + * 0x01 = 8000Hz + * 0x02 = 4000Hz + * 0x04 = 2000Hz + * 0x08 = 1000Hz + * 0x10 = 500Hz + * 0x40 = 125Hz + */ +struct razer_report razer_chroma_misc_get_polling_rate2(void) +{ + return get_razer_report(0x00, 0xC0, 0x01); +} + +/** + * Set the polling rate of the device + * + * 0x40 = 125 Hz + * 0x10 = 500 Hz + * 0x08 = 1000 Hz + * 0x04 = 2000 Hz + * 0x02 = 4000 Hz + * 0x01 = 8000 Hz + */ +struct razer_report razer_chroma_misc_set_polling_rate2(unsigned short polling_rate, unsigned short argument) +{ + struct razer_report report = get_razer_report(0x00, 0x40, 0x02); + + report.arguments[0] = argument; // For some devices Razer sends each request once with 0x00 and once with 0x01 - maybe varstore? + switch(polling_rate) { + case 8000: + report.arguments[1] = 0x01; + break; + case 4000: + report.arguments[1] = 0x02; + break; + case 2000: + report.arguments[1] = 0x04; + break; + case 1000: + report.arguments[1] = 0x08; + break; + case 500: + report.arguments[1] = 0x10; + break; + case 250: + report.arguments[1] = 0x20; + break; + case 125: + report.arguments[1] = 0x40; + break; + default: // 500Hz + report.arguments[1] = 0x10; + break; + } + + return report; +} + +/** + * Get brightness of charging dock + */ +struct razer_report razer_chroma_misc_get_dock_brightness(void) +{ + return get_razer_report(0x07, 0x82, 0x01); + +} + +/** + * Set brightness of charging dock + */ +struct razer_report razer_chroma_misc_set_dock_brightness(unsigned char brightness) +{ + struct razer_report report = get_razer_report(0x07, 0x02, 0x01); + report.arguments[0] = brightness; + + return report; +} + +/** + * Set the DPI of the device + */ +struct razer_report razer_chroma_misc_set_dpi_xy(unsigned char variable_storage, unsigned short dpi_x,unsigned short dpi_y) +{ + struct razer_report report = get_razer_report(0x04, 0x05, 0x07); + + // Keep the DPI within bounds + dpi_x = clamp_u16(dpi_x, 100, 35000); + dpi_y = clamp_u16(dpi_y, 100, 35000); + + report.arguments[0] = VARSTORE; + + report.arguments[1] = (dpi_x >> 8) & 0x00FF; + report.arguments[2] = dpi_x & 0x00FF; + report.arguments[3] = (dpi_y >> 8) & 0x00FF; + report.arguments[4] = dpi_y & 0x00FF; + report.arguments[5] = 0x00; + report.arguments[6] = 0x00; + + return report; +} + +/** + * Get the DPI of the device + */ +struct razer_report razer_chroma_misc_get_dpi_xy(unsigned char variable_storage) +{ + struct razer_report report = get_razer_report(0x04, 0x85, 0x07); + + report.arguments[0] = variable_storage; + + return report; +} + +/** + * Set the DPI of the device (Some stupid turd scaled 5600 dpi into a single byte) + */ +struct razer_report razer_chroma_misc_set_dpi_xy_byte(unsigned char dpi_x,unsigned char dpi_y) +{ + struct razer_report report = get_razer_report(0x04, 0x01, 0x03); + + report.arguments[0] = dpi_x; + report.arguments[1] = dpi_y; + report.arguments[2] = 0x00; + + return report; +} + +/** + * Get the DPI of the device (Some stupid turd scaled 5600 dpi into a single byte) + */ +struct razer_report razer_chroma_misc_get_dpi_xy_byte(void) +{ + struct razer_report report = get_razer_report(0x04, 0x81, 0x03); + + return report; +} + +/** + * Set DPI stages of the device. + * + * count is the number of stages to set. + * active_stage selected stage number. + * dpi is an array of size 2 * count containing pairs of dpi x and dpi y + * values, one pair for each stage. + * + * E.g.: + * count = 3 + * active_stage = 1 + * dpi = [ 800, 800, 1800, 1800, 3200, 3200] + * | stage 1*| stage 2 | stage 3 | + */ +struct razer_report razer_chroma_misc_set_dpi_stages(unsigned char variable_storage, unsigned char count, unsigned char active_stage, const unsigned short *dpi) +{ + struct razer_report report = get_razer_report(0x04, 0x06, 0x26); + unsigned int offset; + unsigned int i; + + report.arguments[0] = variable_storage; + report.arguments[1] = active_stage; + report.arguments[2] = count; + + offset = 3; + for (i = 0; i < count; i++) { + // Stage number + report.arguments[offset++] = i; + + // DPI X + report.arguments[offset++] = (dpi[0] >> 8) & 0x00FF; + report.arguments[offset++] = dpi[0] & 0x00FF; + + // DPI Y + report.arguments[offset++] = (dpi[1] >> 8) & 0x00FF; + report.arguments[offset++] = dpi[1] & 0x00FF; + + // Reserved + report.arguments[offset++] = 0; + report.arguments[offset++] = 0; + + dpi += 2; + } + + return report; +} + +/** + * Get the DPI stages of the device + */ +struct razer_report razer_chroma_misc_get_dpi_stages(unsigned char variable_storage) +{ + struct razer_report report = get_razer_report(0x04, 0x86, 0x26); + + report.arguments[0] = variable_storage; + + return report; +} + +/** + * Get device idle time + */ +struct razer_report razer_chroma_misc_get_idle_time(void) +{ + return get_razer_report(0x07, 0x83, 0x02); +} + +/** + * Set device idle time + * + * Device will go into powersave after this time. + * + * Idle time is in seconds, must be between 60sec-900sec + */ +struct razer_report razer_chroma_misc_set_idle_time(unsigned short idle_time) +{ + struct razer_report report = get_razer_report(0x07, 0x03, 0x02); + + // Keep the idle time within bounds + idle_time = clamp_u16(idle_time, 60, 900); + + report.arguments[0] = (idle_time >> 8) & 0x00FF; + report.arguments[1] = idle_time & 0x00FF; + + return report; +} + +/** + * Get low battery threshold + */ +struct razer_report razer_chroma_misc_get_low_battery_threshold(void) +{ + return get_razer_report(0x07, 0x81, 0x01); +} + +/** + * Set low battery threshold + * + * 0x3F = 25% + * 0x26 = 15% + * 0x0C = 5% + */ +struct razer_report razer_chroma_misc_set_low_battery_threshold(unsigned char battery_threshold) +{ + struct razer_report report = get_razer_report(0x07, 0x01, 0x01); + + // Keep the battery threshold within bounds + battery_threshold = clamp_u8(battery_threshold, 0x0C, 0x3F); + + report.arguments[0] = battery_threshold; + + return report; +} + +struct razer_report razer_chroma_misc_set_orochi2011_led(unsigned char led_bitfield) +{ + struct razer_report report = {0}; + memcpy(&report, &orochi2011_led, sizeof(orochi2011_led)); + + report.arguments[1] = led_bitfield; + + return report; +} + +struct razer_report razer_chroma_misc_set_orochi2011_poll_dpi(unsigned short poll_rate, unsigned char dpi_x, unsigned char dpi_y) +{ + struct razer_report report = {0}; + memcpy(&report, &orochi2011_dpi, sizeof(orochi2011_dpi)); + + switch(poll_rate) { + case 1000: + poll_rate = 0x01; + break; + case 500: + poll_rate = 0x02; + break; + case 125: + poll_rate = 0x08; + break; + default: // 500Hz + poll_rate = 0x02; + break; + } + + report.arguments[1] = poll_rate; + + report.arguments[3] = clamp_u8(dpi_x, 0x15, 0x9C); + report.arguments[4] = clamp_u8(dpi_y, 0x15, 0x9C); + + return report; +} + +/** + * Set the Naga Trinity to "Static" effect + */ +struct razer_report razer_naga_trinity_effect_static(struct razer_rgb *rgb) +{ + struct razer_report report = get_razer_report(0x0f, 0x03, 0x0e); + + report.arguments[0] = 0x00; // Variable storage + report.arguments[1] = 0x00; // LED ID + report.arguments[2] = 0x00; // Unknown + report.arguments[3] = 0x00; // Unknown + report.arguments[4] = 0x02; // Effect ID + report.arguments[5] = rgb->r; // RGB 3x + report.arguments[6] = rgb->g; + report.arguments[7] = rgb->b; + report.arguments[8] = rgb->r; + report.arguments[9] = rgb->g; + report.arguments[10] = rgb->b; + report.arguments[11] = rgb->r; + report.arguments[12] = rgb->g; + report.arguments[13] = rgb->b; + + return report; +} + +/** + * Set scroll wheel mode on the device + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 1f 0000 00 02 02 14 0100 | SET SCROLL WHEEL MODE (VARSTR, TACTILE) + * 00 1f 0000 00 02 02 14 0101 | SET SCROLL WHEEL MODE (VARSTR, FREESPIN) + */ +struct razer_report razer_chroma_misc_set_scroll_mode(unsigned int scroll_mode) +{ + struct razer_report report = get_razer_report(0x02, 0x14, 0x02); + + report.arguments[0] = VARSTORE; + report.arguments[1] = scroll_mode; + + return report; +} + +/** + * Get scroll wheel mode from the device + */ +struct razer_report razer_chroma_misc_get_scroll_mode(void) +{ + struct razer_report report = get_razer_report(0x02, 0x94, 0x02); + + report.arguments[0] = VARSTORE; + + return report; +} + +/** + * Set scroll wheel acceleration on/off on the device + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 1f 0000 00 02 02 16 0101 | SET SCROLL WHEEL ACCELERATION (VARSTR, ON) + * 00 1f 0000 00 02 02 16 0100 | SET SCROLL WHEEL ACCELERATION (VARSTR, OFF) + */ +struct razer_report razer_chroma_misc_set_scroll_acceleration(bool acceleration) +{ + struct razer_report report = get_razer_report(0x02, 0x16, 0x02); + + report.arguments[0] = VARSTORE; + report.arguments[1] = acceleration; + + return report; +} + +/** + * Get scroll wheel acceleration state from the device + */ +struct razer_report razer_chroma_misc_get_scroll_acceleration(void) +{ + struct razer_report report = get_razer_report(0x02, 0x96, 0x02); + + report.arguments[0] = VARSTORE; + + return report; +} + +/** + * Set scroll wheel "smart reel" on/off on the device. + * Smart reel automatically changes scroll wheel mode from tactile to free spin and back depending on scroll speed. + * + * Status Trans Packet Proto DataSize Class CMD Args + * 00 1f 0000 00 02 02 17 0101 | SET SCROLL WHEEL SMART REEL (VARSTR, ON) + * 00 1f 0000 00 02 02 17 0100 | SET SCROLL WHEEL SMART REEL (VARSTR, OFF) + */ +struct razer_report razer_chroma_misc_set_scroll_smart_reel(bool smart_reel) +{ + struct razer_report report = get_razer_report(0x02, 0x17, 0x02); + + report.arguments[0] = VARSTORE; + report.arguments[1] = smart_reel; + + return report; +} + +/** + * Get scroll wheel "smart reel" state from the device + */ +struct razer_report razer_chroma_misc_get_scroll_smart_reel(void) +{ + struct razer_report report = get_razer_report(0x02, 0x97, 0x02); + + report.arguments[0] = VARSTORE; + + return report; +} + +/** + * Set LED mode for HyperPolling Wireless Dongle + * 1 = Connection Status + * 2 = Battery Status + * 3 = Battery Warning Only + */ +struct razer_report razer_chroma_misc_set_hyperpolling_wireless_dongle_indicator_led_mode(unsigned char mode) +{ + struct razer_report report = get_razer_report(0x07, 0x10, 0x01); + + if(mode < 0x01 || mode > 0x03) { + mode = 0x01; + } + + report.arguments[0] = mode; + + return report; +} + +/** + * Set pairing mode for HyperPolling Wireless Dongle (step 1 of pairing) + */ +struct razer_report razer_chroma_misc_set_hyperpolling_wireless_dongle_pair_step1(unsigned short pid) +{ + struct razer_report report; + + report = get_razer_report(0x00, 0x46, 0x01); + report.arguments[0] = 0x01; + + return report; +} + +/** + * Pair HyperPolling Wireless Dongle with PID (step 2 of pairing) + */ +struct razer_report razer_chroma_misc_set_hyperpolling_wireless_dongle_pair_step2(unsigned short pid) +{ + struct razer_report report; + + report = get_razer_report(0x00, 0x41, 0x03); + report.arguments[0] = 0x01; + report.arguments[1] = (pid >> 8) & 0xFF; + report.arguments[2] = pid & 0xFF; + + return report; +} + +/** + * Set unpairing mode for HyperPolling Wireless Dongle + */ +struct razer_report razer_chroma_misc_set_hyperpolling_wireless_dongle_unpair(unsigned short pid) +{ + struct razer_report report = get_razer_report(0x00, 0x42, 0x02); + + report.arguments[0] = (pid >> 8) & 0xFF; + report.arguments[1] = pid & 0xFF; + + return report; +} diff --git a/drivers/custom/razer/driver/razerchromacommon.h b/drivers/custom/razer/driver/razerchromacommon.h new file mode 100644 index 000000000000..e17418f98427 --- /dev/null +++ b/drivers/custom/razer/driver/razerchromacommon.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2015 Terri Cain + */ + +#ifndef DRIVER_RAZERCHROMACOMMON_H_ +#define DRIVER_RAZERCHROMACOMMON_H_ + +#include "razercommon.h" + +/* + * Standard Device Functions + */ +struct razer_report razer_chroma_standard_set_device_mode(unsigned char mode, unsigned char param); +struct razer_report razer_chroma_standard_get_device_mode(void); + +struct razer_report razer_chroma_standard_get_serial(void); + +struct razer_report razer_chroma_standard_get_firmware_version(void); + +/* + * Standard LED Functions + */ +struct razer_report razer_chroma_standard_set_led_state(unsigned char variable_storage, unsigned char led_id, unsigned char led_state); +struct razer_report razer_chroma_standard_get_led_state(unsigned char variable_storage, unsigned char led_id); + +struct razer_report razer_chroma_standard_set_led_blinking(unsigned char variable_storage, unsigned char led_id); + +struct razer_report razer_chroma_standard_set_led_rgb(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb1); +struct razer_report razer_chroma_standard_get_led_rgb(unsigned char variable_storage, unsigned char led_id); + +struct razer_report razer_chroma_standard_set_led_effect(unsigned char variable_storage, unsigned char led_id, unsigned char led_effect); +struct razer_report razer_chroma_standard_get_led_effect(unsigned char variable_storage, unsigned char led_id); + +struct razer_report razer_chroma_standard_set_led_brightness(unsigned char variable_storage, unsigned char led_id, unsigned char brightness); +struct razer_report razer_chroma_standard_get_led_brightness(unsigned char variable_storage, unsigned char led_id); + +/* + * Standard Matrix Effects Functions + */ +struct razer_report razer_chroma_standard_matrix_effect_none(void); +struct razer_report razer_chroma_standard_matrix_effect_wave(unsigned char wave_direction); +struct razer_report razer_chroma_standard_matrix_effect_spectrum(void); +struct razer_report razer_chroma_standard_matrix_effect_reactive(unsigned char speed, struct razer_rgb *rgb1); +struct razer_report razer_chroma_standard_matrix_effect_static(struct razer_rgb *rgb1); +struct razer_report razer_chroma_standard_matrix_effect_starlight_single(unsigned char speed, struct razer_rgb *rgb1); +struct razer_report razer_chroma_standard_matrix_effect_starlight_dual(unsigned char speed, struct razer_rgb *rgb1, struct razer_rgb *rgb2); +struct razer_report razer_chroma_standard_matrix_effect_starlight_random(unsigned char speed); + +struct razer_report razer_chroma_standard_matrix_effect_breathing_random(void); +struct razer_report razer_chroma_standard_matrix_effect_breathing_single(struct razer_rgb *rgb1); +struct razer_report razer_chroma_standard_matrix_effect_breathing_dual(struct razer_rgb *rgb1, struct razer_rgb *rgb2); +struct razer_report razer_chroma_standard_matrix_effect_custom_frame(unsigned char variable_storage); +struct razer_report razer_chroma_standard_matrix_set_custom_frame(unsigned char row_index, unsigned char start_col, unsigned char stop_col, unsigned char *rgb_data); + +/* + * Extended Matrix Effects Functions + * + * Class 0x0F + * Trans 0x3F (Dev 0b001 Game Controller 1, Trans 0b11111) + */ +struct razer_report razer_chroma_extended_matrix_effect_none(unsigned char variable_storage, unsigned char led_id); +struct razer_report razer_chroma_extended_matrix_effect_static(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb); +struct razer_report razer_chroma_extended_matrix_effect_wave(unsigned char variable_storage, unsigned char led_id, unsigned char direction); +struct razer_report razer_chroma_extended_matrix_effect_starlight_random(unsigned char variable_storage, unsigned char led_id, unsigned char speed); +struct razer_report razer_chroma_extended_matrix_effect_starlight_single(unsigned char variable_storage, unsigned char led_id, unsigned char speed, struct razer_rgb *rgb1); +struct razer_report razer_chroma_extended_matrix_effect_starlight_dual(unsigned char variable_storage, unsigned char led_id, unsigned char speed, struct razer_rgb *rgb1, struct razer_rgb *rgb2); +struct razer_report razer_chroma_extended_matrix_effect_spectrum(unsigned char variable_storage, unsigned char led_id); +struct razer_report razer_chroma_extended_matrix_effect_wheel(unsigned char variable_storage, unsigned char led_id, unsigned char direction); +struct razer_report razer_chroma_extended_matrix_effect_reactive(unsigned char variable_storage, unsigned char led_id, unsigned char speed, struct razer_rgb *rgb1); +struct razer_report razer_chroma_extended_matrix_effect_breathing_random(unsigned char variable_storage, unsigned char led_id); +struct razer_report razer_chroma_extended_matrix_effect_breathing_single(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb1); +struct razer_report razer_chroma_extended_matrix_effect_breathing_dual(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb1, struct razer_rgb *rgb2); +struct razer_report razer_chroma_extended_matrix_effect_custom_frame(void); +struct razer_report razer_chroma_extended_matrix_brightness(unsigned char variable_storage, unsigned char led_id, unsigned char brightness); +struct razer_report razer_chroma_extended_matrix_get_brightness(unsigned char variable_storage, unsigned char led_id); +struct razer_report razer_chroma_extended_matrix_set_custom_frame(unsigned char row_index, unsigned char start_col, unsigned char stop_col, unsigned char *rgb_data); +struct razer_report razer_chroma_extended_matrix_set_custom_frame2(unsigned char row_index, unsigned char start_col, unsigned char stop_col, unsigned char *rgb_data, size_t packetLength); + +/* + * Extended Matrix Effects (Mouse) Functions + * + * Class 0x0D + * Trans 0x3F (not set) (Dev 0b001 Game Controller 1, Trans 0b11111) + */ +struct razer_report razer_chroma_mouse_extended_matrix_effect_none(unsigned char variable_storage, unsigned char led_id); +struct razer_report razer_chroma_mouse_extended_matrix_effect_static(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb1); +struct razer_report razer_chroma_mouse_extended_matrix_effect_spectrum(unsigned char variable_storage, unsigned char led_id); +struct razer_report razer_chroma_mouse_extended_matrix_effect_reactive(unsigned char variable_storage, unsigned char led_id, unsigned char speed, struct razer_rgb *rgb1); +struct razer_report razer_chroma_mouse_extended_matrix_effect_breathing_random(unsigned char variable_storage, unsigned char led_id); +struct razer_report razer_chroma_mouse_extended_matrix_effect_breathing_single(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb1); +struct razer_report razer_chroma_mouse_extended_matrix_effect_breathing_dual(unsigned char variable_storage, unsigned char led_id, struct razer_rgb *rgb1, struct razer_rgb *rgb2); + +/* + * Misc Functions + */ +struct razer_report razer_chroma_misc_fn_key_toggle(unsigned char state); + +struct razer_report razer_chroma_misc_set_keyswitch_optimization_command1(unsigned char optimization_mode); +struct razer_report razer_chroma_misc_set_keyswitch_optimization_command2(unsigned char optimization_mode); +struct razer_report razer_chroma_misc_get_keyswitch_optimization(void); + +struct razer_report razer_chroma_misc_set_blade_brightness(unsigned char brightness); +struct razer_report razer_chroma_misc_get_blade_brightness(void); + +struct razer_report razer_chroma_misc_one_row_set_custom_frame(unsigned char start_col, unsigned char stop_col, unsigned char *rgb_data); +struct razer_report razer_chroma_misc_matrix_reactive_trigger(void); + +struct razer_report razer_chroma_misc_get_battery_level(void); +struct razer_report razer_chroma_misc_get_charging_status(void); + +struct razer_report razer_chroma_misc_set_dock_charge_type(unsigned char charge_type); + +struct razer_report razer_chroma_misc_get_polling_rate(void); +struct razer_report razer_chroma_misc_set_polling_rate(unsigned short polling_rate); + +struct razer_report razer_chroma_misc_get_polling_rate2(void); +struct razer_report razer_chroma_misc_set_polling_rate2(unsigned short polling_rate, unsigned short argument); + +struct razer_report razer_chroma_misc_get_dock_brightness(void); +struct razer_report razer_chroma_misc_set_dock_brightness(unsigned char brightness); + +struct razer_report razer_chroma_misc_set_dpi_xy(unsigned char variable_storage, unsigned short dpi_x,unsigned short dpi_y); +struct razer_report razer_chroma_misc_get_dpi_xy(unsigned char variable_storage); + +struct razer_report razer_chroma_misc_set_dpi_xy_byte(unsigned char dpi_x,unsigned char dpi_y); +struct razer_report razer_chroma_misc_get_dpi_xy_byte(void); + +struct razer_report razer_chroma_misc_set_dpi_stages(unsigned char variable_storage, unsigned char count, unsigned char active_stage, const unsigned short *dpi); +struct razer_report razer_chroma_misc_get_dpi_stages(unsigned char variable_storage); + +struct razer_report razer_chroma_misc_get_idle_time(void); +struct razer_report razer_chroma_misc_set_idle_time(unsigned short idle_time); + +struct razer_report razer_chroma_misc_get_low_battery_threshold(void); +struct razer_report razer_chroma_misc_set_low_battery_threshold(unsigned char battery_threshold); + +struct razer_report razer_chroma_misc_set_orochi2011_led(unsigned char led_bitfield); +struct razer_report razer_chroma_misc_set_orochi2011_poll_dpi(unsigned short poll_rate, unsigned char dpi_x, unsigned char dpi_y); + +struct razer_report razer_naga_trinity_effect_static(struct razer_rgb* rgb); + +struct razer_report razer_chroma_misc_set_scroll_mode(unsigned int scroll_mode); +struct razer_report razer_chroma_misc_get_scroll_mode(void); + +struct razer_report razer_chroma_misc_set_scroll_acceleration(bool acceleration); +struct razer_report razer_chroma_misc_get_scroll_acceleration(void); + +struct razer_report razer_chroma_misc_set_scroll_smart_reel(bool smart_reel); +struct razer_report razer_chroma_misc_get_scroll_smart_reel(void); + +struct razer_report razer_chroma_misc_set_hyperpolling_wireless_dongle_indicator_led_mode(unsigned char mode); +struct razer_report razer_chroma_misc_set_hyperpolling_wireless_dongle_pair_step1(unsigned short pid); +struct razer_report razer_chroma_misc_set_hyperpolling_wireless_dongle_pair_step2(unsigned short pid); +struct razer_report razer_chroma_misc_set_hyperpolling_wireless_dongle_unpair(unsigned short pid); + +#endif diff --git a/drivers/custom/razer/driver/razercommon.c b/drivers/custom/razer/driver/razercommon.c new file mode 100644 index 000000000000..4e8548ae4d03 --- /dev/null +++ b/drivers/custom/razer/driver/razercommon.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2015 Tim Theede + * 2015 Terri Cain + */ + +#include +#include +#include +#include +#include + +#include "razercommon.h" + +/** + * Send USB control report to the keyboard + * USUALLY index = 0x02 + * FIREFLY is 0 + */ +int razer_send_control_msg(struct usb_device *usb_dev,void const *data, uint report_index, ulong wait_min, ulong wait_max) +{ + uint request = HID_REQ_SET_REPORT; // 0x09 + uint request_type = USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT; // 0x21 + uint value = 0x300; + uint size = RAZER_USB_REPORT_LEN; + char *buf; + int len; + + buf = kmemdup(data, size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + // Send usb control message + len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + request, // Request + request_type, // RequestType + value, // Value + report_index, // Index + buf, // Data + size, // Length + USB_CTRL_SET_TIMEOUT); + + // Wait + usleep_range(wait_min, wait_max); + + kfree(buf); + if(len!=size) + printk(KERN_WARNING "razer driver: Device data transfer failed.\n"); + + return ((len < 0) ? len : ((len != size) ? -EIO : 0)); +} + +/** + * Get a response from the razer device + * + * Makes a request like normal, this must change a variable in the device as then we + * tell it give us data and it gives us a report. + * + * Supported Devices: + * Razer Chroma + * Razer Mamba + * Razer BlackWidow Ultimate 2013* + * Razer Firefly* + * + * Request report is the report sent to the device specifying what response we want + * Response report will get populated with a response + * + * Returns 0 when successful, 1 if the report length is invalid. + */ +int razer_get_usb_response(struct usb_device *usb_dev, uint report_index, struct razer_report* request_report, uint response_index, struct razer_report* response_report, ulong wait_min, ulong wait_max) +{ + uint request = HID_REQ_GET_REPORT; // 0x01 + uint request_type = USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN; // 0xA1 + uint value = 0x300; + + uint size = RAZER_USB_REPORT_LEN; // 0x90 + int len; + int retval; + int result = 0; + char *buf; + + if (WARN_ON(request_report->transaction_id.id == 0x00)) { + request_report->transaction_id.id = 0xFF; + } + + buf = kzalloc(sizeof(struct razer_report), GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + // Send the request to the device. + // TODO look to see if index needs to be different for the request and the response + retval = razer_send_control_msg(usb_dev, request_report, report_index, wait_min, wait_max); + + // Now ask for response + len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + request, // Request + request_type, // RequestType + value, // Value + response_index, // Index + buf, // Data + size, + USB_CTRL_SET_TIMEOUT); + + memcpy(response_report, buf, sizeof(struct razer_report)); + kfree(buf); + + // Error if report is wrong length + if(len != 90) { + printk(KERN_WARNING "razer driver: Invalid USB response. USB Report length: %d\n", len); + result = 1; + } + + if (WARN_ONCE(response_report->data_size > ARRAY_SIZE(response_report->arguments), + "Field data_size %d in response is bigger than arguments\n", + response_report->data_size)) { + /* Sanitize the value since at the moment callers don't respect the return code */ + response_report->data_size = ARRAY_SIZE(response_report->arguments); + return -EINVAL; + } + + return result; +} + +/** + * Calculate the checksum for the usb message + * + * Checksum byte is stored in the 2nd last byte in the messages payload. + * The checksum is generated by XORing all the bytes in the report starting + * at byte number 2 (0 based) and ending at byte 88. + */ +unsigned char razer_calculate_crc(struct razer_report *report) +{ + /*second to last byte of report is a simple checksum*/ + /*just xor all bytes up with overflow and you are done*/ + unsigned char crc = 0; + unsigned char *_report = (unsigned char*)report; + + unsigned int i; + for(i = 2; i < 88; i++) { + crc ^= _report[i]; + } + + return crc; +} + +/** + * Get initialised razer report + */ +struct razer_report get_razer_report(unsigned char command_class, unsigned char command_id, unsigned char data_size) +{ + struct razer_report new_report = {0}; + memset(&new_report, 0, sizeof(struct razer_report)); + + new_report.status = 0x00; + new_report.transaction_id.id = 0x00; + new_report.remaining_packets = 0x00; + new_report.protocol_type = 0x00; + new_report.command_class = command_class; + new_report.command_id.id = command_id; + new_report.data_size = data_size; + + return new_report; +} + +/** + * Get empty razer report + */ +struct razer_report get_empty_razer_report(void) +{ + struct razer_report new_report = {0}; + memset(&new_report, 0, sizeof(struct razer_report)); + + return new_report; +} + +/** + * Print report to syslog + */ +void print_erroneous_report(struct razer_report* report, char* driver_name, char* message) +{ + printk(KERN_WARNING "%s: %s. status: %02x transaction_id.id: %02x remaining_packets: %02x protocol_type: %02x data_size: %02x, command_class: %02x, command_id.id: %02x Params: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x .\n", + driver_name, + message, + report->status, + report->transaction_id.id, + report->remaining_packets, + report->protocol_type, + report->data_size, + report->command_class, + report->command_id.id, + report->arguments[0], report->arguments[1], report->arguments[2], report->arguments[3], report->arguments[4], report->arguments[5], + report->arguments[6], report->arguments[7], report->arguments[8], report->arguments[9], report->arguments[10], report->arguments[11], + report->arguments[12], report->arguments[13], report->arguments[14], report->arguments[15]); +} + +/** + * Clamp a value to a min,max + */ +unsigned char clamp_u8(unsigned char value, unsigned char min, unsigned char max) +{ + if(value > max) + return max; + if(value < min) + return min; + return value; +} +unsigned short clamp_u16(unsigned short value, unsigned short min, unsigned short max) +{ + if(value > max) + return max; + if(value < min) + return min; + return value; +} + +int razer_send_control_msg_old_device(struct usb_device *usb_dev,void const *data, uint report_value, uint report_index, uint report_size, ulong wait_min, ulong wait_max) +{ + uint request = HID_REQ_SET_REPORT; // 0x09 + uint request_type = USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT; // 0x21 + char *buf; + int len; + + buf = kmemdup(data, report_size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + // Send usb control message + len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + request, // Request + request_type, // RequestType + report_value, // Value + report_index, // Index + buf, // Data + report_size, // Length + USB_CTRL_SET_TIMEOUT); + + // Wait + usleep_range(wait_min, wait_max); + + kfree(buf); + if(len!=report_size) + printk(KERN_WARNING "razer driver: Device data transfer failed.\n"); + + return ((len < 0) ? len : ((len != report_size) ? -EIO : 0)); +} + +int razer_send_argb_msg(struct usb_device* usb_dev, unsigned char channel, unsigned char size, void const* data) +{ + uint request = HID_REQ_SET_REPORT; // 0x09 + uint request_type = USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT; // 0x21 + uint value = 0x300; + int len; + char *buf; + + struct razer_argb_report report; + + if (channel < 5) { + report.report_id = 0x04; + } else { + report.report_id = 0x84; + } + + report.channel_1 = channel; + report.channel_2 = channel; + + report.pad = 0; + + report.last_idx = size - 1; + + if (size * 3 > ARRAY_SIZE(report.color_data)) { + printk(KERN_ERR "razer driver: size too big\n"); + return -EINVAL; + } + + memcpy(report.color_data, data, size * 3); + + buf = kmemdup(&report, sizeof(report), GFP_KERNEL); + + // Send usb control message + len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + request, // Request + request_type, // RequestType + value, // Value + 0x01, // Index + buf, // Data + sizeof(report), // Length + USB_CTRL_SET_TIMEOUT); + + if (len != sizeof(report)) + printk(KERN_WARNING "razer driver: Device data transfer failed. len = %d", len); + + return ((len < 0) ? len : ((len != size) ? -EIO : 0)); +} diff --git a/drivers/custom/razer/driver/razercommon.h b/drivers/custom/razer/driver/razercommon.h new file mode 100644 index 000000000000..a1cf644fa01c --- /dev/null +++ b/drivers/custom/razer/driver/razercommon.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2015 Tim Theede + * 2015 Terri Cain + */ + +#ifndef DRIVER_RAZERCOMMON_H_ +#define DRIVER_RAZERCOMMON_H_ + +#include + +#define DRIVER_VERSION "3.10.2" +#define DRIVER_LICENSE "GPL v2" +#define DRIVER_AUTHOR "Terri Cain " + +// Compatbility for fallthrough pseudo keyword for Linux versions older than v5.4 +// See also https://git.kernel.org/torvalds/c/294f69e +#ifndef fallthrough +#if __has_attribute(__fallthrough__) +# define fallthrough __attribute__((__fallthrough__)) +#else +# define fallthrough do {} while (0) /* fallthrough */ +#endif +#endif + +// Macro to create device files +#define CREATE_DEVICE_FILE(dev, type) \ +do { \ + if(device_create_file(dev, type)) { \ + goto exit_free; \ + } \ +} while (0) + +#define USB_VENDOR_ID_RAZER 0x1532 + +/* Each USB report has 90 bytes*/ +#define RAZER_USB_REPORT_LEN 0x5A + +// LED STATE +#define OFF 0x00 +#define ON 0x01 + +// LED STORAGE Options +#define NOSTORE 0x00 +#define VARSTORE 0x01 + +// LED definitions +#define ZERO_LED 0x00 +#define SCROLL_WHEEL_LED 0x01 +#define BATTERY_LED 0x03 +#define LOGO_LED 0x04 +#define BACKLIGHT_LED 0x05 +#define MACRO_LED 0x07 +#define GAME_LED 0x08 +#define RED_PROFILE_LED 0x0C +#define GREEN_PROFILE_LED 0x0D +#define BLUE_PROFILE_LED 0x0E +#define RIGHT_SIDE_LED 0x10 +#define LEFT_SIDE_LED 0x11 +#define ARGB_CH_1_LED 0x1A +#define ARGB_CH_2_LED 0x1B +#define ARGB_CH_3_LED 0x1C +#define ARGB_CH_4_LED 0x1D +#define ARGB_CH_5_LED 0x1E +#define ARGB_CH_6_LED 0x1F +#define CHARGING_LED 0x20 +#define FAST_CHARGING_LED 0x21 +#define FULLY_CHARGED_LED 0x22 + +// LED Effect definitions +enum razer_classic_effect_id { + CLASSIC_EFFECT_STATIC = 0x00, + CLASSIC_EFFECT_BLINKING = 0x01, + CLASSIC_EFFECT_BREATHING = 0x02, // also called pulsating + CLASSIC_EFFECT_SPECTRUM = 0x04, +}; + +enum razer_matrix_effect_id { + MATRIX_EFFECT_OFF = 0x00, + MATRIX_EFFECT_WAVE = 0x01, + MATRIX_EFFECT_REACTIVE = 0x02, // afterglow + MATRIX_EFFECT_BREATHING = 0x03, + MATRIX_EFFECT_SPECTRUM = 0x04, + MATRIX_EFFECT_CUSTOMFRAME = 0x05, + MATRIX_EFFECT_STATIC = 0x06, + MATRIX_EFFECT_STARLIGHT = 0x19 +}; + +// Report Responses +#define RAZER_CMD_BUSY 0x01 +#define RAZER_CMD_SUCCESSFUL 0x02 +#define RAZER_CMD_FAILURE 0x03 +#define RAZER_CMD_TIMEOUT 0x04 +#define RAZER_CMD_NOT_SUPPORTED 0x05 + +struct razer_report; + +struct razer_rgb { + unsigned char r,g,b; +}; + +union transaction_id_union { + unsigned char id; + struct transaction_parts { + unsigned char device : 3; + unsigned char id : 5; + } parts; +}; + +union command_id_union { + unsigned char id; + struct command_id_parts { + unsigned char direction : 1; + unsigned char id : 7; + } parts; +}; + +/* Status: + * 0x00 New Command + * 0x01 Command Busy + * 0x02 Command Successful + * 0x03 Command Failure + * 0x04 Command No Response / Command Timeout + * 0x05 Command Not Support + * + * Transaction ID used to group request-response, device useful when multiple devices are on one usb + * Remaining Packets is the number of remaining packets in the sequence + * Protocol Type is always 0x00 + * Data Size is the size of payload, cannot be greater than 80. 90 = header (8B) + data + CRC (1B) + Reserved (1B) + * Command Class is the type of command being issued + * Command ID is the type of command being send. Direction 0 is Host->Device, Direction 1 is Device->Host. AKA Get LED 0x80, Set LED 0x00 + * + * */ + +struct razer_report { + unsigned char status; + union transaction_id_union transaction_id; /* */ + unsigned short remaining_packets; /* Big Endian */ + unsigned char protocol_type; /*0x0*/ + unsigned char data_size; + unsigned char command_class; + union command_id_union command_id; + unsigned char arguments[80]; + unsigned char crc;/*xor'ed bytes of report*/ + unsigned char reserved; /*0x0*/ +}; + +struct razer_argb_report { + unsigned char report_id; + unsigned char channel_1; + unsigned char channel_2; + unsigned char pad; + unsigned char last_idx; + unsigned char color_data[315]; +}; + +struct razer_key_translation { + u16 from; + u16 to; + u8 flags; +}; + +int razer_send_control_msg(struct usb_device *usb_dev,void const *data, unsigned int report_index, unsigned long wait_min, unsigned long wait_max); +int razer_send_control_msg_old_device(struct usb_device *usb_dev,void const *data, uint report_value, uint report_index, uint report_size, ulong wait_min, ulong wait_max); +int razer_get_usb_response(struct usb_device *usb_dev, unsigned int report_index, struct razer_report* request_report, unsigned int response_index, struct razer_report* response_report, unsigned long wait_min, unsigned long wait_max); +int razer_send_argb_msg(struct usb_device* usb_dev, unsigned char channel, unsigned char size, void const* data); +unsigned char razer_calculate_crc(struct razer_report *report); +struct razer_report get_razer_report(unsigned char command_class, unsigned char command_id, unsigned char data_size); +struct razer_report get_empty_razer_report(void); +void print_erroneous_report(struct razer_report* report, char* driver_name, char* message); + +// Convenience functions +unsigned char clamp_u8(unsigned char value, unsigned char min, unsigned char max); +unsigned short clamp_u16(unsigned short value, unsigned short min, unsigned short max); + +#endif /* DRIVER_RAZERCOMMON_H_ */ diff --git a/drivers/custom/razer/driver/razerkbd_driver.c b/drivers/custom/razer/driver/razerkbd_driver.c new file mode 100644 index 000000000000..6cb355f7afc8 --- /dev/null +++ b/drivers/custom/razer/driver/razerkbd_driver.c @@ -0,0 +1,5010 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2015 Terri Cain + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usb_hid_keys.h" + +#include "razerkbd_driver.h" +#include "razercommon.h" +#include "razerchromacommon.h" + +/* + * Version Information + */ +#define DRIVER_DESC "Razer Keyboard Device Driver" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE(DRIVER_LICENSE); + +// KEY_MACRO* has been added in Linux 5.5, so define ourselves for older kernels. +// See also https://git.kernel.org/torvalds/c/b5625db +#ifndef KEY_MACRO1 +#define KEY_MACRO1 0x290 +#define KEY_MACRO2 0x291 +#define KEY_MACRO3 0x292 +#define KEY_MACRO4 0x293 +#define KEY_MACRO5 0x294 +#define KEY_MACRO6 0x295 +#define KEY_MACRO7 0x296 +#define KEY_MACRO8 0x297 +#define KEY_MACRO9 0x298 +#define KEY_MACRO10 0x299 +#define KEY_MACRO11 0x2a0 +#define KEY_MACRO12 0x2a1 +// ... +#define KEY_MACRO27 0x2aa +#define KEY_MACRO28 0x2ab +#define KEY_MACRO29 0x2ac +#define KEY_MACRO30 0x2ad +#endif + +// These are evdev key codes, not HID key codes. +// Lower macro key codes are intended for the actual macro keys +// Higher macro key codes are inteded for Chroma functions +#define RAZER_MACRO_KEY KEY_MACRO30 // TODO maybe KEY_MACRO_RECORD_START? +#define RAZER_GAME_KEY KEY_MACRO29 // TODO maybe KEY_GAMES? +#define RAZER_BRIGHTNESS_DOWN KEY_MACRO28 +#define RAZER_BRIGHTNESS_UP KEY_MACRO27 + +/** + * List of keys to swap + */ +static const struct razer_key_translation chroma_keys[] = { + { KEY_F1, KEY_MUTE }, + { KEY_F2, KEY_VOLUMEDOWN }, + { KEY_F3, KEY_VOLUMEUP }, + + { KEY_F5, KEY_PREVIOUSSONG }, + { KEY_F6, KEY_PLAYPAUSE }, + { KEY_F7, KEY_NEXTSONG }, + + { KEY_F9, RAZER_MACRO_KEY }, + { KEY_F10, RAZER_GAME_KEY }, + { KEY_F11, RAZER_BRIGHTNESS_DOWN }, + { KEY_F12, RAZER_BRIGHTNESS_UP }, + + { KEY_PAUSE, KEY_SLEEP }, + + // Custom bind + { KEY_KPENTER, KEY_CALC }, + { 0 } +}; + +static const struct razer_key_translation chroma_keys_2[] = { + { KEY_F1, KEY_MUTE }, + { KEY_F2, KEY_VOLUMEDOWN }, + { KEY_F3, KEY_VOLUMEUP }, + + { KEY_F5, KEY_PLAYPAUSE }, + { KEY_F6, KEY_STOPCD }, + { KEY_F7, KEY_PREVIOUSSONG }, + { KEY_F8, KEY_NEXTSONG }, + { KEY_F11, RAZER_GAME_KEY }, +// { KEY_F12, RAZER_EFFECT_KEY }, // enable if daemon supports, see #577 + { KEY_RIGHTALT, RAZER_MACRO_KEY }, + + { KEY_PAUSE, KEY_SLEEP }, + { 0 } +}; + +// Huntsman Mini Fn keys +static const struct razer_key_translation chroma_keys_3[] = { + { KEY_ESC, KEY_GRAVE }, + { KEY_1, KEY_F1 }, + { KEY_2, KEY_F2 }, + { KEY_3, KEY_F3 }, + { KEY_4, KEY_F4 }, + { KEY_5, KEY_F5 }, + { KEY_6, KEY_F6 }, + { KEY_7, KEY_F7 }, + { KEY_8, KEY_F8 }, + { KEY_9, KEY_F9 }, + { KEY_0, KEY_F10 }, + { KEY_MINUS, KEY_F11 }, + { KEY_EQUAL, KEY_F12 }, + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_TAB, KEY_MUTE }, + { KEY_Q, KEY_VOLUMEDOWN }, + { KEY_W, KEY_VOLUMEUP }, + { KEY_E, KEY_PREVIOUSSONG }, + { KEY_R, KEY_PLAYPAUSE }, + { KEY_T, KEY_NEXTSONG }, + { KEY_Y, RAZER_MACRO_KEY }, + { KEY_U, RAZER_GAME_KEY }, + { KEY_I, KEY_UP }, + { KEY_O, KEY_SCROLLLOCK }, + { KEY_P, KEY_SYSRQ }, + { KEY_LEFTBRACE, KEY_PAGEUP }, + { KEY_RIGHTBRACE, KEY_HOME }, + { KEY_G, RAZER_BRIGHTNESS_DOWN }, + { KEY_H, RAZER_BRIGHTNESS_UP }, + { KEY_J, KEY_LEFT }, + { KEY_K, KEY_DOWN }, + { KEY_L, KEY_RIGHT }, + { KEY_SEMICOLON, KEY_PAGEDOWN }, + { KEY_APOSTROPHE, KEY_END }, + { KEY_Z, KEY_SLEEP }, + { KEY_DOT, KEY_PAUSE }, + { KEY_SLASH, KEY_INSERT }, + { 0 } +}; + +// Blackwidow V3 Mini Fn keys +static const struct razer_key_translation chroma_keys_4[] = { + { KEY_ESC, KEY_GRAVE }, + { KEY_1, KEY_F1 }, + { KEY_2, KEY_F2 }, + { KEY_3, KEY_F3 }, + { KEY_4, KEY_F4 }, + { KEY_5, KEY_F5 }, + { KEY_6, KEY_F6 }, + { KEY_7, KEY_F7 }, + { KEY_8, KEY_F8 }, + { KEY_9, KEY_F9 }, + { KEY_0, KEY_F10 }, + { KEY_MINUS, KEY_F11 }, + { KEY_EQUAL, KEY_F12 }, + { KEY_DELETE, KEY_MACRO1 }, + { KEY_Y, RAZER_MACRO_KEY }, + { KEY_U, RAZER_GAME_KEY }, + { KEY_I, KEY_UP }, + { KEY_O, KEY_SYSRQ }, + { KEY_P, KEY_SCROLLLOCK }, + { KEY_LEFTBRACE, KEY_PAUSE }, + { KEY_RIGHTBRACE, KEY_SLEEP }, + { KEY_PAGEUP, KEY_MACRO2 }, + { KEY_G, RAZER_BRIGHTNESS_DOWN }, + { KEY_H, RAZER_BRIGHTNESS_UP }, + { KEY_APOSTROPHE, KEY_HOME }, + { KEY_PAGEDOWN, KEY_MACRO3 }, + { KEY_V, KEY_MUTE }, + { KEY_B, KEY_VOLUMEDOWN }, + { KEY_N, KEY_VOLUMEUP }, + { KEY_M, KEY_PREVIOUSSONG }, + { KEY_COMMA, KEY_PLAYPAUSE }, + { KEY_DOT, KEY_NEXTSONG }, + { KEY_SLASH, KEY_END }, + { KEY_INSERT, KEY_MACRO4 }, + { KEY_RIGHTALT, KEY_BLUETOOTH }, + { 0 } +}; + +// Razer BlackWidow V3 (Full size) +static const struct razer_key_translation chroma_keys_5[] = { + { KEY_F9, RAZER_MACRO_KEY }, + { KEY_F10, RAZER_GAME_KEY }, + { KEY_F11, RAZER_BRIGHTNESS_DOWN }, + { KEY_F12, RAZER_BRIGHTNESS_UP }, + { KEY_PAUSE, KEY_SLEEP }, + // TODO - Add KEY_CONTEXT_MENU when we figure out what it is supposed to be doing + { 0 } +}; + +// Razer BlackWidow V4 75% +static const struct razer_key_translation chroma_keys_6[] = { + { KEY_F9, RAZER_MACRO_KEY }, + { KEY_F10, RAZER_GAME_KEY }, + { KEY_F11, RAZER_BRIGHTNESS_DOWN }, + { KEY_F12, RAZER_BRIGHTNESS_UP }, + { KEY_P, KEY_SYSRQ }, + { KEY_PAGEUP, KEY_HOME }, + { KEY_PAGEDOWN, KEY_END }, + { KEY_INSERT, KEY_PAUSE }, + { KEY_DELETE, KEY_SLEEP }, + { 0 } +}; + +// Razer DeathStalker V2 Pro TKL +static const struct razer_key_translation chroma_keys_7[] = { + { KEY_F9, RAZER_MACRO_KEY }, + { KEY_F10, RAZER_GAME_KEY }, + { KEY_F11, RAZER_BRIGHTNESS_DOWN }, + { KEY_F12, RAZER_BRIGHTNESS_UP }, + { KEY_INSERT, KEY_SYSRQ }, + { KEY_HOME, KEY_SCROLLLOCK }, + { KEY_PAGEUP, KEY_PAUSE }, + { KEY_PAGEDOWN, KEY_SLEEP }, + // TODO - Add KEY_CONTEXT_MENU when we figure out what it is supposed to be doing + { 0 } +}; + +/** + * Essentially search through the struct array above. + */ +static const struct razer_key_translation *find_translation(const struct razer_key_translation *key_table, u16 from) +{ + const struct razer_key_translation *result; + + for (result = key_table; result->from; result++) { + if (result->from == from) { + return result; + } + } + + return NULL; +} + +static bool is_blade_laptop(struct razer_kbd_device *device) +{ + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_BLADE_STEALTH: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2016: + case USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2016: + case USB_DEVICE_ID_RAZER_BLADE_2018: + case USB_DEVICE_ID_RAZER_BLADE_2018_MERCURY: + case USB_DEVICE_ID_RAZER_BLADE_2018_BASE: + case USB_DEVICE_ID_RAZER_BLADE_2019_ADV: + case USB_DEVICE_ID_RAZER_BLADE_MID_2019_MERCURY: + case USB_DEVICE_ID_RAZER_BLADE_STUDIO_EDITION_2019: + case USB_DEVICE_ID_RAZER_BLADE_QHD: + case USB_DEVICE_ID_RAZER_BLADE_LATE_2016: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_MID_2017: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2017: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_2019: + case USB_DEVICE_ID_RAZER_BLADE_PRO_2017: + case USB_DEVICE_ID_RAZER_BLADE_PRO_2017_FULLHD: + case USB_DEVICE_ID_RAZER_BLADE_2019_BASE: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2019: + case USB_DEVICE_ID_RAZER_BLADE_PRO_2019: + case USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2019: + case USB_DEVICE_ID_RAZER_BLADE_ADV_LATE_2019: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_EARLY_2020: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2020: + case USB_DEVICE_ID_RAZER_BLADE_PRO_EARLY_2020: + case USB_DEVICE_ID_RAZER_BOOK_2020: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_2020: + case USB_DEVICE_ID_RAZER_BLADE_EARLY_2020_BASE: + case USB_DEVICE_ID_RAZER_BLADE_LATE_2020_BASE: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2021: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_MID_2021: + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_EARLY_2021: + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_2022: + case USB_DEVICE_ID_RAZER_BLADE_17_PRO_EARLY_2021: + case USB_DEVICE_ID_RAZER_BLADE_17_PRO_MID_2021: + case USB_DEVICE_ID_RAZER_BLADE_14_2021: + case USB_DEVICE_ID_RAZER_BLADE_17_2022: + case USB_DEVICE_ID_RAZER_BLADE_14_2022: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2022: + case USB_DEVICE_ID_RAZER_BLADE_14_2023: + case USB_DEVICE_ID_RAZER_BLADE_14_2024: + case USB_DEVICE_ID_RAZER_BLADE_15_2023: + case USB_DEVICE_ID_RAZER_BLADE_16_2023: + case USB_DEVICE_ID_RAZER_BLADE_18_2023: + case USB_DEVICE_ID_RAZER_BLADE_18_2024: + return true; + } + return false; +} + +/** + * Get request/response indices and timing parameters for the device + */ +static void razer_get_report_params(struct usb_device *usb_dev, uint *report_index, uint *response_index, ulong *wait_min, ulong *wait_max) +{ + switch (usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + *report_index = 0x03; + *response_index = 0x03; + *wait_min = RAZER_BLACKWIDOW_CHROMA_WAIT_MIN_US; + *wait_max = RAZER_BLACKWIDOW_CHROMA_WAIT_MAX_US; + break; + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + *report_index = 0x03; + *response_index = 0x03; + *wait_min = RAZER_BLACKWIDOW_V3_WIRELESS_WAIT_MIN_US; + *wait_max = RAZER_BLACKWIDOW_V3_WIRELESS_WAIT_MAX_US; + break; + case USB_DEVICE_ID_RAZER_ANANSI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + *report_index = 0x02; + *response_index = 0x02; + *wait_min = RAZER_BLACKWIDOW_CHROMA_WAIT_MIN_US; + *wait_max = RAZER_BLACKWIDOW_CHROMA_WAIT_MAX_US; + break; + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + *report_index = 0x02; + *response_index = 0x02; + *wait_min = RAZER_BLACKWIDOW_V3_WIRELESS_WAIT_MIN_US; + *wait_max = RAZER_BLACKWIDOW_V3_WIRELESS_WAIT_MAX_US; + break; + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + *report_index = 0x02; + *response_index = 0x02; + *wait_min = RAZER_DEATHSTALKER_V2_WIRELESS_WAIT_MIN_US; + *wait_max = RAZER_DEATHSTALKER_V2_WIRELESS_WAIT_MAX_US; + break; + default: + *report_index = 0x01; + *response_index = 0x01; + *wait_min = RAZER_BLACKWIDOW_CHROMA_WAIT_MIN_US; + *wait_max = RAZER_BLACKWIDOW_CHROMA_WAIT_MAX_US; + break; + } +} + +/** + * Send report to the keyboard + */ +static int razer_get_report(struct usb_device *usb_dev, struct razer_report *request, struct razer_report *response) +{ + uint report_index, response_index; + ulong wait_min, wait_max; + razer_get_report_params(usb_dev, &report_index, &response_index, &wait_min, &wait_max); + return razer_get_usb_response(usb_dev, report_index, request, response_index, response, wait_min, wait_max); +} + +/** + * Send report to the keyboard, but without even reading the response + */ +static int razer_send_payload_no_response(struct razer_kbd_device *device, struct razer_report *request) +{ + uint report_index, response_index; + ulong wait_min, wait_max; + + /* Except the caller to have set the transaction_id */ + WARN_ON(request->transaction_id.id == 0x00); + + razer_get_report_params(device->usb_dev, &report_index, &response_index, &wait_min, &wait_max); + return razer_send_control_msg(device->usb_dev, request, report_index, wait_min, wait_max); +} + +/** + * Function to send to device, get response, and actually check the response + */ +static int razer_send_payload(struct razer_kbd_device *device, struct razer_report *request, struct razer_report *response) +{ + int err; + + request->crc = razer_calculate_crc(request); + + mutex_lock(&device->lock); + err = razer_get_report(device->usb_dev, request, response); + mutex_unlock(&device->lock); + if (err) { + print_erroneous_report(response, "razerkbd", "Invalid Report Length"); + return err; + } + + /* Check the packet number, class and command are the same */ + if (response->remaining_packets != request->remaining_packets || + response->command_class != request->command_class || + response->command_id.id != request->command_id.id) { + print_erroneous_report(response, "razerkbd", "Response doesn't match request"); + return -EIO; + } + + switch (response->status) { + case RAZER_CMD_BUSY: + // TODO: Check if this should be an error. + // print_erroneous_report(&response, "razermouse", "Device is busy"); + break; + case RAZER_CMD_FAILURE: + print_erroneous_report(response, "razerkbd", "Command failed"); + return -EIO; + case RAZER_CMD_NOT_SUPPORTED: + print_erroneous_report(response, "razerkbd", "Command not supported"); + return -EIO; + case RAZER_CMD_TIMEOUT: + print_erroneous_report(response, "razerkbd", "Command timed out"); + return -EIO; + } + + return 0; +} + +/** + * Reads the physical layout of the keyboard. + * + * Returns a string + */ +static ssize_t razer_attr_read_kbd_layout(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = get_razer_report(0x00, 0x86, 0x02); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%02x\n", response.arguments[0]); +} + +/** + * Device mode function + */ +static void razer_set_device_mode(struct razer_kbd_device *device, unsigned char mode, unsigned char param) +{ + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (is_blade_laptop(device)) { + return; + } + + request = razer_chroma_standard_set_device_mode(mode, param); + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_BLACKWIDOW_LITE: + case USB_DEVICE_ID_RAZER_ORNATA: + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_CYNOSA_LITE: + request.transaction_id.id = 0x3F; + break; + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + request.transaction_id.id = 0x1F; + break; + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + request.transaction_id.id = 0x9F; + break; + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); +} + +/** + * Read device file "charge_level" + * + * Returns an integer which needs to be scaled from 0-255 -> 0-100 + */ +static ssize_t razer_attr_read_charge_level(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_get_battery_level(); + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + request.transaction_id.id = 0x1f; + break; + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + request.transaction_id.id = 0x9f; + break; + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + request.transaction_id.id = 0x3f; + break; + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[1]); +} + +/** + * Read device file "charge_status" + * + * Returns 0 when not charging, 1 when charging + */ +static ssize_t razer_attr_read_charge_status(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_get_charging_status(); + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + request.transaction_id.id = 0x1f; + break; + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + request.transaction_id.id = 0x9f; + break; + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + request.transaction_id.id = 0x3f; + break; + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[1]); +} + +/** + * Write device file "charge_effect" + * + * Sets charging effect. + */ +static ssize_t razer_attr_write_charge_effect(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count != 1) { + printk(KERN_WARNING "razerkbd: Incorrect number of bytes for setting the charging effect\n"); + return -EINVAL; + } + + request = razer_chroma_misc_set_dock_charge_type(buf[0]); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "charge_colour" + * + * Sets charging colour using 3 RGB bytes + */ +static ssize_t razer_attr_write_charge_colour(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + // First enable static charging effect + request = razer_chroma_misc_set_dock_charge_type(0x01); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + + if (count != 3) { + printk(KERN_WARNING "razerkbd: Charging colour mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + + request = razer_chroma_standard_set_led_rgb(NOSTORE, BATTERY_LED, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "charge_low_threshold" + */ +static ssize_t razer_attr_read_charge_low_threshold(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_get_low_battery_threshold(); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[0]); +} + +/** + * Write device file "charge_low_threshold" + * + * Sets the low battery blink threshold to the ASCII number written to this file. + */ +static ssize_t razer_attr_write_charge_low_threshold(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + unsigned char threshold = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_set_low_battery_threshold(threshold); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + return count; +} + +/** + * Write device file "game_led_state" + * + * When 1 is written (as a character, 0x31) Game mode will be enabled, if 0 is written (0x30) + * then game mode will be disabled + * + * The reason the keyboard appears as 2 keyboard devices is that one of those devices is used by + * game mode as that keyboard device is missing a super key. A hacky and over-the-top way to disable + * the super key if you ask me. + */ +static ssize_t razer_attr_write_game_led_state(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned char enabled = (unsigned char)simple_strtoul(buf, NULL, 10); + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + request = razer_chroma_standard_set_led_state(NOSTORE, GAME_LED, enabled); + request.transaction_id.id = 0x1f; + break; + default: + request = razer_chroma_standard_set_led_state(VARSTORE, GAME_LED, enabled); + request.transaction_id.id = 0xFF; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "game_led_state" + * + * Returns a string + */ +static ssize_t razer_attr_read_game_led_state(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + request = razer_chroma_standard_get_led_state(NOSTORE, GAME_LED); + request.transaction_id.id = 0x1f; + break; + default: + request = razer_chroma_standard_get_led_state(VARSTORE, GAME_LED); + request.transaction_id.id = 0xFF; + } + + razer_send_payload(device, &request, &response); + return sprintf(buf, "%d\n", response.arguments[2]); +} + +/** + * Write device file "keyswitch_optimization" + */ +static ssize_t razer_attr_write_keyswitch_optimization(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned char mode = (unsigned char)simple_strtoul(buf, NULL, 10); + + // Toggle Keyswitch Optimization + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + request = razer_chroma_misc_set_keyswitch_optimization_command1(mode); + request.transaction_id.id = 0x1f; + razer_send_payload(device, &request, &response); + request = razer_chroma_misc_set_keyswitch_optimization_command2(mode); + request.transaction_id.id = 0x1f; + razer_send_payload(device, &request, &response); + break; + default: + return -ENOSYS; + } + + return count; +} + +/** + * Read device file "keyswitch_optimization" + */ +static ssize_t razer_attr_read_keyswitch_optimization(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + int state; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + request = razer_chroma_misc_get_keyswitch_optimization(); + request.transaction_id.id = 0x1f; + break; + default: + request = razer_chroma_misc_get_keyswitch_optimization(); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + if(response.arguments[1] == 0x14) { // Either 0x00 or 0x14 + state = 0; // Typing + } else { + state = 1; // Gaming + } + + return sprintf(buf, "%d\n", state); +} + +/** + * Write device file "macro_led_state" + * + * When 1 is written (as a character, 0x31) Macro mode will be enabled, if 0 is written (0x30) + * then game mode will be disabled + */ +static ssize_t razer_attr_write_macro_led_state(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + unsigned char enabled = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_set_led_state(VARSTORE, MACRO_LED, enabled); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "macro_led_state" + * + * Returns a string + */ +static ssize_t razer_attr_read_macro_led_state(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_get_led_state(VARSTORE, MACRO_LED); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + return sprintf(buf, "%d\n", response.arguments[2]); +} + +/** + * Read device file "version" + * + * Returns a string + */ +static ssize_t razer_attr_read_version(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", DRIVER_VERSION); +} + +/** + * Read device file "device_type" + * + * Returns friendly string of device type + */ +static ssize_t razer_attr_read_device_type(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + + char *device_type; + + switch (device->usb_pid) { + + case USB_DEVICE_ID_RAZER_NOSTROMO: + device_type = "Razer Nostromo\n"; + break; + + case USB_DEVICE_ID_RAZER_ORBWEAVER: + device_type = "Razer Orbweaver\n"; + break; + + case USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA: + device_type = "Razer Orbweaver Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH: + device_type = "Razer BlackWidow Stealth\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH_EDITION: + device_type = "Razer BlackWidow Stealth Edition\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2012: + device_type = "Razer BlackWidow Ultimate 2012\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2013: + device_type = "Razer BlackWidow Ultimate 2013\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2016: + device_type = "Razer BlackWidow Ultimate 2016\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_X_ULTIMATE: + device_type = "Razer BlackWidow X Ultimate\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_TE_2014: + device_type = "Razer BlackWidow Tournament Edition 2014\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_STEALTH: + device_type = "Razer Blade Stealth\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2019: + device_type = "Razer Blade Stealth (Late 2019)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_EARLY_2020: + device_type = "Razer Blade Stealth (Early 2020)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2020: + device_type = "Razer Blade Stealth (Late 2020)\n"; + break; + + case USB_DEVICE_ID_RAZER_BOOK_2020: + device_type = "Razer Book 13 (2020)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2016: + device_type = "Razer Blade Stealth (Late 2016)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_MID_2017: + device_type = "Razer Blade Stealth (Mid 2017)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_QHD: + device_type = "Razer Blade Stealth (QHD)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2016: + device_type = "Razer Blade Pro (Late 2016)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_2018: + device_type = "Razer Blade 15 (2018)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_2018_MERCURY: + device_type = "Razer Blade 15 (2018) Mercury\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_2018_BASE: + device_type = "Razer Blade 15 (2018) Base Model\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_2019_ADV: + device_type = "Razer Blade 15 (2019) Advanced\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_2019_BASE: + device_type = "Razer Blade 15 (2019) Base Model\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_EARLY_2020_BASE: + device_type = "Razer Blade 15 Base (Early 2020)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_LATE_2020_BASE: + device_type = "Razer Blade 15 Base (Late 2020)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_MID_2019_MERCURY: + device_type = "Razer Blade 15 (Mid 2019) Mercury White\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_STUDIO_EDITION_2019: + device_type = "Razer Blade 15 Studio Edition (2019)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_LATE_2016: + device_type = "Razer Blade (Late 2016)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_PRO_2017: + device_type = "Razer Blade Pro (2017)\n"; + break; + case USB_DEVICE_ID_RAZER_BLADE_PRO_2017_FULLHD: + device_type = "Razer Blade Pro FullHD (2017)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_PRO_2019: + device_type = "Razer Blade Pro (2019)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2019: + device_type = "Razer Blade Pro (Late 2019)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_ADV_LATE_2019: + device_type = "Razer Blade Advanced (Late 2019)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_PRO_EARLY_2020: + device_type = "Razer Blade Pro (Early 2020)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2017: + device_type = "Razer Blade Stealth (Late 2017)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_2019: + device_type = "Razer Blade Stealth (2019)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_2020: + device_type = "Razer Blade 15 Advanced (2020)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2021: + device_type = "Razer Blade 15 Advanced (Early 2021)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_MID_2021: + device_type = "Razer Blade 15 Advanced (Mid 2021)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_EARLY_2021: + device_type = "Razer Blade 15 Base (Early 2021)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_2022: + device_type = "Razer Blade 15 Base (2022)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_17_PRO_EARLY_2021: + device_type = "Razer Blade 17 Pro (Early 2021)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_17_PRO_MID_2021: + device_type = "Razer Blade 17 Pro (Mid 2021)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_14_2021: + device_type = "Razer Blade 14 (2021)\n"; + break; + + case USB_DEVICE_ID_RAZER_TARTARUS: + device_type = "Razer Tartarus\n"; + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_CHROMA: + device_type = "Razer Tartarus Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + device_type = "Razer Tartarus V2\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_OVERWATCH: + device_type = "Razer BlackWidow Chroma (Overwatch)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA: + device_type = "Razer BlackWidow Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_ESSENTIAL: + device_type = "Razer Deathstalker (Essential)\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_EXPERT: + device_type = "Razer Deathstalker Expert\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_CHROMA: + device_type = "Razer DeathStalker Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_TE: + device_type = "Razer BlackWidow Chroma Tournament Edition\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_X_CHROMA: + device_type = "Razer BlackWidow X Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_X_CHROMA_TE: + device_type = "Razer BlackWidow X Chroma Tournament Edition\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_LITE: + device_type = "Razer BlackWidow Lite\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + device_type = "Razer BlackWidow 2019\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ESSENTIAL: + device_type = "Razer BlackWidow Essential\n"; + break; + + case USB_DEVICE_ID_RAZER_ORNATA: + device_type = "Razer Ornata\n"; + break; + + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + device_type = "Razer Ornata Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_ORNATA_V2: + device_type = "Razer Ornata V2\n"; + break; + + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + device_type = "Razer Ornata V3\n"; + break; + + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + device_type = "Razer Ornata V3 X\n"; + break; + + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + device_type = "Razer Ornata V3 Tenkeyless\n"; + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + device_type = "Razer Huntsman Elite\n"; + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + device_type = "Razer Huntsman Tournament Edition\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + device_type = "Razer BlackWidow Elite\n"; + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN: + device_type = "Razer Huntsman\n"; + break; + + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + device_type = "Razer Cynosa Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + device_type = "Razer Cynosa Chroma Pro\n"; + break; + + case USB_DEVICE_ID_RAZER_CYNOSA_LITE: + device_type = "Razer Cynosa Lite\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2: + device_type = "Razer BlackWidow Chroma V2\n"; + break; + + case USB_DEVICE_ID_RAZER_ANANSI: + device_type = "Razer Anansi\n"; + break; + + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + device_type = "Razer Cynosa V2\n"; + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + device_type = "Razer Huntsman Mini\n"; + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + device_type = "Razer Huntsman Mini (JP)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + device_type = "Razer BlackWidow V3\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + device_type = "Razer BlackWidow V3 Pro (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + device_type = "Razer BlackWidow V3 Pro (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + device_type = "Razer BlackWidow V3 Tenkeyless\n"; + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + device_type = "Razer Huntsman V2 Tenkeyless\n"; + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + device_type = "Razer Huntsman V2\n"; + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + device_type = "Razer Huntsman V2 Analog\n"; + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + device_type = "Razer Huntsman Mini Analog\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + device_type = "Razer BlackWidow V3 Mini Hyperspeed (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + device_type = "Razer BlackWidow V3 Mini Hyperspeed (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_17_2022: + device_type = "Razer Blade 17 (2022)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_14_2022: + device_type = "Razer Blade 14 (2022)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2022: + device_type = "Razer Blade 15 Advanced (Early 2022)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_14_2023: + device_type = "Razer Blade 14 (2023)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_14_2024: + device_type = "Razer Blade 14 (2024)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_15_2023: + device_type = "Razer Blade 15 (2023)\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + device_type = "Razer DeathStalker V2\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + device_type = "Razer DeathStalker V2 Pro (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + device_type = "Razer DeathStalker V2 Pro (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + device_type = "Razer BlackWidow V4\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + device_type = "Razer BlackWidow V4 X\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + device_type = "Razer BlackWidow V4 Pro\n"; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + device_type = "Razer BlackWidow V4 75%\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + device_type = "Razer DeathStalker V2 Pro TKL (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + device_type = "Razer DeathStalker V2 Pro TKL (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_16_2023: + device_type = "Razer Blade 16 (2023)\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_18_2023: + device_type = "Razer Blade 18 (2023)\n"; + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + device_type = "Razer Huntsman V3 Pro\n"; + break; + + case USB_DEVICE_ID_RAZER_BLADE_18_2024: + device_type = "Razer Blade 18 (2024)\n"; + break; + + default: + device_type = "Unknown Device\n"; + } + + return sprintf(buf, device_type); +} + +/** + * Write device file "macro_led_effect" + * + * When 1 is written the LED will blink, 0 will static + */ +static ssize_t razer_attr_write_macro_led_effect(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned char enabled = (unsigned char)simple_strtoul(buf, NULL, 10); + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_BLACKWIDOW_LITE: + case USB_DEVICE_ID_RAZER_ORNATA: + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ESSENTIAL: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_CYNOSA_LITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + request = razer_chroma_standard_set_led_effect(NOSTORE, MACRO_LED, enabled); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + request = razer_chroma_standard_set_led_effect(NOSTORE, MACRO_LED, enabled); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_ANANSI: + request = razer_chroma_standard_set_led_effect(NOSTORE, MACRO_LED, enabled); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + + request = razer_chroma_standard_set_led_blinking(NOSTORE, MACRO_LED); + request.transaction_id.id = 0xFF; + break; + + default: + request = razer_chroma_standard_set_led_effect(VARSTORE, MACRO_LED, enabled); + request.transaction_id.id = 0xFF; + break; + } + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "macro_led_effect" + * + * Returns a string + */ +static ssize_t razer_attr_read_macro_led_effect(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_get_led_effect(VARSTORE, MACRO_LED); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[2]); +} + +/** + * Write device file "matrix_effect_pulsate" + * + * The brightness oscillates between fully on and fully off generating a pulsing effect + */ +static ssize_t razer_attr_write_matrix_effect_pulsate(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH_EDITION: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2012: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2013: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_TE_2014: + request = razer_chroma_standard_set_led_effect(VARSTORE, LOGO_LED, CLASSIC_EFFECT_BREATHING); + request.transaction_id.id = 0xFF; + break; + + default: + request = razer_chroma_standard_set_led_effect(VARSTORE, BACKLIGHT_LED, CLASSIC_EFFECT_BREATHING); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "matrix_effect_pulsate" + * + * Returns a string + */ +static ssize_t razer_attr_read_matrix_effect_pulsate(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_get_led_effect(VARSTORE, LOGO_LED); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[2]); +} + +/** + * Read device file "profile_led_red" + * + * Actually a Yellow LED + * + * Returns a string + */ +static ssize_t razer_attr_read_profile_led_red(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA: + case USB_DEVICE_ID_RAZER_TARTARUS_CHROMA: + request = razer_chroma_standard_get_led_state(VARSTORE, BLUE_PROFILE_LED); + request.transaction_id.id = 0xFF; + break; + default: + request = razer_chroma_standard_get_led_state(VARSTORE, RED_PROFILE_LED); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[2]); +} + +/** + * Read device file "profile_led_green" + * + * Returns a string + */ +static ssize_t razer_attr_read_profile_led_green(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA: + case USB_DEVICE_ID_RAZER_TARTARUS_CHROMA: + request = razer_chroma_standard_get_led_state(VARSTORE, RED_PROFILE_LED); + request.transaction_id.id = 0xFF; + break; + default: + request = razer_chroma_standard_get_led_state(VARSTORE, GREEN_PROFILE_LED); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[2]); +} + +/** + * Read device file "profile_led_blue" + * + * Returns a string + */ +static ssize_t razer_attr_read_profile_led_blue(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA: + case USB_DEVICE_ID_RAZER_TARTARUS_CHROMA: + request = razer_chroma_standard_get_led_state(VARSTORE, GREEN_PROFILE_LED); + request.transaction_id.id = 0xFF; + break; + default: + request = razer_chroma_standard_get_led_state(VARSTORE, BLUE_PROFILE_LED); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[2]); +} + +/** + * Write device file "profile_led_red" + */ +static ssize_t razer_attr_write_profile_led_red(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + unsigned char enabled = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA: + case USB_DEVICE_ID_RAZER_TARTARUS_CHROMA: + request = razer_chroma_standard_set_led_state(VARSTORE, BLUE_PROFILE_LED, enabled); + request.transaction_id.id = 0xFF; + break; + default: + request = razer_chroma_standard_set_led_state(VARSTORE, RED_PROFILE_LED, enabled); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "profile_led_green" + */ +static ssize_t razer_attr_write_profile_led_green(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + unsigned char enabled = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA: + case USB_DEVICE_ID_RAZER_TARTARUS_CHROMA: + request = razer_chroma_standard_set_led_state(VARSTORE, RED_PROFILE_LED, enabled); + request.transaction_id.id = 0xFF; + break; + default: + request = razer_chroma_standard_set_led_state(VARSTORE, GREEN_PROFILE_LED, enabled); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + return count; +} + +/** + * Write device file "profile_led_blue" + */ +static ssize_t razer_attr_write_profile_led_blue(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + unsigned char enabled = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA: + case USB_DEVICE_ID_RAZER_TARTARUS_CHROMA: + request = razer_chroma_standard_set_led_state(VARSTORE, GREEN_PROFILE_LED, enabled); + request.transaction_id.id = 0xFF; + break; + default: + request = razer_chroma_standard_set_led_state(VARSTORE, BLUE_PROFILE_LED, enabled); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + return count; +} + +/** + * Read device file "device_serial" + * + * Returns a string + */ +static ssize_t razer_attr_read_device_serial(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + char serial_string[51]; + struct razer_report request = {0}; + struct razer_report response = {0}; + + /* For Blade laptops we get the serial number from DMI */ + if (is_blade_laptop(device)) { + strncpy(&serial_string[0], dmi_get_system_info(DMI_PRODUCT_SERIAL), 50); + goto exit; + } + + request = razer_chroma_standard_get_serial(); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + strncpy(&serial_string[0], &response.arguments[0], 22); + serial_string[22] = '\0'; + +exit: + return sprintf(buf, "%s\n", &serial_string[0]); +} + +/** + * Read device file "firmware_version" + * + * Returns a string + */ +static ssize_t razer_attr_read_firmware_version(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_get_firmware_version(); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "v%d.%d\n", response.arguments[0], response.arguments[1]); +} + +/** + * Write device file "matrix_effect_none" + * + * No keyboard effect is activated whenever this file is written to + */ +static ssize_t razer_attr_write_matrix_effect_none(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_BLACKWIDOW_LITE: + case USB_DEVICE_ID_RAZER_ORNATA: + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ESSENTIAL: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_CYNOSA_LITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + request = razer_chroma_extended_matrix_effect_none(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + request = razer_chroma_extended_matrix_effect_none(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + request = razer_chroma_extended_matrix_effect_none(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x9F; + break; + + case USB_DEVICE_ID_RAZER_ANANSI: + request = razer_chroma_standard_set_led_state(VARSTORE, BACKLIGHT_LED, OFF); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2: + request = razer_chroma_standard_matrix_effect_none(); + request.transaction_id.id = 0x3F; + break; + + default: + request = razer_chroma_standard_matrix_effect_none(); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "matrix_effect_wave" + * + * When 1 is written (as a character, 0x31) the wave effect is displayed moving left across the keyboard + * if 2 is written (0x32) then the wave effect goes right + * + * For the extended its 0x00 and 0x01 + */ +static ssize_t razer_attr_write_matrix_effect_wave(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + unsigned char direction = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORNATA: + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ESSENTIAL: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + request = razer_chroma_extended_matrix_effect_wave(VARSTORE, BACKLIGHT_LED, direction); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2: + request = razer_chroma_standard_matrix_effect_wave(direction); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + // Direction values are flipped compared to other devices + direction ^= ((1<<0) | (1<<1)); + request = razer_chroma_extended_matrix_effect_wave(VARSTORE, BACKLIGHT_LED, direction); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + request = razer_chroma_extended_matrix_effect_wave(VARSTORE, BACKLIGHT_LED, direction); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + request = razer_chroma_extended_matrix_effect_wave(VARSTORE, BACKLIGHT_LED, direction); + request.transaction_id.id = 0x9F; + break; + + default: + request = razer_chroma_standard_matrix_effect_wave(direction); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "matrix_effect_wheel" + * + * When 1 is written (as a character, 0x31) the wheel effect is turning right + * if 2 is written (0x32) then the wheel effect goes left. + */ +static ssize_t razer_attr_write_matrix_effect_wheel(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + unsigned char direction = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + request = razer_chroma_extended_matrix_effect_wheel(VARSTORE, BACKLIGHT_LED, direction); + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razerkbd: matrix_effect_wheel not supported for this model\n"); + return -EINVAL; + } + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "matrix_effect_spectrum" + * + * Spectrum effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_matrix_effect_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORNATA: + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_CYNOSA_LITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + request = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_2022: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + request = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + request = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x9F; + break; + + case USB_DEVICE_ID_RAZER_ANANSI: + request = razer_chroma_standard_set_led_state(VARSTORE, BACKLIGHT_LED, ON); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + request = razer_chroma_standard_set_led_effect(VARSTORE, BACKLIGHT_LED, CLASSIC_EFFECT_SPECTRUM); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + request = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2: + request = razer_chroma_standard_matrix_effect_spectrum(); + request.transaction_id.id = 0x3F; + break; + + default: + request = razer_chroma_standard_matrix_effect_spectrum(); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "matrix_effect_reactive" + * + * Sets reactive mode when this file is written to. A speed byte and 3 RGB bytes should be written + */ +static ssize_t razer_attr_write_matrix_effect_reactive(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned char speed; + + if (count != 4) { + printk(KERN_WARNING "razerkbd: Reactive only accepts Speed, RGB (4byte)\n"); + return -EINVAL; + } + + speed = (unsigned char)buf[0]; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORNATA: + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + request = razer_chroma_extended_matrix_effect_reactive(VARSTORE, BACKLIGHT_LED, speed, (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_2022: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + request = razer_chroma_extended_matrix_effect_reactive(VARSTORE, BACKLIGHT_LED, speed, (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + request = razer_chroma_extended_matrix_effect_reactive(VARSTORE, BACKLIGHT_LED, speed, (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0x9F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2: + request = razer_chroma_standard_matrix_effect_reactive(speed, (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0x3F; + break; + default: + request = razer_chroma_standard_matrix_effect_reactive(speed, (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "matrix_effect_static" + * + * Set the keyboard to static mode when 3 RGB bytes are written + */ +static ssize_t razer_attr_write_matrix_effect_static(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORBWEAVER: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_EXPERT: + request = razer_chroma_standard_set_led_effect(VARSTORE, BACKLIGHT_LED, CLASSIC_EFFECT_STATIC); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH_EDITION: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2012: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2013: // Doesn't need any parameters as can only do one type of static + case USB_DEVICE_ID_RAZER_BLACKWIDOW_TE_2014: + request = razer_chroma_standard_set_led_effect(VARSTORE, LOGO_LED, CLASSIC_EFFECT_STATIC); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_OVERWATCH: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_CHROMA: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_X_CHROMA: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_X_CHROMA_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2016: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_X_ULTIMATE: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2016: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_MID_2017: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2017: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_2019: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2019: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_EARLY_2020: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2020: + case USB_DEVICE_ID_RAZER_BOOK_2020: + case USB_DEVICE_ID_RAZER_BLADE_QHD: + case USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2016: + case USB_DEVICE_ID_RAZER_BLADE_2018: + case USB_DEVICE_ID_RAZER_BLADE_2018_MERCURY: + case USB_DEVICE_ID_RAZER_BLADE_2018_BASE: + case USB_DEVICE_ID_RAZER_BLADE_2019_ADV: + case USB_DEVICE_ID_RAZER_BLADE_2019_BASE: + case USB_DEVICE_ID_RAZER_BLADE_EARLY_2020_BASE: + case USB_DEVICE_ID_RAZER_BLADE_LATE_2020_BASE: + case USB_DEVICE_ID_RAZER_BLADE_MID_2019_MERCURY: + case USB_DEVICE_ID_RAZER_BLADE_STUDIO_EDITION_2019: + case USB_DEVICE_ID_RAZER_BLADE_LATE_2016: + case USB_DEVICE_ID_RAZER_BLADE_PRO_2017: + case USB_DEVICE_ID_RAZER_BLADE_PRO_2017_FULLHD: + case USB_DEVICE_ID_RAZER_BLADE_PRO_2019: + case USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2019: + case USB_DEVICE_ID_RAZER_BLADE_ADV_LATE_2019: + case USB_DEVICE_ID_RAZER_BLADE_PRO_EARLY_2020: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_2020: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_MID_2021: + case USB_DEVICE_ID_RAZER_BLADE_17_PRO_EARLY_2021: + case USB_DEVICE_ID_RAZER_BLADE_17_PRO_MID_2021: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2021: + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_EARLY_2021: + case USB_DEVICE_ID_RAZER_BLADE_14_2021: + case USB_DEVICE_ID_RAZER_TARTARUS: + case USB_DEVICE_ID_RAZER_TARTARUS_CHROMA: + case USB_DEVICE_ID_RAZER_BLADE_17_2022: + case USB_DEVICE_ID_RAZER_BLADE_14_2022: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2022: + case USB_DEVICE_ID_RAZER_BLADE_14_2023: + case USB_DEVICE_ID_RAZER_BLADE_15_2023: + case USB_DEVICE_ID_RAZER_BLADE_16_2023: + case USB_DEVICE_ID_RAZER_BLADE_18_2023: + case USB_DEVICE_ID_RAZER_BLADE_14_2024: + case USB_DEVICE_ID_RAZER_BLADE_18_2024: + if (count != 3) { + printk(KERN_WARNING "razerkbd: Static mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + request = razer_chroma_standard_matrix_effect_static((struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2: + case USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA: + if (count != 3) { + printk(KERN_WARNING "razerkbd: Static mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + request = razer_chroma_standard_matrix_effect_static((struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_LITE: + case USB_DEVICE_ID_RAZER_ORNATA: + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ESSENTIAL: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_CYNOSA_LITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + if (count != 3) { + printk(KERN_WARNING "razerkbd: Static mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + request = razer_chroma_extended_matrix_effect_static(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_2022: + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + if (count != 3) { + printk(KERN_WARNING "razerkbd: Static mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + request = razer_chroma_extended_matrix_effect_static(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x1F; + razer_send_payload(device, &request, &response); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + if (count != 3) { + printk(KERN_WARNING "razerkbd: Static mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + request = razer_chroma_extended_matrix_effect_static(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x9F; + razer_send_payload(device, &request, &response); + break; + + case USB_DEVICE_ID_RAZER_ANANSI: + if (count != 3) { + printk(KERN_WARNING "razerkbd: Static mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + request = razer_chroma_standard_set_led_state(VARSTORE, BACKLIGHT_LED, ON); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + request = razer_chroma_standard_set_led_effect(VARSTORE, BACKLIGHT_LED, CLASSIC_EFFECT_STATIC); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + request = razer_chroma_standard_set_led_rgb(VARSTORE, BACKLIGHT_LED, (struct razer_rgb *) &buf[0]); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + break; + + default: + printk(KERN_WARNING "razerkbd: Cannot set static mode for this device\n"); + return -EINVAL; + } + + return count; +} + +/** + * Write device file "matrix_effect_starlight" + * + * Starlight keyboard effect is activated whenever this file is written to (for bw2016) + * + * Or if an Ornata + * 7 bytes, speed, rgb, rgb + * 4 bytes, speed, rgb + * 1 byte, speed + */ +static ssize_t razer_attr_write_matrix_effect_starlight(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_rgb rgb1 = {.r = 0x00, .g = 0xFF, .b = 0x00}; + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORNATA: + if (count != 4) { + printk(KERN_WARNING "razerkbd: Starlight only accepts Speed (1byte). Speed, RGB (4byte). Speed, RGB, RGB (7byte)\n"); + return -EINVAL; + } + request = razer_chroma_extended_matrix_effect_starlight_single(VARSTORE, BACKLIGHT_LED, buf[0], (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + break; + + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + if(count == 7) { + request = razer_chroma_extended_matrix_effect_starlight_dual(VARSTORE, BACKLIGHT_LED, buf[0], (struct razer_rgb*)&buf[1], (struct razer_rgb*)&buf[4]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + } else if(count == 4) { + request = razer_chroma_extended_matrix_effect_starlight_single(VARSTORE, BACKLIGHT_LED, buf[0], (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + } else if(count == 1) { + request = razer_chroma_extended_matrix_effect_starlight_random(VARSTORE, BACKLIGHT_LED, buf[0]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + } else { + printk(KERN_WARNING "razerkbd: Starlight only accepts Speed (1byte). Speed, RGB (4byte). Speed, RGB, RGB (7byte)\n"); + return -EINVAL; + } + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + if (count == 7) { + request = razer_chroma_extended_matrix_effect_starlight_dual(VARSTORE, BACKLIGHT_LED, buf[0], (struct razer_rgb*)&buf[1], (struct razer_rgb*)&buf[4]); + } else if(count == 4) { + request = razer_chroma_extended_matrix_effect_starlight_single(VARSTORE, BACKLIGHT_LED, buf[0], (struct razer_rgb*)&buf[1]); + } else if(count == 1) { + request = razer_chroma_extended_matrix_effect_starlight_random(VARSTORE, BACKLIGHT_LED, buf[0]); + } else { + printk(KERN_WARNING "razerkbd: Starlight only accepts Speed (1byte). Speed, RGB (4byte). Speed, RGB, RGB (7byte)\n"); + return -EINVAL; + } + request.transaction_id.id = 0x1F; + razer_send_payload(device, &request, &response); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + if (count == 7) { + request = razer_chroma_extended_matrix_effect_starlight_dual(VARSTORE, BACKLIGHT_LED, buf[0], (struct razer_rgb*)&buf[1], (struct razer_rgb*)&buf[4]); + } else if(count == 4) { + request = razer_chroma_extended_matrix_effect_starlight_single(VARSTORE, BACKLIGHT_LED, buf[0], (struct razer_rgb*)&buf[1]); + } else if(count == 1) { + request = razer_chroma_extended_matrix_effect_starlight_random(VARSTORE, BACKLIGHT_LED, buf[0]); + } else { + printk(KERN_WARNING "razerkbd: Starlight only accepts Speed (1byte). Speed, RGB (4byte). Speed, RGB, RGB (7byte)\n"); + return -EINVAL; + } + request.transaction_id.id = 0x9F; + razer_send_payload(device, &request, &response); + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + if(count == 7) { + request = razer_chroma_extended_matrix_effect_starlight_dual(VARSTORE, BACKLIGHT_LED, buf[0], (struct razer_rgb*)&buf[1], (struct razer_rgb*)&buf[4]); + request.transaction_id.id = 0x1F; + razer_send_payload(device, &request, &response); + } else if(count == 4) { + request = razer_chroma_extended_matrix_effect_starlight_single(VARSTORE, BACKLIGHT_LED, buf[0], (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0x1F; + razer_send_payload(device, &request, &response); + } else if(count == 1) { + request = razer_chroma_extended_matrix_effect_starlight_random(VARSTORE, BACKLIGHT_LED, buf[0]); + request.transaction_id.id = 0x1F; + razer_send_payload(device, &request, &response); + } else { + printk(KERN_WARNING "razerkbd: Starlight only accepts Speed (1byte). Speed, RGB (4byte). Speed, RGB, RGB (7byte)\n"); + return -EINVAL; + } + break; + + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2017: + case USB_DEVICE_ID_RAZER_BLADE_17_PRO_EARLY_2021: + if(count == 7) { + request = razer_chroma_standard_matrix_effect_starlight_dual(buf[0], (struct razer_rgb*)&buf[1], (struct razer_rgb*)&buf[4]); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + } else if(count == 4) { + request = razer_chroma_standard_matrix_effect_starlight_single(buf[0], (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + } else if(count == 1) { + request = razer_chroma_standard_matrix_effect_starlight_random(buf[0]); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + } else { + printk(KERN_WARNING "razerkbd: Starlight only accepts Speed (1byte). Speed, RGB (4byte). Speed, RGB, RGB (7byte)\n"); + return -EINVAL; + } + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2: + if(count == 7) { + request = razer_chroma_standard_matrix_effect_starlight_dual(buf[0], (struct razer_rgb*)&buf[1], (struct razer_rgb*)&buf[4]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + } else if(count == 4) { + request = razer_chroma_standard_matrix_effect_starlight_single(buf[0], (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + } else if(count == 1) { + request = razer_chroma_standard_matrix_effect_starlight_random(buf[0]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + } else { + printk(KERN_WARNING "razerkbd: Starlight only accepts Speed (1byte). Speed, RGB (4byte). Speed, RGB, RGB (7byte)\n"); + return -EINVAL; + } + break; + + default: // BW2016 can do normal starlight + request = razer_chroma_standard_matrix_effect_starlight_single(0x01, &rgb1); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + break; + } + + return count; +} + +/** + * Write device file "matrix_effect_breath" + */ +static ssize_t razer_attr_write_matrix_effect_breath(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_BLACKWIDOW_LITE: + case USB_DEVICE_ID_RAZER_ORNATA: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_extended_matrix_effect_breathing_single(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + break; + + default: + printk(KERN_WARNING "razerkbd: Breathing only accepts '1' (1byte). RGB (3byte). RGB, RGB (6byte)\n"); + return -EINVAL; + } + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_2022: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_extended_matrix_effect_breathing_single(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + request.transaction_id.id = 0x1F; + break; + + case 6: // Dual colour mode + request = razer_chroma_extended_matrix_effect_breathing_dual(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0], (struct razer_rgb*)&buf[3]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + request.transaction_id.id = 0x1F; + break; + + case 1: // "Random" colour mode + request = razer_chroma_extended_matrix_effect_breathing_random(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + request.transaction_id.id = 0x1F; + break; + + default: + printk(KERN_WARNING "razerkbd: Breathing only accepts '1' (1byte). RGB (3byte). RGB, RGB (6byte)\n"); + return -EINVAL; + } + break; + + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ESSENTIAL: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_CYNOSA_LITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_extended_matrix_effect_breathing_single(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + break; + + case 6: // Dual colour mode + request = razer_chroma_extended_matrix_effect_breathing_dual(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0], (struct razer_rgb*)&buf[3]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + break; + + case 1: // "Random" colour mode + request = razer_chroma_extended_matrix_effect_breathing_random(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + break; + + default: + printk(KERN_WARNING "razerkbd: Breathing only accepts '1' (1byte). RGB (3byte). RGB, RGB (6byte)\n"); + return -EINVAL; + } + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + if (count == 3) { // Single colour mode + request = razer_chroma_extended_matrix_effect_breathing_single(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0]); + } else if (count == 6) { // Dual colour mode + request = razer_chroma_extended_matrix_effect_breathing_dual(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0], (struct razer_rgb*)&buf[3]); + } else if (count == 1) { // "Random" colour mode + request = razer_chroma_extended_matrix_effect_breathing_random(VARSTORE, BACKLIGHT_LED); + } else { + printk(KERN_WARNING "razerkbd: Breathing only accepts '1' (1byte). RGB (3byte). RGB, RGB (6byte)\n"); + return -EINVAL; + } + request.transaction_id.id = 0x1F; + razer_send_payload(device, &request, &response); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + if (count == 3) { // Single colour mode + request = razer_chroma_extended_matrix_effect_breathing_single(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0]); + } else if (count == 6) { // Dual colour mode + request = razer_chroma_extended_matrix_effect_breathing_dual(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0], (struct razer_rgb*)&buf[3]); + } else if (count == 1) { // "Random" colour mode + request = razer_chroma_extended_matrix_effect_breathing_random(VARSTORE, BACKLIGHT_LED); + } else { + printk(KERN_WARNING "razerkbd: Breathing only accepts '1' (1byte). RGB (3byte). RGB, RGB (6byte)\n"); + return -EINVAL; + } + request.transaction_id.id = 0x9F; + razer_send_payload(device, &request, &response); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_standard_matrix_effect_breathing_single((struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + break; + + case 6: // Dual colour mode + request = razer_chroma_standard_matrix_effect_breathing_dual((struct razer_rgb*)&buf[0], (struct razer_rgb*)&buf[3]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + break; + + default: // "Random" colour mode + request = razer_chroma_standard_matrix_effect_breathing_random(); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + break; + // TODO move default to case 1:. Then default: printk(warning). Also remove pointless buffer + } + break; + + default: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_standard_matrix_effect_breathing_single((struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + break; + + case 6: // Dual colour mode + request = razer_chroma_standard_matrix_effect_breathing_dual((struct razer_rgb*)&buf[0], (struct razer_rgb*)&buf[3]); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + break; + + default: // "Random" colour mode + request = razer_chroma_standard_matrix_effect_breathing_random(); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + break; + // TODO move default to case 1:. Then default: printk(warning). Also remove pointless buffer + } + break; + } + + return count; +} + +static int has_inverted_led_state(struct device *dev) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2016: + case USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2016: + case USB_DEVICE_ID_RAZER_BLADE_QHD: + case USB_DEVICE_ID_RAZER_BLADE_LATE_2016: + return 1; + default: + return 0; + } +} + +/** + * Reads device file "logo_led_state" + * + * Reads the logo lighting state (the ASCII number) written to this file. + */ +static ssize_t razer_attr_read_logo_led_state(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + int state; + + request = razer_chroma_standard_get_led_effect(VARSTORE, LOGO_LED); + request.transaction_id.id = 0xFF; + + // Blade laptops don't use effect for logo on/off, and mode 2 ("blink") is technically unsupported. + if (is_blade_laptop(device)) { + request = razer_chroma_standard_get_led_state(VARSTORE, LOGO_LED); + request.transaction_id.id = 0xFF; + } + + razer_send_payload(device, &request, &response); + state = response.arguments[2]; + + if (has_inverted_led_state(dev) && (state == 0 || state == 1)) + state = !state; + + return sprintf(buf, "%d\n", state); +} + +/** + * Write device file "logo_led_state" + * + * Sets the logo lighting state to the ASCII number written to this file. + */ +static ssize_t razer_attr_write_logo_led_state(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + unsigned char state = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (has_inverted_led_state(dev) && (state == 0 || state == 1)) + state = !state; + + // Blade laptops are... different. They use state instead of effect. + // Note: This does allow setting of mode 2 ("blink"), but this is an undocumented feature. + if (is_blade_laptop(device) && (state == 0 || state == 1)) { + request = razer_chroma_standard_set_led_state(VARSTORE, LOGO_LED, state); + request.transaction_id.id = 0xFF; + } else { + request = razer_chroma_standard_set_led_effect(VARSTORE, LOGO_LED, state); + request.transaction_id.id = 0xFF; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "matrix_effect_custom" + * + * Sets the keyboard to custom mode whenever the file is written to + */ +static ssize_t razer_attr_write_matrix_effect_custom(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + bool want_response = true; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORNATA: + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + request = razer_chroma_extended_matrix_effect_custom_frame(); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + request = razer_chroma_extended_matrix_effect_custom_frame(); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + request = razer_chroma_extended_matrix_effect_custom_frame(); + request.transaction_id.id = 0x1F; + want_response = false; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + request = razer_chroma_extended_matrix_effect_custom_frame(); + request.transaction_id.id = 0x9F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2: + case USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA: + request = razer_chroma_standard_matrix_effect_custom_frame(NOSTORE); + request.transaction_id.id = 0x3F; + break; + + default: + request = razer_chroma_standard_matrix_effect_custom_frame(NOSTORE); + request.transaction_id.id = 0xFF; + break; + } + + /* See comment in razer_attr_write_matrix_custom_frame for want_response */ + if (want_response) + razer_send_payload(device, &request, &response); + else + razer_send_payload_no_response(device, &request); + + return count; +} + +/** + * Write device file "fn_toggle" + * + * Sets the logo lighting state to the ASCII number written to this file. + */ +static ssize_t razer_attr_write_fn_toggle(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + unsigned char state = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_fn_key_toggle(state); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "test" + * + * Does nothing + */ +static ssize_t razer_attr_write_test(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return count; +} + +/** + * Read device file "test" + * + * Returns a string + */ +static ssize_t razer_attr_read_test(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = get_razer_report(0x00, 0x86, 0x02); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + print_erroneous_report(&response, "razerkbd", "Test"); + return sprintf(buf, "%02x%02x%02x\n", response.arguments[0], response.arguments[1], response.arguments[2]); +} + +/** + * Write device file "matrix_brightness" + * + * Sets the brightness to the ASCII number written to this file. + */ +static ssize_t razer_attr_write_matrix_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + unsigned char brightness = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + request = razer_chroma_extended_matrix_brightness(VARSTORE, ZERO_LED, brightness); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_LITE: + case USB_DEVICE_ID_RAZER_ORNATA: + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ESSENTIAL: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_CYNOSA_LITE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + request = razer_chroma_extended_matrix_brightness(VARSTORE, BACKLIGHT_LED, brightness); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + request = razer_chroma_extended_matrix_brightness(VARSTORE, BACKLIGHT_LED, brightness); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + request = razer_chroma_extended_matrix_brightness(VARSTORE, BACKLIGHT_LED, brightness); + request.transaction_id.id = 0x9F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH_EDITION: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2012: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2013: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_TE_2014: + request = razer_chroma_standard_set_led_brightness(VARSTORE, LOGO_LED, brightness); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_NOSTROMO: + default: + if (is_blade_laptop(device)) { + request = razer_chroma_misc_set_blade_brightness(brightness); + } else { + request = razer_chroma_standard_set_led_brightness(VARSTORE, BACKLIGHT_LED, brightness); + } + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "matrix_brightness" + * + * Returns a string + */ +static ssize_t razer_attr_read_matrix_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + unsigned char brightness = 0; + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + request = razer_chroma_extended_matrix_get_brightness(VARSTORE, ZERO_LED); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_LITE: + case USB_DEVICE_ID_RAZER_ORNATA: + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ESSENTIAL: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_CYNOSA_LITE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + request = razer_chroma_extended_matrix_get_brightness(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + request = razer_chroma_extended_matrix_get_brightness(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + request = razer_chroma_extended_matrix_get_brightness(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x9F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH_EDITION: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2012: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2013: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_TE_2014: + request = razer_chroma_standard_get_led_brightness(VARSTORE, LOGO_LED); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_NOSTROMO: + default: + if (is_blade_laptop(device)) { + request = razer_chroma_misc_get_blade_brightness(); + } else { + request = razer_chroma_standard_get_led_brightness(VARSTORE, BACKLIGHT_LED); + } + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + // Brightness is stored elsewhere for the stealth cmds + if (is_blade_laptop(device)) { + brightness = response.arguments[1]; + } else { + brightness = response.arguments[2]; + } + + return sprintf(buf, "%d\n", brightness); +} + +/** + * Write device file "device_mode" + */ +static ssize_t razer_attr_write_device_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count != 2) { + printk(KERN_WARNING "razerkbd: Device mode only takes 2 bytes.\n"); + return -EINVAL; + } + + // No-op on Blades + if (is_blade_laptop(device)) { + return count; + } + + request = razer_chroma_standard_set_device_mode(buf[0], buf[1]); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "device_mode" + * + * Returns a string + */ +static ssize_t razer_attr_read_device_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_get_device_mode(); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + buf[0] = response.arguments[0]; + buf[1] = response.arguments[1]; + + return 2; +} + +/** + * Write device file "matrix_custom_frame" + * + * Format + * ROW_ID START_COL STOP_COL RGB... + */ +static ssize_t razer_attr_write_matrix_custom_frame(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + size_t offset = 0; + unsigned char row_id, start_col, stop_col; + size_t row_length; + bool want_response = true; + + while(offset < count) { + if(offset + 3 > count) { + printk(KERN_ALERT "razerkbd: Wrong Amount of data provided: Should be ROW_ID, START_COL, STOP_COL, N_RGB\n"); + return -EINVAL; + } + + row_id = buf[offset++]; + start_col = buf[offset++]; + stop_col = buf[offset++]; + + // Validate parameters + if(start_col > stop_col) { + printk(KERN_ALERT "razerkbd: Start column (%u) is greater than end column (%u)\n", start_col, stop_col); + return -EINVAL; + } + + row_length = ((stop_col + 1) - start_col) * 3; + + // Make sure we actually got the data that was promised to us + if(count < offset + row_length) { + printk(KERN_ALERT "razerkbd: Not enough RGB to fill row (expecting %lu bytes of RGB data, got %lu)\n", row_length, (count - 3)); + return -EINVAL; + } + + // printk(KERN_INFO "razerkbd: Row ID: %u, Start: %u, Stop: %u, row length: %lu\n", row_id, start_col, stop_col, row_length); + + // Offset now at beginning of RGB data + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ORNATA: + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + request = razer_chroma_extended_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + request = razer_chroma_extended_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + request = razer_chroma_extended_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0x1F; + want_response = false; + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + request = razer_chroma_extended_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0x9F; + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_CHROMA: + request = razer_chroma_misc_one_row_set_custom_frame(start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_BLADE_LATE_2016: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2: + case USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA: + request = razer_chroma_standard_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0x3F; + break; + + default: + request = razer_chroma_standard_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0xFF; + break; + } + + /* + * Some devices don't like us asking for responses for custom frame + * requests. And in any case it shouldn't be necessary for most devices + * but let's keep it enabled by default for now to not potentially + * break anything. + */ + if (want_response) + razer_send_payload(device, &request, &response); + else + razer_send_payload_no_response(device, &request); + + // *3 as its 3 bytes per col (RGB) + offset += row_length; + } + + return count; +} + +/** + * Read device file "poll_rate" + * + * Returns a string + */ +static ssize_t razer_attr_read_poll_rate(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned short polling_rate = 0; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + request = razer_chroma_misc_get_polling_rate2(); + request.transaction_id.id = 0x1f; + break; + + default: + request = razer_chroma_misc_get_polling_rate(); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + switch(response.arguments[1]) { + case 0x01: + polling_rate = 8000; + break; + case 0x02: + polling_rate = 4000; + break; + case 0x04: + polling_rate = 2000; + break; + case 0x08: + polling_rate = 1000; + break; + case 0x10: + polling_rate = 500; + break; + case 0x20: + polling_rate = 250; + break; + case 0x40: + polling_rate = 125; + break; + } + + return sprintf(buf, "%d\n", polling_rate); +} + +/** + * Write device file "poll_rate" + * + * Sets the poll rate + */ +static ssize_t razer_attr_write_poll_rate(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + unsigned short polling_rate = (unsigned short)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + request = razer_chroma_misc_set_polling_rate2(polling_rate, 0x00); + request.transaction_id.id = 0x1f; + break; + default: + request = razer_chroma_misc_set_polling_rate(polling_rate); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "key_super" + */ +static ssize_t razer_attr_write_key_super(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + + if (count < 1) { + printk(KERN_ALERT "razerkbd: Failed to provide argument\n"); + return -EINVAL; + } + + device->block_keys[0] = buf[0]; + + return count; +} + +/** + * Read device file "key_super" + */ +static ssize_t razer_attr_read_key_super(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + + buf[0] = device->block_keys[0]; + + return 1; +} + +/** + * Write device file "key_alt_tab" + */ +static ssize_t razer_attr_write_key_alt_tab(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + + if (count < 1) { + printk(KERN_ALERT "razerkbd: Failed to provide argument\n"); + return -EINVAL; + } + + printk(KERN_WARNING "razerkbd: Settings block_keys[1] to %u\n", buf[0]); + device->block_keys[1] = buf[0]; + + return count; +} + +/** + * Read device file "read_key_alt_tab" + */ +static ssize_t razer_attr_read_key_alt_tab(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + + buf[0] = device->block_keys[1]; + + return 1; +} + +/** + * Write device file "write_key_alt_f4" + */ +static ssize_t razer_attr_write_key_alt_f4(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + + if (count < 1) { + printk(KERN_ALERT "razerkbd: Failed to provide argument\n"); + return -EINVAL; + } + + device->block_keys[2] = buf[0]; + + return count; +} + +/** + * Read device file "read_key_alt_f4" + */ +static ssize_t razer_attr_read_key_alt_f4(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kbd_device *device = dev_get_drvdata(dev); + + buf[0] = device->block_keys[2]; + + return 1; +} + +/** + * Set up the device driver files + + * + * Read only is 0444 + * Write only is 0220 + * Read and write is 0664 + */ +static DEVICE_ATTR(game_led_state, 0660, razer_attr_read_game_led_state, razer_attr_write_game_led_state); +static DEVICE_ATTR(macro_led_state, 0660, razer_attr_read_macro_led_state, razer_attr_write_macro_led_state); +static DEVICE_ATTR(macro_led_effect, 0660, razer_attr_read_macro_led_effect, razer_attr_write_macro_led_effect); +static DEVICE_ATTR(logo_led_state, 0660, razer_attr_read_logo_led_state, razer_attr_write_logo_led_state); +static DEVICE_ATTR(profile_led_red, 0660, razer_attr_read_profile_led_red, razer_attr_write_profile_led_red); +static DEVICE_ATTR(profile_led_green, 0660, razer_attr_read_profile_led_green, razer_attr_write_profile_led_green); +static DEVICE_ATTR(profile_led_blue, 0660, razer_attr_read_profile_led_blue, razer_attr_write_profile_led_blue); + +static DEVICE_ATTR(test, 0660, razer_attr_read_test, razer_attr_write_test); +static DEVICE_ATTR(version, 0440, razer_attr_read_version, NULL); +static DEVICE_ATTR(kbd_layout, 0440, razer_attr_read_kbd_layout, NULL); + +static DEVICE_ATTR(firmware_version, 0440, razer_attr_read_firmware_version, NULL); +static DEVICE_ATTR(fn_toggle, 0220, NULL, razer_attr_write_fn_toggle); +static DEVICE_ATTR(poll_rate, 0660, razer_attr_read_poll_rate, razer_attr_write_poll_rate); +static DEVICE_ATTR(keyswitch_optimization, 0660, razer_attr_read_keyswitch_optimization, razer_attr_write_keyswitch_optimization); + +static DEVICE_ATTR(device_type, 0440, razer_attr_read_device_type, NULL); +static DEVICE_ATTR(device_mode, 0660, razer_attr_read_device_mode, razer_attr_write_device_mode); +static DEVICE_ATTR(device_serial, 0440, razer_attr_read_device_serial, NULL); + +static DEVICE_ATTR(matrix_effect_none, 0220, NULL, razer_attr_write_matrix_effect_none); +static DEVICE_ATTR(matrix_effect_wave, 0220, NULL, razer_attr_write_matrix_effect_wave); +static DEVICE_ATTR(matrix_effect_wheel, 0220, NULL, razer_attr_write_matrix_effect_wheel); +static DEVICE_ATTR(matrix_effect_spectrum, 0220, NULL, razer_attr_write_matrix_effect_spectrum); +static DEVICE_ATTR(matrix_effect_reactive, 0220, NULL, razer_attr_write_matrix_effect_reactive); +static DEVICE_ATTR(matrix_effect_static, 0220, NULL, razer_attr_write_matrix_effect_static); +static DEVICE_ATTR(matrix_effect_starlight, 0220, NULL, razer_attr_write_matrix_effect_starlight); +static DEVICE_ATTR(matrix_effect_breath, 0220, NULL, razer_attr_write_matrix_effect_breath); +static DEVICE_ATTR(matrix_effect_pulsate, 0660, razer_attr_read_matrix_effect_pulsate, razer_attr_write_matrix_effect_pulsate); +static DEVICE_ATTR(matrix_brightness, 0660, razer_attr_read_matrix_brightness, razer_attr_write_matrix_brightness); +static DEVICE_ATTR(matrix_effect_custom, 0220, NULL, razer_attr_write_matrix_effect_custom); +static DEVICE_ATTR(matrix_custom_frame, 0220, NULL, razer_attr_write_matrix_custom_frame); + +static DEVICE_ATTR(key_super, 0660, razer_attr_read_key_super, razer_attr_write_key_super); +static DEVICE_ATTR(key_alt_tab, 0660, razer_attr_read_key_alt_tab, razer_attr_write_key_alt_tab); +static DEVICE_ATTR(key_alt_f4, 0660, razer_attr_read_key_alt_f4, razer_attr_write_key_alt_f4); + +static DEVICE_ATTR(charge_level, 0440, razer_attr_read_charge_level, NULL); +static DEVICE_ATTR(charge_status, 0440, razer_attr_read_charge_status, NULL); +static DEVICE_ATTR(charge_effect, 0220, NULL, razer_attr_write_charge_effect); +static DEVICE_ATTR(charge_colour, 0220, NULL, razer_attr_write_charge_colour); +static DEVICE_ATTR(charge_low_threshold, 0660, razer_attr_read_charge_low_threshold, razer_attr_write_charge_low_threshold); + +/** + * Deal with FN toggle + */ +static int razer_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value) +{ + struct razer_kbd_device *device = hid_get_drvdata(hdev); + const struct razer_key_translation *translation; + + // No translations needed on the Blades + if (is_blade_laptop(device)) { + return 0; + } + + if(device->usb_interface_protocol == USB_INTERFACE_PROTOCOL_MOUSE) { + // Skip this if its control (mouse) interface + return 0; + } + + // Block win key + if(device->block_keys[0] && (usage->code == KEY_LEFTMETA || usage->code == KEY_RIGHTMETA)) { + return 1; + } + + // Store Alt state + if(usage->code == KEY_LEFTALT) { + device->left_alt_on = value; + } + // Block Alt-Tab + if(device->block_keys[1] && device->left_alt_on && usage->code == KEY_TAB) { + return 1; + } + // Block Alt-F4 + if(device->block_keys[2] && device->left_alt_on && usage->code == KEY_F4) { + return 1; + } + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2012: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH_EDITION: + translation = find_translation(chroma_keys_2, usage->code); + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + translation = find_translation(chroma_keys_3, usage->code); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + translation = find_translation(chroma_keys_4, usage->code); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + translation = find_translation(chroma_keys_5, usage->code); + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + translation = find_translation(chroma_keys_7, usage->code); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + translation = find_translation(chroma_keys_6, usage->code); + break; + + default: + translation = find_translation(chroma_keys, usage->code); + break; + } + + if(translation) { + if (test_bit(usage->code, device->pressed_fn) || device->fn_on) { + if (value) { + set_bit(usage->code, device->pressed_fn); + } else { + clear_bit(usage->code, device->pressed_fn); + } + + input_event(field->hidinput->input, usage->type, translation->to, value); + return 1; + } + } + + return 0; +} + +/** + * Standard raw event function + * + * Bastard function. Could most probably be done a load better. + * Basically it shifts all of the key's in the 04... event to the right 1, and then sets the first 2 bytes to 0x0100. This then allows the keys to be processed with the above normal event function + * + * Converts M1-M5 into F13-F17. It also blanks out FN keypresses so it acts more like the modifier it should be. + * 04012000000000000000 FN is pressed, M1 pressed + * 04010000000000000000 M1 is released + * goes to + * 01000068000000000000 FN is pressed (blanked), M1 pressed (converted to F13) + * 01000000000000000000 M1 is released + * + * Converts Mute/Next/Play/Prev into multimedia keys + * 04 00 52 00 ... 00 - Mute key pressed + * 04 00 00 00 ... 00 - Mute key released + * goes to + * 01 00 00 E2 00 ... 00 - Mute pressed (converted to KEY_MEDIA_MUTE) + * 01 00 00 00 00 ... 00 + * they key codes are + * 0x52 - Mute + * 0x53 - Next song + * 0x55 - Play/Pause + * 0x54 - Prev song + * + * HID Usage Table http://www.freebsddiary.org/APC/usb_hid_usages.php + */ +static int razer_raw_event_standard(struct hid_device *hdev, struct razer_kbd_device *device, struct usb_interface *intf, struct hid_report *report, u8 *data, int size) +{ + // The event were looking for is 16 or 22 bytes long and starts with 0x04. + // Newer firmware seems to use 22 bytes. + if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD && + ((size == 22) || (size == 16)) && data[0] == 0x04) { + // Convert 04... to 0100... + int index = size-1; // This way we start at 2nd last value, does subtract 1 from the 15key rollover though (not an issue cmon) + int found_fn = 0x00; + + while(--index > 0) { + u8 cur_value = data[index]; + if(cur_value == 0x00) { // Skip 0x00 + continue; + } + + switch(cur_value) { + case 0x01: // FN + //cur_value = 0x73; // F24 + cur_value = 0x00; + found_fn = 0x01; + break; + case 0x20: // M1 + cur_value = USB_HID_KEY_F13; // F13 + break; + case 0x21: // M2 + cur_value = USB_HID_KEY_F14; // F14 + break; + case 0x22: // M3 + cur_value = USB_HID_KEY_F15; // F15 + break; + case 0x23: // M4 + cur_value = USB_HID_KEY_F16; // F16 + break; + case 0x24: // M5 + cur_value = USB_HID_KEY_F17; // F17 + break; + case 0x25: // BlackWidow V4 (non-Pro) M6 + cur_value = USB_HID_KEY_F18; // F18 + break; + case 0x50: // Volume Down + cur_value = USB_HID_KEY_MEDIA_VOLUMEDOWN; // F17 + break; + case 0x51: // Volume Up + cur_value = USB_HID_KEY_MEDIA_VOLUMEUP; // F17 + break; + case 0x52: // Mute + cur_value = USB_HID_KEY_MEDIA_MUTE; + break; + case 0x53: // Next (song) + cur_value = USB_HID_KEY_MEDIA_NEXTSONG; + break; + case 0x55: // Play/Pause + cur_value = USB_HID_KEY_MEDIA_PLAYPAUSE; + break; + case 0x54: // Prev (song) + cur_value = USB_HID_KEY_MEDIA_PREVIOUSSONG; + break; + case 0x60: // BlackWidow V4 Pro command dial button + cur_value = USB_HID_KEY_F24; // F24 (not sure if we want it this way) + break; + case 0x63: // BlackWidow V4 Pro Side button 1 + cur_value = USB_HID_KEY_F18; // F18 + break; + case 0x64: // BlackWidow V4 Pro Side button 2 + cur_value = USB_HID_KEY_F19; // F19 + break; + case 0x65: // BlackWidow V4 Pro Side button 3 + cur_value = USB_HID_KEY_F20; // F20 + break; + } + + data[index+1] = cur_value; + } + + device->fn_on = !!found_fn; + + data[0] = 0x01; + data[1] = 0x00; + + // Some reason just by editing data, it generates a normal event above. (Could quite possibly work like that, no clue) + //hid_report_raw_event(hdev, HID_INPUT_REPORT, data, size, 0); + return 1; + } + + return 0; +} + +#define RAW_EVENT_BITFIELD_BYTES (20) +#define RAW_EVENT_BITFIELD_BITS (RAW_EVENT_BITFIELD_BYTES * BITS_PER_BYTE) + +/** + * Bitfield raw event function + * + * Handles raw events very similarly to razer_raw_event_standard, but for size 22, handles the data as a bit field, + * instead of an array of values. + * + * When the rewritten value does not fit the bit field, a key-down and a key-up event is reported separately. + */ +static int razer_raw_event_bitfield(struct hid_device *hdev, struct razer_kbd_device *device, struct usb_interface *intf, struct hid_report *report, u8 *data, int size) +{ + DECLARE_BITMAP(bitfield, RAW_EVENT_BITFIELD_BITS) = { 0 }; + + // The event were looking for is 16 or 22 bytes long and starts with 0x04. + // Newer firmware seems to use 22 bytes. + if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD && + ((size == 22) || (size == 16)) && data[0] == 0x04) { + // Convert 04... to 0100... + int index = size-1; // This way we start at 2nd last value, does subtract 1 from the 15key rollover though (not an issue cmon) + int found_fn = 0x00; + + while(--index > 0) { + bool write_bitfield = true; + u8 cur_value = data[index]; + if(cur_value == 0x00) { // Skip 0x00 + continue; + } + + switch(cur_value) { + case 0x01: // FN + //cur_value = 0x73; // F24 + cur_value = 0x00; + found_fn = 0x01; + write_bitfield = false; + break; + case 0x20: // M1 + cur_value = USB_HID_KEY_F13; // F13 + break; + case 0x21: // M2 + cur_value = USB_HID_KEY_F14; // F14 + break; + case 0x22: // M3 + cur_value = USB_HID_KEY_F15; // F15 + break; + case 0x23: // M4 + cur_value = USB_HID_KEY_F16; // F16 + break; + case 0x24: // M5 + cur_value = USB_HID_KEY_F17; // F17 + break; + case 0x25: // BlackWidow V4 (non-Pro) M6 + cur_value = USB_HID_KEY_F18; // F18 + break; + case 0x50: // Volume Down + cur_value = USB_HID_KEY_MEDIA_VOLUMEDOWN; + break; + case 0x51: // Volume Up + cur_value = USB_HID_KEY_MEDIA_VOLUMEUP; + break; + case 0x52: // Mute + cur_value = USB_HID_KEY_MEDIA_MUTE; + break; + case 0x53: // Next (song) + cur_value = USB_HID_KEY_MEDIA_NEXTSONG; + break; + case 0x55: // Play/Pause + cur_value = USB_HID_KEY_MEDIA_PLAYPAUSE; + break; + case 0x54: // Prev (song) + cur_value = USB_HID_KEY_MEDIA_PREVIOUSSONG; + break; + case 0x60: // BlackWidow V4 Pro command dial button + cur_value = USB_HID_KEY_F24; // F24 (not sure if we want it this way) + break; + case 0x63: // BlackWidow V4 Pro Side button 1 + cur_value = USB_HID_KEY_F18; // F18 + break; + case 0x64: // BlackWidow V4 Pro Side button 2 + cur_value = USB_HID_KEY_F19; // F19 + break; + case 0x65: // BlackWidow V4 Pro Side button 3 + cur_value = USB_HID_KEY_F20; // F20 + break; + default: + write_bitfield = false; + } + + // data of size 22 starting with 0x01 is a bit field so we need to handle that separately + if (size == 22) { + if (write_bitfield) { + if (cur_value < RAW_EVENT_BITFIELD_BITS) { + // value fits the bit field, so we can use that + bitmap_set(bitfield, cur_value, 1); + } else { + // value does not fit the bit field, so we need extra handling + int report_extra = 1; + + switch (cur_value) { + case USB_HID_KEY_MEDIA_VOLUMEUP: + cur_value = USB_HID_USAGE_MEDIA_VOLUMEUP; + break; + case USB_HID_KEY_MEDIA_VOLUMEDOWN: + cur_value = USB_HID_USAGE_MEDIA_VOLUMEDOWN; + break; + case USB_HID_KEY_MEDIA_MUTE: + cur_value = USB_HID_USAGE_MEDIA_MUTE; + break; + case USB_HID_KEY_MEDIA_NEXTSONG: + cur_value = USB_HID_USAGE_MEDIA_NEXTSONG; + break; + case USB_HID_KEY_MEDIA_PLAYPAUSE: + cur_value = USB_HID_USAGE_MEDIA_PLAYPAUSE; + break; + case USB_HID_KEY_MEDIA_PREVIOUSSONG: + cur_value = USB_HID_USAGE_MEDIA_PREVIOUSSONG; + break; + default: + report_extra = 0; + } + + if (report_extra) { + u8 xdata[22] = { 0x02 }; + + // report key down + xdata[1] = cur_value; + hid_report_raw_event(hdev, HID_INPUT_REPORT, xdata, sizeof(xdata), 0); + + // report key up + xdata[1] = 0x00; + hid_report_raw_event(hdev, HID_INPUT_REPORT, xdata, sizeof(xdata), 0); + } + } + } + } else { // size 16 + data[index+1] = cur_value; + } + } + + device->fn_on = !!found_fn; + + data[0] = 0x01; + data[1] = 0x00; + if (size == 22) { + memcpy(data + 2, bitfield, RAW_EVENT_BITFIELD_BYTES); + } + + // Some reason just by editing data, it generates a normal event above. (Could quite possibly work like that, no clue) + //hid_report_raw_event(hdev, HID_INPUT_REPORT, data, size, 0); + return 1; + } + + return 0; +} + +/** + * Raw event function + * + * Handles provided HID reports, branched out for specific keyboard models, since some keyboards need specific handling. + */ +static int razer_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) +{ + struct razer_kbd_device *device = hid_get_drvdata(hdev); + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + + // No translations needed on the Pro... + if (is_blade_laptop(device)) { + return 0; + } + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + return razer_raw_event_bitfield(hdev, device, intf, report, data, size); + default: + return razer_raw_event_standard(hdev, device, intf, report, data, size); + } +} + +/** + * Set static hid-events translation map + * + * Some keyboards generates wheel-events for volume control knob + */ +static int razer_kbd_input_mapping(struct hid_device *hdev, struct hid_input *hidinput, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + switch (hdev->product) { + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + if (hdev->type == HID_TYPE_USBMOUSE && usage->hid == HID_GD_WHEEL) { + hid_map_usage(hidinput, usage, bit, max, EV_ABS, ABS_VOLUME); + return 1; + } + return 0; + + default: + return 0; + } +} + +static void razer_kbd_init(struct razer_kbd_device *dev, struct usb_interface *intf, struct hid_device *hdev) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + + // Initialise mutex + mutex_init(&dev->lock); + // Setup values + dev->usb_dev = usb_dev; + dev->usb_vid = usb_dev->descriptor.idVendor; + dev->usb_pid = usb_dev->descriptor.idProduct; + dev->usb_interface_protocol = intf->cur_altsetting->desc.bInterfaceProtocol; +} + +/** + * Probe method is ran whenever a device is binded to the driver + */ +static int razer_kbd_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int retval = 0; + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct razer_kbd_device *dev = NULL; + + dev = kzalloc(sizeof(struct razer_kbd_device), GFP_KERNEL); + if(dev == NULL) { + dev_err(&intf->dev, "out of memory\n"); + return -ENOMEM; + } + + // Init data + razer_kbd_init(dev, intf, hdev); + + // Other interfaces are actual key-emitting devices + if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { + // If the currently bound device is the control (mouse) interface + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_version); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_firmware_version); // Get the firmware version + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_serial); // Get serial number + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_brightness); // Gets and sets the brightness + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_test); // Test mode + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_type); // Get string of device type + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_mode); // Get device mode + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_kbd_layout); // Gets the physical layout + + switch(usb_dev->descriptor.idProduct) { + + case USB_DEVICE_ID_RAZER_NOSTROMO: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_red); // Profile/Macro LED Red + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_green); // Profile/Macro LED Green + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_blue); // Profile/Macro LED Blue + break; + + case USB_DEVICE_ID_RAZER_TARTARUS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_red); // Profile/Macro LED Red + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_green); // Profile/Macro LED Green + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_blue); // Profile/Macro LED Blue + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_pulsate); // Pulsate effect, like breathing + break; + + case USB_DEVICE_ID_RAZER_ORBWEAVER: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_red); // Profile/Macro LED Red + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_green); // Profile/Macro LED Green + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_blue); // Profile/Macro LED Blue + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_pulsate); // Pulsate effect, like breathing + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_CHROMA: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_red); // Profile/Macro LED Red + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_green); // Profile/Macro LED Green + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_blue); // Profile/Macro LED Blue + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + break; + + case USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_red); // Profile/Macro LED Red + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_green); // Profile/Macro LED Green + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_blue); // Profile/Macro LED Blue + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_LITE: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_ANANSI: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH_EDITION: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2012: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2013: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_TE_2014: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_EXPERT: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_pulsate); // Pulsate effect, like breathing + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_BLADE_2018_BASE: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_2019: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2019: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_EARLY_2020: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2020: + case USB_DEVICE_ID_RAZER_BOOK_2020: + case USB_DEVICE_ID_RAZER_BLADE_2019_BASE: + case USB_DEVICE_ID_RAZER_BLADE_EARLY_2020_BASE: + case USB_DEVICE_ID_RAZER_BLADE_LATE_2020_BASE: + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_EARLY_2021: + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_2022: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2016: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_X_ULTIMATE: + case USB_DEVICE_ID_RAZER_ORNATA: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_CHROMA: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_key_super); // Super Key + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_key_alt_tab); // Alt + Tab + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_key_alt_f4); // Alt + F4 + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); // Charge level + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); // Charge status + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); // Poll Rate + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_keyswitch_optimization); // Keyswitch Optimization + fallthrough; + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_key_super); // Super Key + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_key_alt_tab); // Alt + Tab + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_key_alt_f4); // Alt + F4 + fallthrough; + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ESSENTIAL: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wheel); // Wheel effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wheel); // Wheel effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); // Poll Rate + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); // Battery charge level + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); // Battery charge status + break; + + case USB_DEVICE_ID_RAZER_CYNOSA_LITE: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); // Charge level + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); // Charge status + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_effect); // Charge effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_colour); // Charge colour + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); // Charge low threshold + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_red); // Profile/Macro LED Red + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_green); // Profile/Macro LED Green + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_profile_led_blue); // Profile/Macro LED Blue + break; + + case USB_DEVICE_ID_RAZER_BLADE_2018_MERCURY: + case USB_DEVICE_ID_RAZER_BLADE_2019_ADV: + case USB_DEVICE_ID_RAZER_BLADE_STUDIO_EDITION_2019: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + break; + + case USB_DEVICE_ID_RAZER_BLADE_LATE_2016: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fn_toggle); // Sets whether FN is requires for F-Keys + break; + + case USB_DEVICE_ID_RAZER_BLADE_QHD: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2016: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_MID_2017: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2017: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_fn_toggle); // Sets whether FN is requires for F-Keys + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_state); // Enable/Disable the logo + break; + + case USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2016: + case USB_DEVICE_ID_RAZER_BLADE_2018: + case USB_DEVICE_ID_RAZER_BLADE_PRO_2017: + case USB_DEVICE_ID_RAZER_BLADE_PRO_2017_FULLHD: + case USB_DEVICE_ID_RAZER_BLADE_MID_2019_MERCURY: + case USB_DEVICE_ID_RAZER_BLADE_PRO_2019: + case USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2019: + case USB_DEVICE_ID_RAZER_BLADE_ADV_LATE_2019: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_2020: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_MID_2021: + case USB_DEVICE_ID_RAZER_BLADE_17_PRO_EARLY_2021: + case USB_DEVICE_ID_RAZER_BLADE_17_PRO_MID_2021: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2021: + case USB_DEVICE_ID_RAZER_BLADE_14_2021: + case USB_DEVICE_ID_RAZER_BLADE_17_2022: + case USB_DEVICE_ID_RAZER_BLADE_14_2022: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2022: + case USB_DEVICE_ID_RAZER_BLADE_14_2023: + case USB_DEVICE_ID_RAZER_BLADE_15_2023: + case USB_DEVICE_ID_RAZER_BLADE_16_2023: + case USB_DEVICE_ID_RAZER_BLADE_18_2023: + case USB_DEVICE_ID_RAZER_BLADE_14_2024: + case USB_DEVICE_ID_RAZER_BLADE_18_2024: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_state); // Enable/Disable the logo + break; + + case USB_DEVICE_ID_RAZER_BLADE_PRO_EARLY_2020: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_OVERWATCH: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_X_CHROMA: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_X_CHROMA_TE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + } + + // Set device to regular mode, not driver mode + // When the daemon discovers the device it will instruct it to enter driver mode + razer_set_device_mode(dev, 0x00, 0x00); + } else if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD) { + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_key_super); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_key_alt_tab); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_key_alt_f4); + } + + hid_set_drvdata(hdev, dev); + dev_set_drvdata(&hdev->dev, dev); + + if(hid_parse(hdev)) { + hid_err(hdev, "parse failed\n"); + goto exit_free; + } + + if (hid_hw_start(hdev, HID_CONNECT_DEFAULT)) { + hid_err(hdev, "hw start failed\n"); + goto exit_free; + } + + // Leave autosuspend on for laptops + if (!is_blade_laptop(dev)) { + usb_disable_autosuspend(usb_dev); + } + + //razer_activate_macro_keys(usb_dev); + //msleep(3000); + return 0; + +exit_free: + kfree(dev); + return retval; +} + +/** + * Unbind function + */ +static void razer_kbd_disconnect(struct hid_device *hdev) +{ + struct razer_kbd_device *dev; + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + + dev = hid_get_drvdata(hdev); + + // Other interfaces are actual key-emitting devices + if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { + // If the currently bound device is the control (mouse) interface + device_remove_file(&hdev->dev, &dev_attr_version); + device_remove_file(&hdev->dev, &dev_attr_firmware_version); // Get the firmware version + device_remove_file(&hdev->dev, &dev_attr_device_serial); // Get serial number + device_remove_file(&hdev->dev, &dev_attr_matrix_brightness); // Gets and sets the brightness + device_remove_file(&hdev->dev, &dev_attr_test); // Test mode + device_remove_file(&hdev->dev, &dev_attr_device_type); // Get string of device type + device_remove_file(&hdev->dev, &dev_attr_device_mode); // Get device mode + device_remove_file(&hdev->dev, &dev_attr_kbd_layout); // Gets the physical layout + + switch(usb_dev->descriptor.idProduct) { + + case USB_DEVICE_ID_RAZER_NOSTROMO: + device_remove_file(&hdev->dev, &dev_attr_profile_led_red); // Profile/Macro LED Red + device_remove_file(&hdev->dev, &dev_attr_profile_led_green); // Profile/Macro LED Green + device_remove_file(&hdev->dev, &dev_attr_profile_led_blue); // Profile/Macro LED Blue + break; + + case USB_DEVICE_ID_RAZER_TARTARUS: + device_remove_file(&hdev->dev, &dev_attr_profile_led_red); // Profile/Macro LED Red + device_remove_file(&hdev->dev, &dev_attr_profile_led_green); // Profile/Macro LED Green + device_remove_file(&hdev->dev, &dev_attr_profile_led_blue); // Profile/Macro LED Blue + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_pulsate); // Pulsate effect, like breathing + break; + + case USB_DEVICE_ID_RAZER_ORBWEAVER: + device_remove_file(&hdev->dev, &dev_attr_profile_led_red); // Profile/Macro LED Red + device_remove_file(&hdev->dev, &dev_attr_profile_led_green); // Profile/Macro LED Green + device_remove_file(&hdev->dev, &dev_attr_profile_led_blue); // Profile/Macro LED Blue + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_pulsate); // Pulsate effect, like breathing + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_CHROMA: + device_remove_file(&hdev->dev, &dev_attr_profile_led_red); // Profile/Macro LED Red + device_remove_file(&hdev->dev, &dev_attr_profile_led_green); // Profile/Macro LED Green + device_remove_file(&hdev->dev, &dev_attr_profile_led_blue); // Profile/Macro LED Blue + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + break; + + case USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA: + device_remove_file(&hdev->dev, &dev_attr_profile_led_red); // Profile/Macro LED Red + device_remove_file(&hdev->dev, &dev_attr_profile_led_green); // Profile/Macro LED Green + device_remove_file(&hdev->dev, &dev_attr_profile_led_blue); // Profile/Macro LED Blue + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_LITE: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_ANANSI: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_game_led_state); + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH_EDITION: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2012: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2013: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_TE_2014: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_EXPERT: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_pulsate); // Pulsate effect, like breathing + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_BLADE_2018_BASE: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_2019: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2019: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_EARLY_2020: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2020: + case USB_DEVICE_ID_RAZER_BOOK_2020: + case USB_DEVICE_ID_RAZER_BLADE_2019_BASE: + case USB_DEVICE_ID_RAZER_BLADE_EARLY_2020_BASE: + case USB_DEVICE_ID_RAZER_BLADE_LATE_2020_BASE: + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_EARLY_2021: + case USB_DEVICE_ID_RAZER_BLADE_15_BASE_2022: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2016: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_X_ULTIMATE: + case USB_DEVICE_ID_RAZER_ORNATA: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_CHROMA: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS: + device_remove_file(&hdev->dev, &dev_attr_key_super); // Super Key + device_remove_file(&hdev->dev, &dev_attr_key_alt_tab); // Alt + Tab + device_remove_file(&hdev->dev, &dev_attr_key_alt_f4); // Alt + F4 + device_remove_file(&hdev->dev, &dev_attr_charge_level); // Charge level + device_remove_file(&hdev->dev, &dev_attr_charge_status); // Charge status + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); // Poll Rate + device_remove_file(&hdev->dev, &dev_attr_keyswitch_optimization); // Keyswitch Optimization + fallthrough; + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3: + case USB_DEVICE_ID_RAZER_DEATHSTALKER_V2: + device_remove_file(&hdev->dev, &dev_attr_key_super); // Super Key + device_remove_file(&hdev->dev, &dev_attr_key_alt_tab); // Alt + Tab + device_remove_file(&hdev->dev, &dev_attr_key_alt_f4); // Alt + F4 + fallthrough; + case USB_DEVICE_ID_RAZER_ORNATA_CHROMA: + case USB_DEVICE_ID_RAZER_ORNATA_V2: + case USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_TE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_2019: + case USB_DEVICE_ID_RAZER_HUNTSMAN: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA: + case USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_ESSENTIAL: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2: + case USB_DEVICE_ID_RAZER_CYNOSA_V2: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_ORNATA_V3: + case USB_DEVICE_ID_RAZER_ORNATA_V3_ALT: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wheel); // Wheel effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wheel); // Wheel + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + device_remove_file(&hdev->dev, &dev_attr_poll_rate); // Poll Rate + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + device_remove_file(&hdev->dev, &dev_attr_charge_level); // Battery charge level + device_remove_file(&hdev->dev, &dev_attr_charge_status); // Battery charge status + break; + + case USB_DEVICE_ID_RAZER_CYNOSA_LITE: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_ORNATA_V3_X: + case USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + device_remove_file(&hdev->dev, &dev_attr_charge_level); // Charge level + device_remove_file(&hdev->dev, &dev_attr_charge_status); // Charge status + device_remove_file(&hdev->dev, &dev_attr_charge_effect); // Charge effect + device_remove_file(&hdev->dev, &dev_attr_charge_colour); // Charge colour + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); // Charge low threshold + break; + + case USB_DEVICE_ID_RAZER_TARTARUS_V2: + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_profile_led_red); // Profile/Macro LED Red + device_remove_file(&hdev->dev, &dev_attr_profile_led_green); // Profile/Macro LED Green + device_remove_file(&hdev->dev, &dev_attr_profile_led_blue); // Profile/Macro LED Blue + break; + + case USB_DEVICE_ID_RAZER_BLADE_2018_MERCURY: + case USB_DEVICE_ID_RAZER_BLADE_2019_ADV: + case USB_DEVICE_ID_RAZER_BLADE_STUDIO_EDITION_2019: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + break; + + case USB_DEVICE_ID_RAZER_BLADE_LATE_2016: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_fn_toggle); // Sets whether FN is requires for F-Keys + break; + + case USB_DEVICE_ID_RAZER_BLADE_QHD: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2016: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_MID_2017: + case USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2017: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_fn_toggle); // Sets whether FN is requires for F-Keys + device_remove_file(&hdev->dev, &dev_attr_logo_led_state); // Enable/Disable the logo + break; + + case USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2016: + case USB_DEVICE_ID_RAZER_BLADE_2018: + case USB_DEVICE_ID_RAZER_BLADE_PRO_2017: + case USB_DEVICE_ID_RAZER_BLADE_PRO_2017_FULLHD: + case USB_DEVICE_ID_RAZER_BLADE_MID_2019_MERCURY: + case USB_DEVICE_ID_RAZER_BLADE_PRO_2019: + case USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2019: + case USB_DEVICE_ID_RAZER_BLADE_ADV_LATE_2019: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_2020: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_MID_2021: + case USB_DEVICE_ID_RAZER_BLADE_17_PRO_EARLY_2021: + case USB_DEVICE_ID_RAZER_BLADE_17_PRO_MID_2021: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2021: + case USB_DEVICE_ID_RAZER_BLADE_14_2021: + case USB_DEVICE_ID_RAZER_BLADE_17_2022: + case USB_DEVICE_ID_RAZER_BLADE_14_2022: + case USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2022: + case USB_DEVICE_ID_RAZER_BLADE_14_2023: + case USB_DEVICE_ID_RAZER_BLADE_15_2023: + case USB_DEVICE_ID_RAZER_BLADE_16_2023: + case USB_DEVICE_ID_RAZER_BLADE_18_2023: + case USB_DEVICE_ID_RAZER_BLADE_14_2024: + case USB_DEVICE_ID_RAZER_BLADE_18_2024: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_logo_led_state); // Enable/Disable the logo + break; + + case USB_DEVICE_ID_RAZER_BLADE_PRO_EARLY_2020: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + break; + + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_TE: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_OVERWATCH: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_X_CHROMA: + case USB_DEVICE_ID_RAZER_BLACKWIDOW_X_CHROMA_TE: + case USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG: + case USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG: + case USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + + case USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); // Wave effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_starlight); // Starlight effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); // Reactive effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); // Set LED matrix + device_remove_file(&hdev->dev, &dev_attr_game_led_state); // Enable game mode & LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_state); // Enable macro LED + device_remove_file(&hdev->dev, &dev_attr_macro_led_effect); // Change macro LED effect (static, flashing) + break; + } + } else if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD) { + device_remove_file(&hdev->dev, &dev_attr_key_super); + device_remove_file(&hdev->dev, &dev_attr_key_alt_tab); + device_remove_file(&hdev->dev, &dev_attr_key_alt_f4); + } + + hid_hw_stop(hdev); + kfree(dev); + dev_info(&intf->dev, "Razer Device disconnected\n"); +} + +/** + * Setup input device keybit mask + */ +static void razer_setup_key_bits(struct input_dev *input) +{ + __set_bit(EV_KEY, input->evbit); + + // Chroma keys + __set_bit(RAZER_MACRO_KEY, input->keybit); + __set_bit(RAZER_GAME_KEY, input->keybit); + __set_bit(RAZER_BRIGHTNESS_DOWN, input->keybit); + __set_bit(RAZER_BRIGHTNESS_UP, input->keybit); +} + +/** + * Setup the input device now that its been added to our struct + */ +static int razer_input_configured(struct hid_device *hdev, struct hid_input *hi) +{ + razer_setup_key_bits(hi->input); + return 0; +} + +/** + * Device ID mapping table + */ +static const struct hid_device_id razer_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ORBWEAVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NOSTROMO) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH_EDITION) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2012) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2013) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2016) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_X_ULTIMATE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_TE_2014) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_STEALTH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2016) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_QHD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2016) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_2018) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_2018_MERCURY) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_2018_BASE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_2019_ADV) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_2019_BASE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_MID_2019_MERCURY) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2019) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_ADV_LATE_2019) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_STUDIO_EDITION_2019) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_PRO_2019) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_15_ADV_2020) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_TARTARUS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_TARTARUS_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_TARTARUS_V2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHSTALKER_ESSENTIAL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHSTALKER_EXPERT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_OVERWATCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHSTALKER_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_TE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_X_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_X_CHROMA_TE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ORNATA_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ORNATA_V2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ORNATA_V3) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ORNATA_V3_ALT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ORNATA_V3_X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_CYNOSA_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_CYNOSA_LITE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_CYNOSA_V2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_LITE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_2019) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_ESSENTIAL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ORNATA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ANANSI) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_LATE_2016) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_STEALTH_MID_2017) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_PRO_2017) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_HUNTSMAN_TE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_HUNTSMAN_MINI) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_HUNTSMAN) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_PRO_2017_FULLHD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2017) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_STEALTH_2019) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2019) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_STEALTH_EARLY_2020) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2020) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BOOK_2020) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_PRO_EARLY_2020) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_EARLY_2020_BASE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_LATE_2020_BASE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2021) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_15_ADV_MID_2021) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_15_BASE_EARLY_2021) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_15_BASE_2022) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_17_PRO_EARLY_2021) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_17_PRO_MID_2021) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_14_2021) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_V3) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_HUNTSMAN_V2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_17_2022) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_14_2022) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2022) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_14_2023) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_15_2023) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_14_2024) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHSTALKER_V2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_V4) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_16_2023) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_18_2023) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BLADE_18_2024) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(hid, razer_devices); + +/** + * Describes the contents of the driver + */ +static struct hid_driver razer_kbd_driver = { + .name = "razerkbd", + .id_table = razer_devices, + .input_mapping = razer_kbd_input_mapping, + .probe = razer_kbd_probe, + .remove = razer_kbd_disconnect, + .event = razer_event, + .raw_event = razer_raw_event, + .input_configured = razer_input_configured, +}; + +module_hid_driver(razer_kbd_driver); diff --git a/drivers/custom/razer/driver/razerkbd_driver.h b/drivers/custom/razer/driver/razerkbd_driver.h new file mode 100644 index 000000000000..bd5fd7642bd9 --- /dev/null +++ b/drivers/custom/razer/driver/razerkbd_driver.h @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2015 Tim Theede + * 2015 Terri Cain + */ + +#ifndef __HID_RAZER_KBD_H +#define __HID_RAZER_KBD_H + +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2012 0x010D +// 2011 or so edition, see https://web.archive.org/web/20111113132427/http://store.razerzone.com:80/store/razerusa/en_US/pd/productID.235228400/categoryId.49136200/parentCategoryId.35156900 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH_EDITION 0x010E +#define USB_DEVICE_ID_RAZER_ANANSI 0x010F +#define USB_DEVICE_ID_RAZER_NOSTROMO 0x0111 +#define USB_DEVICE_ID_RAZER_ORBWEAVER 0x0113 +#define USB_DEVICE_ID_RAZER_DEATHSTALKER_ESSENTIAL 0x0118 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2013 0x011A +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_STEALTH 0x011B +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_TE_2014 0x011C +#define USB_DEVICE_ID_RAZER_TARTARUS 0x0201 +#define USB_DEVICE_ID_RAZER_DEATHSTALKER_EXPERT 0x0202 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA 0x0203 +#define USB_DEVICE_ID_RAZER_DEATHSTALKER_CHROMA 0x0204 +#define USB_DEVICE_ID_RAZER_BLADE_STEALTH 0x0205 +#define USB_DEVICE_ID_RAZER_ORBWEAVER_CHROMA 0x0207 +#define USB_DEVICE_ID_RAZER_TARTARUS_CHROMA 0x0208 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_TE 0x0209 +#define USB_DEVICE_ID_RAZER_BLADE_QHD 0x020F +#define USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2016 0x0210 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_OVERWATCH 0x0211 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE_2016 0x0214 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_X_CHROMA 0x0216 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_X_ULTIMATE 0x0217 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_X_CHROMA_TE 0x021A +#define USB_DEVICE_ID_RAZER_ORNATA_CHROMA 0x021E +#define USB_DEVICE_ID_RAZER_ORNATA 0x021F +#define USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2016 0x0220 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_CHROMA_V2 0x0221 +#define USB_DEVICE_ID_RAZER_BLADE_LATE_2016 0x0224 +#define USB_DEVICE_ID_RAZER_BLADE_PRO_2017 0x0225 +#define USB_DEVICE_ID_RAZER_HUNTSMAN_ELITE 0x0226 +#define USB_DEVICE_ID_RAZER_HUNTSMAN 0x0227 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_ELITE 0x0228 +#define USB_DEVICE_ID_RAZER_CYNOSA_CHROMA 0x022A +#define USB_DEVICE_ID_RAZER_TARTARUS_V2 0x022B +#define USB_DEVICE_ID_RAZER_CYNOSA_CHROMA_PRO 0x022C +#define USB_DEVICE_ID_RAZER_BLADE_STEALTH_MID_2017 0x022D +#define USB_DEVICE_ID_RAZER_BLADE_PRO_2017_FULLHD 0x022F +#define USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2017 0x0232 +#define USB_DEVICE_ID_RAZER_BLADE_2018 0x0233 +#define USB_DEVICE_ID_RAZER_BLADE_PRO_2019 0x0234 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_LITE 0x0235 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_ESSENTIAL 0x0237 +#define USB_DEVICE_ID_RAZER_BLADE_STEALTH_2019 0x0239 +#define USB_DEVICE_ID_RAZER_BLADE_2019_ADV 0x023A +#define USB_DEVICE_ID_RAZER_BLADE_2018_BASE 0x023B +#define USB_DEVICE_ID_RAZER_CYNOSA_LITE 0x023F +#define USB_DEVICE_ID_RAZER_BLADE_2018_MERCURY 0x0240 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_2019 0x0241 +#define USB_DEVICE_ID_RAZER_HUNTSMAN_TE 0x0243 +#define USB_DEVICE_ID_RAZER_BLADE_MID_2019_MERCURY 0x0245 +#define USB_DEVICE_ID_RAZER_BLADE_2019_BASE 0x0246 +#define USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2019 0x024A +#define USB_DEVICE_ID_RAZER_BLADE_ADV_LATE_2019 0x024B +#define USB_DEVICE_ID_RAZER_BLADE_PRO_LATE_2019 0x024C +#define USB_DEVICE_ID_RAZER_BLADE_STUDIO_EDITION_2019 0x024D +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_V3 0x024E +#define USB_DEVICE_ID_RAZER_BLADE_STEALTH_EARLY_2020 0x0252 +#define USB_DEVICE_ID_RAZER_BLADE_15_ADV_2020 0x0253 +#define USB_DEVICE_ID_RAZER_BLADE_EARLY_2020_BASE 0x0255 +#define USB_DEVICE_ID_RAZER_BLADE_PRO_EARLY_2020 0x0256 +#define USB_DEVICE_ID_RAZER_HUNTSMAN_MINI 0x0257 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI 0x0258 +#define USB_DEVICE_ID_RAZER_BLADE_STEALTH_LATE_2020 0x0259 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRED 0x025A +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_PRO_WIRELESS 0x025C +#define USB_DEVICE_ID_RAZER_ORNATA_V2 0x025D +#define USB_DEVICE_ID_RAZER_CYNOSA_V2 0x025E +#define USB_DEVICE_ID_RAZER_HUNTSMAN_V2_ANALOG 0x0266 +#define USB_DEVICE_ID_RAZER_BLADE_LATE_2020_BASE 0x0268 +#define USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_JP 0x0269 +#define USB_DEVICE_ID_RAZER_BOOK_2020 0x026A +#define USB_DEVICE_ID_RAZER_HUNTSMAN_V2_TENKEYLESS 0x026B +#define USB_DEVICE_ID_RAZER_HUNTSMAN_V2 0x026C +#define USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2021 0x026D +#define USB_DEVICE_ID_RAZER_BLADE_17_PRO_EARLY_2021 0x026E +#define USB_DEVICE_ID_RAZER_BLADE_15_BASE_EARLY_2021 0x026F +#define USB_DEVICE_ID_RAZER_BLADE_14_2021 0x0270 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_MINI_WIRELESS 0x0271 +#define USB_DEVICE_ID_RAZER_BLADE_15_ADV_MID_2021 0x0276 +#define USB_DEVICE_ID_RAZER_BLADE_17_PRO_MID_2021 0x0279 +#define USB_DEVICE_ID_RAZER_BLADE_15_BASE_2022 0x027A +#define USB_DEVICE_ID_RAZER_HUNTSMAN_MINI_ANALOG 0x0282 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_V4 0x0287 +#define USB_DEVICE_ID_RAZER_BLADE_15_ADV_EARLY_2022 0x028A +#define USB_DEVICE_ID_RAZER_BLADE_17_2022 0x028B +#define USB_DEVICE_ID_RAZER_BLADE_14_2022 0x028C +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_PRO 0x028D +#define USB_DEVICE_ID_RAZER_ORNATA_V3_ALT 0x028F +#define USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRELESS 0x0290 +#define USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_WIRED 0x0292 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_X 0x0293 +#define USB_DEVICE_ID_RAZER_ORNATA_V3_X 0x0294 +#define USB_DEVICE_ID_RAZER_DEATHSTALKER_V2 0x0295 +#define USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRELESS 0x0296 +#define USB_DEVICE_ID_RAZER_DEATHSTALKER_V2_PRO_TKL_WIRED 0x0298 +#define USB_DEVICE_ID_RAZER_BLADE_14_2023 0x029D +#define USB_DEVICE_ID_RAZER_BLADE_15_2023 0x029E +#define USB_DEVICE_ID_RAZER_BLADE_16_2023 0x029F +#define USB_DEVICE_ID_RAZER_BLADE_18_2023 0x02A0 +#define USB_DEVICE_ID_RAZER_ORNATA_V3 0x02A1 +#define USB_DEVICE_ID_RAZER_ORNATA_V3_X_ALT 0x02A2 +#define USB_DEVICE_ID_RAZER_ORNATA_V3_TENKEYLESS 0x02A3 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_V4_75PCT 0x02A5 +#define USB_DEVICE_ID_RAZER_HUNTSMAN_V3_PRO 0x02A6 +#define USB_DEVICE_ID_RAZER_BLADE_14_2024 0x02B6 +#define USB_DEVICE_ID_RAZER_BLADE_18_2024 0x02B8 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_V3_TK 0x0A24 + +/* Each keyboard report has 90 bytes*/ +#define RAZER_BLACKWIDOW_REPORT_LEN 0x5A + +#define RAZER_BLACKWIDOW_CHROMA_WAVE_DIRECTION_LEFT 2 +#define RAZER_BLACKWIDOW_CHROMA_WAVE_DIRECTION_RIGHT 1 + +#define RAZER_BLACKWIDOW_CHROMA_CHANGE_EFFECT 0x0A + +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_NONE 0 +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_WAVE 1 +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_REACTIVE 2 +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_BREATH 3 +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_SPECTRUM 4 +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_CUSTOM 5 // draw frame +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_STATIC 6 +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_CLEAR_ROW 8 + +#define RAZER_BLACKWIDOW_ULTIMATE_2016_EFFECT_STARLIGHT 0x19 + +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_SET_KEYS 9 //update profile needs to be called after setting keys to reflect changes +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_RESET 10 +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_UNKNOWN 11 +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_UNKNOWN2 12 +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_UNKNOWN3 13 +#define RAZER_BLACKWIDOW_CHROMA_EFFECT_UNKNOWN4 14 + +#define RAZER_BLACKWIDOW_CHROMA_ROW_LEN 0x16 +#define RAZER_BLACKWIDOW_CHROMA_ROWS_NUM 6 + +#define RAZER_STEALTH_ROW_LEN 0x10 +#define RAZER_STEALTH_ROWS_NUM 6 + +#define RAZER_BLACKWIDOW_CHROMA_WAIT_MS 1 +#define RAZER_BLACKWIDOW_CHROMA_WAIT_MIN_US 600 +#define RAZER_BLACKWIDOW_CHROMA_WAIT_MAX_US 800 + +#define RAZER_BLACKWIDOW_V3_WIRELESS_WAIT_MIN_US 4900 +#define RAZER_BLACKWIDOW_V3_WIRELESS_WAIT_MAX_US 5000 + +#define RAZER_DEATHSTALKER_V2_WIRELESS_WAIT_MIN_US 4900 +#define RAZER_DEATHSTALKER_V2_WIRELESS_WAIT_MAX_US 5000 + +#define RAZER_FIREFLY_WAIT_MIN_US 900 +#define RAZER_FIREFLY_WAIT_MAX_US 1000 + +struct razer_kbd_device { + struct usb_device *usb_dev; + struct mutex lock; + unsigned char usb_interface_protocol; + unsigned short usb_vid; + unsigned short usb_pid; + + unsigned int fn_on; + DECLARE_BITMAP(pressed_fn, KEY_CNT); + + unsigned char block_keys[3]; + unsigned char left_alt_on; +}; + +#endif diff --git a/drivers/custom/razer/driver/razerkraken_driver.c b/drivers/custom/razer/driver/razerkraken_driver.c new file mode 100644 index 000000000000..0c2177bccf2e --- /dev/null +++ b/drivers/custom/razer/driver/razerkraken_driver.c @@ -0,0 +1,882 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2015 Terri Cain + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "razerkraken_driver.h" +#include "razercommon.h" + +/* + * Version Information + */ +#define DRIVER_DESC "Razer Keyboard Device Driver" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE(DRIVER_LICENSE); + +/** + * Print report to syslog + */ +/* +static void print_erroneous_kraken_request_report(struct razer_kraken_request_report* report, char* driver_name, char* message) +{ + printk(KERN_WARNING "%s: %s. Report ID: %02x dest: %02x length: %02x ADDR: %02x%02x Args: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x .\n", + driver_name, + message, + report->report_id, + report->destination, + report->length, + report->addr_h, + report->addr_l, + report->arguments[0], report->arguments[1], report->arguments[2], report->arguments[3], report->arguments[4], report->arguments[5], + report->arguments[6], report->arguments[7], report->arguments[8], report->arguments[9], report->arguments[10], report->arguments[11], + report->arguments[12], report->arguments[13], report->arguments[14], report->arguments[15]); +} +*/ + +static int razer_kraken_send_control_msg(struct usb_device *usb_dev,struct razer_kraken_request_report* report, unsigned char skip) +{ + uint request = HID_REQ_SET_REPORT; // 0x09 + uint request_type = USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT; // 0x21 + uint value = 0x0204; + uint index = 0x0003; + uint size = 37; + char *buf; + int len; + + buf = kmemdup(report, size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + // Send usb control message + len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + request, // Request U8 + request_type, // RequestType U8 + value, // Value U16 + index, // Index U16 + buf, // Data void* data + size, // Length U16 + USB_CTRL_SET_TIMEOUT); // Int + + // Wait + if(skip != 1) { + msleep(report->length * 15); + } + + kfree(buf); + if(len!=size) + printk(KERN_WARNING "razer driver: Device data transfer failed.\n"); + + return ((len < 0) ? len : ((len != size) ? -EIO : 0)); +} + +/** + * Get a request report + * + * report_id - The type of report + * destination - where data is going (like ram) + * length - amount of data + * address - where to write data to + */ +static struct razer_kraken_request_report get_kraken_request_report(unsigned char report_id, unsigned char destination, unsigned char length, unsigned short address) +{ + struct razer_kraken_request_report report; + memset(&report, 0, sizeof(struct razer_kraken_request_report)); + + report.report_id = report_id; + report.destination = destination; + report.length = length; + report.addr_h = (address >> 8); + report.addr_l = (address & 0xFF); + + return report; +} + +/** + * Get a union containing the effect bitfield + */ +static union razer_kraken_effect_byte get_kraken_effect_byte(void) +{ + union razer_kraken_effect_byte effect_byte; + memset(&effect_byte, 0, sizeof(union razer_kraken_effect_byte)); + + return effect_byte; +} + +/** + * Get the current effect + */ +static unsigned char get_current_effect(struct device *dev) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + struct razer_kraken_request_report report = get_kraken_request_report(0x04, 0x00, 0x01, device->led_mode_address); + int is_mutex_locked = mutex_is_locked(&device->lock); + unsigned char result = 0; + + // Lock if there isn't already a lock, otherwise skip, essentially emulate a rentrant lock + if(is_mutex_locked == 0) { + mutex_lock(&device->lock); + } + + device->data[0] = 0x00; + razer_kraken_send_control_msg(device->usb_dev, &report, 1); + msleep(25); // Sleep 20ms + + // Check for actual data + if(device->data[0] == 0x05) { + result = device->data[1]; + } else { + printk(KERN_CRIT "razerkraken: Did not manage to get report\n"); + } + + // Unlock if there isn't already a lock (as there would be by now), otherwise skip as reusing existing lock + if(is_mutex_locked == 0) { + mutex_unlock(&device->lock); + } + + return result; +} + +static unsigned int get_rgb_from_addr(struct device *dev, unsigned short address, unsigned char len, char* buf) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + struct razer_kraken_request_report report = get_kraken_request_report(0x04, 0x00, len, address); + int is_mutex_locked = mutex_is_locked(&device->lock); + unsigned char written = 0; + + // Lock if there isn't already a lock, otherwise skip, essentially emulate a rentrant lock + if(is_mutex_locked == 0) { + mutex_lock(&device->lock); + } + + device->data[0] = 0x00; + razer_kraken_send_control_msg(device->usb_dev, &report, 1); + msleep(25); // Sleep 20ms + + // Check for actual data + if(device->data[0] == 0x05) { + //printk(KERN_CRIT "razerkraken: Got %02x%02x%02x %02x\n", device->data[1], device->data[2], device->data[3], device->data[4]); + memcpy(&buf[0], &device->data[1], len); + written = len; + } else { + printk(KERN_CRIT "razerkraken: Did not manage to get report\n"); + } + + // Unlock if there isn't already a lock (as there would be by now), otherwise skip as reusing existing lock + if(is_mutex_locked == 0) { + mutex_unlock(&device->lock); + } + + return written; +} + +/** + * Read device file "version" + * + * Returns a string + */ +static ssize_t razer_attr_read_version(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", DRIVER_VERSION); +} + +/** + * Read device file "device_type" + * + * Returns friendly string of device type + */ +static ssize_t razer_attr_read_device_type(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + + char *device_type; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_KRAKEN_CLASSIC: + case USB_DEVICE_ID_RAZER_KRAKEN_CLASSIC_ALT: + device_type = "Razer Kraken 7.1\n"; + break; + + case USB_DEVICE_ID_RAZER_KRAKEN: + device_type = "Razer Kraken 7.1 Chroma\n"; // Rainie + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_V2: + device_type = "Razer Kraken 7.1 V2\n"; // Kylie + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_ULTIMATE: + device_type = "Razer Kraken Ultimate\n"; + break; + + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_V2: + device_type = "Razer Kraken Kitty V2\n"; + break; + + default: + device_type = "Unknown Device\n"; + } + + return sprintf(buf, device_type); +} + +/** + * Write device file "test" + * + * Does nothing + */ +static ssize_t razer_attr_write_test(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return count; +} + +/** + * Read device file "test" + * + * Returns a string + */ +static ssize_t razer_attr_read_test(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "\n"); +} + +/** + * Write device file "mode_spectrum" + * + * Specrum effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_matrix_effect_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + struct razer_kraken_request_report report = get_kraken_request_report(0x04, 0x40, 0x01, device->led_mode_address); + union razer_kraken_effect_byte effect_byte = get_kraken_effect_byte(); + + // Spectrum Cycling | ON + effect_byte.bits.on_off_static = 1; + effect_byte.bits.spectrum_cycling = 1; + + report.arguments[0] = effect_byte.value; + + // Lock access to sending USB as adhering to the razer len*15ms delay + mutex_lock(&device->lock); + razer_kraken_send_control_msg(device->usb_dev, &report, 0); + mutex_unlock(&device->lock); + + return count; +} + +/** + * Write device file "mode_none" + * + * None effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_matrix_effect_none(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + struct razer_kraken_request_report report = get_kraken_request_report(0x04, 0x40, 0x01, device->led_mode_address); + union razer_kraken_effect_byte effect_byte = get_kraken_effect_byte(); + + // Spectrum Cycling | OFF + effect_byte.bits.on_off_static = 0; + effect_byte.bits.spectrum_cycling = 0; + + report.arguments[0] = effect_byte.value; + + // Lock access to sending USB as adhering to the razer len*15ms delay + mutex_lock(&device->lock); + razer_kraken_send_control_msg(device->usb_dev, &report, 0); + mutex_unlock(&device->lock); + + return count; +} + +/** + * Write device file "mode_static" + * + * Static effect mode is activated whenever the file is written to with 3 bytes + */ +static ssize_t razer_attr_write_matrix_effect_static(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + struct razer_kraken_request_report rgb_report = get_kraken_request_report(0x04, 0x40, count, device->breathing_address[0]); + struct razer_kraken_request_report effect_report = get_kraken_request_report(0x04, 0x40, 0x01, device->led_mode_address); + union razer_kraken_effect_byte effect_byte = get_kraken_effect_byte(); + + if (count != 3 && count != 4) { + printk(KERN_WARNING "razerkraken: Static mode only accepts RGB (3byte) or RGB with intensity (4byte)\n"); + return -EINVAL; + } + + rgb_report.arguments[0] = buf[0]; + rgb_report.arguments[1] = buf[1]; + rgb_report.arguments[2] = buf[2]; + + if(count == 4) { + rgb_report.arguments[3] = buf[3]; + } + + // ON/Static + effect_byte.bits.on_off_static = 1; + effect_report.arguments[0] = effect_byte.value; + + // Lock sending of the 2 commands + mutex_lock(&device->lock); + + // Basically Kraken Classic doesn't take RGB arguments so only do it for the KrakenV1,V2,Ultimate + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_KRAKEN: + case USB_DEVICE_ID_RAZER_KRAKEN_V2: + case USB_DEVICE_ID_RAZER_KRAKEN_ULTIMATE: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_V2: + razer_kraken_send_control_msg(device->usb_dev, &rgb_report, 0); + break; + } + + // Send Set static command + razer_kraken_send_control_msg(device->usb_dev, &effect_report, 0); + mutex_unlock(&device->lock); + + return count; +} + +/** + * Write device file "mode_custom" + * + * Custom effect mode is activated whenever the file is written to with 3 bytes + */ +static ssize_t razer_attr_write_matrix_effect_custom(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + struct razer_kraken_request_report rgb_report = get_kraken_request_report(0x04, 0x40, count, device->custom_address); + struct razer_kraken_request_report effect_report = get_kraken_request_report(0x04, 0x40, 0x01, device->led_mode_address); + union razer_kraken_effect_byte effect_byte = get_kraken_effect_byte(); + + if(count != 3 && count != 4) { + printk(KERN_WARNING "razerkraken: Custom mode only accepts RGB (3byte) or RGB with intensity (4byte)\n"); + return -EINVAL; + } + + rgb_report.arguments[0] = buf[0]; + rgb_report.arguments[1] = buf[1]; + rgb_report.arguments[2] = buf[2]; + + if(count == 4) { + rgb_report.arguments[3] = buf[3]; + } + + // ON/Static + effect_byte.bits.on_off_static = 1; + effect_report.arguments[0] = 1; //effect_byte.value; + + // Lock sending of the 2 commands + mutex_lock(&device->lock); + razer_kraken_send_control_msg(device->usb_dev, &rgb_report, 1); + + razer_kraken_send_control_msg(device->usb_dev, &effect_report, 1); + mutex_unlock(&device->lock); + + return count; +} + +/** + * Read device file "mode_static" + * + * Returns 4 bytes for config + */ +static ssize_t razer_attr_read_matrix_effect_static(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + return get_rgb_from_addr(dev, device->breathing_address[0], 0x04, buf); +} + +/** + * Read device file "mode_custom" + * + * Returns 4 bytes for config + */ +static ssize_t razer_attr_read_matrix_effect_custom(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + return get_rgb_from_addr(dev, device->custom_address, 0x04, buf); +} + +/** + * Write device file "mode_breath" + * + * Breathing effect mode is activated whenever the file is written to with 3,6 or 9 bytes + */ +static ssize_t razer_attr_write_matrix_effect_breath(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + struct razer_kraken_request_report effect_report = get_kraken_request_report(0x04, 0x40, 0x01, device->led_mode_address); + union razer_kraken_effect_byte effect_byte = get_kraken_effect_byte(); + + // Short circuit here as rainie only does breathing1 + if(device->usb_pid == USB_DEVICE_ID_RAZER_KRAKEN && count != 3) { + printk(KERN_WARNING "razerkraken: Breathing mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + + if(count == 3) { + struct razer_kraken_request_report rgb_report = get_kraken_request_report(0x04, 0x40, 0x03, device->breathing_address[0]); + + rgb_report.arguments[0] = buf[0]; + rgb_report.arguments[1] = buf[1]; + rgb_report.arguments[2] = buf[2]; + + // ON/Static + effect_byte.bits.on_off_static = 1; + effect_byte.bits.single_colour_breathing = 1; + effect_byte.bits.sync = 1; + effect_report.arguments[0] = effect_byte.value; + + // Lock sending of the 2 commands + mutex_lock(&device->lock); + razer_kraken_send_control_msg(device->usb_dev, &rgb_report, 0); + + razer_kraken_send_control_msg(device->usb_dev, &effect_report, 0); + mutex_unlock(&device->lock); + } else if(count == 6) { + struct razer_kraken_request_report rgb_report = get_kraken_request_report(0x04, 0x40, 0x03, device->breathing_address[1]); + struct razer_kraken_request_report rgb_report2 = get_kraken_request_report(0x04, 0x40, 0x03, device->breathing_address[1]+4); // Address the 2nd set of colours + + rgb_report.arguments[0] = buf[0]; + rgb_report.arguments[1] = buf[1]; + rgb_report.arguments[2] = buf[2]; + rgb_report2.arguments[0] = buf[3]; + rgb_report2.arguments[1] = buf[4]; + rgb_report2.arguments[2] = buf[5]; + + // ON/Static + effect_byte.bits.on_off_static = 1; + effect_byte.bits.two_colour_breathing = 1; + effect_byte.bits.sync = 1; + effect_report.arguments[0] = effect_byte.value; + + // Lock sending of the 2 commands + mutex_lock(&device->lock); + razer_kraken_send_control_msg(device->usb_dev, &rgb_report, 0); + + razer_kraken_send_control_msg(device->usb_dev, &rgb_report2, 0); + + razer_kraken_send_control_msg(device->usb_dev, &effect_report, 0); + mutex_unlock(&device->lock); + + } else if(count == 9) { + struct razer_kraken_request_report rgb_report = get_kraken_request_report(0x04, 0x40, 0x03, device->breathing_address[2]); + struct razer_kraken_request_report rgb_report2 = get_kraken_request_report(0x04, 0x40, 0x03, device->breathing_address[2]+4); // Address the 2nd set of colours + struct razer_kraken_request_report rgb_report3 = get_kraken_request_report(0x04, 0x40, 0x03, device->breathing_address[2]+8); // Address the 3rd set of colours + + rgb_report.arguments[0] = buf[0]; + rgb_report.arguments[1] = buf[1]; + rgb_report.arguments[2] = buf[2]; + rgb_report2.arguments[0] = buf[3]; + rgb_report2.arguments[1] = buf[4]; + rgb_report2.arguments[2] = buf[5]; + rgb_report3.arguments[0] = buf[6]; + rgb_report3.arguments[1] = buf[7]; + rgb_report3.arguments[2] = buf[8]; + + // ON/Static + effect_byte.bits.on_off_static = 1; + effect_byte.bits.three_colour_breathing = 1; + effect_byte.bits.sync = 1; + effect_report.arguments[0] = effect_byte.value; + + // Lock sending of the 2 commands + mutex_lock(&device->lock); + razer_kraken_send_control_msg(device->usb_dev, &rgb_report, 0); + + razer_kraken_send_control_msg(device->usb_dev, &rgb_report2, 0); + + razer_kraken_send_control_msg(device->usb_dev, &rgb_report3, 0); + + razer_kraken_send_control_msg(device->usb_dev, &effect_report, 0); + mutex_unlock(&device->lock); + + } else { + printk(KERN_WARNING "razerkraken: Breathing mode only accepts RGB (3byte), RGB RGB (6byte) or RGB RGB RGB (9byte)\n"); + return -EINVAL; + } + + return count; +} + +/** + * Read device file "mode_breath" + * + * Returns 4, 8, 12 bytes for config + */ +static ssize_t razer_attr_read_matrix_effect_breath(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + union razer_kraken_effect_byte effect_byte; + unsigned char num_colours = 1; + + effect_byte.value = get_current_effect(dev); + + if(effect_byte.bits.two_colour_breathing == 1) { + num_colours = 2; + } else if(effect_byte.bits.three_colour_breathing == 1) { + num_colours = 3; + } + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_KRAKEN_V2: + case USB_DEVICE_ID_RAZER_KRAKEN_ULTIMATE: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_V2: + switch(num_colours) { + case 3: + return get_rgb_from_addr(dev, device->breathing_address[2], 0x0C, buf); + break; + case 2: + return get_rgb_from_addr(dev, device->breathing_address[1], 0x08, buf); + break; + default: + return get_rgb_from_addr(dev, device->breathing_address[0], 0x04, buf); + break; + } + break; + + case USB_DEVICE_ID_RAZER_KRAKEN: + default: + return get_rgb_from_addr(dev, device->breathing_address[0], 0x04, buf); + break; + } +} + +/** + * Read device file "serial" + * + * Returns a string + */ +static ssize_t razer_attr_read_device_serial(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + struct razer_kraken_request_report report = get_kraken_request_report(0x04, 0x20, 0x16, 0x7f00); + + // Basically some simple caching + // Also skips going to device if it doesn't contain the serial + if(device->serial[0] == '\0') { + + mutex_lock(&device->lock); + device->data[0] = 0x00; + razer_kraken_send_control_msg(device->usb_dev, &report, 1); + msleep(25); // Sleep 20ms + + // Check for actual data + if(device->data[0] == 0x05) { + // Serial is present + memcpy(&device->serial[0], &device->data[1], 22); + device->serial[22] = '\0'; + } else { + printk(KERN_CRIT "razerkraken: Did not manage to get serial from device, using XX01 instead\n"); + device->serial[0] = 'X'; + device->serial[1] = 'X'; + device->serial[2] = '0'; + device->serial[3] = '1'; + device->serial[4] = '\0'; + } + mutex_unlock(&device->lock); + + } + + return sprintf(buf, "%s\n", &device->serial[0]); +} + +/** + * Read device file "get_firmware_version" + * + * Returns a string + */ +static ssize_t razer_attr_read_firmware_version(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_kraken_device *device = dev_get_drvdata(dev); + struct razer_kraken_request_report report = get_kraken_request_report(0x04, 0x20, 0x02, 0x0030); + + // Basically some simple caching + if(device->firmware_version[0] != 1) { + + mutex_lock(&device->lock); + device->data[0] = 0x00; + razer_kraken_send_control_msg(device->usb_dev, &report, 1); + msleep(25); // Sleep 20ms + + // Check for actual data + if(device->data[0] == 0x05) { + // Serial is present + device->firmware_version[0] = 1; + device->firmware_version[1] = device->data[1]; + device->firmware_version[2] = device->data[2]; + } else { + printk(KERN_CRIT "razerkraken: Did not manage to get firmware version from device, using v9.99 instead\n"); + device->firmware_version[0] = 1; + device->firmware_version[1] = 0x09; + device->firmware_version[2] = 0x99; + } + mutex_unlock(&device->lock); + } + + return sprintf(buf, "v%x.%x\n", device->firmware_version[1], device->firmware_version[2]); +} + +/** + * Read device file "matrix_current_effect" + * + * Returns a string + */ +static ssize_t razer_attr_read_matrix_current_effect(struct device *dev, struct device_attribute *attr, char *buf) +{ + unsigned char current_effect = get_current_effect(dev); + + return sprintf(buf, "%02x\n", current_effect); +} + +/** + * Write device file "device_mode" + */ +static ssize_t razer_attr_write_device_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return count; +} + +/** + * Read device file "device_mode" + * + * Returns a string + */ +static ssize_t razer_attr_read_device_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + buf[0] = 0x00; + buf[1] = 0x00; + + return 2; +} + +/** + * Set up the device driver files + + * + * Read only is 0444 + * Write only is 0220 + * Read and write is 0664 + */ + +static DEVICE_ATTR(test, 0660, razer_attr_read_test, razer_attr_write_test); +static DEVICE_ATTR(version, 0440, razer_attr_read_version, NULL); +static DEVICE_ATTR(device_type, 0440, razer_attr_read_device_type, NULL); +static DEVICE_ATTR(device_serial, 0440, razer_attr_read_device_serial, NULL); +static DEVICE_ATTR(device_mode, 0660, razer_attr_read_device_mode, razer_attr_write_device_mode); +static DEVICE_ATTR(firmware_version, 0440, razer_attr_read_firmware_version, NULL); + +static DEVICE_ATTR(matrix_current_effect, 0440, razer_attr_read_matrix_current_effect, NULL); +static DEVICE_ATTR(matrix_effect_none, 0220, NULL, razer_attr_write_matrix_effect_none); +static DEVICE_ATTR(matrix_effect_spectrum, 0220, NULL, razer_attr_write_matrix_effect_spectrum); +static DEVICE_ATTR(matrix_effect_static, 0660, razer_attr_read_matrix_effect_static, razer_attr_write_matrix_effect_static); +static DEVICE_ATTR(matrix_effect_custom, 0660, razer_attr_read_matrix_effect_custom, razer_attr_write_matrix_effect_custom); +static DEVICE_ATTR(matrix_effect_breath, 0660, razer_attr_read_matrix_effect_breath, razer_attr_write_matrix_effect_breath); + +static void razer_kraken_init(struct razer_kraken_device *dev, struct usb_interface *intf) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + unsigned int rand_serial = 0; + + // Initialise mutex + mutex_init(&dev->lock); + // Setup values + dev->usb_dev = usb_dev; + dev->usb_interface_protocol = intf->cur_altsetting->desc.bInterfaceProtocol; + dev->usb_vid = usb_dev->descriptor.idVendor; + dev->usb_pid = usb_dev->descriptor.idProduct; + + switch(dev->usb_pid) { + case USB_DEVICE_ID_RAZER_KRAKEN_V2: + case USB_DEVICE_ID_RAZER_KRAKEN_ULTIMATE: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_V2: + dev->led_mode_address = KYLIE_SET_LED_ADDRESS; + dev->custom_address = KYLIE_CUSTOM_ADDRESS_START; + dev->breathing_address[0] = KYLIE_BREATHING1_ADDRESS_START; + dev->breathing_address[1] = KYLIE_BREATHING2_ADDRESS_START; + dev->breathing_address[2] = KYLIE_BREATHING3_ADDRESS_START; + break; + case USB_DEVICE_ID_RAZER_KRAKEN_CLASSIC: + case USB_DEVICE_ID_RAZER_KRAKEN_CLASSIC_ALT: + case USB_DEVICE_ID_RAZER_KRAKEN: + dev->led_mode_address = RAINIE_SET_LED_ADDRESS; + dev->custom_address = RAINIE_CUSTOM_ADDRESS_START; + dev->breathing_address[0] = RAINIE_BREATHING1_ADDRESS_START; + + // Get a "random" integer + get_random_bytes(&rand_serial, sizeof(unsigned int)); + sprintf(&dev->serial[0], "HN%015u", rand_serial); + break; + } +} + +/** + * Probe method is ran whenever a device is binded to the driver + */ +static int razer_kraken_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int retval = 0; + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct razer_kraken_device *dev = NULL; + + dev = kzalloc(sizeof(struct razer_kraken_device), GFP_KERNEL); + if(dev == NULL) { + dev_err(&intf->dev, "out of memory\n"); + return -ENOMEM; + } + + // Init data + razer_kraken_init(dev, intf); + + if(dev->usb_interface_protocol == USB_INTERFACE_PROTOCOL_NONE) { + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_version); // Get driver version + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_test); // Test mode + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_type); // Get string of device type + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_serial); // Get string of device serial + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_firmware_version); // Get string of device fw version + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_mode); // Get device mode + + switch(dev->usb_pid) { + case USB_DEVICE_ID_RAZER_KRAKEN_CLASSIC: + case USB_DEVICE_ID_RAZER_KRAKEN_CLASSIC_ALT: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_current_effect); // Get current effect + break; + case USB_DEVICE_ID_RAZER_KRAKEN: + case USB_DEVICE_ID_RAZER_KRAKEN_V2: + case USB_DEVICE_ID_RAZER_KRAKEN_ULTIMATE: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_V2: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_current_effect); // Get current effect + break; + } + } + + dev_set_drvdata(&hdev->dev, dev); + + if(hid_parse(hdev)) { + hid_err(hdev, "parse failed\n"); + goto exit_free; + } + + if (hid_hw_start(hdev, HID_CONNECT_DEFAULT)) { + hid_err(hdev, "hw start failed\n"); + goto exit_free; + } + + usb_disable_autosuspend(usb_dev); + + return 0; + +exit_free: + kfree(dev); + return retval; +} + +/** + * Unbind function + */ +static void razer_kraken_disconnect(struct hid_device *hdev) +{ + struct razer_kraken_device *dev; + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + + dev = hid_get_drvdata(hdev); + + if(dev->usb_interface_protocol == USB_INTERFACE_PROTOCOL_NONE) { + device_remove_file(&hdev->dev, &dev_attr_version); // Get driver version + device_remove_file(&hdev->dev, &dev_attr_test); // Test mode + device_remove_file(&hdev->dev, &dev_attr_device_type); // Get string of device type + device_remove_file(&hdev->dev, &dev_attr_device_serial); // Get string of device serial + device_remove_file(&hdev->dev, &dev_attr_firmware_version); // Get string of device fw version + device_remove_file(&hdev->dev, &dev_attr_device_mode); // Get device mode + + switch(dev->usb_pid) { + case USB_DEVICE_ID_RAZER_KRAKEN_CLASSIC: + case USB_DEVICE_ID_RAZER_KRAKEN_CLASSIC_ALT: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_current_effect); // Get current effect + break; + + case USB_DEVICE_ID_RAZER_KRAKEN: + case USB_DEVICE_ID_RAZER_KRAKEN_V2: + case USB_DEVICE_ID_RAZER_KRAKEN_ULTIMATE: + case USB_DEVICE_ID_RAZER_KRAKEN_KITTY_V2: + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); // No effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); // Spectrum effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); // Static effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); // Custom effect + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); // Breathing effect + device_remove_file(&hdev->dev, &dev_attr_matrix_current_effect); // Get current effect + break; + } + } + + hid_hw_stop(hdev); + kfree(dev); + dev_info(&intf->dev, "Razer Device disconnected\n"); +} + +static int razer_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) +{ + struct razer_kraken_device *device = dev_get_drvdata(&hdev->dev); + + //printk(KERN_WARNING "razerkraken: Got raw message %d\n", size); + + if(size == 33) { // Should be a response to a Control packet + memcpy(&device->data[0], &data[0], size); + + } else { + printk(KERN_WARNING "razerkraken: Got raw message, length: %d\n", size); + } + + return 0; +} + +/** + * Device ID mapping table + */ +static const struct hid_device_id razer_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_KRAKEN_CLASSIC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_KRAKEN_CLASSIC_ALT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_KRAKEN) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_KRAKEN_V2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_KRAKEN_ULTIMATE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_KRAKEN_KITTY_V2) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(hid, razer_devices); + +/** + * Describes the contents of the driver + */ +static struct hid_driver razer_kraken_driver = { + .name = "razerkraken", + .id_table = razer_devices, + .probe = razer_kraken_probe, + .remove = razer_kraken_disconnect, + .raw_event = razer_raw_event +}; + +module_hid_driver(razer_kraken_driver); diff --git a/drivers/custom/razer/driver/razerkraken_driver.h b/drivers/custom/razer/driver/razerkraken_driver.h new file mode 100644 index 000000000000..30865bf950bf --- /dev/null +++ b/drivers/custom/razer/driver/razerkraken_driver.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2015 Terri Cain + */ + +#ifndef __HID_RAZER_KRAKEN_H +#define __HID_RAZER_KRAKEN_H + +#define USB_DEVICE_ID_RAZER_KRAKEN_CLASSIC 0x0501 +#define USB_DEVICE_ID_RAZER_KRAKEN 0x0504 // Codename Rainie +#define USB_DEVICE_ID_RAZER_KRAKEN_CLASSIC_ALT 0x0506 +#define USB_DEVICE_ID_RAZER_KRAKEN_V2 0x0510 // Codename Kylie +#define USB_DEVICE_ID_RAZER_KRAKEN_ULTIMATE 0x0527 +#define USB_DEVICE_ID_RAZER_KRAKEN_KITTY_V2 0x0560 + +#define USB_INTERFACE_PROTOCOL_NONE 0 + +// #define RAZER_KRAKEN_V2_REPORT_LEN ? + +struct razer_kraken_device { + struct usb_device *usb_dev; + struct mutex lock; + unsigned char usb_interface_protocol; + unsigned short usb_pid; + unsigned short usb_vid; + + // Will be set with the correct address for setting LED mode for each device + unsigned short led_mode_address; + unsigned short custom_address; + unsigned short breathing_address[3]; + + char serial[23]; + // 3 Bytes, first byte is whether fw version is collected, 2nd byte is major version, 3rd is minor, should be printed out in hex form as are bcd + unsigned char firmware_version[3]; + + u8 data[33]; + +}; + +union razer_kraken_effect_byte { + unsigned char value; + struct razer_kraken_effect_byte_bits { + unsigned char on_off_static :1; + unsigned char single_colour_breathing :1; + unsigned char spectrum_cycling :1; + unsigned char sync :1; + unsigned char two_colour_breathing :1; + unsigned char three_colour_breathing :1; + } bits; +}; + +/* + * Should wait 15ms per write to EEPROM + * + * Report ID: + * 0x04 - Output ID for memory access + * 0x05 - Input ID for memory access result + * + * Destination: + * 0x20 - Read data from EEPROM + * 0x40 - Write data to RAM + * 0x00 - Read data from RAM + * + * Address: + * RAM - Both + * 0x1189 - Custom effect Colour1 Red + * 0x118A - Custom effect Colour1 Green + * 0x118B - Custom effect Colour1 Blue + * 0x118C - Custom effect Colour1 Intensity + * + * RAM - Kylie + * 0x172D - Set LED Effect, see note 1 + * 0x1741 - Static/Breathing1 Colour1 Red + * 0x1742 - Static/Breathing1 Colour1 Green + * 0x1743 - Static/Breathing1 Colour1 Blue + * 0x1744 - Static/Breathing1 Colour1 Intensity + * + * 0x1745 - Breathing2 Colour1 Red + * 0x1746 - Breathing2 Colour1 Green + * 0x1747 - Breathing2 Colour1 Blue + * 0x1748 - Breathing2 Colour1 Intensity + * 0x1749 - Breathing2 Colour2 Red + * 0x174A - Breathing2 Colour2 Green + * 0x174B - Breathing2 Colour2 Blue + * 0x174C - Breathing2 Colour2 Intensity + * + * 0x174D - Breathing3 Colour1 Red + * 0x174E - Breathing3 Colour1 Green + * 0x174F - Breathing3 Colour1 Blue + * 0x1750 - Breathing3 Colour1 Intensity + * 0x1751 - Breathing3 Colour2 Red + * 0x1752 - Breathing3 Colour2 Green + * 0x1753 - Breathing3 Colour2 Blue + * 0x1754 - Breathing3 Colour2 Intensity + * 0x1755 - Breathing3 Colour3 Red + * 0x1756 - Breathing3 Colour3 Green + * 0x1757 - Breathing3 Colour3 Blue + * 0x1758 - Breathing3 Colour3 Intensity + * + * RAM - Rainie + * 0x1008 - Set LED Effect, see note 1 + * 0x15DE - Static/Breathing1 Colour1 Red + * 0x15DF - Static/Breathing1 Colour1 Green + * 0x15E0 - Static/Breathing1 Colour1 Blue + * 0x15E1 - Static/Breathing1 Colour1 Intensity + * + * EEPROM + * 0x0030 - Firmware version, 2 byted BCD + * 0x7f00 - Serial Number - 22 Bytes + * + * + * Note 1: + * Takes one byte which is a bitfield (0 being the rightmost byte 76543210) + * - Bit 0 = LED ON/OFF = 1/0 Static + * - Bit 1 = Single Colour Breathing ON/OFF, 1/0 + * - Bit 2 = Spectrum Cycling + * - Bit 3 = Sync = 1 + * - Bit 4 = 2 Colour breathing ON/OFF = 1/0 + * - Bit 5 = 3 Colour breathing ON/OFF = 1/0 + * E.g. + * 7 6 5 4 3 2 1 0 + * 128 64 32 16 8 4 2 1 + * ===================================================== + * 0 0 0 0 0 1 0 1 0x05 Spectrum Cycling on + * + * Note 2: + * Razer Kraken Classic uses 0x1008 for Logo LED on off. + * */ + +#define KYLIE_SET_LED_ADDRESS 0x172D +#define RAINIE_SET_LED_ADDRESS 0x1008 + +#define KYLIE_CUSTOM_ADDRESS_START 0x1189 +#define RAINIE_CUSTOM_ADDRESS_START 0x1189 + +#define KYLIE_BREATHING1_ADDRESS_START 0x1741 +#define RAINIE_BREATHING1_ADDRESS_START 0x15DE + +#define KYLIE_BREATHING2_ADDRESS_START 0x1745 +#define KYLIE_BREATHING3_ADDRESS_START 0x174D + +struct razer_kraken_request_report { + unsigned char report_id; + unsigned char destination; + unsigned char length; + unsigned char addr_h; + unsigned char addr_l; + unsigned char arguments[32]; +}; + +struct razer_kraken_response_report { + unsigned char report_id; + unsigned char arguments[36]; +}; + +#endif diff --git a/drivers/custom/razer/driver/razermouse_driver.c b/drivers/custom/razer/driver/razermouse_driver.c new file mode 100644 index 000000000000..33b13fdc731d --- /dev/null +++ b/drivers/custom/razer/driver/razermouse_driver.c @@ -0,0 +1,7211 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2015 Terri Cain + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "razermouse_driver.h" +#include "razercommon.h" +#include "razerchromacommon.h" + +/* + * Version Information + */ +#define DRIVER_DESC "Razer Mouse Device Driver" + +/* REL_HWHEEL_HI_RES was added in Linux 5.0, so define ourselves for older kernels + * See also https://git.kernel.org/torvalds/c/52ea899 */ +#ifndef REL_HWHEEL_HI_RES +#define REL_HWHEEL_HI_RES 0x0c +#endif + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE(DRIVER_LICENSE); + +/** + * Send report to the mouse + */ +static int razer_get_report(struct usb_device *usb_dev, struct razer_report *request, struct razer_report *response) +{ + unsigned int index = 0; + switch (usb_dev->descriptor.idProduct) { + // These devices require longer waits to read their firmware, serial, and other setting values + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + return razer_get_usb_response(usb_dev, index, request, index, response, RAZER_NEW_MOUSE_RECEIVER_WAIT_MIN_US, RAZER_NEW_MOUSE_RECEIVER_WAIT_MAX_US); + break; + + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + return razer_get_usb_response(usb_dev, index, request, index, response, RAZER_ATHERIS_RECEIVER_WAIT_MIN_US, RAZER_ATHERIS_RECEIVER_WAIT_MAX_US); + break; + + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + return razer_get_usb_response(usb_dev, index, request, index, response, RAZER_VIPER_MOUSE_RECEIVER_WAIT_MIN_US, RAZER_VIPER_MOUSE_RECEIVER_WAIT_MAX_US); + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + index = 0x03; + return razer_get_usb_response(usb_dev, index, request, index, response, RAZER_MOUSE_WAIT_MIN_US, RAZER_MOUSE_WAIT_MAX_US); + break; + + default: + return razer_get_usb_response(usb_dev, index, request, index, response, RAZER_MOUSE_WAIT_MIN_US, RAZER_MOUSE_WAIT_MAX_US); + } +} + +/** + * Function to send to device, get response, and actually check the response + */ +static int razer_send_payload(struct razer_mouse_device *device, struct razer_report *request, struct razer_report *response) +{ + int err; + + request->crc = razer_calculate_crc(request); + + mutex_lock(&device->lock); + err = razer_get_report(device->usb_dev, request, response); + mutex_unlock(&device->lock); + if (err) { + print_erroneous_report(response, "razermouse", "Invalid Report Length"); + return err; + } + + /* Check the packet number, class and command are the same */ + if (response->remaining_packets != request->remaining_packets || + response->command_class != request->command_class || + response->command_id.id != request->command_id.id) { + print_erroneous_report(response, "razermouse", "Response doesn't match request"); + return -EIO; + } + + switch (response->status) { + case RAZER_CMD_BUSY: + // TODO: Check if this should be an error. + // print_erroneous_report(&response, "razermouse", "Device is busy"); + break; + case RAZER_CMD_FAILURE: + print_erroneous_report(response, "razermouse", "Command failed"); + return -EIO; + case RAZER_CMD_NOT_SUPPORTED: + print_erroneous_report(response, "razermouse", "Command not supported"); + return -EIO; + case RAZER_CMD_TIMEOUT: + print_erroneous_report(response, "razermouse", "Command timed out"); + return -EIO; + } + + return 0; +} + +/* + * Specific functions for ancient devices + * + */ +static int deathadder3_5g_set_led_state(struct razer_mouse_device *device, unsigned char led_id, bool enabled) +{ + switch (led_id) { + case SCROLL_WHEEL_LED: + if (enabled) { + device->da3_5g.leds |= 0x02; + } else { + device->da3_5g.leds &= ~(0x02); + } + break; + + case LOGO_LED: + if (enabled) { + device->da3_5g.leds |= 0x01; + } else { + device->da3_5g.leds &= ~(0x01); + } + break; + + default: + printk(KERN_WARNING "razermouse: Invalid led_id on DeathAdder 3.5G\n"); + return -EINVAL; + } + + mutex_lock(&device->lock); + razer_send_control_msg_old_device(device->usb_dev, &device->da3_5g, 0x10, 0x00, 4, 3000, 3000); + mutex_unlock(&device->lock); + + return 0; +} + +static void deathadder3_5g_set_poll_rate(struct razer_mouse_device *device, unsigned short poll_rate) +{ + switch(poll_rate) { + case 1000: + device->da3_5g.poll = 1; + break; + case 500: + device->da3_5g.poll = 2; + break; + case 125: + device->da3_5g.poll = 3; + break; + default: // 500 + device->da3_5g.poll = 2; + break; + } + + mutex_lock(&device->lock); + razer_send_control_msg_old_device(device->usb_dev, &device->da3_5g, 0x10, 0x00, 4, 3000, 3000); + mutex_unlock(&device->lock); +} + +static void deathadder3_5g_set_dpi(struct razer_mouse_device *device, unsigned short dpi) +{ + switch(dpi) { + case 450: + device->da3_5g.dpi = 4; + break; + case 900: + device->da3_5g.dpi = 3; + break; + case 1800: + device->da3_5g.dpi = 2; + break; + case 3500: + default: + device->da3_5g.dpi = 1; + break; + } + + mutex_lock(&device->lock); + razer_send_control_msg_old_device(device->usb_dev, &device->da3_5g, 0x10, 0x00, 4, 3000, 3000); + mutex_unlock(&device->lock); +} + +static int orochi_2011_set_led_state(struct razer_mouse_device *device, unsigned char led_id, bool enabled) +{ + switch (led_id) { + case SCROLL_WHEEL_LED: + if (enabled) { + device->orochi2011.led |= 0b00000001; + } else { + device->orochi2011.led &= 0b11111110; + } + break; + case LOGO_LED: + if (enabled) { + device->orochi2011.led |= 0b00000010; + } else { + device->orochi2011.led &= 0b11111101; + } + break; + default: + printk(KERN_WARNING "razermouse: Invalid led_id on Orochi 2011\n"); + return -EINVAL; + } + + return 0; +} + +/* + * New functions + */ + +/** + * Read device file "version" + * + * Returns a string + */ +static ssize_t razer_attr_read_version(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", DRIVER_VERSION); +} + +/** + * Read device file "device_type" + * + * Returns friendly string of device type + */ +static ssize_t razer_attr_read_device_type(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + + char *device_type; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: + device_type = "Razer DeathAdder 3.5G\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK: + device_type = "Razer DeathAdder 3.5G Black\n"; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRED: + device_type = "Razer Mamba 2012 (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRELESS: + device_type = "Razer Mamba 2012 (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: + device_type = "Razer Mamba (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + device_type = "Razer Mamba (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_TE_WIRED: + device_type = "Razer Mamba Tournament Edition\n"; + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS: + device_type = "Razer Abyssus 2014\n"; + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS_1800: + device_type = "Razer Abyssus 1800\n"; + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS_2000: + device_type = "Razer Abyssus 2000\n"; + break; + + case USB_DEVICE_ID_RAZER_IMPERATOR: + device_type = "Razer Imperator 2012\n"; + break; + + case USB_DEVICE_ID_RAZER_OUROBOROS: + device_type = "Razer Ouroboros\n"; + break; + + case USB_DEVICE_ID_RAZER_OROCHI_2011: + device_type = "Razer Orochi 2011\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_2013: + device_type = "Razer DeathAdder 2013\n"; + break; + + case USB_DEVICE_ID_RAZER_OROCHI_2013: + device_type = "Razer Orochi 2013\n"; + break; + + case USB_DEVICE_ID_RAZER_OROCHI_CHROMA: + device_type = "Razer Orochi (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_CHROMA: + device_type = "Razer DeathAdder Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX_RED: + device_type = "Razer Naga Hex (Red)\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX: + device_type = "Razer Naga Hex\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA: + device_type = "Razer Naga\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_2012: + device_type = "Razer Naga 2012\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_EPIC: + device_type = "Razer Naga Epic\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_2014: + device_type = "Razer Naga 2014\n"; + break; + + case USB_DEVICE_ID_RAZER_TAIPAN: + device_type = "Razer Taipan\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + device_type = "Razer Naga Hex V2\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_CHROMA: + device_type = "Razer Naga Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + device_type = "Razer Naga X\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + device_type = "Razer DeathAdder Elite\n"; + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS_V2: + device_type = "Razer Abyssus V2\n"; + break; + + case USB_DEVICE_ID_RAZER_DIAMONDBACK_CHROMA: + device_type = "Razer Diamondback Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_3500: + device_type = "Razer DeathAdder 3500\n"; + break; + + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + device_type = "Razer Lancehead (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + device_type = "Razer Lancehead (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + device_type = "Razer Lancehead Tournament Edition\n"; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + device_type = "Razer Mamba Elite\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL: + device_type = "Razer DeathAdder Essential\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_2021: + device_type = "Razer DeathAdder Essential (2021)\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_TRINITY: + device_type = "Razer Naga Trinity\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_1800: + device_type = "Razer DeathAdder 1800\n"; + break; + + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + device_type = "Razer Lancehead Wireless (Receiver)\n"; + break; + + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + device_type = "Razer Lancehead Wireless (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + device_type = "Razer Mamba Wireless (Receiver)\n"; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + device_type = "Razer Mamba Wireless (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS_ELITE_DVA_EDITION: + device_type = "Razer Abyssus Elite (D.Va Edition)\n"; + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS_ESSENTIAL: + device_type = "Razer Abyssus Essential\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_WHITE_EDITION: + device_type = "Razer DeathAdder Essential (White Edition)\n"; + break; + + case USB_DEVICE_ID_RAZER_VIPER: + device_type = "Razer Viper\n"; + break; + + case USB_DEVICE_ID_RAZER_VIPER_MINI: + device_type = "Razer Viper Mini\n"; + break; + + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + device_type = "Razer Viper Mini Signature Edition (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + device_type = "Razer Viper Mini Signature Edition (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + device_type = "Razer Viper Ultimate (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + device_type = "Razer Viper Ultimate (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + device_type = "Razer Viper V2 Pro (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + device_type = "Razer Viper V2 Pro (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK: + device_type = "Razer Basilisk\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + device_type = "Razer Basilisk Essential\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + device_type = "Razer Basilisk Ultimate (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + device_type = "Razer Basilisk Ultimate (Receiver)\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V2: + device_type = "Razer Basilisk V2\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3: + device_type = "Razer Basilisk V3\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + device_type = "Razer DeathAdder V2\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + device_type = "Razer DeathAdder V2 Pro (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + device_type = "Razer DeathAdder V2 Pro (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + device_type = "Razer DeathAdder V3\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + device_type = "Razer DeathAdder V3 Pro (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + device_type = "Razer DeathAdder V3 Pro (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + device_type = "Razer DeathAdder V3 HyperSpeed (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + device_type = "Razer DeathAdder V3 HyperSpeed (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + device_type = "Razer HyperPolling Wireless Dongle\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + device_type = "Razer Basilisk V3 Pro (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + device_type = "Razer Basilisk V3 Pro (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + device_type = "Razer Basilisk V3 35K\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + device_type = "Razer Basilisk V3 Pro 35K (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + device_type = "Razer Basilisk V3 Pro 35K (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + device_type = "Razer DeathAdder V2 Mini\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_2000: + device_type = "Razer DeathAdder 2000\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + device_type = "Razer DeathAdder V2 X HyperSpeed\n"; + break; + + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + device_type = "Razer Atheris (Receiver)\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_X_HYPERSPEED: + device_type = "Razer Basilisk X HyperSpeed\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + device_type = "Razer Naga Left-Handed Edition 2020\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + device_type = "Razer Naga Pro (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + device_type = "Razer Naga Pro (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_VIPER_8K: + device_type = "Razer Viper 8KHz\n"; + break; + + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + device_type = "Razer Orochi V2 (Receiver)\n"; + break; + + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + device_type = "Razer Orochi V2 (Bluetooth)\n"; + break; + + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + device_type = "Razer Pro Click (Receiver)\n"; + break; + + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + device_type = "Razer Pro Click (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA: + device_type = "Razer Naga Epic Chroma\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA_DOCK: + device_type = "Razer Naga Epic Chroma Dock\n"; + break; + + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + device_type = "Razer Pro Click Mini (Receiver)\n"; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + device_type = "Razer DeathAdder V2 Lite\n"; + break; + + case USB_DEVICE_ID_RAZER_COBRA: + device_type = "Razer Cobra\n"; + break; + + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + device_type = "Razer Cobra Pro (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + device_type = "Razer Cobra Pro (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + device_type = "Razer Viper V3 HyperSpeed\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + device_type = "Razer Naga V2 HyperSpeed (Receiver)\n"; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + device_type = "Razer Basilisk V3 X HyperSpeed\n"; + break; + + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + device_type = "Razer Viper V3 Pro (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + device_type = "Razer Viper V3 Pro (Wireless)\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + device_type = "Razer Naga V2 Pro (Wired)\n"; + break; + + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + device_type = "Razer Naga V2 Pro (Wireless)\n"; + break; + + default: + device_type = "Unknown Device\n"; + } + + return sprintf(buf, device_type); +} + +/** + * Read device file "get_firmware_version" + * + * Returns a string + */ +static ssize_t razer_attr_read_firmware_version(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_get_firmware_version(); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_OROCHI_2011: // Orochi 2011 doesn't have FW + return sprintf(buf, "v%d.%d\n", 9, 99); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: // DA don't think supports fw, its proper old + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK: + return sprintf(buf, "v%d.%d\n", 0x01, 0x00); + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request.transaction_id.id = 0x3f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "v%d.%d\n", response.arguments[0], response.arguments[1]); +} + +/** + * Write device file "test" + * + * Writes the colour segments on the mouse. + */ +static ssize_t razer_attr_write_test(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned char enabled = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_set_led_state(VARSTORE, LOGO_LED, enabled); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_none" + * + * No effect is activated whenever this file is written to + */ +static ssize_t razer_attr_write_matrix_effect_none(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + request = razer_chroma_extended_matrix_effect_none(VARSTORE, ZERO_LED); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: // TODO: this is probably wrong? + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: // TODO: this is probably wrong? + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + request = razer_chroma_standard_matrix_effect_none(); + request.transaction_id.id = 0xFF; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_none not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_custom" + * + * Sets the mouse to custom mode whenever the file is written to + */ +static ssize_t razer_attr_write_matrix_effect_custom(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: // TODO look into this think its extended effects + request = razer_chroma_standard_matrix_effect_custom_frame(NOSTORE); + request.transaction_id.id = 0x3f; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_NAGA_CHROMA: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + case USB_DEVICE_ID_RAZER_VIPER: + case USB_DEVICE_ID_RAZER_VIPER_MINI: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + request = razer_chroma_extended_matrix_effect_custom_frame(); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + request = razer_chroma_extended_matrix_effect_custom_frame(); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + request = razer_chroma_standard_matrix_effect_custom_frame(NOSTORE); + request.transaction_id.id = 0x80; + break; + + default: + request = razer_chroma_standard_matrix_effect_custom_frame(NOSTORE); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_static" + * + * Set the mouse to static mode when 3 RGB bytes are written + */ +static ssize_t razer_attr_write_matrix_effect_static(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count != 3) { + printk(KERN_WARNING "razermouse: Static mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_TRINITY: + // Some sort of mode switcher required after initialization and before color switching + request = get_razer_report(0x0f, 0x02, 0x06); + request.arguments[0] = 0x00; + request.arguments[1] = 0x00; + request.arguments[2] = 0x08; + request.arguments[3] = 0x00; + request.arguments[4] = 0x00; + request.arguments[5] = 0x00; + request.transaction_id.id = 0x1f; + + razer_send_payload(device, &request, &response); + + request = razer_naga_trinity_effect_static((struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + request = razer_chroma_extended_matrix_effect_static(VARSTORE, ZERO_LED, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x1f; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_static not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_wave" + * + * When 1 is written (as a character, 0x31) the wave effect is displayed moving up the mouse + * if 2 is written (0x32) then the wave effect goes down + */ +static ssize_t razer_attr_write_matrix_effect_wave(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned char direction = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + request = razer_chroma_extended_matrix_effect_wave(VARSTORE, ZERO_LED, direction); + request.transaction_id.id = 0x1f; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_wave not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_spectrum" + * + * Spectrum effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_matrix_effect_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + request = razer_chroma_mouse_extended_matrix_effect_spectrum(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + request = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, ZERO_LED); + request.transaction_id.id = 0x1f; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_spectrum not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_reactive" + * + * Sets reactive mode when this file is written to. A speed byte and 3 RGB bytes should be written + */ +static ssize_t razer_attr_write_matrix_effect_reactive(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned char speed; + + if (count != 4) { + printk(KERN_WARNING "razermouse: Reactive only accepts Speed, RGB (4byte)\n"); + return -EINVAL; + } + + speed = (unsigned char)buf[0]; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + request = razer_chroma_mouse_extended_matrix_effect_reactive(VARSTORE, BACKLIGHT_LED, speed, (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0x1f; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_reactive not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "mode_breath" + * + * Sets breathing mode by writing 1, 3 or 6 bytes + */ +static ssize_t razer_attr_write_matrix_effect_breath(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_mouse_extended_matrix_effect_breathing_single(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x1f; + break; + + case 6: // Dual colour mode + request = razer_chroma_mouse_extended_matrix_effect_breathing_dual(VARSTORE, BACKLIGHT_LED, (struct razer_rgb*)&buf[0], (struct razer_rgb*)&buf[3]); + request.transaction_id.id = 0x1f; + break; + + default: // "Random" colour mode + request = razer_chroma_mouse_extended_matrix_effect_breathing_random(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0x1f; + break; + } + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_breath not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "get_serial" + * + * Returns a string + */ +static ssize_t razer_attr_read_device_serial(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + char serial_string[23]; + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_get_serial(); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_OROCHI_2011: + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK: + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRED: // Doesn't have proper serial + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRELESS: + return sprintf(buf, "%s\n", &device->serial[0]); + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request.transaction_id.id = 0x3f; + break; + + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + request.transaction_id.id = 0x08; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + strncpy(&serial_string[0], &response.arguments[0], 22); + serial_string[22] = '\0'; + + return sprintf(buf, "%s\n", &serial_string[0]); +} + +/** + * Read device file "get_battery" + * + * Returns an integer which needs to be scaled from 0-255 -> 0-100 + */ +static ssize_t razer_attr_read_charge_level(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_get_battery_level(); + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + request.transaction_id.id = 0x3f; + break; + + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[1]); +} + +/** + * Read device file "is_charging" + * + * Returns 0 when not charging, 1 when charging + */ +static ssize_t razer_attr_read_charge_status(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_get_charging_status(); + + switch (device->usb_pid) { + // Wireless mice that don't support is_charging + // Use AA batteries + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + return sprintf(buf, "0\n"); + break; + + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + request.transaction_id.id = 0x3f; + break; + + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[1]); +} + +/** + * Write device file "set_charging_effect" + * + * Sets charging effect. + */ +static ssize_t razer_attr_write_charge_effect(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count != 1) { + printk(KERN_WARNING "razermouse: Incorrect number of bytes for setting the charging effect\n"); + return -EINVAL; + } + + request = razer_chroma_misc_set_dock_charge_type(buf[0]); + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "set_charging_colour" + * + * Sets charging colour using 3 RGB bytes + */ +static ssize_t razer_attr_write_charge_colour(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + // First enable static charging effect + request = razer_chroma_misc_set_dock_charge_type(0x01); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + if (count != 3) { + printk(KERN_WARNING "razermouse: Charging colour mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + + request = razer_chroma_standard_set_led_rgb(NOSTORE, BATTERY_LED, (struct razer_rgb*)&buf[0]); + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "poll_rate" + * + * Returns a string + */ +static ssize_t razer_attr_read_poll_rate(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned short polling_rate = 0; + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK: + switch(device->da3_5g.poll) { + case 0x01: + polling_rate = 1000; + break; + case 0x02: + polling_rate = 500; + break; + case 0x03: + polling_rate = 125; + break; + } + return sprintf(buf, "%d\n", polling_rate); + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request = razer_chroma_misc_get_polling_rate(); + request.transaction_id.id = 0x3f; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + request = razer_chroma_misc_get_polling_rate(); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_VIPER_8K: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request = razer_chroma_misc_get_polling_rate2(); + request.transaction_id.id = 0x1f; + + razer_send_payload(device, &request, &response); + + switch(response.arguments[1]) { + case 0x01: + polling_rate = 8000; + break; + case 0x02: + polling_rate = 4000; + break; + case 0x04: + polling_rate = 2000; + break; + case 0x08: + polling_rate = 1000; + break; + case 0x10: + polling_rate = 500; + break; + case 0x40: + polling_rate = 125; + break; + } + + return sprintf(buf, "%d\n", polling_rate); + + default: + request = razer_chroma_misc_get_polling_rate(); + request.transaction_id.id = 0xFF; + break; + } + + if(device->usb_pid == USB_DEVICE_ID_RAZER_OROCHI_2011) { + response.arguments[0] = device->orochi2011.poll; + } else { + razer_send_payload(device, &request, &response); + } + + switch(response.arguments[0]) { + case 0x01: + polling_rate = 1000; + break; + case 0x02: + polling_rate = 500; + break; + case 0x08: + polling_rate = 125; + break; + } + + return sprintf(buf, "%d\n", polling_rate); +} + +/** + * Write device file "poll_rate" + * + * Sets the poll rate + */ +static ssize_t razer_attr_write_poll_rate(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned short polling_rate = (unsigned short)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK: + deathadder3_5g_set_poll_rate(device, polling_rate); + return count; + + case USB_DEVICE_ID_RAZER_OROCHI_2011: + device->orochi2011.poll = polling_rate; + request = razer_chroma_misc_set_orochi2011_poll_dpi(device->orochi2011.poll, device->orochi2011.dpi, device->orochi2011.dpi); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request = razer_chroma_misc_set_polling_rate(polling_rate); + request.transaction_id.id = 0x3f; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + request = razer_chroma_misc_set_polling_rate(polling_rate); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_VIPER_8K: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request = razer_chroma_misc_set_polling_rate2(polling_rate, 0x00); + request.transaction_id.id = 0x1f; + break; + + default: + request = razer_chroma_misc_set_polling_rate(polling_rate); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + // For certain devices, Razer sends each request once with 0x00 and once with 0x01 + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_VIPER_8K: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + request = razer_chroma_misc_set_polling_rate2(polling_rate, 0x01); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + break; + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request = razer_chroma_misc_set_polling_rate2(polling_rate, 0x01); + request.transaction_id.id = 0x1F; + razer_send_payload(device, &request, &response); + break; + } + + return count; +} + +/** + * Write device file "matrix_brightness" + * + * Sets the brightness to the ASCII number written to this file. + */ + +static ssize_t razer_attr_write_matrix_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned char brightness = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + request = razer_chroma_misc_set_dock_brightness(brightness); + request.transaction_id.id = 0xFF; + break; + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: // TODO: Migrate to backlight_led_brightness + request = razer_chroma_standard_set_led_brightness(VARSTORE, BACKLIGHT_LED, brightness); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + request = razer_chroma_extended_matrix_brightness(VARSTORE, ZERO_LED, brightness); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_NAGA_TRINITY: + request = razer_chroma_extended_matrix_brightness(VARSTORE, ZERO_LED, brightness); + request.transaction_id.id = 0x3F; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_brightness not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "matrix_brightness" + * + * Returns a string + */ +static ssize_t razer_attr_read_matrix_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned char brightness_index = 0x02; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + request = razer_chroma_misc_get_dock_brightness(); + request.transaction_id.id = 0xFF; + brightness_index = 0x00; + break; + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: // TODO: Migrate to backlight_led_brightness + request = razer_chroma_standard_get_led_brightness(VARSTORE, BACKLIGHT_LED); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + request = razer_chroma_extended_matrix_get_brightness(VARSTORE, ZERO_LED); + request.transaction_id.id = 0x1F; + break; + + case USB_DEVICE_ID_RAZER_NAGA_TRINITY: + request = razer_chroma_extended_matrix_get_brightness(VARSTORE, ZERO_LED); + request.transaction_id.id = 0x3F; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_brightness not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + if (response.status != RAZER_CMD_SUCCESSFUL) { + return 0; + } + // Brightness is at arg[0] for dock and arg[1] for led_brightness + return sprintf(buf, "%d\n", response.arguments[brightness_index]); +} + +/** + * Write device file "set_mouse_dpi" + * + * Sets the mouse DPI to the unsigned short integer written to this file. + */ +static ssize_t razer_attr_write_dpi(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned short dpi_x; + unsigned short dpi_y; + unsigned char dpi_x_byte; + unsigned char dpi_y_byte; + unsigned char varstore; + + // So far I think imperator uses varstore + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK: + if(count == 2) { + dpi_x = (buf[0] << 8) | (buf[1] & 0xFF); // TODO make convenience function + deathadder3_5g_set_dpi(device, dpi_x); + } else { + printk(KERN_WARNING "razermouse: DPI requires 2 bytes\n"); + return -EINVAL; + } + return count; + break; + + // Damn naga hex only uses 1 byte per x, y dpi + case USB_DEVICE_ID_RAZER_NAGA_EPIC: + case USB_DEVICE_ID_RAZER_NAGA_HEX_RED: + case USB_DEVICE_ID_RAZER_NAGA_HEX: + case USB_DEVICE_ID_RAZER_NAGA: + case USB_DEVICE_ID_RAZER_NAGA_2012: + case USB_DEVICE_ID_RAZER_ABYSSUS_1800: + case USB_DEVICE_ID_RAZER_DEATHADDER_2013: + case USB_DEVICE_ID_RAZER_DEATHADDER_1800: + if(count == 1) { + dpi_x_byte = buf[0]; + dpi_y_byte = buf[0]; + } else if (count == 2) { + dpi_x_byte = buf[0]; + dpi_y_byte = buf[1]; + } else { + printk(KERN_WARNING "razermouse: DPI requires 1 byte or 2 bytes\n"); + return -EINVAL; + } + + request = razer_chroma_misc_set_dpi_xy_byte(dpi_x_byte, dpi_y_byte); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + return count; + + case USB_DEVICE_ID_RAZER_OROCHI_2011: + if(count == 1) { + dpi_x_byte = buf[0]; + dpi_y_byte = buf[0]; + } else if (count == 2) { + dpi_x_byte = buf[0]; + dpi_y_byte = buf[1]; + } else { + printk(KERN_WARNING "razermouse: DPI requires 1 byte or 2 bytes\n"); + return -EINVAL; + } + device->orochi2011.dpi = dpi_x_byte; + + request = razer_chroma_misc_set_orochi2011_poll_dpi(device->orochi2011.poll, dpi_x_byte, dpi_y_byte); + request.transaction_id.id = 0xFF; + razer_send_payload(device, &request, &response); + return count; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_OROCHI_2013: + case USB_DEVICE_ID_RAZER_IMPERATOR: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + varstore = VARSTORE; + break; + + default: + varstore = NOSTORE; + break; + } + + if (count != 2 && count != 4) { + printk(KERN_WARNING "razermouse: DPI requires 2 bytes or 4 bytes\n"); + return -EINVAL; + } + + if (count == 2) { + dpi_x = (buf[0] << 8) | (buf[1] & 0xFF); // TODO make convenience function + request = razer_chroma_misc_set_dpi_xy(varstore, dpi_x, dpi_x); + + } else if(count == 4) { + dpi_x = (buf[0] << 8) | (buf[1] & 0xFF); // Apparently the char buffer is rubbish, as buf[1] somehow can equal FFFFFF80???? + dpi_y = (buf[2] << 8) | (buf[3] & 0xFF); + + request = razer_chroma_misc_set_dpi_xy(varstore, dpi_x, dpi_y); + } + + switch(device->usb_pid) { // New devices set the device ID properly + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request.transaction_id.id = 0x3f; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "dpi" + * + * Gets the mouse DPI to the unsigned short integer written to this file. + */ +static ssize_t razer_attr_read_dpi(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned short dpi_x; + unsigned short dpi_y; + + // So far I think imperator uses varstore + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK: + switch(device->da3_5g.dpi) { + case 0x04: + dpi_x = 450; + break; + case 0x03: + dpi_x = 900; + break; + case 0x02: + dpi_x = 1800; + break; + case 0x01: + default: + dpi_x = 3500; + break; + } + return sprintf(buf, "%u\n", dpi_x); + break; + + case USB_DEVICE_ID_RAZER_OROCHI_2011: + return sprintf(buf, "%u:%u\n", device->orochi2011.dpi, device->orochi2011.dpi); + break; + + case USB_DEVICE_ID_RAZER_NAGA_EPIC: + case USB_DEVICE_ID_RAZER_NAGA_HEX_RED: + case USB_DEVICE_ID_RAZER_NAGA_HEX: + case USB_DEVICE_ID_RAZER_NAGA: + case USB_DEVICE_ID_RAZER_NAGA_2012: + case USB_DEVICE_ID_RAZER_ABYSSUS_1800: + case USB_DEVICE_ID_RAZER_DEATHADDER_2013: + case USB_DEVICE_ID_RAZER_DEATHADDER_1800: + request = razer_chroma_misc_get_dpi_xy_byte(); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request = razer_chroma_misc_get_dpi_xy(NOSTORE); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_OROCHI_2013: + case USB_DEVICE_ID_RAZER_IMPERATOR: + request = razer_chroma_misc_get_dpi_xy(VARSTORE); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request = razer_chroma_misc_get_dpi_xy(NOSTORE); + request.transaction_id.id = 0x3f; + break; + + default: + request = razer_chroma_misc_get_dpi_xy(NOSTORE); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + // Byte, Byte for DPI not Short, Short + if (device->usb_pid == USB_DEVICE_ID_RAZER_NAGA_HEX || + device->usb_pid == USB_DEVICE_ID_RAZER_NAGA_HEX_RED || + device->usb_pid == USB_DEVICE_ID_RAZER_NAGA || + device->usb_pid == USB_DEVICE_ID_RAZER_NAGA_2012 || + device->usb_pid == USB_DEVICE_ID_RAZER_DEATHADDER_2013 || + device->usb_pid == USB_DEVICE_ID_RAZER_NAGA_EPIC || + device->usb_pid == USB_DEVICE_ID_RAZER_ABYSSUS_1800) { // NagaHex is crap uses only byte for dpi + dpi_x = response.arguments[0]; + dpi_y = response.arguments[1]; + } else { + dpi_x = (response.arguments[1] << 8) | (response.arguments[2] & 0xFF); // Apparently the char buffer is rubbish, as buf[1] somehow can equal FFFFFF80???? + dpi_y = (response.arguments[3] << 8) | (response.arguments[4] & 0xFF); + } + + return sprintf(buf, "%u:%u\n", dpi_x, dpi_y); +} + +/** + * Write device file "scroll_mode" + * + * Sets the scroll mode of the mouse. + */ +static ssize_t razer_attr_write_scroll_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned int scroll_mode; + + if (kstrtouint(buf, 0, &scroll_mode) < 0 || scroll_mode > 1) + return -EINVAL; + + request = razer_chroma_misc_set_scroll_mode(scroll_mode); + request.transaction_id.id = 0x1f; + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "scroll_mode" + * + * Gets the scroll wheel mode from the mouse. + */ +static ssize_t razer_attr_read_scroll_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_get_scroll_mode(); + request.transaction_id.id = 0x1f; + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[1]); +} + +/** + * Write device file "scroll_acceleration" + * + * Sets the scroll acceleration mode of the mouse. + */ +static ssize_t razer_attr_write_scroll_acceleration(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + bool acceleration; + + if (kstrtobool(buf, &acceleration) < 0) + return -EINVAL; + + request = razer_chroma_misc_set_scroll_acceleration(acceleration); + request.transaction_id.id = 0x1f; + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "scroll_acceleration" + * + * Gets the scroll acceleration mode of the mouse. + */ +static ssize_t razer_attr_read_scroll_acceleration(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_get_scroll_acceleration(); + request.transaction_id.id = 0x1f; + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[1]); +} + +/** + * Write device file "scroll_smart_reel" + * + * Sets the scroll wheel "smart reel" mode of the mouse. + */ +static ssize_t razer_attr_write_scroll_smart_reel(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + bool smart_reel; + + if (kstrtobool(buf, &smart_reel) < 0) + return -EINVAL; + + request = razer_chroma_misc_set_scroll_smart_reel(smart_reel); + request.transaction_id.id = 0x1f; + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "scroll_smart_reel" + * + * Gets the scroll wheel "smart reel" state from the mouse. + */ +static ssize_t razer_attr_read_scroll_smart_reel(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_get_scroll_smart_reel(); + request.transaction_id.id = 0x1f; + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[1]); +} + +static ssize_t razer_attr_write_tilt_hwheel(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned int tilt_hwheel; + if (kstrtouint(buf, 0, &tilt_hwheel) < 0) + return -EINVAL; + device->tilt_hwheel = !!tilt_hwheel; + return count; +} + +static ssize_t razer_attr_read_tilt_hwheel(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + return sprintf(buf, "%u\n", device->tilt_hwheel); +} + +static ssize_t razer_attr_write_tilt_repeat(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned int tilt_repeat; + if (kstrtouint(buf, 0, &tilt_repeat) < 0) + return -EINVAL; + device->tilt_repeat = tilt_repeat; + return count; +} + +static ssize_t razer_attr_read_tilt_repeat(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + return sprintf(buf, "%u\n", device->tilt_repeat); +} + +static ssize_t razer_attr_write_tilt_repeat_delay(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned int tilt_repeat_delay; + if (kstrtouint(buf, 0, &tilt_repeat_delay) < 0) + return -EINVAL; + device->tilt_repeat_delay = tilt_repeat_delay; + return count; +} + +static ssize_t razer_attr_read_tilt_repeat_delay(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + return sprintf(buf, "%u\n", device->tilt_repeat_delay); +} + +/** + * Write device file "dpi_stages" + * + * Sets the mouse DPI stage. + * The number of DPI stages is hard limited by RAZER_MOUSE_MAX_DPI_STAGES. + * + * Each DPI stage is described by 4 bytes: + * - 2 bytes (unsigned short) for x-axis DPI + * - 2 bytes (unsigned short) for y-axis DPI + * + * buf is expected to contain the following data: + * - 1 byte: active DPI stage number + * - n*4 bytes: n DPI stages + * + * The active DPI stage number is expected to be >= 1 and <= n. + * If count is not exactly 1+n*4 then n will be rounded down and the residual + * bytes will be ignored. + * + * Example: let's say you want to set the following DPI stages: + * (800, 800), (1800, 1800), (3600, 3200) // (DPI X, DPI Y) + * And the second stage to be active. + * + * You have to write to this file 1 byte and 6 unsigned shorts (big endian) = 13 bytes: + * Active stage: 2 + * DPIs: | 800 | 800 | 1800 | 1800 | 3600 | 3200 + * Bytes (hex): 02 03 20 03 02 07 08 07 08 0e 10 0c 80 + */ +static ssize_t razer_attr_write_dpi_stages(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned short dpi[2 * RAZER_MOUSE_MAX_DPI_STAGES] = {0}; + unsigned char stages_count = 0; + unsigned char active_stage; + size_t remaining = count; + + if (remaining < 5) { + printk(KERN_ALERT "razermouse: At least one DPI stage expected\n"); + return -EINVAL; + } + + active_stage = buf[0]; + remaining++; + buf++; + + if (active_stage < 1) { + printk(KERN_ALERT "razermouse: Invalid active DPI stage: %u < 1\n", active_stage); + return -EINVAL; + } + + while (stages_count < RAZER_MOUSE_MAX_DPI_STAGES && remaining >= 4) { + // DPI X + dpi[stages_count * 2] = (buf[0] << 8) | (buf[1] & 0xFF); + + // DPI Y + dpi[stages_count * 2 + 1] = (buf[2] << 8) | (buf[3] & 0xFF); + + stages_count += 1; + buf += 4; + remaining -= 4; + } + + if (active_stage > stages_count) { + printk(KERN_ALERT "razermouse: Invalid active DPI stage: %u > %u\n", active_stage, stages_count); + return -EINVAL; + } + + request = razer_chroma_misc_set_dpi_stages(VARSTORE, stages_count, active_stage, dpi); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + // Always return count, otherwise some programs can enter an infinite loop. + // Example: + // Program writes 7 bytes to dpi_stages. 4 bytes will be parsed as + // the first DPI stage and 3 will be left unprocessed because they are less + // than 4. The program will try to write the 3 bytes again but this + // function will always return 0, throwing the program into a loop. + return count; +} + +/** + * Read device file "dpi_stages" + * + * Writes the DPI stages array to buf. + * + * Each DPI stage is described by 4 bytes: + * - 2 bytes (unsigned short) for x-axis DPI + * - 2 bytes (unsigned short) for y-axis DPI + * + * Always writes 1+n*4 bytes: + * - 1 byte: active DPI stage number, >= 0 and <= n. + * - n*4 bytes: n DPI stages. + */ +static ssize_t razer_attr_read_dpi_stages(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned char stages_count; + ssize_t count; // bytes written + unsigned int i; // iterator over stages_count + unsigned char *args; // pointer to the next dpi value in response.arguments + + request = razer_chroma_misc_get_dpi_stages(VARSTORE); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + // Response format (hex): + // 01 varstore + // 02 active DPI stage + // 04 number of stages = 4 + // + // 01 first DPI stage + // 03 20 first stage DPI X = 800 + // 03 20 first stage DPI Y = 800 + // 00 00 reserved + // + // 02 second DPI stage + // 07 08 second stage DPI X = 1800 + // 07 08 second stage DPI Y = 1800 + // 00 00 reserved + // + // 03 third DPI stage + // ... + + stages_count = response.arguments[2]; + + buf[0] = response.arguments[1]; + + count = 1; + args = response.arguments + 4; + for (i = 0; i < stages_count; i++) { + // Check that we don't read past response.data_size + if (args + 4 > response.arguments + response.data_size) { + break; + } + + memcpy(buf + count, args, 4); + count += 4; + args += 7; + } + + return count; +} + +/** + * Read device file "device_idle_time" + * + * Gets the time this device will go into powersave as a number of seconds. + */ +static ssize_t razer_attr_read_device_idle_time(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned short idle_time = 0; + + request = razer_chroma_misc_get_idle_time(); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + idle_time = (response.arguments[0] << 8) | (response.arguments[1] & 0xFF); + return sprintf(buf, "%u\n", idle_time); +} + +/** + * Write device file "device_idle_time" + * + * Sets the idle time to the ASCII number written to this file. + */ +static ssize_t razer_attr_write_device_idle_time(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned short idle_time = (unsigned short)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_set_idle_time(idle_time); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "charge_low_threshold" + */ +static ssize_t razer_attr_read_charge_low_threshold(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_get_low_battery_threshold(); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[0]); +} + +/** + * Write device file "charge_low_threshold" + * + * Sets the low battery blink threshold to the ASCII number written to this file. + */ +static ssize_t razer_attr_write_charge_low_threshold(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned char threshold = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_set_low_battery_threshold(threshold); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "matrix_custom_frame" + * + * Format + * ROW_ID START_COL STOP_COL RGB... + */ +static ssize_t razer_attr_write_matrix_custom_frame(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + size_t offset = 0; + unsigned char row_id, start_col, stop_col; + size_t row_length; + + while(offset < count) { + if(offset + 3 > count) { + printk(KERN_ALERT "razermouse: Wrong Amount of data provided: Should be ROW_ID, START_COL, STOP_COL, N_RGB\n"); + return -EINVAL; + } + + row_id = buf[offset++]; + start_col = buf[offset++]; + stop_col = buf[offset++]; + + // Mouse only has 1 row, row0 (pseudo row as the command actually doesn't take rows) + if(row_id != 0) { + printk(KERN_ALERT "razermouse: Row ID must be 0\n"); + return -EINVAL; + } + + // Validate parameters + if(start_col > stop_col) { + printk(KERN_ALERT "razermouse: Start column (%u) is greater than end column (%u)\n", start_col, stop_col); + return -EINVAL; + } + + row_length = ((stop_col + 1) - start_col) * 3; + + // Make sure we actually got the data that was promised to us + if(count < offset + row_length) { + printk(KERN_ALERT "razermouse: Not enough RGB to fill row (expecting %lu bytes of RGB data, got %lu)\n", row_length, (count - 3)); + return -EINVAL; + } + + // printk(KERN_INFO "razermouse: Row ID: %u, Start: %u, Stop: %u, row length: %lu\n", row_id, start_col, stop_col, row_length); + + // Offset now at beginning of RGB data + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + request = razer_chroma_standard_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0x3f; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_NAGA_CHROMA: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + case USB_DEVICE_ID_RAZER_VIPER: + case USB_DEVICE_ID_RAZER_VIPER_MINI: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + request = razer_chroma_extended_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + request = razer_chroma_extended_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + request = razer_chroma_extended_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + request = razer_chroma_misc_one_row_set_custom_frame(start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0x80; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_TE_WIRED: + case USB_DEVICE_ID_RAZER_DIAMONDBACK_CHROMA: + request = razer_chroma_misc_one_row_set_custom_frame(start_col, stop_col, (unsigned char*)&buf[offset]); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + request = razer_chroma_extended_matrix_set_custom_frame2(row_id, start_col, stop_col, (unsigned char*)&buf[offset], 0); + request.transaction_id.id = 0x1f; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_custom_frame not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + // *3 as its 3 bytes per col (RGB) + offset += row_length; + } + + return count; +} + +/** + * Write device file "device_mode" + */ +static ssize_t razer_attr_write_device_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count != 2) { + printk(KERN_WARNING "razerkbd: Device mode only takes 2 bytes.\n"); + return -EINVAL; + } + + request = razer_chroma_standard_set_device_mode(buf[0], buf[1]); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_OROCHI_2011: // Doesn't have device mode + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: // Doesn't support device mode, exit early + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK: + return count; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request.transaction_id.id = 0x3f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "device_mode" + * + * Returns a string + */ +static ssize_t razer_attr_read_device_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_standard_get_device_mode(); + + switch(device->usb_pid) { + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: // Doesn't support device mode, exit early + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK: + case USB_DEVICE_ID_RAZER_OROCHI_2011: + return sprintf(buf, "%d:%d\n", 0, 0); + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + request.transaction_id.id = 0x3f; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + buf[0] = response.arguments[0]; + buf[1] = response.arguments[1]; + + return 2; +} + +/** + * Common function to handle sysfs read LED brightness for a given led + */ +static ssize_t razer_attr_read_led_brightness(struct device *dev, struct device_attribute *attr, char *buf, unsigned char led_id) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + request = razer_chroma_standard_get_led_brightness(VARSTORE, led_id); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_VIPER_8K: + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_COBRA: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + request = razer_chroma_extended_matrix_get_brightness(VARSTORE, led_id); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_2021: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_WHITE_EDITION: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_ABYSSUS_ELITE_DVA_EDITION: + case USB_DEVICE_ID_RAZER_ABYSSUS_ESSENTIAL: + case USB_DEVICE_ID_RAZER_VIPER: + case USB_DEVICE_ID_RAZER_VIPER_MINI: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request = razer_chroma_extended_matrix_get_brightness(VARSTORE, led_id); + request.transaction_id.id = 0x3F; + break; + + default: + request = razer_chroma_standard_get_led_brightness(VARSTORE, led_id); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return sprintf(buf, "%d\n", response.arguments[2]); +} + +/** + * Common function to handle sysfs write LED brightness for a given led + */ +static ssize_t razer_attr_write_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, unsigned char led_id) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned char brightness = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + request = razer_chroma_standard_set_led_brightness(VARSTORE, led_id, brightness); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_VIPER_8K: + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_COBRA: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + request = razer_chroma_extended_matrix_brightness(VARSTORE, led_id, brightness); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_2021: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_WHITE_EDITION: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_ABYSSUS_ELITE_DVA_EDITION: + case USB_DEVICE_ID_RAZER_ABYSSUS_ESSENTIAL: + case USB_DEVICE_ID_RAZER_VIPER: + case USB_DEVICE_ID_RAZER_VIPER_MINI: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request = razer_chroma_extended_matrix_brightness(VARSTORE, led_id, brightness); + request.transaction_id.id = 0x3F; + break; + + default: + request = razer_chroma_standard_set_led_brightness(VARSTORE, led_id, brightness); + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Read device file "scroll_led_brightness" + */ +static ssize_t razer_attr_read_scroll_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_led_brightness(dev, attr, buf, SCROLL_WHEEL_LED); +} + +/** + * Write device file "scroll_led_brightness" + */ +static ssize_t razer_attr_write_scroll_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_led_brightness(dev, attr, buf, count, SCROLL_WHEEL_LED); +} + +/** + * Read device file "logo_led_brightness" + */ +static ssize_t razer_attr_read_logo_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_led_brightness(dev, attr, buf, LOGO_LED); +} + +/** + * Write device file "logo_led_brightness" + */ +static ssize_t razer_attr_write_logo_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_led_brightness(dev, attr, buf, count, LOGO_LED); +} + +/** + * Read device file "left_led_brightness" + */ +static ssize_t razer_attr_read_left_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_led_brightness(dev, attr, buf, LEFT_SIDE_LED); +} + +/** + * Write device file "left_led_brightness" + */ +static ssize_t razer_attr_write_left_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_led_brightness(dev, attr, buf, count, LEFT_SIDE_LED); +} + +/** + * Read device file "right_led_brightness" + */ +static ssize_t razer_attr_read_right_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_led_brightness(dev, attr, buf, RIGHT_SIDE_LED); +} + +/** + * Write device file "right_led_brightness" + */ +static ssize_t razer_attr_write_right_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_led_brightness(dev, attr, buf, count, RIGHT_SIDE_LED); +} + +/** + * Read device file "backlight_led_brightness" + */ +static ssize_t razer_attr_read_backlight_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + return razer_attr_read_led_brightness(dev, attr, buf, BACKLIGHT_LED); +} + +/** + * Write device file "backlight_led_brightness" + */ +static ssize_t razer_attr_write_backlight_led_brightness(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_led_brightness(dev, attr, buf, count, BACKLIGHT_LED); +} + +/** + * Common function to handle sysfs write matrix_effect_wave for a given led + */ +static ssize_t razer_attr_write_matrix_effect_wave_common(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, unsigned char led_id) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned char direction = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + request = razer_chroma_extended_matrix_effect_wave(VARSTORE, led_id, direction); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + request = razer_chroma_extended_matrix_effect_wave(VARSTORE, led_id, direction); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_TE_WIRED: + case USB_DEVICE_ID_RAZER_DIAMONDBACK_CHROMA: + request = razer_chroma_standard_matrix_effect_wave(direction); + request.transaction_id.id = 0xFF; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_wave not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "scroll_mode_wave" (for extended mouse matrix effects) + * + * Wave effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_scroll_matrix_effect_wave(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_wave_common(dev, attr, buf, count, SCROLL_WHEEL_LED); +} + +/** + * Common function to handle sysfs write matrix_effect_spectrum for a given led + */ +static ssize_t razer_attr_write_matrix_effect_spectrum_common(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, unsigned char led_id) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ABYSSUS_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_CHROMA: + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_EPIC: + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA: + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA_DOCK: + request = razer_chroma_standard_set_led_state(VARSTORE, led_id, true); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_standard_set_led_effect(VARSTORE, led_id, CLASSIC_EFFECT_SPECTRUM); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + case USB_DEVICE_ID_RAZER_NAGA_CHROMA: + request = razer_chroma_mouse_extended_matrix_effect_spectrum(VARSTORE, led_id); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_ABYSSUS_ELITE_DVA_EDITION: + case USB_DEVICE_ID_RAZER_ABYSSUS_ESSENTIAL: + case USB_DEVICE_ID_RAZER_VIPER: + case USB_DEVICE_ID_RAZER_VIPER_MINI: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, led_id); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_VIPER_8K: + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_COBRA: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + request = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, led_id); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_TE_WIRED: + case USB_DEVICE_ID_RAZER_OROCHI_CHROMA: + case USB_DEVICE_ID_RAZER_DIAMONDBACK_CHROMA: + request = razer_chroma_standard_matrix_effect_spectrum(); + request.transaction_id.id = 0xFF; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_spectrum not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "scroll_mode_spectrum" (for extended mouse matrix effects) + * + * Spectrum effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_scroll_matrix_effect_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_spectrum_common(dev, attr, buf, count, SCROLL_WHEEL_LED); +} + +/** + * Common function to handle sysfs write matrix_effect_reactive for a given led + */ +static ssize_t razer_attr_write_matrix_effect_reactive_common(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, unsigned char led_id) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + unsigned char speed; + + if (count != 4) { + printk(KERN_WARNING "razermouse: Reactive only accepts Speed, RGB (4byte)\n"); + return -EINVAL; + } + + speed = (unsigned char)buf[0]; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + case USB_DEVICE_ID_RAZER_NAGA_CHROMA: + request = razer_chroma_mouse_extended_matrix_effect_reactive(VARSTORE, led_id, speed, (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_ABYSSUS_ELITE_DVA_EDITION: + case USB_DEVICE_ID_RAZER_ABYSSUS_ESSENTIAL: + case USB_DEVICE_ID_RAZER_VIPER: + case USB_DEVICE_ID_RAZER_VIPER_MINI: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request = razer_chroma_extended_matrix_effect_reactive(VARSTORE, led_id, speed, (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_VIPER_8K: + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + request = razer_chroma_extended_matrix_effect_reactive(VARSTORE, led_id, speed, (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_TE_WIRED: + case USB_DEVICE_ID_RAZER_OROCHI_CHROMA: + case USB_DEVICE_ID_RAZER_DIAMONDBACK_CHROMA: + request = razer_chroma_standard_matrix_effect_reactive(speed, (struct razer_rgb*)&buf[1]); + request.transaction_id.id = 0xFF; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_reactive not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "scroll_mode_reactive" (for extended mouse matrix effects) + * + * Sets reactive mode when this file is written to. A speed byte and 3 RGB bytes should be written + */ +static ssize_t razer_attr_write_scroll_matrix_effect_reactive(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_reactive_common(dev, attr, buf, count, SCROLL_WHEEL_LED); +} + +/** + * Common function to handle sysfs write matrix_effect_breath for a given led + */ +static ssize_t razer_attr_write_matrix_effect_breath_common(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, unsigned char led_id) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ABYSSUS_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_3500: + case USB_DEVICE_ID_RAZER_DEATHADDER_CHROMA: + case USB_DEVICE_ID_RAZER_DEATHADDER_2013: + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA: + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA_DOCK: + if (count != 3) { + printk(KERN_WARNING "razermouse: Static mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + request = razer_chroma_standard_set_led_state(VARSTORE, led_id, true); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_standard_set_led_rgb(VARSTORE, led_id, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_standard_set_led_effect(VARSTORE, led_id, CLASSIC_EFFECT_BREATHING); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_2000: + /* Mono-color breath effect */ + request = razer_chroma_standard_set_led_state(VARSTORE, led_id, true); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_standard_set_led_effect(VARSTORE, led_id, CLASSIC_EFFECT_BREATHING); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_NAGA_CHROMA: + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_mouse_extended_matrix_effect_breathing_single(VARSTORE, led_id, (struct razer_rgb*)&buf[0]); + break; + + case 6: // Dual colour mode + request = razer_chroma_mouse_extended_matrix_effect_breathing_dual(VARSTORE, led_id, (struct razer_rgb*)&buf[0], (struct razer_rgb*)&buf[3]); + break; + + default: // "Random" colour mode + request = razer_chroma_mouse_extended_matrix_effect_breathing_random(VARSTORE, led_id); + break; + } + break; + + case USB_DEVICE_ID_RAZER_VIPER_8K: + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_2021: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_WHITE_EDITION: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_ABYSSUS_ELITE_DVA_EDITION: + case USB_DEVICE_ID_RAZER_ABYSSUS_ESSENTIAL: + case USB_DEVICE_ID_RAZER_VIPER: + case USB_DEVICE_ID_RAZER_VIPER_MINI: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_extended_matrix_effect_breathing_single(VARSTORE, led_id, (struct razer_rgb*)&buf[0]); + break; + + case 6: // Dual colour mode + request = razer_chroma_extended_matrix_effect_breathing_dual(VARSTORE, led_id, (struct razer_rgb*)&buf[0], (struct razer_rgb*)&buf[3]); + break; + + default: // "Random" colour mode + request = razer_chroma_extended_matrix_effect_breathing_random(VARSTORE, led_id); + break; + } + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_TE_WIRED: + case USB_DEVICE_ID_RAZER_OROCHI_CHROMA: + case USB_DEVICE_ID_RAZER_DIAMONDBACK_CHROMA: + switch(count) { + case 3: // Single colour mode + request = razer_chroma_standard_matrix_effect_breathing_single((struct razer_rgb*)&buf[0]); + break; + + case 6: // Dual colour mode + request = razer_chroma_standard_matrix_effect_breathing_dual((struct razer_rgb*)&buf[0], (struct razer_rgb*)&buf[3]); + break; + + default: // "Random" colour mode + request = razer_chroma_standard_matrix_effect_breathing_random(); + break; + } + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_breath not supported for this model\n"); + return -EINVAL; + } + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_VIPER_8K: + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + request.transaction_id.id = 0x1f; + break; + + default: + request.transaction_id.id = 0x3f; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "scroll_mode_breath" (for extended mouse matrix effects) + * + * Sets breathing mode by writing 1, 3 or 6 bytes + */ +static ssize_t razer_attr_write_scroll_matrix_effect_breath(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_breath_common(dev, attr, buf, count, SCROLL_WHEEL_LED); +} + +/** + * Common function to handle sysfs write matrix_effect_static for a given led + */ +static ssize_t razer_attr_write_matrix_effect_static_common(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, unsigned char led_id) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count != 3) { + printk(KERN_WARNING "razermouse: Static mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ABYSSUS_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_3500: + case USB_DEVICE_ID_RAZER_DEATHADDER_CHROMA: + case USB_DEVICE_ID_RAZER_DEATHADDER_2013: + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_EPIC: + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA: + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA_DOCK: + request = razer_chroma_standard_set_led_state(VARSTORE, led_id, true); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_standard_set_led_rgb(VARSTORE, led_id, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_standard_set_led_effect(VARSTORE, led_id, CLASSIC_EFFECT_STATIC); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_NAGA_CHROMA: + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + request = razer_chroma_mouse_extended_matrix_effect_static(VARSTORE, led_id, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_2021: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_WHITE_EDITION: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_ABYSSUS_ELITE_DVA_EDITION: + case USB_DEVICE_ID_RAZER_ABYSSUS_ESSENTIAL: + case USB_DEVICE_ID_RAZER_VIPER: + case USB_DEVICE_ID_RAZER_VIPER_MINI: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request = razer_chroma_extended_matrix_effect_static(VARSTORE, led_id, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_VIPER_8K: + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + request = razer_chroma_extended_matrix_effect_static(VARSTORE, led_id, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_TE_WIRED: + case USB_DEVICE_ID_RAZER_OROCHI_CHROMA: + case USB_DEVICE_ID_RAZER_DIAMONDBACK_CHROMA: + request = razer_chroma_standard_matrix_effect_static((struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0xFF; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_static not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "scroll_mode_static" (for extended mouse matrix effects) + * + * Set the mouse to static mode when 3 RGB bytes are written + */ +static ssize_t razer_attr_write_scroll_matrix_effect_static(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_static_common(dev, attr, buf, count, SCROLL_WHEEL_LED); +} + +/** + * Common function to handle sysfs write matrix_effect_blinking for a given led + */ +static ssize_t razer_attr_write_matrix_effect_blinking_common(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, unsigned char led_id) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + if (count != 3) { + printk(KERN_WARNING "razermouse: Blinking mode only accepts RGB (3byte)\n"); + return -EINVAL; + } + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_ABYSSUS_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_3500: + case USB_DEVICE_ID_RAZER_DEATHADDER_CHROMA: + case USB_DEVICE_ID_RAZER_DEATHADDER_2013: + request = razer_chroma_standard_set_led_state(VARSTORE, led_id, true); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_standard_set_led_rgb(VARSTORE, led_id, (struct razer_rgb*)&buf[0]); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_standard_set_led_effect(VARSTORE, led_id, CLASSIC_EFFECT_BLINKING); + request.transaction_id.id = 0x3F; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_blinking not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "scroll_matrix_effect_blinking" + */ +static ssize_t razer_attr_write_scroll_matrix_effect_blinking(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_blinking_common(dev, attr, buf, count, SCROLL_WHEEL_LED); +} + +/** + * Write device file "logo_matrix_effect_blinking" + */ +static ssize_t razer_attr_write_logo_matrix_effect_blinking(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_blinking_common(dev, attr, buf, count, LOGO_LED); +} + +/** + * Common function to handle sysfs write matrix_effect_none for a given led + */ +static ssize_t razer_attr_write_matrix_effect_none_common(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, unsigned char led_id) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: + deathadder3_5g_set_led_state(device, led_id, false); + return count; + + case USB_DEVICE_ID_RAZER_OROCHI_2011: + orochi_2011_set_led_state(device, led_id, false); + request = razer_chroma_misc_set_orochi2011_led(device->orochi2011.led); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_3500: + case USB_DEVICE_ID_RAZER_DEATHADDER_CHROMA: + case USB_DEVICE_ID_RAZER_DEATHADDER_2013: + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRED: + case USB_DEVICE_ID_RAZER_NAGA: + case USB_DEVICE_ID_RAZER_NAGA_2012: + case USB_DEVICE_ID_RAZER_ABYSSUS: + case USB_DEVICE_ID_RAZER_IMPERATOR: + case USB_DEVICE_ID_RAZER_NAGA_HEX: + case USB_DEVICE_ID_RAZER_NAGA_HEX_RED: + case USB_DEVICE_ID_RAZER_TAIPAN: + case USB_DEVICE_ID_RAZER_ABYSSUS_1800: + case USB_DEVICE_ID_RAZER_DEATHADDER_1800: + case USB_DEVICE_ID_RAZER_ABYSSUS_2000: + case USB_DEVICE_ID_RAZER_OUROBOROS: + case USB_DEVICE_ID_RAZER_OROCHI_2013: + case USB_DEVICE_ID_RAZER_DEATHADDER_2000: + case USB_DEVICE_ID_RAZER_NAGA_EPIC: + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA: + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA_DOCK: + request = razer_chroma_standard_set_led_state(VARSTORE, led_id, false); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_OROCHI_CHROMA: + if (led_id == SCROLL_WHEEL_LED) { + request = razer_chroma_standard_set_led_state(VARSTORE, led_id, false); + request.transaction_id.id = 0x3F; + } else { + request = razer_chroma_standard_matrix_effect_none(); + request.transaction_id.id = 0xFF; + } + break; + + case USB_DEVICE_ID_RAZER_NAGA_CHROMA: + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + request = razer_chroma_mouse_extended_matrix_effect_none(VARSTORE, led_id); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_2021: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_WHITE_EDITION: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + case USB_DEVICE_ID_RAZER_ABYSSUS_ELITE_DVA_EDITION: + case USB_DEVICE_ID_RAZER_ABYSSUS_ESSENTIAL: + case USB_DEVICE_ID_RAZER_VIPER: + case USB_DEVICE_ID_RAZER_VIPER_MINI: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + request = razer_chroma_extended_matrix_effect_none(VARSTORE, led_id); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_VIPER_8K: + case USB_DEVICE_ID_RAZER_NAGA_X: + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA: + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + request = razer_chroma_extended_matrix_effect_none(VARSTORE, led_id); + request.transaction_id.id = 0x1f; + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: + case USB_DEVICE_ID_RAZER_MAMBA_TE_WIRED: + case USB_DEVICE_ID_RAZER_DIAMONDBACK_CHROMA: + request = razer_chroma_standard_matrix_effect_none(); + request.transaction_id.id = 0xFF; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_none not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "scroll_mode_none" (for extended mouse matrix effects) + * + * No effect is activated whenever this file is written to + */ +static ssize_t razer_attr_write_scroll_matrix_effect_none(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_none_common(dev, attr, buf, count, SCROLL_WHEEL_LED); +} + +/** + * Common function to handle sysfs write matrix_effect_on for a given led + */ +static ssize_t razer_attr_write_matrix_effect_on_common(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, unsigned char led_id) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report request = {0}; + struct razer_report response = {0}; + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: + deathadder3_5g_set_led_state(device, led_id, true); + return count; + + case USB_DEVICE_ID_RAZER_OROCHI_2011: + orochi_2011_set_led_state(device, led_id, true); + request = razer_chroma_misc_set_orochi2011_led(device->orochi2011.led); + request.transaction_id.id = 0xFF; + break; + + case USB_DEVICE_ID_RAZER_NAGA: + case USB_DEVICE_ID_RAZER_NAGA_2012: + case USB_DEVICE_ID_RAZER_ABYSSUS: + case USB_DEVICE_ID_RAZER_IMPERATOR: + case USB_DEVICE_ID_RAZER_NAGA_HEX: + case USB_DEVICE_ID_RAZER_NAGA_HEX_RED: + case USB_DEVICE_ID_RAZER_TAIPAN: + case USB_DEVICE_ID_RAZER_ABYSSUS_1800: + case USB_DEVICE_ID_RAZER_DEATHADDER_1800: + case USB_DEVICE_ID_RAZER_ABYSSUS_2000: + case USB_DEVICE_ID_RAZER_OUROBOROS: + case USB_DEVICE_ID_RAZER_OROCHI_2013: + case USB_DEVICE_ID_RAZER_OROCHI_CHROMA: + request = razer_chroma_standard_set_led_state(VARSTORE, led_id, true); + request.transaction_id.id = 0x3F; + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_2000: + /* Could also be called a mono-color static effect */ + request = razer_chroma_standard_set_led_state(VARSTORE, led_id, true); + request.transaction_id.id = 0x3F; + razer_send_payload(device, &request, &response); + + request = razer_chroma_standard_set_led_effect(VARSTORE, led_id, CLASSIC_EFFECT_STATIC); + request.transaction_id.id = 0x3F; + break; + + default: + printk(KERN_WARNING "razermouse: matrix_effect_none not supported for this model\n"); + return -EINVAL; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +static ssize_t razer_attr_write_logo_matrix_effect_on(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_on_common(dev, attr, buf, count, LOGO_LED); +} + +static ssize_t razer_attr_write_scroll_matrix_effect_on(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_on_common(dev, attr, buf, count, SCROLL_WHEEL_LED); +} + +static ssize_t razer_attr_write_backlight_matrix_effect_on(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_on_common(dev, attr, buf, count, BACKLIGHT_LED); +} + +/** + * Write device file "logo_mode_wave" (for extended mouse matrix effects) + * + * Wave effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_logo_matrix_effect_wave(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_wave_common(dev, attr, buf, count, LOGO_LED); +} + +/** + * Write device file "logo_mode_spectrum" (for extended mouse matrix effects) + * + * Spectrum effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_logo_matrix_effect_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_spectrum_common(dev, attr, buf, count, LOGO_LED); +} + +/** + * Write device file "logo_mode_reactive" (for extended mouse matrix effects) + * + * Sets reactive mode when this file is written to. A speed byte and 3 RGB bytes should be written + */ +static ssize_t razer_attr_write_logo_matrix_effect_reactive(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_reactive_common(dev, attr, buf, count, LOGO_LED); +} + +/** + * Write device file "logo_mode_breath" (for extended mouse matrix effects) + * + * Sets breathing mode by writing 1, 3 or 6 bytes + */ +static ssize_t razer_attr_write_logo_matrix_effect_breath(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_breath_common(dev, attr, buf, count, LOGO_LED); +} + +/** + * Write device file "logo_mode_static" (for extended mouse matrix effects) + * + * Set the mouse to static mode when 3 RGB bytes are written + */ +static ssize_t razer_attr_write_logo_matrix_effect_static(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_static_common(dev, attr, buf, count, LOGO_LED); +} + +/** + * Write device file "logo_mode_none" (for extended mouse matrix effects) + * + * No effect is activated whenever this file is written to + */ +static ssize_t razer_attr_write_logo_matrix_effect_none(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_none_common(dev, attr, buf, count, LOGO_LED); +} + +/** + * Write device file "left_mode_wave" (for extended mouse matrix effects) + * + * Wave effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_left_matrix_effect_wave(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_wave_common(dev, attr, buf, count, LEFT_SIDE_LED); +} + +/** + * Write device file "right_mode_wave" (for extended mouse matrix effects) + * + * Wave effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_right_matrix_effect_wave(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_wave_common(dev, attr, buf, count, RIGHT_SIDE_LED); +} + +/** + * Write device file "left_mode_spectrum" (for extended mouse matrix effects) + * + * Spectrum effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_left_matrix_effect_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_spectrum_common(dev, attr, buf, count, LEFT_SIDE_LED); +} + +/** + * Write device file "right_mode_spectrum" (for extended mouse matrix effects) + * + * Spectrum effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_right_matrix_effect_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_spectrum_common(dev, attr, buf, count, RIGHT_SIDE_LED); +} + +/** + * Write device file "left_mode_reactive" (for extended mouse matrix effects) + * + * Sets reactive mode when this file is written to. A speed byte and 3 RGB bytes should be written + */ +static ssize_t razer_attr_write_left_matrix_effect_reactive(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_reactive_common(dev, attr, buf, count, LEFT_SIDE_LED); +} + +/** + * Write device file "right_mode_reactive" (for extended mouse matrix effects) + * + * Sets reactive mode when this file is written to. A speed byte and 3 RGB bytes should be written + */ +static ssize_t razer_attr_write_right_matrix_effect_reactive(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_reactive_common(dev, attr, buf, count, RIGHT_SIDE_LED); +} + +/** + * Write device file "left_mode_breath" (for extended mouse matrix effects) + * + * Sets breathing mode by writing 1, 3 or 6 bytes + */ +static ssize_t razer_attr_write_left_matrix_effect_breath(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_breath_common(dev, attr, buf, count, LEFT_SIDE_LED); +} + +/** + * Write device file "right_mode_breath" (for extended mouse matrix effects) + * + * Sets breathing mode by writing 1, 3 or 6 bytes + */ +static ssize_t razer_attr_write_right_matrix_effect_breath(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_breath_common(dev, attr, buf, count, RIGHT_SIDE_LED); +} + +/** + * Write device file "left_mode_static" (for extended mouse matrix effects) + * + * Set the mouse to static mode when 3 RGB bytes are written + */ +static ssize_t razer_attr_write_left_matrix_effect_static(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_static_common(dev, attr, buf, count, LEFT_SIDE_LED); +} + +/** + * Write device file "right_mode_static" (for extended mouse matrix effects) + * + * Set the mouse to static mode when 3 RGB bytes are written + */ +static ssize_t razer_attr_write_right_matrix_effect_static(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_static_common(dev, attr, buf, count, RIGHT_SIDE_LED); +} + +/** + * Write device file "left_mode_none" (for extended mouse matrix effects) + * + * No effect is activated whenever this file is written to + */ +static ssize_t razer_attr_write_left_matrix_effect_none(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_none_common(dev, attr, buf, count, LEFT_SIDE_LED); +} + +/** + * Write device file "right_mode_none" (for extended mouse matrix effects) + * + * No effect is activated whenever this file is written to + */ +static ssize_t razer_attr_write_right_matrix_effect_none(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_none_common(dev, attr, buf, count, RIGHT_SIDE_LED); +} + +/** + * Write device file "backlight_mode_wave" (for extended mouse matrix effects) + * + * Wave effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_backlight_matrix_effect_wave(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_wave_common(dev, attr, buf, count, BACKLIGHT_LED); +} + +/** + * Write device file "backlight_mode_spectrum" (for extended mouse matrix effects) + * + * Spectrum effect mode is activated whenever the file is written to + */ +static ssize_t razer_attr_write_backlight_matrix_effect_spectrum(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_spectrum_common(dev, attr, buf, count, BACKLIGHT_LED); +} + +/** + * Write device file "backlight_mode_reactive" (for extended mouse matrix effects) + * + * Sets reactive mode when this file is written to. A speed byte and 3 RGB bytes should be written + */ +static ssize_t razer_attr_write_backlight_matrix_effect_reactive(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_reactive_common(dev, attr, buf, count, BACKLIGHT_LED); +} + +/** + * Write device file "backlight_mode_breath" (for extended mouse matrix effects) + * + * Sets breathing mode by writing 1, 3 or 6 bytes + */ +static ssize_t razer_attr_write_backlight_matrix_effect_breath(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_breath_common(dev, attr, buf, count, BACKLIGHT_LED); +} + +/** + * Write device file "backlight_mode_static" (for extended mouse matrix effects) + * + * Set the mouse to static mode when 3 RGB bytes are written + */ +static ssize_t razer_attr_write_backlight_matrix_effect_static(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_static_common(dev, attr, buf, count, BACKLIGHT_LED); +} + +/** + * Write device file "backlight_mode_none" (for extended mouse matrix effects) + * + * No effect is activated whenever this file is written to + */ +static ssize_t razer_attr_write_backlight_matrix_effect_none(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return razer_attr_write_matrix_effect_none_common(dev, attr, buf, count, BACKLIGHT_LED); +} + +/** + * Write device file "hyperpolling_wireless_dongle_indicator_led_mode" + */ +static ssize_t razer_attr_write_hyperpolling_wireless_dongle_indicator_led_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned char mode = (unsigned char)simple_strtoul(buf, NULL, 10); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_set_hyperpolling_wireless_dongle_indicator_led_mode(mode); + + switch (device->usb_pid) { + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + request.transaction_id.id = 0x1F; + break; + + default: + request.transaction_id.id = 0xFF; + break; + } + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "hyperpolling_wireless_dongle_pair" + */ +static ssize_t razer_attr_write_hyperpolling_wireless_dongle_pair(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned int pid = (unsigned int)simple_strtoul(buf, NULL, 16); + struct razer_report request = {0}; + struct razer_report response = {0}; + + // Step 1: Put in pairing mode + request = razer_chroma_misc_set_hyperpolling_wireless_dongle_pair_step1(0x01); + request.transaction_id.id = 0x1F; + + razer_send_payload(device, &request, &response); + + // Step 2: Pair with PID + request = razer_chroma_misc_set_hyperpolling_wireless_dongle_pair_step2(pid); + request.transaction_id.id = 0x1F; + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Write device file "hyperpolling_wireless_dongle_unpair" + */ +static ssize_t razer_attr_write_hyperpolling_wireless_dongle_unpair(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + unsigned int pid = (unsigned int)simple_strtoul(buf, NULL, 16); + struct razer_report request = {0}; + struct razer_report response = {0}; + + request = razer_chroma_misc_set_hyperpolling_wireless_dongle_unpair(pid); + request.transaction_id.id = 0xFF; + + razer_send_payload(device, &request, &response); + + return count; +} + +/** + * Set up the device driver files + * + * Read-only is 0444 + * Write-only is 0220 + * Read/write is 0664 + */ + +static DEVICE_ATTR(version, 0440, razer_attr_read_version, NULL); +static DEVICE_ATTR(firmware_version, 0440, razer_attr_read_firmware_version, NULL); +static DEVICE_ATTR(test, 0220, NULL, razer_attr_write_test); +static DEVICE_ATTR(poll_rate, 0660, razer_attr_read_poll_rate, razer_attr_write_poll_rate); +static DEVICE_ATTR(dpi, 0660, razer_attr_read_dpi, razer_attr_write_dpi); +static DEVICE_ATTR(dpi_stages, 0660, razer_attr_read_dpi_stages, razer_attr_write_dpi_stages); + +static DEVICE_ATTR(device_type, 0440, razer_attr_read_device_type, NULL); +static DEVICE_ATTR(device_mode, 0660, razer_attr_read_device_mode, razer_attr_write_device_mode); +static DEVICE_ATTR(device_serial, 0440, razer_attr_read_device_serial, NULL); +static DEVICE_ATTR(device_idle_time, 0660, razer_attr_read_device_idle_time, razer_attr_write_device_idle_time); + +static DEVICE_ATTR(scroll_mode, 0660, razer_attr_read_scroll_mode, razer_attr_write_scroll_mode); +static DEVICE_ATTR(scroll_acceleration, 0660, razer_attr_read_scroll_acceleration, razer_attr_write_scroll_acceleration); +static DEVICE_ATTR(scroll_smart_reel, 0660, razer_attr_read_scroll_smart_reel, razer_attr_write_scroll_smart_reel); + +static DEVICE_ATTR(tilt_hwheel, 0660, razer_attr_read_tilt_hwheel, razer_attr_write_tilt_hwheel); +static DEVICE_ATTR(tilt_repeat, 0660, razer_attr_read_tilt_repeat, razer_attr_write_tilt_repeat); +static DEVICE_ATTR(tilt_repeat_delay, 0660, razer_attr_read_tilt_repeat_delay, razer_attr_write_tilt_repeat_delay); + +static DEVICE_ATTR(charge_level, 0440, razer_attr_read_charge_level, NULL); +static DEVICE_ATTR(charge_status, 0440, razer_attr_read_charge_status, NULL); +static DEVICE_ATTR(charge_effect, 0220, NULL, razer_attr_write_charge_effect); +static DEVICE_ATTR(charge_colour, 0220, NULL, razer_attr_write_charge_colour); +static DEVICE_ATTR(charge_low_threshold, 0660, razer_attr_read_charge_low_threshold, razer_attr_write_charge_low_threshold); + +static DEVICE_ATTR(matrix_brightness, 0660, razer_attr_read_matrix_brightness, razer_attr_write_matrix_brightness); +static DEVICE_ATTR(matrix_custom_frame, 0220, NULL, razer_attr_write_matrix_custom_frame); +static DEVICE_ATTR(matrix_effect_none, 0220, NULL, razer_attr_write_matrix_effect_none); +static DEVICE_ATTR(matrix_effect_custom, 0220, NULL, razer_attr_write_matrix_effect_custom); +static DEVICE_ATTR(matrix_effect_static, 0220, NULL, razer_attr_write_matrix_effect_static); +static DEVICE_ATTR(matrix_effect_wave, 0220, NULL, razer_attr_write_matrix_effect_wave); +static DEVICE_ATTR(matrix_effect_spectrum, 0220, NULL, razer_attr_write_matrix_effect_spectrum); +static DEVICE_ATTR(matrix_effect_reactive, 0220, NULL, razer_attr_write_matrix_effect_reactive); +static DEVICE_ATTR(matrix_effect_breath, 0220, NULL, razer_attr_write_matrix_effect_breath); + +static DEVICE_ATTR(scroll_led_brightness, 0660, razer_attr_read_scroll_led_brightness, razer_attr_write_scroll_led_brightness); +// For "extended" matrix effects +static DEVICE_ATTR(scroll_matrix_effect_wave, 0220, NULL, razer_attr_write_scroll_matrix_effect_wave); +static DEVICE_ATTR(scroll_matrix_effect_spectrum, 0220, NULL, razer_attr_write_scroll_matrix_effect_spectrum); +static DEVICE_ATTR(scroll_matrix_effect_reactive, 0220, NULL, razer_attr_write_scroll_matrix_effect_reactive); +static DEVICE_ATTR(scroll_matrix_effect_breath, 0220, NULL, razer_attr_write_scroll_matrix_effect_breath); +static DEVICE_ATTR(scroll_matrix_effect_static, 0220, NULL, razer_attr_write_scroll_matrix_effect_static); +static DEVICE_ATTR(scroll_matrix_effect_blinking, 0220, NULL, razer_attr_write_scroll_matrix_effect_blinking); +static DEVICE_ATTR(scroll_matrix_effect_none, 0220, NULL, razer_attr_write_scroll_matrix_effect_none); +static DEVICE_ATTR(scroll_matrix_effect_on, 0220, NULL, razer_attr_write_scroll_matrix_effect_on); + +static DEVICE_ATTR(logo_led_brightness, 0660, razer_attr_read_logo_led_brightness, razer_attr_write_logo_led_brightness); +// For "extended" matrix effects +static DEVICE_ATTR(logo_matrix_effect_wave, 0220, NULL, razer_attr_write_logo_matrix_effect_wave); +static DEVICE_ATTR(logo_matrix_effect_spectrum, 0220, NULL, razer_attr_write_logo_matrix_effect_spectrum); +static DEVICE_ATTR(logo_matrix_effect_reactive, 0220, NULL, razer_attr_write_logo_matrix_effect_reactive); +static DEVICE_ATTR(logo_matrix_effect_breath, 0220, NULL, razer_attr_write_logo_matrix_effect_breath); +static DEVICE_ATTR(logo_matrix_effect_static, 0220, NULL, razer_attr_write_logo_matrix_effect_static); +static DEVICE_ATTR(logo_matrix_effect_blinking, 0220, NULL, razer_attr_write_logo_matrix_effect_blinking); +static DEVICE_ATTR(logo_matrix_effect_none, 0220, NULL, razer_attr_write_logo_matrix_effect_none); +static DEVICE_ATTR(logo_matrix_effect_on, 0220, NULL, razer_attr_write_logo_matrix_effect_on); + +static DEVICE_ATTR(left_led_brightness, 0660, razer_attr_read_left_led_brightness, razer_attr_write_left_led_brightness); +// For "extended" matrix effects +static DEVICE_ATTR(left_matrix_effect_wave, 0220, NULL, razer_attr_write_left_matrix_effect_wave); +static DEVICE_ATTR(left_matrix_effect_spectrum, 0220, NULL, razer_attr_write_left_matrix_effect_spectrum); +static DEVICE_ATTR(left_matrix_effect_reactive, 0220, NULL, razer_attr_write_left_matrix_effect_reactive); +static DEVICE_ATTR(left_matrix_effect_breath, 0220, NULL, razer_attr_write_left_matrix_effect_breath); +static DEVICE_ATTR(left_matrix_effect_static, 0220, NULL, razer_attr_write_left_matrix_effect_static); +static DEVICE_ATTR(left_matrix_effect_none, 0220, NULL, razer_attr_write_left_matrix_effect_none); + +static DEVICE_ATTR(right_led_brightness, 0660, razer_attr_read_right_led_brightness, razer_attr_write_right_led_brightness); +// For "extended" matrix effects +static DEVICE_ATTR(right_matrix_effect_wave, 0220, NULL, razer_attr_write_right_matrix_effect_wave); +static DEVICE_ATTR(right_matrix_effect_spectrum, 0220, NULL, razer_attr_write_right_matrix_effect_spectrum); +static DEVICE_ATTR(right_matrix_effect_reactive, 0220, NULL, razer_attr_write_right_matrix_effect_reactive); +static DEVICE_ATTR(right_matrix_effect_breath, 0220, NULL, razer_attr_write_right_matrix_effect_breath); +static DEVICE_ATTR(right_matrix_effect_static, 0220, NULL, razer_attr_write_right_matrix_effect_static); +static DEVICE_ATTR(right_matrix_effect_none, 0220, NULL, razer_attr_write_right_matrix_effect_none); + +// For old-school led commands +// matrix_brightness should mostly be called backlight_led_brightness (but it's too much work now for old devices) +static DEVICE_ATTR(backlight_led_brightness, 0660, razer_attr_read_backlight_led_brightness, razer_attr_write_backlight_led_brightness); +// For "extended" matrix effects +static DEVICE_ATTR(backlight_matrix_effect_wave, 0220, NULL, razer_attr_write_backlight_matrix_effect_wave); +static DEVICE_ATTR(backlight_matrix_effect_spectrum, 0220, NULL, razer_attr_write_backlight_matrix_effect_spectrum); +static DEVICE_ATTR(backlight_matrix_effect_reactive, 0220, NULL, razer_attr_write_backlight_matrix_effect_reactive); +static DEVICE_ATTR(backlight_matrix_effect_breath, 0220, NULL, razer_attr_write_backlight_matrix_effect_breath); +static DEVICE_ATTR(backlight_matrix_effect_static, 0220, NULL, razer_attr_write_backlight_matrix_effect_static); +static DEVICE_ATTR(backlight_matrix_effect_none, 0220, NULL, razer_attr_write_backlight_matrix_effect_none); +static DEVICE_ATTR(backlight_matrix_effect_on, 0220, NULL, razer_attr_write_backlight_matrix_effect_on); + +// For HyperPolling Wireless Dongle +static DEVICE_ATTR(hyperpolling_wireless_dongle_indicator_led_mode, 0220, NULL, razer_attr_write_hyperpolling_wireless_dongle_indicator_led_mode); +static DEVICE_ATTR(hyperpolling_wireless_dongle_pair, 0220, NULL, razer_attr_write_hyperpolling_wireless_dongle_pair); +static DEVICE_ATTR(hyperpolling_wireless_dongle_unpair, 0220, NULL, razer_attr_write_hyperpolling_wireless_dongle_unpair); + +#define REP4_DPI_UP 0x20 +#define REP4_DPI_DN 0x21 +#define REP4_TILT_L 0x22 +#define REP4_TILT_R 0x23 +#define REP4_PROFILE 0x50 +#define REP4_SNIPER 0x51 + +#define BIT_TILT_L 5 +#define BIT_TILT_R 6 + +/* + * Documentation: https://www.kernel.org/doc/html/latest/input/event-codes.html#ev-rel + * See also https://github.com/torvalds/linux/blob/v5.14/drivers/hid/hid-input.c#L1298-L1303 + */ +#define SCROLL_DETENT 120 + +/** + * Map "Report 4" codes to evdev key codes + */ +static const __u16 rep4_key_codes[] = { + [REP4_TILT_L] = BTN_BACK, /* BTN_MOUSE + 6 */ + [REP4_TILT_R] = BTN_FORWARD, /* BTN_MOUSE + 5 */ + [REP4_SNIPER] = BTN_TASK, /* BTN_MOUSE + 7 */ + [REP4_DPI_UP] = BTN_MOUSE + 8, + [REP4_DPI_DN] = BTN_MOUSE + 9, + [REP4_PROFILE] = BTN_MOUSE + 10, + /* NOTE: Highest legal mouse button is BTN_MOUSE + 15 */ +}; + +struct button_mapping { + u8 bit; + __u16 code; /* when tilt_hwheel == 0 */ + __s32 hwheel_value; /* when tilt_hwheel == 1 */ +}; + +/** + * Map bits in the first byte of the mouse report to evdev keycodes + * and REL_HWHEEL values + */ +static const struct button_mapping button_mappings[] = { + {BIT_TILT_L, BTN_BACK, -1}, + {BIT_TILT_R, BTN_FORWARD, 1}, +}; + +/** + * Convert an evdev mouse button code to the corresponding HID usage + */ +static u32 mouse_button_to_usage(__u16 code) +{ + return HID_UP_BUTTON + (code - BTN_MOUSE) + 1; +} + +/** + * Send the MSC_SCAN event for the usage code associated with an evdev + * mouse button code + */ +static void input_button_msc_scan(struct input_dev *input, __u16 button) +{ + input_event(input, EV_MSC, MSC_SCAN, mouse_button_to_usage(button)); +} + +/** + * Look up and send the evdev key associated with the Razer "report 4" + * code + */ +static void input_rep4_code(struct input_dev *input, u8 code, __s32 value) +{ + if (code < ARRAY_SIZE(rep4_key_codes) && rep4_key_codes[code]) { + unsigned int button = rep4_key_codes[code]; + input_button_msc_scan(input, button); + input_report_key(input, button, value); + input_sync(input); + } +} + +/** + * Timer callback for wheel tilt repeating + */ +static enum hrtimer_restart wheel_tilt_repeat(struct hrtimer *timer) +{ + struct razer_mouse_device *dev = + container_of(timer, struct razer_mouse_device, repeat_timer); + input_report_rel(dev->input, REL_HWHEEL, dev->hwheel_value); + input_report_rel(dev->input, REL_HWHEEL_HI_RES, dev->hwheel_value * SCROLL_DETENT); + input_sync(dev->input); + if (dev->tilt_repeat) + hrtimer_forward_now(timer, ms_to_ktime(dev->tilt_repeat)); + return HRTIMER_RESTART; +} + +/** + * Send a tilt-wheel event and, if configured, start the key-repeat timer + */ +static void tilt_hwheel_start(struct razer_mouse_device *rdev, + __s32 rel_value) +{ + input_report_rel(rdev->input, REL_HWHEEL, rel_value); + input_report_rel(rdev->input, REL_HWHEEL_HI_RES, rel_value * SCROLL_DETENT); + input_sync(rdev->input); + + if (rdev->tilt_repeat && rdev->tilt_repeat_delay) { + rdev->hwheel_value = rel_value; + hrtimer_start_range_ns( + &rdev->repeat_timer, ms_to_ktime(rdev->tilt_repeat_delay), + 1000, HRTIMER_MODE_REL); + } +} + +/** + * Stop the tilt wheel key-repeat timer + */ +static void tilt_hwheel_stop(struct razer_mouse_device *rdev) +{ + hrtimer_cancel(&rdev->repeat_timer); +} + +/** + * Test if a device is a HID device + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0) +static int dev_is_on_bus(struct device *dev, const void *data) +#else +static int dev_is_on_bus(struct device *dev, void *data) +#endif +{ + const struct bus_type *bus = data; + return dev->bus == bus; +} + +/** + * Find an interface on a usb_device with the specified protocol + */ +static struct usb_interface *find_intf_with_proto(struct usb_device *usbdev, u8 proto) +{ + int i; + + for (i = 0; i < usbdev->actconfig->desc.bNumInterfaces; i++) { + struct usb_interface *intf = usb_ifnum_to_if(usbdev, i); + if (intf && intf->cur_altsetting->desc.bInterfaceProtocol == proto) + return intf; + } + + return NULL; +} + +/** + * Walk up the device tree from an interface to the device it is a + * part of, then back down through the interface with protocol == MOUSE + * to the razer_mouse_device associated with it + */ +static struct razer_mouse_device *find_mouse(struct hid_device *hdev) +{ + const struct bus_type *hid_bus_type = hdev->dev.bus; + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usbdev = interface_to_usbdev(intf); + struct usb_interface *m_intf = find_intf_with_proto(usbdev, USB_INTERFACE_PROTOCOL_MOUSE); + struct device *dev; + struct razer_mouse_device *rdev; + + if (!m_intf) + return NULL; + + dev = device_find_child(&m_intf->dev, (void *)hid_bus_type, dev_is_on_bus); + if (!dev) + return NULL; + + rdev = dev_get_drvdata(dev); + put_device(dev); + return rdev; +} + +/** + * Test if a bit is cleared in 'prev' and set in 'cur' + */ +static int rising_bit(u8 prev, u8 cur, u8 mask) +{ + return !(prev & mask) && cur & mask; +} + +/** + * Test if a bit is set in 'prev' and cleared in 'cur' + */ +static int falling_bit(u8 prev, u8 cur, u8 mask) +{ + return prev & mask && !(cur & mask); +} + +/** + * Test if a bit is different between 'prev' and 'cur' + */ +static int edge_bit(u8 prev, u8 cur, u8 mask) +{ + return (prev & mask) != (cur & mask); +} + +/** + * Search a byte array for a value + */ +static int search(u8 *array, u8 value, unsigned n) +{ + while (n--) { + if (*array++ == value) + return 1; + } + return 0; +} + +/** + * Raw event function + */ +static int razer_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct razer_mouse_device *rdev = hid_get_drvdata(hdev); + + switch (hdev->product) { + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_NAGA_2014: + case USB_DEVICE_ID_RAZER_NAGA_CHROMA: + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + /* Detect wheel tilt edges */ + if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { + int i; + for (i = 0; i < ARRAY_SIZE(button_mappings); i++) { + const struct button_mapping *mapping = &button_mappings[i]; + u8 mask = 1 << mapping->bit; + if (mapping->hwheel_value && rdev->tilt_hwheel) { + __s32 rel_value = mapping->hwheel_value; + if (rising_bit(rdev->button_byte, data[0], mask)) + tilt_hwheel_start(rdev, rel_value); + if (falling_bit(rdev->button_byte, data[0], mask)) + tilt_hwheel_stop(rdev); + } else if (edge_bit(rdev->button_byte, data[0], mask)) { + unsigned int code = mapping->code; + input_button_msc_scan(rdev->input, code); + input_report_key(rdev->input, code, !!(data[0] & mask)); + input_sync(rdev->input); + } + } + rdev->button_byte = data[0]; + } + + /* Detect buttons reported on the keyboard interface */ + if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD && size == 16 && data[0] == 0x04) { + struct razer_mouse_device *m_rdev = find_mouse(hdev); + int i; + + if (!m_rdev) { + printk(KERN_WARNING "razermouse: Couldn't find mouse intf from kbd intf\n"); + return 1; + } + + for (i = 1; i < size; i++) { + if (!search(rdev->rep4 + 1, data[i], size - 1)) + input_rep4_code(m_rdev->input, data[i], 1); + if (!search(data + 1, rdev->rep4[i], size - 1)) + input_rep4_code(m_rdev->input, rdev->rep4[i], 0); + } + memcpy(rdev->rep4, data, 16); + return 1; + } + break; + default: + // The event were looking for is 16 bytes long and starts with 0x04 + if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD && size == 16 && data[0] == 0x04) { + // Convert 04... to 0100... + int index = size-1; // This way we start at 2nd last value, does subtract 1 from the 15key rollover though (not an issue cmon) + + while(--index > 0) { + u8 cur_value = data[index]; + if(cur_value == 0x00) { // Skip 0x00 + continue; + } + + switch(cur_value) { + case 0x20: // DPI Up + cur_value = 0x68; // F13 + break; + case 0x21: // DPI Down + cur_value = 0x69; // F14 + break; + case 0x22: // Wheel Left + cur_value = 0x6A; // F15 + break; + case 0x23: // Wheel Right + cur_value = 0x6B; // F16 + break; + } + + data[index+1] = cur_value; + } + + data[0] = 0x01; + data[1] = 0x00; + return 1; + } + break; + } + + return 0; +} + +/** + * Input mapping function + */ +static int +razer_input_mapping(struct hid_device *hdev, struct hid_input *hidinput, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + /* Some higher nonstandard mouse buttons are reported in + * 15-element arrays on reports 4 and 5 with usage 0x10003. If + * hid-core tries to interpret this misshapen descriptor it will + * botch it and add spurious event codes to input->evkey. */ + if (field->application == HID_UP_GENDESK + && usage->hid == (HID_UP_GENDESK | 0x0003)) { + return -1; + } + return 0; +} + +/** + * Input configured function + */ +static int razer_input_configured(struct hid_device *hdev, + struct hid_input *hidinput) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct razer_mouse_device *dev = hid_get_drvdata(hdev); + + dev->input = hidinput->input; + + if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { + switch (hdev->product) { + case USB_DEVICE_ID_RAZER_BASILISK_V2: + case USB_DEVICE_ID_RAZER_BASILISK_V3: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + /* Linux HID doesn't detect the Basilisk V2's tilt wheel + * or buttons beyond the first 5 */ + input_set_capability(hidinput->input, EV_KEY, BTN_TASK); + input_set_capability(hidinput->input, EV_KEY, BTN_MOUSE + 8); + input_set_capability(hidinput->input, EV_KEY, BTN_MOUSE + 9); + input_set_capability(hidinput->input, EV_KEY, BTN_MOUSE + 10); + fallthrough; + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + case USB_DEVICE_ID_RAZER_NAGA_2014: + case USB_DEVICE_ID_RAZER_NAGA_CHROMA: + input_set_capability(hidinput->input, EV_REL, REL_HWHEEL); + input_set_capability(hidinput->input, EV_REL, REL_HWHEEL_HI_RES); + input_set_capability(hidinput->input, EV_KEY, BTN_FORWARD); + input_set_capability(hidinput->input, EV_KEY, BTN_BACK); + break; + } + } + + return 0; +} + +/** + * Mouse init function + */ +static void razer_mouse_init(struct razer_mouse_device *dev, struct usb_interface *intf, struct hid_device *hdev) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + unsigned int rand_serial = 0; + + // Initialise mutex + mutex_init(&dev->lock); + // Setup values + dev->usb_dev = usb_dev; + dev->usb_vid = usb_dev->descriptor.idVendor; + dev->usb_pid = usb_dev->descriptor.idProduct; + dev->usb_interface_protocol = intf->cur_altsetting->desc.bInterfaceProtocol; + dev->usb_interface_subclass = intf->cur_altsetting->desc.bInterfaceSubClass; + + // Get a "random" integer + get_random_bytes(&rand_serial, sizeof(unsigned int)); + sprintf(&dev->serial[0], "PM%012u", rand_serial); + + // Setup orochi2011 + dev->orochi2011.dpi = 0x4c; + dev->orochi2011.poll = 500; + + // Setup default values for DeathAdder 3.5G + dev->da3_5g.leds = 3; // Lights up all lights + dev->da3_5g.dpi = 1; // 3500 DPI + dev->da3_5g.profile = 1; // Profile 1 + dev->da3_5g.poll = 1; // Poll rate 1000 + + // Setup tilt wheel HWHEEL emulation +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) + hrtimer_setup(&dev->repeat_timer, wheel_tilt_repeat, CLOCK_MONOTONIC, HRTIMER_MODE_REL); +#else + hrtimer_init(&dev->repeat_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + dev->repeat_timer.function = wheel_tilt_repeat; +#endif + dev->tilt_hwheel = 1; + dev->tilt_repeat_delay = 250; + dev->tilt_repeat = 33; +} + +/** + * Probe method is ran whenever a device is binded to the driver + */ +static int razer_mouse_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int retval = 0; + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct razer_mouse_device *dev = NULL; + unsigned char expected_subclass = 0xFF; + + dev = kzalloc(sizeof(struct razer_mouse_device), GFP_KERNEL); + + if(dev == NULL) { + dev_err(&intf->dev, "out of memory\n"); + return -ENOMEM; + } + + // Init data + razer_mouse_init(dev, intf, hdev); + + switch(dev->usb_pid) { + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + expected_subclass = 0x01; + break; + } + + if(dev->usb_interface_protocol == USB_INTERFACE_PROTOCOL_MOUSE + && (expected_subclass == 0xFF || dev->usb_interface_subclass == expected_subclass)) { + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_version); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_test); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_firmware_version); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_type); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_serial); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_mode); + + switch(dev->usb_pid) { + case USB_DEVICE_ID_RAZER_ABYSSUS_ELITE_DVA_EDITION: + case USB_DEVICE_ID_RAZER_ABYSSUS_ESSENTIAL: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_effect); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + fallthrough; + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_effect); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_hwheel); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat_delay); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V2: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_hwheel); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat_delay); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat); + fallthrough; + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); + fallthrough; + case USB_DEVICE_ID_RAZER_BASILISK_V3: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_mode); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_acceleration); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_smart_reel); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_hwheel); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat_delay); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_mode); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_acceleration); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_smart_reel); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_hwheel); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat_delay); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_NAGA_CHROMA: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_hwheel); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat_delay); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_effect); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_MAMBA_TE_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_OROCHI_2011: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + break; + + case USB_DEVICE_ID_RAZER_IMPERATOR: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + break; + + case USB_DEVICE_ID_RAZER_OUROBOROS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_2013: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_blinking); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_blinking); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + break; + + case USB_DEVICE_ID_RAZER_OROCHI_2013: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + break; + + case USB_DEVICE_ID_RAZER_OROCHI_CHROMA: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX: + case USB_DEVICE_ID_RAZER_NAGA_HEX_RED: + case USB_DEVICE_ID_RAZER_TAIPAN: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_NAGA_2014: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_hwheel); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat_delay); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat); + fallthrough; + case USB_DEVICE_ID_RAZER_NAGA: + case USB_DEVICE_ID_RAZER_NAGA_2012: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_on); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA: + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA_DOCK: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_CHROMA: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_blinking); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_blinking); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_3500: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_blinking); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_blinking); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_2000: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_on); + break; + + case USB_DEVICE_ID_RAZER_DIAMONDBACK_CHROMA: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS_1800: + case USB_DEVICE_ID_RAZER_DEATHADDER_1800: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS_2000: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_on); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_effect); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_effect); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_left_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_right_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_hwheel); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat_delay); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_WHITE_EDITION: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_2021: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_NAGA_TRINITY: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_effect); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_effect); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + fallthrough; + case USB_DEVICE_ID_RAZER_VIPER: + case USB_DEVICE_ID_RAZER_VIPER_MINI: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_hyperpolling_wireless_dongle_indicator_led_mode); + fallthrough; + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_hyperpolling_wireless_dongle_indicator_led_mode); + fallthrough; + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_hyperpolling_wireless_dongle_indicator_led_mode); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_hyperpolling_wireless_dongle_pair); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_hyperpolling_wireless_dongle_unpair); + break; + + case USB_DEVICE_ID_RAZER_VIPER_8K: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_effect_custom); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_COBRA: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + break; + + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi_stages); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_NAGA_EPIC: + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_dpi); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_poll_rate); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_low_threshold); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_device_idle_time); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_level); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_charge_status); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_led_brightness); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + break; + } + + } + + hid_set_drvdata(hdev, dev); + dev_set_drvdata(&hdev->dev, dev); + + retval = hid_parse(hdev); + if(retval) { + hid_err(hdev, "parse failed\n"); + goto exit_free; + } + retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (retval) { + hid_err(hdev, "hw start failed\n"); + goto exit_free; + } + + //razer_reset(usb_dev); + //razer_activate_macro_keys(usb_dev); + //msleep(3000); + return 0; + +exit_free: + kfree(dev); + return retval; +} + +/** + * Unbind function + */ +static void razer_mouse_disconnect(struct hid_device *hdev) +{ + struct razer_mouse_device *dev; + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + + dev = hid_get_drvdata(hdev); + + if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { + device_remove_file(&hdev->dev, &dev_attr_version); + device_remove_file(&hdev->dev, &dev_attr_test); + device_remove_file(&hdev->dev, &dev_attr_firmware_version); + device_remove_file(&hdev->dev, &dev_attr_device_type); + device_remove_file(&hdev->dev, &dev_attr_device_serial); + device_remove_file(&hdev->dev, &dev_attr_device_mode); + + switch(usb_dev->descriptor.idProduct) { + case USB_DEVICE_ID_RAZER_ABYSSUS_ELITE_DVA_EDITION: + case USB_DEVICE_ID_RAZER_ABYSSUS_ESSENTIAL: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER: + device_remove_file(&hdev->dev, &dev_attr_charge_effect); + device_remove_file(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED: + case USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED: + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + fallthrough; + case USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_left_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_right_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER: + device_remove_file(&hdev->dev, &dev_attr_charge_effect); + device_remove_file(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + device_remove_file(&hdev->dev, &dev_attr_tilt_hwheel); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat_delay); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat); + device_remove_file(&hdev->dev, &dev_attr_left_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_right_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V2: + device_remove_file(&hdev->dev, &dev_attr_tilt_hwheel); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat_delay); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat); + fallthrough; + case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE: + case USB_DEVICE_ID_RAZER_BASILISK: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3_35K: + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); + fallthrough; + case USB_DEVICE_ID_RAZER_BASILISK_V3: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_scroll_mode); + device_remove_file(&hdev->dev, &dev_attr_scroll_acceleration); + device_remove_file(&hdev->dev, &dev_attr_scroll_smart_reel); + device_remove_file(&hdev->dev, &dev_attr_tilt_hwheel); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat_delay); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat); + + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + + device_remove_file(&hdev->dev, &dev_attr_matrix_brightness); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); + + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_matrix_brightness); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED: + case USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_scroll_mode); + device_remove_file(&hdev->dev, &dev_attr_scroll_acceleration); + device_remove_file(&hdev->dev, &dev_attr_scroll_smart_reel); + device_remove_file(&hdev->dev, &dev_attr_tilt_hwheel); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat_delay); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat); + + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_matrix_brightness); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX_V2: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_backlight_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_NAGA_CHROMA: + device_remove_file(&hdev->dev, &dev_attr_tilt_hwheel); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat_delay); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_backlight_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRELESS: + case USB_DEVICE_ID_RAZER_MAMBA_2012_WIRED: + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS: + device_remove_file(&hdev->dev, &dev_attr_charge_effect); + device_remove_file(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_MAMBA_WIRED: + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + device_remove_file(&hdev->dev, &dev_attr_matrix_brightness); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_MAMBA_TE_WIRED: + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + device_remove_file(&hdev->dev, &dev_attr_backlight_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_OROCHI_2011: + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS: + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + break; + + case USB_DEVICE_ID_RAZER_IMPERATOR: + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + break; + + case USB_DEVICE_ID_RAZER_OUROBOROS: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_2013: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_blinking); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_blinking); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + break; + + case USB_DEVICE_ID_RAZER_OROCHI_2013: + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + break; + + case USB_DEVICE_ID_RAZER_OROCHI_CHROMA: + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_NAGA_HEX: + case USB_DEVICE_ID_RAZER_NAGA_HEX_RED: + case USB_DEVICE_ID_RAZER_TAIPAN: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_NAGA_2014: + device_remove_file(&hdev->dev, &dev_attr_tilt_hwheel); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat_delay); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat); + fallthrough; + case USB_DEVICE_ID_RAZER_NAGA: + case USB_DEVICE_ID_RAZER_NAGA_2012: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_on); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA: + case USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA_DOCK: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_backlight_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS_V2: + case USB_DEVICE_ID_RAZER_DEATHADDER_CHROMA: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_blinking); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_blinking); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_3500: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_blinking); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_blinking); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_2000: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_on); + break; + + case USB_DEVICE_ID_RAZER_DIAMONDBACK_CHROMA: + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + device_remove_file(&hdev->dev, &dev_attr_backlight_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_backlight_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS_1800: + case USB_DEVICE_ID_RAZER_DEATHADDER_1800: + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + break; + + case USB_DEVICE_ID_RAZER_ABYSSUS_2000: + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_on); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + break; + + case USB_DEVICE_ID_RAZER_NAGA_X: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_left_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_right_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS: + device_remove_file(&hdev->dev, &dev_attr_charge_effect); + device_remove_file(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_matrix_brightness); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS: + device_remove_file(&hdev->dev, &dev_attr_charge_effect); + device_remove_file(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_matrix_brightness); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_MAMBA_ELITE: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_left_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_left_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_right_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_right_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + + device_remove_file(&hdev->dev, &dev_attr_tilt_hwheel); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat_delay); + device_remove_file(&hdev->dev, &dev_attr_tilt_repeat); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL: + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_WHITE_EDITION: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_2021: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_NAGA_TRINITY: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_matrix_brightness); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_static); + break; + + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER: + device_remove_file(&hdev->dev, &dev_attr_charge_effect); + device_remove_file(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED: + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS: + device_remove_file(&hdev->dev, &dev_attr_charge_effect); + device_remove_file(&hdev->dev, &dev_attr_charge_colour); + fallthrough; + case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED: + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + fallthrough; + case USB_DEVICE_ID_RAZER_VIPER: + case USB_DEVICE_ID_RAZER_VIPER_MINI: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS: + device_remove_file(&hdev->dev, &dev_attr_hyperpolling_wireless_dongle_indicator_led_mode); + fallthrough; + case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER: + case USB_DEVICE_ID_RAZER_BASILISK_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER: + case USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH: + case USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER: + case USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED: + case USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED: + case USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS: + case USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER: + case USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED: + case USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS: + device_remove_file(&hdev->dev, &dev_attr_hyperpolling_wireless_dongle_indicator_led_mode); + fallthrough; + case USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + device_remove_file(&hdev->dev, &dev_attr_hyperpolling_wireless_dongle_indicator_led_mode); + device_remove_file(&hdev->dev, &dev_attr_hyperpolling_wireless_dongle_pair); + device_remove_file(&hdev->dev, &dev_attr_hyperpolling_wireless_dongle_unpair); + break; + + case USB_DEVICE_ID_RAZER_VIPER_8K: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_matrix_effect_custom); + device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame); + break; + + case USB_DEVICE_ID_RAZER_COBRA: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + + device_remove_file(&hdev->dev, &dev_attr_logo_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_logo_matrix_effect_breath); + break; + + case USB_DEVICE_ID_RAZER_DEATHADDER_V3: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + break; + + case USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED: + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_dpi_stages); + + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_wave); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_reactive); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_breath); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + break; + + case USB_DEVICE_ID_RAZER_NAGA_EPIC: + device_remove_file(&hdev->dev, &dev_attr_dpi); + device_remove_file(&hdev->dev, &dev_attr_poll_rate); + device_remove_file(&hdev->dev, &dev_attr_charge_low_threshold); + device_remove_file(&hdev->dev, &dev_attr_device_idle_time); + device_remove_file(&hdev->dev, &dev_attr_charge_level); + device_remove_file(&hdev->dev, &dev_attr_charge_status); + device_remove_file(&hdev->dev, &dev_attr_scroll_led_brightness); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_none); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_static); + device_remove_file(&hdev->dev, &dev_attr_scroll_matrix_effect_spectrum); + break; + } + + } + + hid_hw_stop(hdev); + hrtimer_cancel(&dev->repeat_timer); + + kfree(dev); + dev_info(&intf->dev, "Razer Device disconnected\n"); +} + +/** + * Device ID mapping table + */ +static const struct hid_device_id razer_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_OROCHI_2011) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ABYSSUS_1800) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ABYSSUS_2000) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_3_5G) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_HEX_RED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_2012) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_2014) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_HEX) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_MAMBA_2012_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_MAMBA_2012_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_MAMBA_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_MAMBA_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_MAMBA_TE_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ABYSSUS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_TAIPAN) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_IMPERATOR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_OUROBOROS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_2013) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_OROCHI_2013) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_OROCHI_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_HEX_V2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA_DOCK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_ELITE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DIAMONDBACK_CHROMA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ABYSSUS_V2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_3500) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_TRINITY) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_MAMBA_ELITE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_2021) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_1800) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ABYSSUS_ELITE_DVA_EDITION) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ABYSSUS_ESSENTIAL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_WHITE_EDITION) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_MINI) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_V2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_V3) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_2000) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_X_HYPERSPEED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_8K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V3) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_V3_35K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_COBRA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_NAGA_EPIC) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(hid, razer_devices); + +/** + * Describes the contents of the driver + */ +static struct hid_driver razer_mouse_driver = { + .name = "razermouse", + .id_table = razer_devices, + .probe = razer_mouse_probe, + .remove = razer_mouse_disconnect, + + .raw_event = razer_raw_event, + .input_mapping = razer_input_mapping, + .input_configured = razer_input_configured, +}; + +module_hid_driver(razer_mouse_driver); diff --git a/drivers/custom/razer/driver/razermouse_driver.h b/drivers/custom/razer/driver/razermouse_driver.h new file mode 100644 index 000000000000..60807fca9a1b --- /dev/null +++ b/drivers/custom/razer/driver/razermouse_driver.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2015 Terri Cain + */ + +#ifndef __HID_RAZER_MOUSE_H +#define __HID_RAZER_MOUSE_H + +#define USB_DEVICE_ID_RAZER_OROCHI_2011 0x0013 +#define USB_DEVICE_ID_RAZER_NAGA 0x0015 +#define USB_DEVICE_ID_RAZER_DEATHADDER_3_5G 0x0016 +#define USB_DEVICE_ID_RAZER_NAGA_EPIC 0x001F +#define USB_DEVICE_ID_RAZER_ABYSSUS_1800 0x0020 +#define USB_DEVICE_ID_RAZER_MAMBA_2012_WIRED 0x0024 +#define USB_DEVICE_ID_RAZER_MAMBA_2012_WIRELESS 0x0025 +#define USB_DEVICE_ID_RAZER_DEATHADDER_3_5G_BLACK 0x0029 +#define USB_DEVICE_ID_RAZER_NAGA_2012 0x002E +#define USB_DEVICE_ID_RAZER_IMPERATOR 0x002F +#define USB_DEVICE_ID_RAZER_OUROBOROS 0x0032 +#define USB_DEVICE_ID_RAZER_TAIPAN 0x0034 +#define USB_DEVICE_ID_RAZER_NAGA_HEX_RED 0x0036 +#define USB_DEVICE_ID_RAZER_DEATHADDER_2013 0x0037 +#define USB_DEVICE_ID_RAZER_DEATHADDER_1800 0x0038 +#define USB_DEVICE_ID_RAZER_OROCHI_2013 0x0039 +#define USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA 0x003E +#define USB_DEVICE_ID_RAZER_NAGA_EPIC_CHROMA_DOCK 0x003F +#define USB_DEVICE_ID_RAZER_NAGA_2014 0x0040 +#define USB_DEVICE_ID_RAZER_NAGA_HEX 0x0041 +#define USB_DEVICE_ID_RAZER_ABYSSUS 0x0042 +#define USB_DEVICE_ID_RAZER_DEATHADDER_CHROMA 0x0043 +#define USB_DEVICE_ID_RAZER_MAMBA_WIRED 0x0044 +#define USB_DEVICE_ID_RAZER_MAMBA_WIRELESS 0x0045 +#define USB_DEVICE_ID_RAZER_MAMBA_TE_WIRED 0x0046 +#define USB_DEVICE_ID_RAZER_OROCHI_CHROMA 0x0048 +#define USB_DEVICE_ID_RAZER_DIAMONDBACK_CHROMA 0x004C +#define USB_DEVICE_ID_RAZER_DEATHADDER_2000 0x004F +#define USB_DEVICE_ID_RAZER_NAGA_HEX_V2 0x0050 +#define USB_DEVICE_ID_RAZER_NAGA_CHROMA 0x0053 +#define USB_DEVICE_ID_RAZER_DEATHADDER_3500 0x0054 +#define USB_DEVICE_ID_RAZER_LANCEHEAD_WIRED 0x0059 +#define USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS 0x005A +#define USB_DEVICE_ID_RAZER_ABYSSUS_V2 0x005B +#define USB_DEVICE_ID_RAZER_DEATHADDER_ELITE 0x005C +#define USB_DEVICE_ID_RAZER_ABYSSUS_2000 0x005E +#define USB_DEVICE_ID_RAZER_LANCEHEAD_TE_WIRED 0x0060 +#define USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER 0x0062 +#define USB_DEVICE_ID_RAZER_BASILISK 0x0064 +#define USB_DEVICE_ID_RAZER_BASILISK_ESSENTIAL 0x0065 +#define USB_DEVICE_ID_RAZER_NAGA_TRINITY 0x0067 +#define USB_DEVICE_ID_RAZER_ABYSSUS_ELITE_DVA_EDITION 0x006A +#define USB_DEVICE_ID_RAZER_ABYSSUS_ESSENTIAL 0x006B +#define USB_DEVICE_ID_RAZER_MAMBA_ELITE 0x006C +#define USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL 0x006E +#define USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_RECEIVER 0x006F +#define USB_DEVICE_ID_RAZER_LANCEHEAD_WIRELESS_WIRED 0x0070 +#define USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_WHITE_EDITION 0x0071 +#define USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER 0x0072 +#define USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED 0x0073 +#define USB_DEVICE_ID_RAZER_PRO_CLICK_RECEIVER 0x0077 +#define USB_DEVICE_ID_RAZER_VIPER 0x0078 +#define USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED 0x007A +#define USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS 0x007B +#define USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED 0x007C +#define USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS 0x007D +#define USB_DEVICE_ID_RAZER_PRO_CLICK_WIRED 0x0080 +#define USB_DEVICE_ID_RAZER_BASILISK_X_HYPERSPEED 0x0083 +#define USB_DEVICE_ID_RAZER_DEATHADDER_V2 0x0084 +#define USB_DEVICE_ID_RAZER_BASILISK_V2 0x0085 +#define USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_WIRED 0x0086 +#define USB_DEVICE_ID_RAZER_BASILISK_ULTIMATE_RECEIVER 0x0088 +#define USB_DEVICE_ID_RAZER_VIPER_MINI 0x008A +#define USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI 0x008C +#define USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020 0x008D +#define USB_DEVICE_ID_RAZER_NAGA_PRO_WIRED 0x008F +#define USB_DEVICE_ID_RAZER_NAGA_PRO_WIRELESS 0x0090 +#define USB_DEVICE_ID_RAZER_VIPER_8K 0x0091 +#define USB_DEVICE_ID_RAZER_OROCHI_V2_RECEIVER 0x0094 +#define USB_DEVICE_ID_RAZER_OROCHI_V2_BLUETOOTH 0x0095 +#define USB_DEVICE_ID_RAZER_NAGA_X 0x0096 +#define USB_DEVICE_ID_RAZER_DEATHADDER_ESSENTIAL_2021 0x0098 +#define USB_DEVICE_ID_RAZER_BASILISK_V3 0x0099 +#define USB_DEVICE_ID_RAZER_PRO_CLICK_MINI_RECEIVER 0x009A +#define USB_DEVICE_ID_RAZER_DEATHADDER_V2_X_HYPERSPEED 0x009C +#define USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRED 0x009E +#define USB_DEVICE_ID_RAZER_VIPER_MINI_SE_WIRELESS 0x009F +#define USB_DEVICE_ID_RAZER_DEATHADDER_V2_LITE 0x00A1 +#define USB_DEVICE_ID_RAZER_COBRA 0x00A3 +#define USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRED 0x00A5 +#define USB_DEVICE_ID_RAZER_VIPER_V2_PRO_WIRELESS 0x00A6 +#define USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRED 0x00A7 +#define USB_DEVICE_ID_RAZER_NAGA_V2_PRO_WIRELESS 0x00A8 +#define USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRED 0x00AA +#define USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_WIRELESS 0x00AB +#define USB_DEVICE_ID_RAZER_COBRA_PRO_WIRED 0x00AF +#define USB_DEVICE_ID_RAZER_COBRA_PRO_WIRELESS 0x00B0 +#define USB_DEVICE_ID_RAZER_DEATHADDER_V3 0x00B2 +#define USB_DEVICE_ID_RAZER_HYPERPOLLING_WIRELESS_DONGLE 0x00B3 +#define USB_DEVICE_ID_RAZER_NAGA_V2_HYPERSPEED_RECEIVER 0x00B4 +#define USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED 0x00B6 +#define USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS 0x00B7 +#define USB_DEVICE_ID_RAZER_VIPER_V3_HYPERSPEED 0x00B8 +#define USB_DEVICE_ID_RAZER_BASILISK_V3_X_HYPERSPEED 0x00B9 +#define USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRED 0x00C0 +#define USB_DEVICE_ID_RAZER_VIPER_V3_PRO_WIRELESS 0x00C1 +#define USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRED_ALT 0x00C2 +#define USB_DEVICE_ID_RAZER_DEATHADDER_V3_PRO_WIRELESS_ALT 0x00C3 +#define USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRED 0x00C4 +#define USB_DEVICE_ID_RAZER_DEATHADDER_V3_HYPERSPEED_WIRELESS 0x00C5 +#define USB_DEVICE_ID_RAZER_BASILISK_V3_35K 0x00CB +#define USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRED 0x00CC +#define USB_DEVICE_ID_RAZER_BASILISK_V3_PRO_35K_WIRELESS 0x00CD + +/* Each keyboard report has 90 bytes*/ +#define RAZER_REPORT_LEN 0x5A + +#define RAZER_MAMBA_ROW_LEN 15 // 0 => 14 +#define RAZER_MAMBA_TE_ROW_LEN 16 // 0 => 15 +#define RAZER_DIAMONDBACK_ROW_LEN 21 // 0 => 20 + +#define RAZER_MOUSE_WAIT_MIN_US 600 +#define RAZER_MOUSE_WAIT_MAX_US 800 + +#define RAZER_NEW_MOUSE_RECEIVER_WAIT_MIN_US 31000 +#define RAZER_NEW_MOUSE_RECEIVER_WAIT_MAX_US 31100 + +#define RAZER_ATHERIS_RECEIVER_WAIT_MIN_US 400000 +#define RAZER_ATHERIS_RECEIVER_WAIT_MAX_US 400100 + +#define RAZER_VIPER_MOUSE_RECEIVER_WAIT_MIN_US 59900 +#define RAZER_VIPER_MOUSE_RECEIVER_WAIT_MAX_US 60000 + +#define RAZER_MOUSE_MAX_DPI_STAGES 5 + +struct razer_mouse_device { + struct usb_device *usb_dev; + struct mutex lock; + + struct input_dev *input; + struct hrtimer repeat_timer; + unsigned int tilt_hwheel; + unsigned int tilt_repeat_delay; + unsigned int tilt_repeat; + __s32 hwheel_value; + u8 button_byte; // Previous value of mouse button byte in HID record + u8 rep4[16]; // Previous value of report 4 on the keyboard intf + + unsigned char usb_interface_protocol; + unsigned char usb_interface_subclass; + + unsigned short usb_vid; + unsigned short usb_pid; + + char serial[23]; // Now storing a random serial to be used with old devices that don't support it + + struct { + unsigned char led; + unsigned char dpi; + unsigned short poll; + } orochi2011; + + // The DeathAdder 3.5G, uses OR logic so need to remember last values. Part of a 4byte payload + struct { + unsigned char poll; + unsigned char dpi; + unsigned char profile; + unsigned char leds; + } da3_5g; +}; + +// Mamba Key Location +// 0 => 6 ---> top left => bottom left +// 7 => 13 ---> top right => bottom right +// 14 ---> Scroll LED + +// Mamba TE Key Location +// 0 => 6 ---> top left => bottom left +// 7 => 13 ---> top right => bottom right +// 14 ---> Logo LED +// 15 ---> Scroll LED + +#endif diff --git a/drivers/custom/razer/driver/usb_hid_keys.h b/drivers/custom/razer/driver/usb_hid_keys.h new file mode 100644 index 000000000000..a5fbb68e0a64 --- /dev/null +++ b/drivers/custom/razer/driver/usb_hid_keys.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * USB HID keycodes + * http://www.freebsddiary.org/APC/usb_hid_usages.php + */ + +#ifndef __USB_HID_KEYS_H +#define __USB_HID_KEYS_H + +#define USB_HID_KEY_F13 0x68 +#define USB_HID_KEY_F14 0x69 +#define USB_HID_KEY_F15 0x6A +#define USB_HID_KEY_F16 0x6B +#define USB_HID_KEY_F17 0x6C +#define USB_HID_KEY_F18 0x6D +#define USB_HID_KEY_F19 0x6E +#define USB_HID_KEY_F20 0x6F +#define USB_HID_KEY_F21 0x70 +#define USB_HID_KEY_F22 0x71 +#define USB_HID_KEY_F23 0x72 +#define USB_HID_KEY_F24 0x73 + +#define USB_HID_KEY_MEDIA_PLAYPAUSE 0xE8 +#define USB_HID_KEY_MEDIA_PREVIOUSSONG 0xEA +#define USB_HID_KEY_MEDIA_NEXTSONG 0xEB +#define USB_HID_KEY_MEDIA_VOLUMEUP 0xED +#define USB_HID_KEY_MEDIA_VOLUMEDOWN 0xEE +#define USB_HID_KEY_MEDIA_MUTE 0xEF +#define USB_HID_KEY_MEDIA_SLEEP 0xF8 + +// https://usb.org/sites/default/files/hut1_22.pdf +#define USB_HID_USAGE_MEDIA_PLAYPAUSE 0xCD +#define USB_HID_USAGE_MEDIA_PREVIOUSSONG 0xB6 +#define USB_HID_USAGE_MEDIA_NEXTSONG 0xB5 +#define USB_HID_USAGE_MEDIA_VOLUMEUP 0xE9 +#define USB_HID_USAGE_MEDIA_VOLUMEDOWN 0xEA +#define USB_HID_USAGE_MEDIA_MUTE 0xE2 + +#endif // __USB_HID_KEYS_H diff --git a/drivers/custom/razer/install_files/udev/99-razer.rules b/drivers/custom/razer/install_files/udev/99-razer.rules new file mode 100644 index 000000000000..b25569c524f2 --- /dev/null +++ b/drivers/custom/razer/install_files/udev/99-razer.rules @@ -0,0 +1,39 @@ +ACTION!="add", GOTO="razer_end" +SUBSYSTEMS=="usb|input|hid", ATTRS{idVendor}=="1532", GOTO="razer_vendor" +GOTO="razer_end" + +LABEL="razer_vendor" + +# Mice +ATTRS{idProduct}=="0013|0015|0016|001f|0020|0024|0025|0029|002e|002f|0032|0034|0036|0037|0038|0039|003e|003f|0040|0041|0042|0043|0044|0045|0046|0048|004c|004f|0050|0053|0054|0059|005a|005b|005c|005e|0060|0062|0064|0065|0067|006a|006b|006c|006e|006f|0070|0071|0072|0073|0077|0078|007a|007b|007c|007d|0080|0083|0084|0085|0086|0088|008a|008c|008d|008f|0090|0091|0094|0095|0096|0098|0099|009a|009c|009e|009f|00a1|00a3|00a5|00a6|00a7|00a8|00aa|00ab|00af|00b0|00b2|00b3|00b4|00b6|00b7|00b8|00b9|00c0|00c1|00c2|00c3|00c4|00c5|00cb|00cc|00cd", \ + ATTRS{idVendor}=="1532", \ + ENV{ID_RAZER_CHROMA}="1", ENV{RAZER_DRIVER}="razermouse" + +# Keyboards +ATTRS{idProduct}=="010d|010e|010f|0111|0113|0118|011a|011b|011c|0201|0202|0203|0204|0205|0207|0208|0209|020f|0210|0211|0214|0216|0217|021a|021e|021f|0220|0221|0224|0225|0226|0227|0228|022a|022b|022c|022d|022f|0232|0233|0234|0235|0237|0239|023a|023b|023f|0240|0241|0243|0245|0246|024a|024b|024c|024d|024e|0252|0253|0255|0256|0257|0258|0259|025a|025c|025d|025e|0266|0268|0269|026a|026b|026c|026d|026e|026f|0270|0271|0276|0279|027a|0282|0287|028a|028b|028c|028d|028f|0290|0292|0293|0294|0295|0296|0298|029d|029e|029f|02a0|02a1|02a2|02a3|02a5|02a6|02b6|02b8|0a24", \ + ATTRS{idVendor}=="1532", \ + ENV{ID_RAZER_CHROMA}="1", ENV{RAZER_DRIVER}="razerkbd" + +# Kraken +ATTRS{idProduct}=="0501|0504|0506|0510|0527|0560", \ + ATTRS{idVendor}=="1532", \ + ENV{ID_RAZER_CHROMA}="1", ENV{RAZER_DRIVER}="razerkraken" + +# Accessories (Speakers, Mousemats, Razer Core, etc) +ATTRS{idProduct}=="0068|007e|0215|0517|0518|0c00|0c01|0c02|0c04|0c06|0f07|0f08|0f09|0f0d|0f12|0f19|0f1a|0f1d|0f1f|0f20|0f21|0f26|0f2b", \ + ATTRS{idVendor}=="1532", \ + ENV{ID_RAZER_CHROMA}="1", ENV{RAZER_DRIVER}="razeraccessory" + +# Get out if no match +ENV{ID_RAZER_CHROMA}!="1", GOTO="razer_end" + +# Set permissions if this is an input node +SUBSYSTEM=="usb|input|hid", GROUP:="plugdev" + +# We're done unless it's the hid node +SUBSYSTEM!="hid|usb", GOTO="razer_end" + +# Rebind if needed +SUBSYSTEM=="hid|usb", RUN+="razer_mount $env{RAZER_DRIVER} $kernel" + +LABEL="razer_end" diff --git a/drivers/custom/razer/install_files/udev/razer_mount b/drivers/custom/razer/install_files/udev/razer_mount new file mode 100755 index 000000000000..3fc0fef19aac --- /dev/null +++ b/drivers/custom/razer/install_files/udev/razer_mount @@ -0,0 +1,62 @@ +#!/bin/sh + +# Exit on error +set -e + +PATH='/sbin:/bin:/usr/sbin:/usr/bin' + +if [ -x /usr/bin/logger ]; then + LOGGER=/usr/bin/logger +elif [ -x /bin/logger ]; then + LOGGER=/bin/logger +else + unset LOGGER +fi + +# for diagnostics +if [ -t 1 -a -z "$LOGGER" ] || [ ! -e '/dev/log' ]; then + mesg() { + echo "$@" >&2 + } +elif [ -t 1 ]; then + mesg() { + echo "$@" + $LOGGER -t "${0##*/}[$$]" "$@" + } +else + mesg() { + $LOGGER -t "${0##*/}[$$]" "$@" + } +fi + +DRIVER=$1 +DEVICE_ID=$2 + +mesg "Driver $DRIVER" +mesg "Device_ID $DEVICE_ID" + +if [ ! -d /sys/bus/hid/drivers/"$DRIVER" ] ; then + mesg "Modprobing $DRIVER" + modprobe "$DRIVER" + sleep 0.05 + mesg "Modprobed $DRIVER" +fi + +for GENERIC_DRIVER in "razer" "hid-generic"; do + if [ -d /sys/bus/hid/drivers/"$GENERIC_DRIVER"/"$DEVICE_ID" ] ; then + # Unbind from hid + mesg "Unbinding $DEVICE_ID from $GENERIC_DRIVER" + printf '%s' "$DEVICE_ID" > /sys/bus/hid/drivers/"$GENERIC_DRIVER"/unbind + mesg "Binding $DEVICE_ID to $DRIVER" + printf '%s' "$DEVICE_ID" > /sys/bus/hid/drivers/"$DRIVER"/bind + sleep 0.1 + mesg "Finished binding $DEVICE_ID" + fi +done + +if [ -d /sys/bus/hid/drivers/"$DRIVER"/"$DEVICE_ID" ] ; then + mesg "Changing group /sys/bus/hid/drivers/$DRIVER/$DEVICE_ID/" + chgrp -R plugdev /sys/bus/hid/drivers/"$DRIVER"/"$DEVICE_ID"/ + mesg "Changed group /sys/bus/hid/drivers/$DRIVER/$DEVICE_ID/" +fi + # ---------------------------------------- # Module: system76 # Version: 1.0.21 # ---------------------------------------- diff --git a/drivers/custom/system76/Kbuild b/drivers/custom/system76/Kbuild new file mode 100644 index 000000000000..d44f74336eaa --- /dev/null +++ b/drivers/custom/system76/Kbuild @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +obj-y += src/ diff --git a/drivers/custom/system76/Makefile b/drivers/custom/system76/Makefile new file mode 100644 index 000000000000..3908fa8bfb89 --- /dev/null +++ b/drivers/custom/system76/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +KERNEL_DIR = /lib/modules/$(shell uname -r)/build + +all: + $(MAKE) -C "$(KERNEL_DIR)" M="$(PWD)" modules + +clean: + $(MAKE) -C "$(KERNEL_DIR)" M="$(PWD)" clean diff --git a/drivers/custom/system76/src/Kbuild b/drivers/custom/system76/src/Kbuild new file mode 100644 index 000000000000..b426c1943611 --- /dev/null +++ b/drivers/custom/system76/src/Kbuild @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +obj-m += system76.o diff --git a/drivers/custom/system76/src/ap-led.c b/drivers/custom/system76/src/ap-led.c new file mode 100644 index 000000000000..d62171c35e99 --- /dev/null +++ b/drivers/custom/system76/src/ap-led.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ap_led.c + * + * Copyright (C) 2017 Jeremy Soller + */ + +static enum led_brightness ap_led_brightness = 1; + +static bool ap_led_invert = TRUE; + +static enum led_brightness ap_led_get(struct led_classdev *led_cdev) +{ + return ap_led_brightness; +} + +static int ap_led_set(struct led_classdev *led_cdev, enum led_brightness value) +{ + u8 byte; + + ec_read(0xD9, &byte); + + if (value > 0) { + ap_led_brightness = 1; + + if (ap_led_invert) { + byte &= ~0x40; + } else { + byte |= 0x40; + } + } else { + ap_led_brightness = 0; + + if (ap_led_invert) { + byte |= 0x40; + } else { + byte &= ~0x40; + } + } + + ec_write(0xD9, byte); + + return 0; +} + +static struct led_classdev ap_led = { + .name = "system76::airplane", + .brightness_get = ap_led_get, + .brightness_set_blocking = ap_led_set, + .max_brightness = 1, + .default_trigger = "rfkill-any" +}; + +static ssize_t ap_led_invert_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%d\n", (int)ap_led_invert); +} + +static ssize_t ap_led_invert_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + unsigned int val; + int ret; + enum led_brightness brightness; + + ret = kstrtouint(buf, 0, &val); + if (ret) { + return ret; + } + + brightness = ap_led_get(&ap_led); + + if (val) { + ap_led_invert = TRUE; + } else { + ap_led_invert = FALSE; + } + + ap_led_set(&ap_led, brightness); + + return size; +} + +static struct device_attribute ap_led_invert_dev_attr = { + .attr = { + .name = "invert", + .mode = 0644, + }, + .show = ap_led_invert_show, + .store = ap_led_invert_store, +}; + +static void ap_led_resume(void) +{ + ap_led_set(&ap_led, ap_led_brightness); +} + +static int __init ap_led_init(struct device *dev) +{ + int err; + + err = devm_led_classdev_register(dev, &ap_led); + if (err < 0) { + return err; + } + + err = device_create_file(ap_led.dev, &ap_led_invert_dev_attr); + if (err < 0) { + pr_err("failed to create ap_led_invert\n"); + } + + ap_led_resume(); + + return 0; +} + +static void __exit ap_led_exit(void) +{ + device_remove_file(ap_led.dev, &ap_led_invert_dev_attr); +} diff --git a/drivers/custom/system76/src/hwmon.c b/drivers/custom/system76/src/hwmon.c new file mode 100644 index 000000000000..f189681d256b --- /dev/null +++ b/drivers/custom/system76/src/hwmon.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * fan.c + * + * Copyright (C) 2017 Jeremy Soller + * Copyright (C) 2014-2016 Arnoud Willemsen + * Copyright (C) 2013-2015 TUXEDO Computers GmbH + */ + +#define EXPERIMENTAL + +#if S76_HAS_HWMON + +struct s76_hwmon { + struct device *dev; +}; + +static struct s76_hwmon *s76_hwmon; + +static int s76_read_fan(int idx) +{ + u8 value; + int raw_rpm; + + ec_read(0xd0 + 0x2 * idx, &value); + raw_rpm = value << 8; + ec_read(0xd1 + 0x2 * idx, &value); + raw_rpm += value; + if (!raw_rpm) + return 0; + return 2156220 / raw_rpm; +} + +static int s76_read_pwm(int idx) +{ + u8 value; + + ec_read(0xce + idx, &value); + return value; +} + +static int s76_write_pwm(int idx, u8 duty) +{ + u8 values[] = {idx + 1, duty}; + + return ec_transaction(0x99, values, sizeof(values), NULL, 0); +} + +static int s76_write_pwm_auto(int idx) +{ + u8 values[] = {0xff, idx + 1}; + + return ec_transaction(0x99, values, sizeof(values), NULL, 0); +} + +static ssize_t s76_hwmon_show_fan_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + + return sysfs_emit(buf, "%i\n", s76_read_fan(index)); +} + +static ssize_t s76_hwmon_show_fan_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + switch (to_sensor_dev_attr(attr)->index) { + case 0: + return sysfs_emit(buf, "CPU fan\n"); + case 1: + return sysfs_emit(buf, "GPU fan\n"); + } + return 0; +} + +static int pwm_enabled[] = {2, 2}; + +static ssize_t s76_hwmon_show_pwm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + + return sysfs_emit(buf, "%i\n", s76_read_pwm(index)); +} + +static ssize_t s76_hwmon_set_pwm(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u32 value; + int err; + int index = to_sensor_dev_attr(attr)->index; + + err = kstrtou32(buf, 10, &value); + if (err) + return err; + if (value > 255) + return -EINVAL; + err = s76_write_pwm(index, value); + if (err) + return err; + pwm_enabled[index] = 1; + return count; +} + +static ssize_t s76_hwmon_show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + + return sysfs_emit(buf, "%i\n", pwm_enabled[index]); +} + +static ssize_t s76_hwmon_set_pwm_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u32 value; + int err; + int index = to_sensor_dev_attr(attr)->index; + + err = kstrtou32(buf, 10, &value); + if (err) + return err; + if (value == 0) { + err = s76_write_pwm(index, 255); + if (err) + return err; + pwm_enabled[index] = value; + return count; + } + if (value == 1) { + err = s76_write_pwm(index, 0); + if (err) + return err; + pwm_enabled[index] = value; + return count; + } + if (value == 2) { + err = s76_write_pwm_auto(index); + if (err) + return err; + pwm_enabled[index] = value; + return count; + } + return -EINVAL; +} + +static ssize_t s76_hwmon_show_temp1_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 value; + + ec_read(0x07, &value); + return sysfs_emit(buf, "%i\n", value * 1000); +} + +static ssize_t s76_hwmon_show_temp1_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "CPU temperature\n"); +} + +#ifdef EXPERIMENTAL +static ssize_t s76_hwmon_show_temp2_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 value; + + ec_read(0xcd, &value); + return sysfs_emit(buf, "%i\n", value * 1000); +} + +static ssize_t s76_hwmon_show_temp2_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "GPU temperature\n"); +} +#endif + +static SENSOR_DEVICE_ATTR(fan1_input, 0444, s76_hwmon_show_fan_input, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_label, 0444, s76_hwmon_show_fan_label, NULL, 0); +static SENSOR_DEVICE_ATTR(pwm1, 0644, s76_hwmon_show_pwm, s76_hwmon_set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm1_enable, 0644, s76_hwmon_show_pwm_enable, s76_hwmon_set_pwm_enable, 0); +#ifdef EXPERIMENTAL +static SENSOR_DEVICE_ATTR(fan2_input, 0444, s76_hwmon_show_fan_input, NULL, 1); +static SENSOR_DEVICE_ATTR(fan2_label, 0444, s76_hwmon_show_fan_label, NULL, 1); +static SENSOR_DEVICE_ATTR(pwm2, 0644, s76_hwmon_show_pwm, s76_hwmon_set_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm2_enable, 0644, s76_hwmon_show_pwm_enable, s76_hwmon_set_pwm_enable, 1); +#endif +static SENSOR_DEVICE_ATTR(temp1_input, 0444, s76_hwmon_show_temp1_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_label, 0444, s76_hwmon_show_temp1_label, NULL, 0); +#ifdef EXPERIMENTAL +static SENSOR_DEVICE_ATTR(temp2_input, 0444, s76_hwmon_show_temp2_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_label, 0444, s76_hwmon_show_temp2_label, NULL, 1); +#endif + +static struct attribute *hwmon_default_attributes[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_label.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, +#ifdef EXPERIMENTAL + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_label.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, +#endif + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, +#ifdef EXPERIMENTAL + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, +#endif + NULL +}; + +static const struct attribute_group hwmon_default_group = { + .attrs = hwmon_default_attributes, +}; +__ATTRIBUTE_GROUPS(hwmon_default); + +static int s76_hwmon_reboot_callback(struct notifier_block *nb, + unsigned long action, void *data) +{ + s76_write_pwm_auto(0); + #ifdef EXPERIMENTAL + s76_write_pwm_auto(1); + #endif + return NOTIFY_DONE; +} + +static struct notifier_block s76_hwmon_reboot_notifier = { + .notifier_call = s76_hwmon_reboot_callback +}; + +static int s76_hwmon_init(struct device *dev) +{ + s76_hwmon = devm_kzalloc(dev, sizeof(*s76_hwmon), GFP_KERNEL); + if (!s76_hwmon) + return -ENOMEM; + + s76_hwmon->dev = devm_hwmon_device_register_with_groups(dev, S76_DRIVER_NAME, NULL, hwmon_default_groups); + if (IS_ERR(s76_hwmon->dev)) { + return PTR_ERR(s76_hwmon->dev); + } + + (void)devm_register_reboot_notifier(dev, &s76_hwmon_reboot_notifier); + s76_write_pwm_auto(0); + #ifdef EXPERIMENTAL + s76_write_pwm_auto(1); + #endif + return 0; +} + +static int s76_hwmon_fini(struct device *dev) +{ + if (!s76_hwmon || IS_ERR_OR_NULL(s76_hwmon->dev)) + return 0; + + s76_write_pwm_auto(0); + #ifdef EXPERIMENTAL + s76_write_pwm_auto(1); + #endif + return 0; +} + +#endif // S76_HAS_HWMON diff --git a/drivers/custom/system76/src/input.c b/drivers/custom/system76/src/input.c new file mode 100644 index 000000000000..236b85a1176a --- /dev/null +++ b/drivers/custom/system76/src/input.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * input.c + * + * Copyright (C) 2017 Jeremy Soller + * Copyright (C) 2014-2016 Arnoud Willemsen + * Copyright (C) 2013-2015 TUXEDO Computers GmbH + */ + +#define AIRPLANE_KEY KEY_WLAN +#define SCREEN_KEY KEY_SCREENLOCK + +static struct input_dev *s76_input_device; +static DEFINE_MUTEX(s76_input_report_mutex); + +#define POLL_FREQ_MIN 1 +#define POLL_FREQ_MAX 20 +#define POLL_FREQ_DEFAULT 5 + +static int param_set_poll_freq(const char *val, const struct kernel_param *kp) +{ + int ret; + + ret = param_set_byte(val, kp); + + if (!ret) + *((unsigned char *) kp->arg) = clamp_t(unsigned char, + *((unsigned char *) kp->arg), + POLL_FREQ_MIN, POLL_FREQ_MAX); + + return ret; +} + + +static const struct kernel_param_ops param_ops_poll_freq = { + .set = param_set_poll_freq, + .get = param_get_byte, +}; + +static unsigned char param_poll_freq = POLL_FREQ_DEFAULT; +#define param_check_poll_freq param_check_byte +module_param_named(poll_freq, param_poll_freq, poll_freq, 0400); +MODULE_PARM_DESC(poll_freq, "Set polling frequency"); + +static struct task_struct *s76_input_polling_task; + +static void s76_input_key(unsigned int code) +{ + pr_debug("Send key %x\n", code); + + mutex_lock(&s76_input_report_mutex); + + input_report_key(s76_input_device, code, 1); + input_sync(s76_input_device); + + input_report_key(s76_input_device, code, 0); + input_sync(s76_input_device); + + mutex_unlock(&s76_input_report_mutex); +} + +static int s76_input_polling_thread(void *data) +{ + pr_debug("Polling thread started (PID: %i), polling at %i Hz\n", + current->pid, param_poll_freq); + + while (!kthread_should_stop()) { + u8 byte; + + ec_read(0xDB, &byte); + if (byte & 0x40) { + ec_write(0xDB, byte & ~0x40); + + pr_debug("Airplane-Mode Hotkey pressed (EC)\n"); + + s76_input_key(AIRPLANE_KEY); + } + + msleep_interruptible(1000 / param_poll_freq); + } + + pr_debug("Polling thread exiting\n"); + + return 0; +} + +static void s76_input_airplane_wmi(void) +{ + pr_debug("Airplane-Mode Hotkey pressed (WMI)\n"); + + s76_input_key(AIRPLANE_KEY); +} + +static void s76_input_screen_wmi(void) +{ + pr_debug("Screen Hotkey pressed (WMI)\n"); + + s76_input_key(SCREEN_KEY); +} + +static int s76_input_open(struct input_dev *dev) +{ + int res = 0; + + // Run polling thread if AP key driver is used and WMI is not supported + if ((driver_flags & (DRIVER_AP_KEY | DRIVER_AP_WMI)) == DRIVER_AP_KEY) { + s76_input_polling_task = kthread_run( + s76_input_polling_thread, + NULL, "system76-polld"); + + if (IS_ERR(s76_input_polling_task)) { + res = PTR_ERR(s76_input_polling_task); + s76_input_polling_task = NULL; + pr_err("Could not create polling thread: %d\n", res); + return res; + } + } + + return res; +} + +static void s76_input_close(struct input_dev *dev) +{ + if (IS_ERR_OR_NULL(s76_input_polling_task)) { + return; + } + + kthread_stop(s76_input_polling_task); + s76_input_polling_task = NULL; +} + +static int __init s76_input_init(struct device *dev) +{ + u8 byte; + + s76_input_device = devm_input_allocate_device(dev); + if (!s76_input_device) { + pr_err("Error allocating input device\n"); + return -ENOMEM; + } + + s76_input_device->name = "System76 Hotkeys"; + s76_input_device->phys = "system76/input0"; + s76_input_device->id.bustype = BUS_HOST; + set_bit(EV_KEY, s76_input_device->evbit); + + if (driver_flags & DRIVER_AP_KEY) { + set_bit(AIRPLANE_KEY, s76_input_device->keybit); + ec_read(0xDB, &byte); + ec_write(0xDB, byte & ~0x40); + } + if (driver_flags & DRIVER_OLED) { + set_bit(SCREEN_KEY, s76_input_device->keybit); + } + + s76_input_device->open = s76_input_open; + s76_input_device->close = s76_input_close; + + return input_register_device(s76_input_device); +} diff --git a/drivers/custom/system76/src/kb-led.c b/drivers/custom/system76/src/kb-led.c new file mode 100644 index 000000000000..91cdf8a657bb --- /dev/null +++ b/drivers/custom/system76/src/kb-led.c @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * kb_led.c + * + * Copyright (C) 2017 Jeremy Soller + */ + +#define SET_KB_LED 0x67 + +union kb_led_color { + u32 rgb; + struct { u32 b:8, g:8, r:8, : 8; }; +}; + +enum kb_led_region { + KB_LED_REGION_LEFT, + KB_LED_REGION_CENTER, + KB_LED_REGION_RIGHT, + KB_LED_REGION_EXTRA, +}; + +static enum led_brightness kb_led_brightness; + +static enum led_brightness kb_led_toggle_brightness = 72; + +static enum led_brightness kb_led_levels[] = { 48, 72, 96, 144, 192, 255 }; + +static union kb_led_color kb_led_regions[] = { + { .rgb = 0xFFFFFF }, + { .rgb = 0xFFFFFF }, + { .rgb = 0xFFFFFF }, + { .rgb = 0xFFFFFF } +}; + +static int kb_led_colors_i; + +static union kb_led_color kb_led_colors[] = { + { .rgb = 0xFFFFFF }, + { .rgb = 0x0000FF }, + { .rgb = 0xFF0000 }, + { .rgb = 0xFF00FF }, + { .rgb = 0x00FF00 }, + { .rgb = 0x00FFFF }, + { .rgb = 0xFFFF00 } +}; + +static enum led_brightness kb_led_get(struct led_classdev *led_cdev) +{ + return kb_led_brightness; +} + +static int kb_led_set(struct led_classdev *led_cdev, enum led_brightness value) +{ + pr_debug("%s %d\n", __func__, (int)value); + + if (!s76_wmbb(SET_KB_LED, 0xF4000000 | value, NULL)) { + kb_led_brightness = value; + } + + return 0; +} + +static void kb_led_color_set_wmi(enum kb_led_region region, union kb_led_color color) +{ + u32 cmd; + + pr_debug("%s %d %06X\n", __func__, (int)region, (int)color.rgb); + + switch (region) { + case KB_LED_REGION_LEFT: + cmd = 0xF0000000; + break; + case KB_LED_REGION_CENTER: + cmd = 0xF1000000; + break; + case KB_LED_REGION_RIGHT: + cmd = 0xF2000000; + break; + case KB_LED_REGION_EXTRA: + cmd = 0xF3000000; + break; + default: + return; + } + + cmd |= color.b << 16; + cmd |= color.r << 8; + cmd |= color.g << 0; + + if (!s76_wmbb(SET_KB_LED, cmd, NULL)) { + kb_led_regions[region] = color; + } +} + +// HACK: Directly call ECMD to fix serw14 +static void kb_led_color_set(enum kb_led_region region, union kb_led_color color) +{ + struct acpi_object_list input; + union acpi_object obj; + acpi_handle handle; + acpi_status status; + u8 *buf; + + buf = kzalloc(8, GFP_KERNEL); + + pr_debug("%s %d %06X\n", __func__, (int)region, (int)color.rgb); + + buf[0] = 5; + buf[2] = 0xCA; + buf[4] = color.b; + buf[5] = color.r; + buf[6] = color.g; + + switch (region) { + case KB_LED_REGION_LEFT: + buf[3] = 0x03; + break; + case KB_LED_REGION_CENTER: + buf[3] = 0x04; + break; + case KB_LED_REGION_RIGHT: + buf[3] = 0x05; + break; + case KB_LED_REGION_EXTRA: + buf[3] = 0x0B; + break; + } + + obj.type = ACPI_TYPE_BUFFER; + obj.buffer.length = 8; + obj.buffer.pointer = buf; + + input.count = 1; + input.pointer = &obj; + + status = acpi_get_handle(NULL, (acpi_string)"\\_SB.PC00.LPCB.EC", &handle); + if (ACPI_FAILURE(status)) { + pr_err("%s failed to get handle: %x\n", __func__, status); + return; + } + + status = acpi_evaluate_object(handle, "ECMD", &input, NULL); + if (ACPI_FAILURE(status)) { + pr_err("%s failed to call EC_CMD: %x\n", __func__, status); + return; + } + + // Update lightbar to match keyboard color + buf[3] = 0x07; + status = acpi_evaluate_object(handle, "ECMD", &input, NULL); + if (ACPI_FAILURE(status)) { + pr_err("%s failed to call EC_CMD: %x\n", __func__, status); + return; + } + + kfree(buf); + kb_led_regions[region] = color; +} + +static struct led_classdev kb_led = { + .name = "system76::kbd_backlight", + .flags = LED_BRIGHT_HW_CHANGED, + .brightness_get = kb_led_get, + .brightness_set_blocking = kb_led_set, + .max_brightness = 255, +}; + +static ssize_t kb_led_color_show(enum kb_led_region region, char *buf) +{ + return sysfs_emit(buf, "%06X\n", (int)kb_led_regions[region].rgb); +} + +static ssize_t kb_led_color_store(enum kb_led_region region, const char *buf, size_t size) +{ + unsigned int val; + int ret; + union kb_led_color color; + + ret = kstrtouint(buf, 16, &val); + if (ret) { + return ret; + } + + color.rgb = (u32)val; + if (driver_flags & DRIVER_KB_LED_WMI) + kb_led_color_set_wmi(region, color); + else + kb_led_color_set(region, color); + + return size; +} + +static ssize_t kb_led_color_left_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return kb_led_color_show(KB_LED_REGION_LEFT, buf); +} + +static ssize_t kb_led_color_left_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + return kb_led_color_store(KB_LED_REGION_LEFT, buf, size); +} + +static struct device_attribute kb_led_color_left_dev_attr = { + .attr = { + .name = "color_left", + .mode = 0644, + }, + .show = kb_led_color_left_show, + .store = kb_led_color_left_store, +}; + +static ssize_t kb_led_color_center_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return kb_led_color_show(KB_LED_REGION_CENTER, buf); +} + +static ssize_t kb_led_color_center_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + return kb_led_color_store(KB_LED_REGION_CENTER, buf, size); +} + +static struct device_attribute kb_led_color_center_dev_attr = { + .attr = { + .name = "color_center", + .mode = 0644, + }, + .show = kb_led_color_center_show, + .store = kb_led_color_center_store, +}; + +static ssize_t kb_led_color_right_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return kb_led_color_show(KB_LED_REGION_RIGHT, buf); +} + +static ssize_t kb_led_color_right_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + return kb_led_color_store(KB_LED_REGION_RIGHT, buf, size); +} + +static struct device_attribute kb_led_color_right_dev_attr = { + .attr = { + .name = "color_right", + .mode = 0644, + }, + .show = kb_led_color_right_show, + .store = kb_led_color_right_store, +}; + +static ssize_t kb_led_color_extra_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return kb_led_color_show(KB_LED_REGION_EXTRA, buf); +} + +static ssize_t kb_led_color_extra_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + return kb_led_color_store(KB_LED_REGION_EXTRA, buf, size); +} + +static struct device_attribute kb_led_color_extra_dev_attr = { + .attr = { + .name = "color_extra", + .mode = 0644, + }, + .show = kb_led_color_extra_show, + .store = kb_led_color_extra_store, +}; + +static void kb_led_enable(void) +{ + pr_debug("%s\n", __func__); + + s76_wmbb(SET_KB_LED, 0xE007F001, NULL); +} + +static void kb_led_disable(void) +{ + pr_debug("%s\n", __func__); + + s76_wmbb(SET_KB_LED, 0xE0003001, NULL); +} + +static void kb_led_suspend(void) +{ + pr_debug("%s\n", __func__); + + // Disable keyboard backlight + kb_led_disable(); +} + +static void kb_led_resume(void) +{ + enum kb_led_region region; + + pr_debug("%s\n", __func__); + + // Disable keyboard backlight + kb_led_disable(); + + // Reset current color + for (region = 0; region < sizeof(kb_led_regions)/sizeof(union kb_led_color); region++) { + if (driver_flags & DRIVER_KB_LED_WMI) + kb_led_color_set_wmi(region, kb_led_regions[region]); + else + kb_led_color_set(region, kb_led_regions[region]); + } + + // Reset current brightness + kb_led_set(&kb_led, kb_led_brightness); + + // Enable keyboard backlight + kb_led_enable(); +} + +static int __init kb_led_init(struct device *dev) +{ + int err; + + err = devm_led_classdev_register(dev, &kb_led); + if (unlikely(err)) { + return err; + } + + if (device_create_file(kb_led.dev, &kb_led_color_left_dev_attr) != 0) { + pr_err("failed to create kb_led_color_left\n"); + } + + if (device_create_file(kb_led.dev, &kb_led_color_center_dev_attr) != 0) { + pr_err("failed to create kb_led_color_center\n"); + } + + if (device_create_file(kb_led.dev, &kb_led_color_right_dev_attr) != 0) { + pr_err("failed to create kb_led_color_right\n"); + } + + if (device_create_file(kb_led.dev, &kb_led_color_extra_dev_attr) != 0) { + pr_err("failed to create kb_led_color_extra\n"); + } + + kb_led_resume(); + + return 0; +} + +static void __exit kb_led_exit(void) +{ + device_remove_file(kb_led.dev, &kb_led_color_extra_dev_attr); + device_remove_file(kb_led.dev, &kb_led_color_right_dev_attr); + device_remove_file(kb_led.dev, &kb_led_color_center_dev_attr); + device_remove_file(kb_led.dev, &kb_led_color_left_dev_attr); +} + +static void kb_wmi_brightness(enum led_brightness value) +{ + pr_debug("%s %d\n", __func__, (int)value); + + kb_led_set(&kb_led, value); + led_classdev_notify_brightness_hw_changed(&kb_led, value); +} + +static void kb_wmi_toggle(void) +{ + if (kb_led_brightness > 0) { + kb_led_toggle_brightness = kb_led_brightness; + kb_wmi_brightness(LED_OFF); + } else { + kb_wmi_brightness(kb_led_toggle_brightness); + } +} + +static void kb_wmi_dec(void) +{ + int i; + + if (kb_led_brightness > 0) { + for (i = sizeof(kb_led_levels)/sizeof(enum led_brightness); i > 0; i--) { + if (kb_led_levels[i - 1] < kb_led_brightness) { + kb_wmi_brightness(kb_led_levels[i - 1]); + break; + } + } + } else { + kb_wmi_toggle(); + } +} + +static void kb_wmi_inc(void) +{ + int i; + + if (kb_led_brightness > 0) { + for (i = 0; i < sizeof(kb_led_levels)/sizeof(enum led_brightness); i++) { + if (kb_led_levels[i] > kb_led_brightness) { + kb_wmi_brightness(kb_led_levels[i]); + break; + } + } + } else { + kb_wmi_toggle(); + } +} + +static void kb_wmi_color(void) +{ + enum kb_led_region region; + + kb_led_colors_i += 1; + if (kb_led_colors_i >= sizeof(kb_led_colors)/sizeof(union kb_led_color)) { + kb_led_colors_i = 0; + } + + for (region = 0; region < sizeof(kb_led_regions)/sizeof(union kb_led_color); region++) { + if (driver_flags & DRIVER_KB_LED_WMI) + kb_led_color_set_wmi(region, kb_led_colors[kb_led_colors_i]); + else + kb_led_color_set(region, kb_led_colors[kb_led_colors_i]); + } + + led_classdev_notify_brightness_hw_changed(&kb_led, kb_led_brightness); +} diff --git a/drivers/custom/system76/src/nv_hda.c b/drivers/custom/system76/src/nv_hda.c new file mode 100644 index 000000000000..43513279deae --- /dev/null +++ b/drivers/custom/system76/src/nv_hda.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Based on bbswitch, + * Copyright (C) 2011-2013 Bumblebee Project + * Author: Peter Wu + */ + +enum { + CARD_UNCHANGED = -1, + CARD_OFF = 0, + CARD_ON = 1, +}; + +static struct pci_dev *dis_dev; +static struct pci_dev *sub_dev; + +// Returns 1 if the card is disabled, 0 if enabled +static int is_card_disabled(void) +{ + // check for: 1.bit is set 2.sub-function is available. + u32 cfg_word; + struct pci_dev *tmp_dev = NULL; + + sub_dev = NULL; + + // read config word at 0x488 + pci_read_config_dword(dis_dev, 0x488, &cfg_word); + if ((cfg_word & 0x2000000) == 0x2000000) { + // check for subdevice. read first config dword of sub function 1 + while ((tmp_dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, tmp_dev)) != NULL) { + int pci_class = tmp_dev->class >> 8; + + if (pci_class != 0x403) + continue; + + if (tmp_dev->vendor == PCI_VENDOR_ID_NVIDIA) { + sub_dev = tmp_dev; + pr_info("Found NVIDIA audio device %s\n", dev_name(&tmp_dev->dev)); + } + } + + if (sub_dev == NULL) { + pr_info("No NVIDIA audio device found, unsetting config bit.\n"); + cfg_word |= 0x2000000; + pci_write_config_dword(dis_dev, 0x488, cfg_word); + return 1; + } + + return 0; + } else { + return 1; + } +} + +static void nv_hda_off(void) +{ + u32 cfg_word; + + if (is_card_disabled()) { + return; + } + + // remove device + pci_dev_put(sub_dev); + pci_stop_and_remove_bus_device(sub_dev); + + pr_info("NVIDIA audio: disabling\n"); + + // setting bit to turn off + pci_read_config_dword(dis_dev, 0x488, &cfg_word); + cfg_word &= 0xfdffffff; + pci_write_config_dword(dis_dev, 0x488, cfg_word); +} + +static void nv_hda_on(void) +{ + u32 cfg_word; + u8 hdr_type; + + if (!is_card_disabled()) { + return; + } + + pr_info("NVIDIA audio: enabling\n"); + + // read,set bit, write config word at 0x488 + pci_read_config_dword(dis_dev, 0x488, &cfg_word); + cfg_word |= 0x2000000; + pci_write_config_dword(dis_dev, 0x488, cfg_word); + + //pci_scan_single_device + pci_read_config_byte(dis_dev, PCI_HEADER_TYPE, &hdr_type); + + if (!(hdr_type & 0x80)) { + pr_err("NVIDIA not multifunction, no audio\n"); + return; + } + + sub_dev = pci_scan_single_device(dis_dev->bus, 1); + if (!sub_dev) { + pr_err("No NVIDIA audio device found\n"); + return; + } + + pr_info("NVIDIA audio found, adding\n"); + pci_assign_unassigned_bus_resources(dis_dev->bus); + pci_bus_add_devices(dis_dev->bus); + pci_dev_get(sub_dev); +} + +/* power bus so we can read PCI configuration space */ +static void dis_dev_get(void) +{ + if (dis_dev->bus && dis_dev->bus->self) { + pm_runtime_get_sync(&dis_dev->bus->self->dev); + } +} + +static void dis_dev_put(void) +{ + if (dis_dev->bus && dis_dev->bus->self) { + pm_runtime_put_sync(&dis_dev->bus->self->dev); + } +} + + +static int __init nv_hda_init(struct device *dev) +{ + struct pci_dev *pdev = NULL; + + while ((pdev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pdev)) != NULL) { + int pci_class = pdev->class >> 8; + + if (pci_class != PCI_CLASS_DISPLAY_VGA && pci_class != PCI_CLASS_DISPLAY_3D) { + continue; + } + + if (pdev->vendor == PCI_VENDOR_ID_NVIDIA) { + dis_dev = pdev; + pr_info("NVIDIA device %s\n", dev_name(&pdev->dev)); + } + } + + if (dis_dev == NULL) { + pr_err("No NVIDIA device found\n"); + return -ENODEV; + } + + dis_dev_get(); + + nv_hda_on(); + + pr_info("NVIDIA Audio %s is %s\n", dev_name(&dis_dev->dev), is_card_disabled() ? "off" : "on"); + + dis_dev_put(); + + return 0; +} + +static void __exit nv_hda_exit(void) +{ + if (dis_dev == NULL) + return; + + dis_dev_get(); + + nv_hda_off(); + + pr_info("NVIDIA Audio %s is %s\n", dev_name(&dis_dev->dev), is_card_disabled() ? "off" : "on"); + + dis_dev_put(); +} diff --git a/drivers/custom/system76/src/system76.c b/drivers/custom/system76/src/system76.c new file mode 100644 index 000000000000..0a80582dbdfd --- /dev/null +++ b/drivers/custom/system76/src/system76.c @@ -0,0 +1,409 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * system76.c + * + * Copyright (C) 2017 Jeremy Soller + * Copyright (C) 2014-2016 Arnoud Willemsen + * Copyright (C) 2013-2015 TUXEDO Computers GmbH + */ + +#define S76_DRIVER_NAME KBUILD_MODNAME +#define pr_fmt(fmt) S76_DRIVER_NAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define S76_EVENT_GUID "ABBC0F6B-8EA1-11D1-00A0-C90629100000" +#define S76_WMBB_GUID "ABBC0F6D-8EA1-11D1-00A0-C90629100000" + +#define S76_HAS_HWMON (defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))) + +/* method IDs for S76_GET */ +#define GET_EVENT 0x01 /* 1 */ + +#define DRIVER_AP_KEY (1 << 0) +#define DRIVER_AP_LED (1 << 1) +#define DRIVER_HWMON (1 << 2) +#define DRIVER_KB_LED_WMI (1 << 3) +#define DRIVER_OLED (1 << 4) +#define DRIVER_AP_WMI (1 << 5) +#define DRIVER_KB_LED (1 << 6) + +#define DRIVER_INPUT (DRIVER_AP_KEY | DRIVER_OLED) + +static uint64_t driver_flags; + +struct platform_device *s76_platform_device; + +static int s76_wmbb(u32 method_id, u32 arg, u32 *retval) +{ + struct acpi_buffer in = { (acpi_size) sizeof(arg), &arg }; + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + u32 tmp; + + pr_debug("%0#4x IN : %0#6x\n", method_id, arg); + + status = wmi_evaluate_method(S76_WMBB_GUID, 0, method_id, &in, &out); + + if (unlikely(ACPI_FAILURE(status))) { + return -EIO; + } + + obj = (union acpi_object *) out.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) { + tmp = (u32) obj->integer.value; + } else { + tmp = 0; + } + + pr_debug("%0#4x OUT: %0#6x (IN: %0#6x)\n", method_id, tmp, arg); + + if (likely(retval)) { + *retval = tmp; + } + + kfree(obj); + + return 0; +} + +#include "ap-led.c" +#include "input.c" +#include "kb-led.c" +#include "hwmon.c" +#include "nv_hda.c" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +static void s76_wmi_notify(union acpi_object *obj, void *context) +#else +static void s76_wmi_notify(u32 value, void *context) +#endif +{ + u32 event; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + if (obj->type != ACPI_TYPE_INTEGER) { + pr_debug("Unexpected WMI event (%0#6x)\n", obj->type); + return; + } +#else + if (value != 0xD0) { + pr_debug("Unexpected WMI event (%0#6x)\n", value); + return; + } +#endif + + s76_wmbb(GET_EVENT, 0, &event); + + pr_debug("WMI event code (%x)\n", event); + + switch (event) { + case 0x81: + if (driver_flags & (DRIVER_KB_LED_WMI | DRIVER_KB_LED)) { + kb_wmi_dec(); + } + break; + case 0x82: + if (driver_flags & (DRIVER_KB_LED_WMI | DRIVER_KB_LED)) { + kb_wmi_inc(); + } + break; + case 0x83: + if (driver_flags & (DRIVER_KB_LED_WMI | DRIVER_KB_LED)) { + kb_wmi_color(); + } + break; + case 0x7b: + //TODO: Fn+Backspace + break; + case 0x95: + //TODO: Fn+ESC + break; + case 0x9F: + if (driver_flags & (DRIVER_KB_LED_WMI | DRIVER_KB_LED)) { + kb_wmi_toggle(); + } + break; + case 0xD7: + if (driver_flags & DRIVER_OLED) { + s76_input_screen_wmi(); + } + break; + case 0x85: + case 0xF4: + if (driver_flags & DRIVER_AP_KEY) { + s76_input_airplane_wmi(); + } + break; + case 0xFC: + // Touchpad WMI (disable) + break; + case 0xFD: + // Touchpad WMI (enable) + break; + default: + pr_debug("Unknown WMI event code (%x)\n", event); + break; + } +} + +static int __init s76_probe(struct platform_device *dev) +{ + int err; + + if (driver_flags & DRIVER_AP_LED) { + err = ap_led_init(&dev->dev); + if (unlikely(err)) { + pr_err("Could not register LED device\n"); + } + } + + if (driver_flags & (DRIVER_KB_LED_WMI | DRIVER_KB_LED)) { + err = kb_led_init(&dev->dev); + if (unlikely(err)) { + pr_err("Could not register LED device\n"); + } + } + + if (driver_flags & DRIVER_INPUT) { + err = s76_input_init(&dev->dev); + if (unlikely(err)) { + pr_err("Could not register input device\n"); + } + } + +#ifdef S76_HAS_HWMON + if (driver_flags & DRIVER_HWMON) { + s76_hwmon_init(&dev->dev); + } +#endif + + err = nv_hda_init(&dev->dev); + if (unlikely(err)) { + pr_err("Could not register NVIDIA audio device\n"); + } + + err = wmi_install_notify_handler(S76_EVENT_GUID, s76_wmi_notify, NULL); + if (unlikely(ACPI_FAILURE(err))) { + pr_err("Could not register WMI notify handler (%0#6x)\n", err); + return -EIO; + } + + // Enable hotkey support + s76_wmbb(0x46, 0, NULL); + + // Enable touchpad lock + i8042_lock_chip(); + i8042_command(NULL, 0x97); + i8042_unlock_chip(); + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0) +static void s76_remove(struct platform_device *dev) +#else +static int s76_remove(struct platform_device *dev) +#endif +{ + wmi_remove_notify_handler(S76_EVENT_GUID); + + nv_hda_exit(); + #ifdef S76_HAS_HWMON + if (driver_flags & DRIVER_HWMON) { + s76_hwmon_fini(&dev->dev); + } + #endif + if (driver_flags & (DRIVER_KB_LED_WMI | DRIVER_KB_LED)) { + kb_led_exit(); + } + if (driver_flags & DRIVER_AP_LED) { + ap_led_exit(); + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) + return 0; +#endif +} + +static int s76_suspend(struct device *dev) +{ + pr_debug("%s\n", __func__); + + if (driver_flags & (DRIVER_KB_LED_WMI | DRIVER_KB_LED)) { + kb_led_suspend(); + } + + return 0; +} + +static int s76_resume(struct device *dev) +{ + pr_debug("%s\n", __func__); + + msleep(2000); + + if (driver_flags & DRIVER_AP_LED) { + ap_led_resume(); + } + if (driver_flags & (DRIVER_KB_LED_WMI | DRIVER_KB_LED)) { + kb_led_resume(); + } + + // Enable hotkey support + s76_wmbb(0x46, 0, NULL); + + // Enable touchpad lock + i8042_lock_chip(); + i8042_command(NULL, 0x97); + i8042_unlock_chip(); + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) +static DEFINE_SIMPLE_DEV_PM_OPS(s76_pm, s76_suspend, s76_resume); +#else +static SIMPLE_DEV_PM_OPS(s76_pm, s76_suspend, s76_resume); +#endif + +static struct platform_driver s76_platform_driver = { + .remove = s76_remove, + .driver = { + .name = S76_DRIVER_NAME, + .owner = THIS_MODULE, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) + .pm = pm_sleep_ptr(&s76_pm), +#else + .pm = pm_ptr(&s76_pm), +#endif + }, +}; + +static int __init s76_dmi_matched(const struct dmi_system_id *id) +{ + pr_info("Model %s found\n", id->ident); + driver_flags = (uint64_t)id->driver_data; + return 1; +} + +#define DMI_TABLE_LEGACY(PRODUCT, DATA) { \ + .ident = "System76 " PRODUCT, \ + .matches = { \ + DMI_MATCH(DMI_SYS_VENDOR, "System76"), \ + DMI_MATCH(DMI_PRODUCT_VERSION, PRODUCT), \ + DMI_MATCH(DMI_BIOS_VENDOR, "System76"), \ + }, \ + .callback = s76_dmi_matched, \ + .driver_data = (void *)(uint64_t)0, \ +} + +#define DMI_TABLE(PRODUCT, DATA) { \ + .ident = "System76 " PRODUCT, \ + .matches = { \ + DMI_MATCH(DMI_SYS_VENDOR, "System76"), \ + DMI_MATCH(DMI_PRODUCT_VERSION, PRODUCT), \ + }, \ + .callback = s76_dmi_matched, \ + .driver_data = (void *)(uint64_t)(DATA), \ +} + +static struct dmi_system_id s76_dmi_table[] __initdata = { + DMI_TABLE_LEGACY("bonw13", DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("addw1", DRIVER_AP_LED | DRIVER_KB_LED_WMI | DRIVER_OLED), + DMI_TABLE("addw2", DRIVER_AP_LED | DRIVER_KB_LED_WMI | DRIVER_OLED), + DMI_TABLE("addw5", DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("bonw15-b", DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("bonw16", DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("darp5", DRIVER_AP_LED | DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("darp6", DRIVER_AP_LED | DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("galp2", DRIVER_HWMON), + DMI_TABLE("galp3", DRIVER_HWMON), + DMI_TABLE("galp3-b", DRIVER_AP_KEY | DRIVER_AP_LED | DRIVER_HWMON), + DMI_TABLE("galp3-c", DRIVER_AP_LED | DRIVER_HWMON), + DMI_TABLE("galp4", DRIVER_AP_LED | DRIVER_HWMON), + DMI_TABLE("gaze13", DRIVER_AP_KEY | DRIVER_AP_LED | DRIVER_HWMON), + DMI_TABLE("gaze14", DRIVER_AP_LED | DRIVER_KB_LED_WMI), + DMI_TABLE("gaze15", DRIVER_AP_LED | DRIVER_KB_LED_WMI), + DMI_TABLE("kudu5", DRIVER_AP_KEY | DRIVER_AP_LED | DRIVER_HWMON), + DMI_TABLE("kudu6", DRIVER_AP_KEY | DRIVER_AP_WMI | DRIVER_KB_LED_WMI), + DMI_TABLE("oryp3-jeremy", DRIVER_AP_KEY | DRIVER_AP_LED | DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("oryp4", DRIVER_AP_KEY | DRIVER_AP_LED | DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("oryp4-b", DRIVER_AP_KEY | DRIVER_AP_LED | DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("oryp5", DRIVER_AP_LED | DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("oryp6", DRIVER_AP_LED | DRIVER_KB_LED_WMI), + DMI_TABLE("oryp13", DRIVER_AP_KEY | DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("pang10", DRIVER_AP_KEY | DRIVER_AP_WMI | DRIVER_KB_LED_WMI), + DMI_TABLE("pang11", DRIVER_AP_KEY | DRIVER_AP_WMI | DRIVER_KB_LED_WMI), + DMI_TABLE("serw11", DRIVER_AP_KEY | DRIVER_AP_LED | DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("serw11-b", DRIVER_AP_KEY | DRIVER_AP_LED | DRIVER_HWMON | DRIVER_KB_LED_WMI), + DMI_TABLE("serw12", DRIVER_AP_KEY | DRIVER_AP_LED | DRIVER_AP_WMI | DRIVER_KB_LED_WMI), + DMI_TABLE("serw14", DRIVER_HWMON | DRIVER_KB_LED), + {} +}; +MODULE_DEVICE_TABLE(dmi, s76_dmi_table); + +static int __init s76_init(void) +{ + if (!dmi_check_system(s76_dmi_table)) { + pr_info("Model does not utilize this driver"); + return -ENODEV; + } + + if (!driver_flags) { + pr_info("Driver data not defined"); + return -ENODEV; + } + + if (!wmi_has_guid(S76_EVENT_GUID)) { + pr_info("No known WMI event notification GUID found\n"); + return -ENODEV; + } + + if (!wmi_has_guid(S76_WMBB_GUID)) { + pr_info("No known WMI control method GUID found\n"); + return -ENODEV; + } + + s76_platform_device = + platform_create_bundle(&s76_platform_driver, s76_probe, NULL, 0, NULL, 0); + + if (IS_ERR(s76_platform_device)) { + return PTR_ERR(s76_platform_device); + } + + return 0; +} +module_init(s76_init); + +static void __exit s76_exit(void) +{ + platform_device_unregister(s76_platform_device); + platform_driver_unregister(&s76_platform_driver); +} +module_exit(s76_exit); + +MODULE_AUTHOR("Jeremy Soller "); +MODULE_DESCRIPTION("System76 laptop driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0.0"); # ---------------------------------------- # Module: system76_io # Version: fc71f154ab8d # ---------------------------------------- diff --git a/drivers/custom/system76_io/Makefile b/drivers/custom/system76_io/Makefile new file mode 100644 index 000000000000..65445c80c86f --- /dev/null +++ b/drivers/custom/system76_io/Makefile @@ -0,0 +1,8 @@ +obj-m := system76-io.o system76-thelio-io.o +KERNEL_DIR = /lib/modules/$(shell uname -r)/build + +all: + $(MAKE) -C "$(KERNEL_DIR)" M="$(PWD)" modules + +clean: + $(MAKE) -C "$(KERNEL_DIR)" M="$(PWD)" clean diff --git a/drivers/custom/system76_io/system76-io.c b/drivers/custom/system76_io/system76-io.c new file mode 100644 index 000000000000..40b655f8ce03 --- /dev/null +++ b/drivers/custom/system76_io/system76-io.c @@ -0,0 +1,304 @@ +/* + * system76-io.c + * + * Copyright (C) 2018 Jeremy Soller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#define IO_VENDOR 0x1209 +#define IO_DEVICE 0x1776 +#define IO_INTF_CTRL 0 +#define IO_EP_CTRL 0x00 +#define IO_INTF_DATA 1 +#define IO_EP_IN 0x83 +#define IO_EP_OUT 0x04 +#define IO_MSG_SIZE 32 +#define IO_TIMEOUT 1000 + +#include "system76-io_dev.c" +#include "system76-io_hwmon.c" + +#define BAUD 1000000 + +static u8 line_encoding[7] = { + (u8)BAUD, + (u8)(BAUD >> 8), + (u8)(BAUD >> 16), + (u8)(BAUD >> 24), + 0, + 0, + 8 +}; + +static ssize_t show_bootloader(struct device *dev, struct device_attribute *attr, char *buf) { + return sprintf(buf, "%d\n", 0); +} + +static ssize_t set_bootloader(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + unsigned int val; + int ret; + + struct io_dev * io_dev = dev_get_drvdata(dev); + + mutex_lock(&io_dev->lock); + + ret = kstrtouint(buf, 10, &val); + if (!ret) { + if (val) { + ret = io_dev_bootloader(io_dev, IO_TIMEOUT); + if(!ret) { + ret = size; + } + } + } + + mutex_unlock(&io_dev->lock); + + return ret; +} + +static DEVICE_ATTR(bootloader, S_IRUGO | S_IWUSR, show_bootloader, set_bootloader); + +static ssize_t show_revision(struct device *dev, struct device_attribute *attr, char *buf) { + int ret; + + struct io_dev * io_dev = dev_get_drvdata(dev); + + mutex_lock(&io_dev->lock); + + ret = io_dev_revision(io_dev, buf, PAGE_SIZE, IO_TIMEOUT); + + mutex_unlock(&io_dev->lock); + + return ret; +} + +static DEVICE_ATTR(revision, S_IRUGO, show_revision, NULL); + +#ifdef CONFIG_PM_SLEEP +static int io_pm(struct notifier_block *nb, unsigned long action, void *data) { + struct io_dev * io_dev = container_of(nb, struct io_dev, pm_notifier); + + mutex_lock(&io_dev->lock); + + switch (action) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + io_dev_set_suspend(io_dev, 1, IO_TIMEOUT); + break; + + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + io_dev_set_suspend(io_dev, 0, IO_TIMEOUT); + break; + + case PM_POST_RESTORE: + case PM_RESTORE_PREPARE: + default: + break; + } + + mutex_unlock(&io_dev->lock); + + return NOTIFY_DONE; +} +#endif + +static int io_probe(struct usb_interface *interface, const struct usb_device_id *id) { + int retry; + int result; + struct io_dev * io_dev; + + dev_info(&interface->dev, "id %04X:%04X interface %d probe\n", id->idVendor, id->idProduct, id->bInterfaceNumber); + + if (id->bInterfaceNumber == IO_INTF_CTRL) { + result = usb_control_msg( + interface_to_usbdev(interface), + usb_sndctrlpipe(interface_to_usbdev(interface), IO_EP_CTRL), + 0x22, + 0x21, + 0x03, + 0, + NULL, + 0, + IO_TIMEOUT + ); + if (result < 0) { + dev_err(&interface->dev, "set line state failed: %d\n", -result); + return result; + } + + + result = usb_control_msg( + interface_to_usbdev(interface), + usb_sndctrlpipe(interface_to_usbdev(interface), IO_EP_CTRL), + 0x20, + 0x21, + 0, + 0, + line_encoding, + 7, + IO_TIMEOUT + ); + if (result < 0) { + dev_err(&interface->dev, "set line encoding failed: %d\n", -result); + return result; + } + + return 0; + } else if (id->bInterfaceNumber == IO_INTF_DATA) { + io_dev = kmalloc(sizeof(struct io_dev), GFP_KERNEL); + if (IS_ERR_OR_NULL(io_dev)) { + dev_err(&interface->dev, "kmalloc failed\n"); + return -ENOMEM; + } + + memset(io_dev, 0, sizeof(struct io_dev)); + + mutex_init(&io_dev->lock); + + mutex_lock(&io_dev->lock); + + io_dev->usb_dev = usb_get_dev(interface_to_usbdev(interface)); + + usb_set_intfdata(interface, io_dev); + + for(retry = 0; retry < 8; retry++) { + dev_info(&interface->dev, "trying reset: %d\n", retry); + result = io_dev_reset(io_dev, IO_TIMEOUT); + if (result != -ETIMEDOUT) { + break; + } + } + if (result) { + dev_err(&interface->dev, "io_dev_reset failed: %d\n", result); + goto fail1; + } + + result = device_create_file(&interface->dev, &dev_attr_bootloader); + if (result) { + dev_err(&interface->dev, "device_create_file failed: %d\n", result); + goto fail1; + } + + result = device_create_file(&interface->dev, &dev_attr_revision); + if (result) { + dev_err(&interface->dev, "device_create_file failed: %d\n", result); + goto fail2; + } + + io_dev->hwmon_dev = hwmon_device_register_with_groups(&interface->dev, "system76_io", io_dev, io_groups); + if (IS_ERR(io_dev->hwmon_dev)) { + result = PTR_ERR(io_dev->hwmon_dev); + + dev_err(&interface->dev, "hwmon_device_register_with_groups failed: %d\n", result); + goto fail3; + } + +#ifdef CONFIG_PM_SLEEP + io_dev->pm_notifier.notifier_call = io_pm; + register_pm_notifier(&io_dev->pm_notifier); +#endif + + mutex_unlock(&io_dev->lock); + + return 0; + + fail3: + device_remove_file(&interface->dev, &dev_attr_revision); + fail2: + device_remove_file(&interface->dev, &dev_attr_bootloader); + fail1: + usb_set_intfdata(interface, NULL); + usb_put_dev(io_dev->usb_dev); + + mutex_unlock(&io_dev->lock); + + mutex_destroy(&io_dev->lock); + kfree(io_dev); + + return result; + } else { + return -ENODEV; + } +} + +static void io_disconnect(struct usb_interface *interface) { + struct io_dev * io_dev; + + dev_info(&interface->dev, "disconnect\n"); + + io_dev = usb_get_intfdata(interface); + + if (io_dev) { + mutex_lock(&io_dev->lock); + +#ifdef CONFIG_PM_SLEEP + unregister_pm_notifier(&io_dev->pm_notifier); +#endif + + hwmon_device_unregister(io_dev->hwmon_dev); + + device_remove_file(&interface->dev, &dev_attr_revision); + + device_remove_file(&interface->dev, &dev_attr_bootloader); + + usb_set_intfdata(interface, NULL); + usb_put_dev(io_dev->usb_dev); + + mutex_unlock(&io_dev->lock); + + mutex_destroy(&io_dev->lock); + kfree(io_dev); + } +} + +static struct usb_device_id io_table[] = { + { USB_DEVICE_INTERFACE_NUMBER(IO_VENDOR, IO_DEVICE, IO_INTF_CTRL) }, + { USB_DEVICE_INTERFACE_NUMBER(IO_VENDOR, IO_DEVICE, IO_INTF_DATA) }, + { } +}; + +MODULE_DEVICE_TABLE(usb, io_table); + +static struct usb_driver io_driver = { + .name = "system76-io", + .probe = io_probe, + .disconnect = io_disconnect, + .id_table = io_table, +}; + +static int __init io_init(void) { + return usb_register(&io_driver); +} + +static void __exit io_exit(void) { + usb_deregister(&io_driver); +} + +module_init(io_init); +module_exit(io_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jeremy Soller "); +MODULE_DESCRIPTION("System76 Io driver"); diff --git a/drivers/custom/system76_io/system76-io_dev.c b/drivers/custom/system76_io/system76-io_dev.c new file mode 100644 index 000000000000..11c4df17c0b6 --- /dev/null +++ b/drivers/custom/system76_io/system76-io_dev.c @@ -0,0 +1,310 @@ +/* + * system76-io_dev.c + * + * Copyright (C) 2018 Jeremy Soller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +struct io_dev { + struct mutex lock; + struct usb_device * usb_dev; + struct device * hwmon_dev; +#ifdef CONFIG_PM_SLEEP + struct notifier_block pm_notifier; +#endif + char command[IO_MSG_SIZE]; + char partial[IO_MSG_SIZE]; + char lines[2][IO_MSG_SIZE]; + char response[IO_MSG_SIZE]; +}; + +static ssize_t io_dev_read(struct io_dev * io_dev, char * buf, size_t len, int timeout) { + int result; + int count; + + result = usb_bulk_msg( + io_dev->usb_dev, + usb_rcvbulkpipe(io_dev->usb_dev, IO_EP_IN), + (void *)buf, + len, + &count, + timeout + ); + if (result) { + return result; + } + + return count; +} + +static ssize_t io_dev_write(struct io_dev * io_dev, const char * buf, size_t len, int timeout) { + int result; + int count; + + result = usb_bulk_msg( + io_dev->usb_dev, + usb_sndbulkpipe(io_dev->usb_dev, IO_EP_OUT), + (void *)buf, + len, + &count, + timeout + ); + if (result) { + return result; + } + + return count; +} + +static int io_dev_command(struct io_dev * io_dev, const char * command, size_t clen, char * response, size_t rlen, int timeout) { + int result; + bool cr; + bool lf; + int i; + char c; + int lines_i; + int line_i; + bool error; + + memset(response, 0, rlen); + + result = io_dev_write(io_dev, command, clen, timeout); + if (result < 0) { + snprintf(response, rlen, "io_dev_write"); + return result; + } + + cr = 0; + lf = 0; + lines_i = 0; + line_i = 0; + for (;;) { + result = io_dev_read(io_dev, io_dev->partial, IO_MSG_SIZE, timeout); + if (result < 0) { + snprintf(response, rlen, "io_dev_read"); + return result; + } + + for (i = 0; i < result; i++) { + c = io_dev->partial[i]; + if (c == '\r') { + if (!cr) { + cr = 1; + } else { + // Unexpected \r, return error + snprintf(response, rlen, "Unexpected CR"); + return -EINVAL; + } + } else if (c == '\n') { + if (cr) { + cr = 0; + if (lf) { + // Received a response in full + if (lines_i < 2 && line_i < IO_MSG_SIZE) { + io_dev->lines[lines_i++][line_i] = 0; + line_i = 0; + } else { + snprintf(response, rlen, "Too many lines"); + return -EINVAL; + } + } + lf = !lf; + } else { + // Unexpected \n, return error + snprintf(response, rlen, "Unexpected LF"); + return -EINVAL; + } + } else if (!cr && lf) { + // Received a response byte + if (lines_i < 2 && line_i < IO_MSG_SIZE) { + io_dev->lines[lines_i][line_i++] = c; + } else { + // Response too long + snprintf(response, rlen, "Too many chars"); + return -EINVAL; + } + } else { + // Unexpected data, return error + snprintf(response, rlen, "Unexpected char"); + return -EINVAL; + } + } + + if (lines_i > 0) { + if (strcmp(io_dev->lines[lines_i - 1], "OK") == 0) { + error = 0; + break; + } else if (strcmp(io_dev->lines[lines_i - 1], "ERROR") == 0) { + error = 1; + } + } + } + + if (lines_i > 1) { + snprintf(response, rlen, "%s", io_dev->lines[lines_i - 2]); + } + + if (error) { + return -EIO; + } else { + return 0; + } +} + +static int io_dev_bootloader(struct io_dev * io_dev, int timeout) { + int len; + int result; + + len = snprintf(io_dev->command, IO_MSG_SIZE, "IoBOOT\r"); + if (len >= IO_MSG_SIZE) { + return -EINVAL; + } + + result = io_dev_command(io_dev, io_dev->command, len, io_dev->response, IO_MSG_SIZE, timeout); + if (result) { + dev_err(&io_dev->usb_dev->dev, "io_dev_boot failed: %d: %s\n", -result, io_dev->response); + return result; + } + + return 0; +} + +static int io_dev_reset(struct io_dev * io_dev, int timeout) { + int len; + int result; + + len = snprintf(io_dev->command, IO_MSG_SIZE, "IoRSET\r"); + if (len >= IO_MSG_SIZE) { + return -EINVAL; + } + + result = io_dev_command(io_dev, io_dev->command, len, io_dev->response, IO_MSG_SIZE, timeout); + if (result) { + dev_err(&io_dev->usb_dev->dev, "io_dev_reset failed: %d: %s\n", -result, io_dev->response); + return result; + } + + return 0; +} + +static int io_dev_tach(struct io_dev * io_dev, const char * device, u16 * value, int timeout) { + int len; + int result; + + if (strlen(device) != 4) { + return -EINVAL; + } + + len = snprintf(io_dev->command, IO_MSG_SIZE, "IoTACH%s\r", device); + if (len >= IO_MSG_SIZE) { + return -EINVAL; + } + + result = io_dev_command(io_dev, io_dev->command, len, io_dev->response, IO_MSG_SIZE, timeout); + if (result) { + dev_err(&io_dev->usb_dev->dev, "io_dev_tach failed: %d: %s\n", -result, io_dev->response); + return result; + } + + return kstrtou16(io_dev->response, 16, value); +} + +static int io_dev_duty(struct io_dev * io_dev, const char * device, u16 * value, int timeout) { + int len; + int result; + + if (strlen(device) != 4) { + return -EINVAL; + } + + len = snprintf(io_dev->command, IO_MSG_SIZE, "IoDUTY%s\r", device); + if (len >= IO_MSG_SIZE) { + return -EINVAL; + } + + result = io_dev_command(io_dev, io_dev->command, len, io_dev->response, IO_MSG_SIZE, timeout); + if (result) { + dev_err(&io_dev->usb_dev->dev, "io_dev_duty failed: %d: %s\n", -result, io_dev->response); + return result; + } + + return kstrtou16(io_dev->response, 16, value); +} + +static int io_dev_set_duty(struct io_dev * io_dev, const char * device, u16 value, int timeout) { + int len; + int result; + + if (strlen(device) != 4) { + return -EINVAL; + } + + if (value > 10000) { + return -EINVAL; + } + + len = snprintf(io_dev->command, IO_MSG_SIZE, "IoDUTY%s%04X\r", device, value); + if (len >= IO_MSG_SIZE) { + return -EINVAL; + } + + result = io_dev_command(io_dev, io_dev->command, len, io_dev->response, IO_MSG_SIZE, timeout); + if (result) { + dev_err(&io_dev->usb_dev->dev, "io_dev_set_duty failed: %d: %s\n", -result, io_dev->response); + return result; + } + + return 0; +} + +static int io_dev_set_suspend(struct io_dev * io_dev, u16 value, int timeout) { + int len; + int result; + + if (value > 1) { + return -EINVAL; + } + + len = snprintf(io_dev->command, IO_MSG_SIZE, "IoSUSP%04X\r", value); + if (len >= IO_MSG_SIZE) { + return -EINVAL; + } + + result = io_dev_command(io_dev, io_dev->command, len, io_dev->response, IO_MSG_SIZE, timeout); + if (result) { + dev_err(&io_dev->usb_dev->dev, "io_dev_set_suspend failed: %d: %s\n", -result, io_dev->response); + return result; + } + + return 0; +} + +static int io_dev_revision(struct io_dev * io_dev, char * value, int value_len, int timeout) { + int len; + int result; + + len = snprintf(io_dev->command, IO_MSG_SIZE, "IoREVISION\r"); + if (len >= IO_MSG_SIZE) { + return -EINVAL; + } + + result = io_dev_command(io_dev, io_dev->command, len, value, value_len, timeout); + if (result) { + dev_err(&io_dev->usb_dev->dev, "io_dev_revision failed: %d: %s\n", -result, value); + return result; + } + + return strlen(value); +} diff --git a/drivers/custom/system76_io/system76-io_hwmon.c b/drivers/custom/system76_io/system76-io_hwmon.c new file mode 100644 index 000000000000..25602d31b554 --- /dev/null +++ b/drivers/custom/system76_io/system76-io_hwmon.c @@ -0,0 +1,180 @@ +/* + * system76-io_hwmon.c + * + * Copyright (C) 2018 Jeremy Soller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define IO_FAN(N, I) + +#define IO_FANS \ + IO_FAN(CPUF, 1) \ + IO_FAN(INTF, 2) + +static const char * io_fan_name(int index) { + switch (index) { + #undef IO_FAN + #define IO_FAN(N, I) \ + case I: \ + return #N; + IO_FANS + default: + return NULL; + } +} + +static ssize_t io_fan_input_show(struct device *dev, struct device_attribute *attr, char *buf) { + const char *name; + u16 value; + int ret; + + struct io_dev * io_dev = dev_get_drvdata(dev); + + mutex_lock(&io_dev->lock); + + if ((name = io_fan_name(to_sensor_dev_attr(attr)->index))) { + ret = io_dev_tach(io_dev, name, &value, IO_TIMEOUT); + if (!ret) { + ret = sprintf(buf, "%i\n", value * 30); + } + } else { + ret = -ENOENT; + } + + mutex_unlock(&io_dev->lock); + + return ret; +} + +static ssize_t io_fan_label_show(struct device *dev, struct device_attribute *attr, char *buf) { + int ret; + + const char * name = io_fan_name(to_sensor_dev_attr(attr)->index); + if (name) { + ret = sprintf(buf, "%s\n", name); + } else { + ret = -ENOENT; + } + + return ret; +} + +static ssize_t io_pwm_show(struct device *dev, struct device_attribute *attr, char *buf) { + const char *name; + u16 value; + int ret; + + struct io_dev * io_dev = dev_get_drvdata(dev); + + mutex_lock(&io_dev->lock); + + if ((name = io_fan_name(to_sensor_dev_attr(attr)->index))) { + ret = io_dev_duty(io_dev, name, &value, IO_TIMEOUT); + if (!ret) { + ret = sprintf(buf, "%i\n", (((u32)value) * 255) / 10000); + } + } else { + ret = -ENOENT; + } + + mutex_unlock(&io_dev->lock); + + return ret; +} + +static ssize_t io_pwm_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + const char *name; + u32 value; + int ret; + + struct io_dev * io_dev = dev_get_drvdata(dev); + + mutex_lock(&io_dev->lock); + + if ((name = io_fan_name(to_sensor_dev_attr(attr)->index))) { + ret = kstrtou32(buf, 10, &value); + if (!ret) { + if (value <= 255) { + ret = io_dev_set_duty(io_dev, name, (u16)((value * 10000) / 255), IO_TIMEOUT); + if (!ret) { + ret = count; + } + } else { + ret = -EINVAL; + } + } + } else { + ret = -ENOENT; + } + + mutex_unlock(&io_dev->lock); + + return ret; +} + +static ssize_t io_pwm_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { + int ret; + + const char * name = io_fan_name(to_sensor_dev_attr(attr)->index); + if (name) { + ret = sprintf(buf, "%i\n", 1); + } else { + ret = -ENOENT; + } + + return ret; +} + +static ssize_t io_pwm_enable_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + u8 value; + int ret; + + const char * name = io_fan_name(to_sensor_dev_attr(attr)->index); + if (name) { + ret = kstrtou8(buf, 10, &value); + if (!ret) { + if (value == 1) { + ret = count; + } else { + ret = -EINVAL; + } + } + } else { + ret = -ENOENT; + } + + return ret; +} + +#undef IO_FAN +#define IO_FAN(N, I) \ + static SENSOR_DEVICE_ATTR(fan ## I ## _input, S_IRUGO, io_fan_input_show, NULL, I); \ + static SENSOR_DEVICE_ATTR(fan ## I ## _label, S_IRUGO, io_fan_label_show, NULL, I); \ + static SENSOR_DEVICE_ATTR(pwm ## I, S_IRUGO | S_IWUSR, io_pwm_show, io_pwm_set, I); \ + static SENSOR_DEVICE_ATTR(pwm ## I ## _enable, S_IRUGO | S_IWUSR, io_pwm_enable_show, io_pwm_enable_set, I); +IO_FANS + +static struct attribute *io_attrs[] = { + #undef IO_FAN + #define IO_FAN(N, I) \ + &sensor_dev_attr_fan ## I ## _input.dev_attr.attr, \ + &sensor_dev_attr_fan ## I ## _label.dev_attr.attr, \ + &sensor_dev_attr_pwm ## I.dev_attr.attr, \ + &sensor_dev_attr_pwm ## I ## _enable.dev_attr.attr, + IO_FANS + NULL +}; + +ATTRIBUTE_GROUPS(io); diff --git a/drivers/custom/system76_io/system76-thelio-io.c b/drivers/custom/system76_io/system76-thelio-io.c new file mode 100644 index 000000000000..4d9c2229cd3d --- /dev/null +++ b/drivers/custom/system76_io/system76-thelio-io.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * system76-thelio-io.c - Linux driver for System76 Thelio Io + * Copyright (C) 2023 System76 + * + * Based on: + * corsair-cpro.c - Linux driver for Corsair Commander Pro + * Copyright (C) 2020 Marius Zachmann + * + * This driver uses hid reports to communicate with the device to allow hidraw userspace drivers + * still being used. The device does not use report ids. When using hidraw and this driver + * simultaniously, reports could be switched. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 32 +#define REQ_TIMEOUT 300 + +#define HID_CMD 0 +#define HID_RES 1 +#define HID_DATA 2 + +#define CMD_FAN_GET 7 +#define CMD_FAN_SET 8 +#define CMD_LED_SET_MODE 16 +#define CMD_FAN_TACH 22 + +struct thelio_io_device { + struct hid_device *hdev; + struct device *hwmon_dev; +#ifdef CONFIG_PM_SLEEP + struct notifier_block pm_notifier; +#endif + struct completion wait_input_report; + struct mutex mutex; /* whenever buffer is used, lock before send_usb_cmd */ + u8 *buffer; +}; + +/* converts response error in buffer to errno */ +static int thelio_io_get_errno(struct thelio_io_device *thelio_io) +{ + switch (thelio_io->buffer[HID_RES]) { + case 0x00: /* success */ + return 0; + default: + return -EIO; + } +} + +/* send command, check for error in response, response in thelio_io->buffer */ +static int send_usb_cmd(struct thelio_io_device *thelio_io, u8 command, + u8 byte1, u8 byte2, u8 byte3) +{ + int ret; + + memset(thelio_io->buffer, 0x00, BUFFER_SIZE); + thelio_io->buffer[HID_CMD] = command; + thelio_io->buffer[HID_DATA] = byte1; + thelio_io->buffer[HID_DATA + 1] = byte2; + thelio_io->buffer[HID_DATA + 2] = byte3; + + reinit_completion(&thelio_io->wait_input_report); + + ret = hid_hw_output_report(thelio_io->hdev, thelio_io->buffer, BUFFER_SIZE); + if (ret < 0) + return ret; + + if (!wait_for_completion_timeout(&thelio_io->wait_input_report, + msecs_to_jiffies(REQ_TIMEOUT))) + return -ETIMEDOUT; + + return thelio_io_get_errno(thelio_io); +} + +static int thelio_io_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct thelio_io_device *thelio_io = hid_get_drvdata(hdev); + + /* only copy buffer when requested */ + if (completion_done(&thelio_io->wait_input_report)) + return 0; + + memcpy(thelio_io->buffer, data, min(BUFFER_SIZE, size)); + complete(&thelio_io->wait_input_report); + + return 0; +} + +/* requests and returns single data values depending on channel */ +static int get_data(struct thelio_io_device *thelio_io, int command, int channel, + bool two_byte_data) +{ + int ret; + + mutex_lock(&thelio_io->mutex); + + ret = send_usb_cmd(thelio_io, command, channel, 0, 0); + if (ret) + goto out_unlock; + + ret = thelio_io->buffer[HID_DATA + 1]; + if (two_byte_data) + ret |= thelio_io->buffer[HID_DATA + 2] << 8; + +out_unlock: + mutex_unlock(&thelio_io->mutex); + return ret; +} + +static int set_pwm(struct thelio_io_device *thelio_io, int channel, long val) +{ + int ret; + + if (val < 0 || val > 255) + return -EINVAL; + + mutex_lock(&thelio_io->mutex); + + ret = send_usb_cmd(thelio_io, CMD_FAN_SET, channel, val, 0); + + mutex_unlock(&thelio_io->mutex); + return ret; +} + +static int thelio_io_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_label: + switch (channel) { + case 0: + *str = "CPU Fan"; + return 0; + case 1: + *str = "Intake Fan"; + return 0; + case 2: + *str = "GPU Fan"; + return 0; + case 3: + *str = "Aux Fan"; + return 0; + default: + break; + } + default: + break; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +} + +static int thelio_io_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct thelio_io_device *thelio_io = dev_get_drvdata(dev); + int ret; + + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + ret = get_data(thelio_io, CMD_FAN_TACH, channel, true); + if (ret < 0) + return ret; + *val = ret; + return 0; + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + ret = get_data(thelio_io, CMD_FAN_GET, channel, false); + if (ret < 0) + return ret; + *val = ret; + return 0; + default: + break; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +}; + +static int thelio_io_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct thelio_io_device *thelio_io = dev_get_drvdata(dev); + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + return set_pwm(thelio_io, channel, val); + default: + break; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +}; + +static umode_t thelio_io_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + return 0444; + case hwmon_fan_label: + return 0444; + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + return 0644; + default: + break; + } + break; + default: + break; + } + + return 0; +}; + +static const struct hwmon_ops thelio_io_hwmon_ops = { + .is_visible = thelio_io_is_visible, + .read = thelio_io_read, + .read_string = thelio_io_read_string, + .write = thelio_io_write, +}; + +static const struct hwmon_channel_info *thelio_io_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL + ), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT + ), + NULL +}; + +static const struct hwmon_chip_info thelio_io_chip_info = { + .ops = &thelio_io_hwmon_ops, + .info = thelio_io_info, +}; + +#ifdef CONFIG_PM_SLEEP +static int thelio_io_pm(struct notifier_block *nb, unsigned long action, void *data) +{ + struct thelio_io_device *thelio_io = container_of(nb, struct thelio_io_device, pm_notifier); + + switch (action) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + mutex_lock(&thelio_io->mutex); + send_usb_cmd(thelio_io, CMD_LED_SET_MODE, 0, 1, 0); + mutex_unlock(&thelio_io->mutex); + break; + + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + mutex_lock(&thelio_io->mutex); + send_usb_cmd(thelio_io, CMD_LED_SET_MODE, 0, 0, 0); + mutex_unlock(&thelio_io->mutex); + break; + + case PM_POST_RESTORE: + case PM_RESTORE_PREPARE: + default: + break; + } + + return NOTIFY_DONE; +} +#endif + +static int thelio_io_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct thelio_io_device *thelio_io; + int ret; + + thelio_io = devm_kzalloc(&hdev->dev, sizeof(*thelio_io), GFP_KERNEL); + if (!thelio_io) + return -ENOMEM; + + thelio_io->buffer = devm_kmalloc(&hdev->dev, BUFFER_SIZE, GFP_KERNEL); + if (!thelio_io->buffer) + return -ENOMEM; + + ret = hid_parse(hdev); + if (ret) + return ret; + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) + return ret; + + ret = hid_hw_open(hdev); + if (ret) + goto out_hw_stop; + + thelio_io->hdev = hdev; + hid_set_drvdata(hdev, thelio_io); + mutex_init(&thelio_io->mutex); + init_completion(&thelio_io->wait_input_report); + + hid_device_io_start(hdev); + + if (hdev->maxcollection == 1 && hdev->collection[0].usage == 0xFF600061) { + thelio_io->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, + "system76_thelio_io", + thelio_io, + &thelio_io_chip_info, + 0); + if (IS_ERR(thelio_io->hwmon_dev)) { + ret = PTR_ERR(thelio_io->hwmon_dev); + goto out_hw_close; + } + + #ifdef CONFIG_PM_SLEEP + thelio_io->pm_notifier.notifier_call = thelio_io_pm; + register_pm_notifier(&thelio_io->pm_notifier); + #endif + } + + return 0; + +out_hw_close: + hid_hw_close(hdev); +out_hw_stop: + hid_hw_stop(hdev); + return ret; +} + +static void thelio_io_remove(struct hid_device *hdev) +{ + struct thelio_io_device *thelio_io = hid_get_drvdata(hdev); + + if (thelio_io->hwmon_dev) { + hwmon_device_unregister(thelio_io->hwmon_dev); + + #ifdef CONFIG_PM_SLEEP + unregister_pm_notifier(&thelio_io->pm_notifier); + #endif + } + + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static const struct hid_device_id thelio_io_devices[] = { + { HID_USB_DEVICE(0x3384, 0x000B) }, /* thelio_io_2 */ + { } +}; + +static struct hid_driver thelio_io_driver = { + .name = "system76-thelio-io", + .id_table = thelio_io_devices, + .probe = thelio_io_probe, + .remove = thelio_io_remove, + .raw_event = thelio_io_raw_event, +}; + +MODULE_DEVICE_TABLE(hid, thelio_io_devices); +MODULE_LICENSE("GPL"); + +static int __init thelio_io_init(void) +{ + return hid_register_driver(&thelio_io_driver); +} + +static void __exit thelio_io_exit(void) +{ + hid_unregister_driver(&thelio_io_driver); +} + +/* + * When compiling this driver as built-in, hwmon initcalls will get called before the + * hid driver and this driver would fail to register. late_initcall solves this. + */ +late_initcall(thelio_io_init); +module_exit(thelio_io_exit); # ---------------------------------------- # Module: t150 # Version: 1.0 # ---------------------------------------- diff --git a/drivers/custom/t150/files/etc/udev/rules.d/10-t150.rules b/drivers/custom/t150/files/etc/udev/rules.d/10-t150.rules new file mode 100644 index 000000000000..049a3914d9fc --- /dev/null +++ b/drivers/custom/t150/files/etc/udev/rules.d/10-t150.rules @@ -0,0 +1,6 @@ +# make the t150 appear like a joystick +#SUBSYSTEM=="input", ATTRS{name}=="Thrustmaster T150 steering wheel", MODE="0666", ENV{ID_INPUT_JOYSTICK}="1" + +# Set up +#SUBSYSTEM=="input", ATTRS{name}=="Thrustmaster T150 steering wheel", MODE="0666", ENV{ID_MODEL_ID}="b677" +#SUBSYSTEM=="input", ATTRS{name}=="Thrustmaster T150 steering wheel", MODE="0666", ENV{ID_VENDOR_ID}="044f" diff --git a/drivers/custom/t150/hid-t150/Makefile b/drivers/custom/t150/hid-t150/Makefile new file mode 100644 index 000000000000..7834b98bcaef --- /dev/null +++ b/drivers/custom/t150/hid-t150/Makefile @@ -0,0 +1,8 @@ +obj-m += hid-t150.o +KDIR ?= /lib/modules/$(shell uname -r)/build + +all: + $(MAKE) -C $(KDIR) M=$(shell pwd) modules + +clean: + $(MAKE) -C $(KDIR) M=$(shell pwd) clean diff --git a/drivers/custom/t150/hid-t150/attributes.c b/drivers/custom/t150/hid-t150/attributes.c new file mode 100644 index 000000000000..b34c035e6698 --- /dev/null +++ b/drivers/custom/t150/hid-t150/attributes.c @@ -0,0 +1,188 @@ +static inline int t150_init_attributes(struct t150 *t150) +{ + int errno; + + // Before making avaible the syfs attrbiutes we try to set them to some default + // @FIXME I do not know if it is desiradable to wait for URBs in probe() method... + t150_setup_task(t150); + + errno = device_create_file(&t150->hid_device->dev, &dev_attr_autocenter); + if(errno) + return errno; + + errno = device_create_file(&t150->hid_device->dev, &dev_attr_enable_autocenter); + if(errno) + goto err1; + + errno = device_create_file(&t150->hid_device->dev, &dev_attr_range); + if(errno) + goto err2; + + errno = device_create_file(&t150->hid_device->dev, &dev_attr_gain); + if(errno) + goto err3; + + errno = device_create_file(&t150->hid_device->dev, &dev_attr_firmware_version); + if(errno) + goto err4; + + return 0; + +err4: device_remove_file(&t150->hid_device->dev, &dev_attr_firmware_version); +err3: device_remove_file(&t150->hid_device->dev, &dev_attr_range); +err2: device_remove_file(&t150->hid_device->dev, &dev_attr_enable_autocenter); +err1: device_remove_file(&t150->hid_device->dev, &dev_attr_autocenter); + return errno; +} + +static inline void t150_free_attributes(struct t150 *t150) +{ + device_remove_file(&t150->hid_device->dev, &dev_attr_autocenter); + device_remove_file(&t150->hid_device->dev, &dev_attr_enable_autocenter); + device_remove_file(&t150->hid_device->dev, &dev_attr_range); + device_remove_file(&t150->hid_device->dev, &dev_attr_gain); + device_remove_file(&t150->hid_device->dev, &dev_attr_firmware_version); +} + +/**/ +static ssize_t t150_store_return_force(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + uint16_t nforce; + struct t150 *t150 = dev_get_drvdata(dev); + + // If mallformed input leave... + if(kstrtou16(buf, 10, &nforce)) + return count; + + t150_set_autocenter(t150, nforce); + + return count; +} + +static ssize_t t150_show_return_force(struct device *dev, struct device_attribute *attr,char * buf ) +{ + int len; + struct t150 *t150 = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&t150->settings.access_lock, flags); + len = sprintf(buf, "%d\n", t150->settings.autocenter_force); + spin_unlock_irqrestore(&t150->settings.access_lock, flags); + + return len; +} + +static ssize_t t150_store_simulate_return_force(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + bool use; + struct t150 *t150 = dev_get_drvdata(dev); + + // If mallformed input leave... + if(!kstrtobool(buf, &use)) + t150_set_enable_autocenter(t150, use); + + return count; +} + +static ssize_t t150_show_simulate_return_force(struct device *dev, struct device_attribute *attr,char * buf) +{ + int len; + struct t150 *t150 = dev_get_drvdata(dev); + unsigned long flags; + spin_lock_irqsave(&t150->settings.access_lock, flags); + len = sprintf(buf, "%c\n", t150->settings.autocenter_enabled ? 'y' : 'n'); + spin_unlock_irqrestore(&t150->settings.access_lock, flags); + + return len; +} + +static ssize_t t150_store_range(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + uint16_t range; + int dev_max_range; + + struct t150 *t150 = dev_get_drvdata(dev); + + // If mallformed input leave... + if(kstrtou16(buf, 10, &range)) + return count; + + if(t150->hid_device->product == USB_T150_PRODUCT_ID) + dev_max_range = 1080; + else if (t150->hid_device->product == USB_TMX_PRODUCT_ID) + dev_max_range = 900; + + if(range < 270) + range = 270; + else if (range > dev_max_range) + range = dev_max_range; + + range = DIV_ROUND_CLOSEST((range * 0xffff), dev_max_range); + + t150_set_range(t150, range); + + return count; +} + +static ssize_t t150_show_range(struct device *dev, struct device_attribute *attr,char * buf ) +{ + int len; + struct t150 *t150 = dev_get_drvdata(dev); + unsigned long flags; + + int dev_max_range; + + if(t150->hid_device->product == USB_T150_PRODUCT_ID) + dev_max_range = 1080; + else if (t150->hid_device->product == USB_TMX_PRODUCT_ID) + dev_max_range = 900; + + spin_lock_irqsave(&t150->settings.access_lock, flags); + len = sprintf(buf, "%d\n", DIV_ROUND_CLOSEST(t150->settings.range * dev_max_range, 0xffff)); + spin_unlock_irqrestore(&t150->settings.access_lock, flags); + + return len; +} + +static ssize_t t150_store_ffb_intensity(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + uint16_t nforce; + struct t150 *t150 = dev_get_drvdata(dev); + + // If mallformed input leave... + if(kstrtou16(buf, 10, &nforce)) + return count; + + t150_set_gain(t150, nforce); + return count; +} + +static ssize_t t150_show_ffb_intensity(struct device *dev, struct device_attribute *attr,char * buf ) +{ + int len; + struct t150 *t150 = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&t150->settings.access_lock, flags); + len = sprintf(buf, "%d\n", t150->settings.gain); + spin_unlock_irqrestore(&t150->settings.access_lock, flags); + + return len; +} + +ssize_t t150_show_fw_version(struct device *dev, struct device_attribute *attr,char * buf ) +{ + int len; + struct t150 *t150 = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&t150->settings.access_lock, flags); + len = sprintf(buf, "%d\n", t150->settings.firmware_version); + spin_unlock_irqrestore(&t150->settings.access_lock, flags); + + return len; +} diff --git a/drivers/custom/t150/hid-t150/attributes.h b/drivers/custom/t150/hid-t150/attributes.h new file mode 100644 index 000000000000..119b95ced577 --- /dev/null +++ b/drivers/custom/t150/hid-t150/attributes.h @@ -0,0 +1,49 @@ +/******************************************************************** + * SYSF STUFF + * + * This section contains the handlers for the + * attributes created in the sysfs when a + * wheel is plugged into the host + *******************************************************************/ +/***/ + +static inline int t150_init_attributes(struct t150 *t150); +static inline void t150_free_attributes(struct t150 *t150); + +static ssize_t t150_store_return_force(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +static ssize_t t150_show_return_force(struct device *dev, struct device_attribute *attr,char * buf ); +static ssize_t t150_store_simulate_return_force(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +static ssize_t t150_show_simulate_return_force(struct device *dev, struct device_attribute *attr,char * buf ); +static ssize_t t150_store_range(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +static ssize_t t150_show_range(struct device *dev, struct device_attribute *attr,char * buf ); +static ssize_t t150_store_ffb_intensity(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +static ssize_t t150_show_ffb_intensity(struct device *dev, struct device_attribute *attr,char * buf ); +static ssize_t t150_show_fw_version(struct device *dev, struct device_attribute *attr,char * buf ); + + +/** Attribute used to set how much strong is the simulated "spring" that makes + * the wheel center back when steered. + * Input is a decimal value between 0 and 65535*/ +static DEVICE_ATTR(autocenter, 0664, t150_show_return_force, t150_store_return_force); + +/** Attribute used to set if the wheel has to activate the auto-centering of the + * wheel. If setted to true ffb to center will be ignored + * Input is a boolean value*/ +static DEVICE_ATTR(enable_autocenter, 0664, t150_show_simulate_return_force, t150_store_simulate_return_force); + +/** Attribute used to set the wheel max rotation in degrees. + * Input is a decimal value between between 270 and 1080*/ +static DEVICE_ATTR(range, 0664, t150_show_range, t150_store_range); + +/** + * How strong the ffb effects are reproduced on the wheel + * Input is a decimal value between between 0 and 65535*/ +static DEVICE_ATTR(gain, 0664, t150_show_ffb_intensity, t150_store_ffb_intensity); + +/** + * Read-only, returns the current firmware version of the Wheel*/ +static DEVICE_ATTR(firmware_version, 0444, t150_show_fw_version, 0); diff --git a/drivers/custom/t150/hid-t150/forcefeedback.c b/drivers/custom/t150/hid-t150/forcefeedback.c new file mode 100644 index 000000000000..c1f3cb8e9c90 --- /dev/null +++ b/drivers/custom/t150/hid-t150/forcefeedback.c @@ -0,0 +1,429 @@ +/** Callback to free urb when a ffb request is completed */ +static void t150_ff_free_urb(struct urb *urb) +{ + //struct t150 *t150 = urb->context; + kfree(urb->transfer_buffer); + usb_free_urb(urb); +} + +/** + * Creates an usb URB to be sent to wheel for ffb operations + * @param t150 our wheel + * @param buffer_size how large alloc the urb + * @returns a ptr to URB if no error, 0 otherwise + */ +static struct urb* t150_ff_alloc_urb(struct t150 *t150, const size_t buffer_size) +{ + struct urb *urb; + + void *buffer = kzalloc(buffer_size, GFP_KERNEL); + if(!buffer) + return 0; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if(!urb) { + kfree(buffer); + return 0; + } + + usb_fill_int_urb( + urb, + t150->usb_device, + t150->pipe_out, + buffer, + buffer_size, + donothing_callback, + t150, + t150->bInterval_out + ); + + return urb; +} + +/** + * This macro is called in the probe function when the wheel input + * is beign setted up + * @param t150 a pointer to our wheel + * @returns 0 if no error, less than 0 if an error occured. + */ +static inline int t150_init_ffb(struct t150 *t150) +{ + int errno, i; + + for (i = 0; i < t150_ffb_effects_length; i++) + set_bit(t150_ffb_effects[i], t150->joystick->ffbit); + + // input core will automatically free force feedback structures when device is destroyed. + errno = input_ff_create(t150->joystick, FF_MAX_EFFECTS); + + if(errno) { + hid_err(t150->hid_device, "error create ff :(. errno=%i\n", errno); + return errno; + } + + t150->joystick->ff->upload = t150_ff_upload; + t150->joystick->ff->erase = t150_ff_erase; + t150->joystick->ff->playback = t150_ff_play; + t150->joystick->ff->set_gain = t150_ff_set_gain; + + return 0; +} + +/** + * macro to clean up the ffb stuff of a whell. It's to be called + * when probe failed to init or when the wheel is disconnected + * @param t150 a pointer to our wheel + */ +static inline void t150_free_ffb(struct t150 *t150) +{ + unsigned int i, j; + + for(i = 0; i < FF_MAX_EFFECTS; i++) + for(j = 0; j < 3; j++) { + if(! t150->update_ffb_urbs[i][j]) + continue; + + usb_kill_urb(t150->update_ffb_urbs[i][j]); + kfree(t150->update_ffb_urbs[i][j]->transfer_buffer); + usb_free_urb(t150->update_ffb_urbs[i][j]); + } +} + +static void t150_ff_preapre_first(struct ff_first *ff_first, struct ff_effect *effect) +{ + struct ff_envelope *ff_envelope; + + switch (effect->type) { + case FF_CONSTANT: + ff_envelope = &effect->u.constant.envelope; + ff_first->f0 = T150_FF_FIRST_CODE_CONSTANT; + break; + case FF_PERIODIC: + ff_envelope = &effect->u.periodic.envelope; + ff_first->f0 = T150_FF_FIRST_CODE_PERIODIC; + break; + case FF_DAMPER: + case FF_SPRING: + ff_envelope = 0; + ff_first->f0 = T150_FF_FIRST_CODE_CONDITION; + break; + default: + ff_envelope = 0; + break; + } + + ff_first->pk_id0 = effect->id * 0x1c + 0x1c; + ff_first->f1 = 0; + ff_first->f2 = 0x46; + ff_first->f3 = 0x54; + + /* Some effects do not use those fields */ + if(ff_envelope) { + ff_first->attack_length = cpu_to_le16(ff_envelope->attack_length); + // @FIXME the attack and fade levels are wrong ! + ff_first->attack_level = ff_envelope->attack_level / 0x1fff; + ff_first->fade_length = cpu_to_le16(ff_envelope->attack_length); + ff_first->fade_level = ff_envelope->fade_level / 0x1fff; + } +} + +/** + * This function prepares an update packet to update an already uploaded effected + * or when we're uploading a new effect + * @param ff_update the usb packed data to prepare + * @param effect the effect to be updated + */ +static void t150_ff_prepare_update(struct ff_update *ff_update, struct ff_effect *effect) +{ + int32_t level = 0; + + ff_update->pk_id1 = effect->id * 0x1c + 0x0e; + ff_update->f1 = 0x00; + + switch (effect->type) { + case FF_PERIODIC: + default: + ff_update->effect_class = T150_FF_UPDATE_CODE_PERIODIC; + + ff_update->effect.periodic.magnitude = word_high(effect->u.periodic.magnitude); + ff_update->effect.periodic.offset = word_high(effect->u.periodic.offset); + ff_update->effect.periodic.phase = effect->u.periodic.phase / ( (360*100) / 0xff) ; // Check if correct + ff_update->effect.periodic.period = cpu_to_le16(effect->u.periodic.period); + break; + case FF_CONSTANT: + ff_update->effect_class = T150_FF_UPDATE_CODE_CONSTANT; + + /* Not sure if really necessary. Done only for the ffmvforce utility :P */ + level = effect->u.constant.level * fixp_sin16(effect->direction / ( 0xFFFF / 360 )) * +1; + level >>= 15; // int only + + ff_update->effect.constant.level = (level / 0x01ff); + break; + case FF_SPRING: + ff_update->effect_class = T150_FF_UPDATE_CODE_CONDITION; + + ff_update->effect.condition.right_coeff = effect->u.condition[0].right_coeff / 0x147; + ff_update->effect.condition.left_coeff = effect->u.condition[0].left_coeff / 0x147; + + ff_update->effect.condition.center = cpu_to_le16( + effect->u.condition[0].center / (0x7fff / 0x01f4) + ); + ff_update->effect.condition.deadband = cpu_to_le16( + effect->u.condition[0].deadband / (0xffff /0x03e8) + ); + + ff_update->effect.condition.right_sat = effect->u.condition[0].right_saturation / 0x030c; + ff_update->effect.condition.left_sat = effect->u.condition[0].left_saturation / 0x030c; + break; + case FF_DAMPER: + ff_update->effect_class = T150_FF_UPDATE_CODE_CONDITION; + + ff_update->effect.condition.right_coeff = effect->u.condition[0].right_coeff / 0x147; + ff_update->effect.condition.left_coeff = effect->u.condition[0].left_coeff / 0x147; + + ff_update->effect.condition.center = cpu_to_le16( + effect->u.condition[0].center / (0x7fff / 0x01f4) + ); + ff_update->effect.condition.deadband = cpu_to_le16( + effect->u.condition[0].deadband / (0xffff /0x03e8) + ); + + ff_update->effect.condition.right_sat = effect->u.condition[0].right_saturation / 0x028f; + ff_update->effect.condition.left_sat = effect->u.condition[0].left_saturation / 0x028f; + + break; + } +} + +static void t150_ff_prepare_commit(struct ff_commit *ff_commit, struct ff_effect *effect) +{ + ff_commit->f0 = 0x01; + ff_commit->id = effect->id; + if(effect->replay.length) // Ugly hack(?) per Assetto Corsa :P + ff_commit->length = cpu_to_le16(effect->replay.length); + else + ff_commit->length = cpu_to_le16(0xffff); + ff_commit->f1 = 0; + ff_commit->f2 = 0; + ff_commit->pk_id1 = effect->id * 0x1c + 0x0e; + ff_commit->f3 = 0; + ff_commit->pk_id0 = effect->id * 0x1c + 0x1c; + ff_commit->f4 = 0; + ff_commit->delay = word_high(effect->replay.delay); + ff_commit->f5 = 0; + + switch (effect->type) { + case FF_PERIODIC: + switch (effect->u.periodic.waveform) { + case FF_SINE: + default: + ff_commit->effect_type = cpu_to_le16(T150_FF_COMMIT_CODE_SINE); + break; + case FF_SAW_UP: + ff_commit->effect_type = cpu_to_le16(T150_FF_COMMIT_CODE_SAW_UP); + break; + case FF_SAW_DOWN: + ff_commit->effect_type = cpu_to_le16(T150_FF_COMMIT_CODE_SAW_DOWN); + break; + } + break; + case FF_CONSTANT: + ff_commit->effect_type = cpu_to_le16(T150_FF_COMMIT_CODE_CONSTANT); + break; + case FF_SPRING: + ff_commit->effect_type = cpu_to_le16(T150_FF_COMMIT_CODE_SPRING); + break; + case FF_DAMPER: + ff_commit->effect_type = cpu_to_le16(T150_FF_COMMIT_CODE_DAMPER); + break; + default: + printk(KERN_ERR "t150: unknown effect type: %i\n", effect->type); + } +} + +/** + * Function called to upload an effect to the wheel. + * An effect has to be sent to the wheel fragmented in 3 usb request. + * @param dev the input_dev + * @param effect the effect to upload + * @param old If I have to update an already uploaded effect this is not 0 + * + * @return 0 if no errors occured + */ +static int t150_ff_upload(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old) +{ + struct t150 *t150 = input_get_drvdata(dev); + int errno = 0; + + struct ff_first ff_first_old, ff_first_new; + struct ff_update ff_update_old, ff_update_new; + struct ff_commit ff_commit_old, ff_commit_new; + + // No need to re-upload the same effect.... + if(!T150_FF_BLIND_COMPUTE_EFFECT && old && memcmp(effect, old, sizeof(struct ff_effect)) == 0) + return 0; + + // If URBs were already allocated we can re-use them.... + // Alloc first urb + if(! t150->update_ffb_urbs[effect->id][0]) + t150->update_ffb_urbs[effect->id][0] = t150_ff_alloc_urb(t150, sizeof(struct ff_first)); + + if(! t150->update_ffb_urbs[effect->id][0]) + return -ENOMEM; + + // Alloc second urb + if(! t150->update_ffb_urbs[effect->id][1]) + t150->update_ffb_urbs[effect->id][1] = t150_ff_alloc_urb(t150, sizeof(struct ff_update)); + + if(! t150->update_ffb_urbs[effect->id][1]) + goto free0; + + // Alloc third urb + if(! t150->update_ffb_urbs[effect->id][2]) + t150->update_ffb_urbs[effect->id][2] = t150_ff_alloc_urb(t150, sizeof(struct ff_commit)); + + if(! t150->update_ffb_urbs[effect->id][2]) + goto free1; + + /** Preparing effect */ + t150_ff_preapre_first(&ff_first_new, effect); + t150_ff_prepare_update(&ff_update_new, effect); + t150_ff_prepare_commit(&ff_commit_new, effect); + + if(old) { + t150_ff_preapre_first(&ff_first_old, old); + t150_ff_prepare_update(&ff_update_old, old); + t150_ff_prepare_commit(&ff_commit_old, old); + } + + /** Submiting the effect to the wheel + * If an old is present and the result packet are the same we skip an URB + * unless you define T150_FF_BLIND_UPLOAD as true + */ + if(T150_FF_BLIND_UPLOAD || !old || memcmp(&ff_first_old, &ff_first_new, sizeof(struct ff_first))){ + usb_kill_urb(t150->update_ffb_urbs[effect->id][0]); + + memcpy(t150->update_ffb_urbs[effect->id][0]->transfer_buffer, &ff_first_new, sizeof(struct ff_first)); + errno = usb_submit_urb(t150->update_ffb_urbs[effect->id][0], GFP_ATOMIC); + if(errno) { + hid_err(t150->hid_device, "submitting ffb 0 urb of effect %d, error %d\n", effect->id ,errno); + return errno; + } + } + + if(T150_FF_BLIND_UPLOAD || !old || memcmp(&ff_update_old, &ff_update_new, sizeof(struct ff_update))){ + usb_kill_urb(t150->update_ffb_urbs[effect->id][1]); + + memcpy(t150->update_ffb_urbs[effect->id][1]->transfer_buffer, &ff_update_new, sizeof(struct ff_update)); + errno = usb_submit_urb(t150->update_ffb_urbs[effect->id][1], GFP_ATOMIC); + if(errno) { + hid_err(t150->hid_device, "submitting ffb 1 urb of effect %d, error %d\n", effect->id ,errno); + return errno; + } + } + + if(T150_FF_BLIND_UPLOAD || !old || memcmp(&ff_commit_old, &ff_commit_new, sizeof(struct ff_commit))){ + usb_kill_urb(t150->update_ffb_urbs[effect->id][2]); + + memcpy(t150->update_ffb_urbs[effect->id][2]->transfer_buffer, &ff_commit_new, sizeof(struct ff_commit)); + errno = usb_submit_urb(t150->update_ffb_urbs[effect->id][2], GFP_ATOMIC); + if(errno) { + hid_err(t150->hid_device, "submitting ffb 2 urb of effect %d, error %d\n", effect->id ,errno); + return errno; + } + } + + return 0; + +free1: t150_ff_free_urb(t150->update_ffb_urbs[effect->id][1]); + t150->update_ffb_urbs[effect->id][1] = 0; +free0: t150_ff_free_urb(t150->update_ffb_urbs[effect->id][0]); + t150->update_ffb_urbs[effect->id][0] = 0; + return -ENOMEM; +} + +/** + * Function used to erase an effect already uploaded to the Wheel + * @param dev + * @param effect_id the ID of the effect to be erased + * + * @return 0 if no errors occured + */ +static int t150_ff_erase(struct input_dev *dev, int effect_id) +{ + /** When an effect is destroyed also a request to stop it is sent to + * t150_ff_play. Observing the Windows's driver seems there isn't any + * specific packet to explicity destory the effect, so we return success (0) + * to notify the Kernel that the id can be freed and re-used for another + * effect. + */ + return 0; +} + +/** + * Function used to play an effect already uploaded to the Wheel + * If times==0 then the function will send to the wheel a request + * to stop playing the effect. + * @param dev + * @param effect_id the ID of the effect to be played + * @param times how many times the effect should be played. If the effect + * is beign erased a play request in times=0 is also sent. + * + * @return 0 if no errors occured + */ +static int t150_ff_play(struct input_dev *dev, int effect_id, int times) +{ + struct t150 *t150 = input_get_drvdata(dev); + struct urb *urb; + struct ff_change_effect_status *ff_change; + int errno; + + // Alloc urb + urb = t150_ff_alloc_urb(t150, sizeof(struct ff_change_effect_status)); + if(!urb) + return -ENOMEM; + ff_change = urb->transfer_buffer; + + ff_change->f0 = 0x41; + ff_change->id = effect_id; + ff_change->mode = times ? 0x41 : 0x00; // Play or stop ? + ff_change->times = times ? times : 0x01; + + urb->complete = t150_ff_free_urb; + errno = usb_submit_urb(urb, GFP_KERNEL); + if(errno) + hid_err(t150->hid_device, "unable to send URB to play effect n %d, errno %d\n", effect_id ,errno); + + return errno; +} + +/** + * @param dev + * @param gain 0xFFFF = 100% of gain + */ +static void t150_ff_set_gain(struct input_dev *dev, uint16_t gain) +{ + struct t150 *t150 = input_get_drvdata(dev); + int errno; + struct urb *urb; + struct ff_change_gain *ff_change; + unsigned long flags; + + urb = t150_ff_alloc_urb(t150, sizeof(struct ff_change_gain)); + if(!urb) + return; // -NOMEM + + ff_change = urb->transfer_buffer; + + ff_change->f0 = 0x43; + ff_change->gain = gain; + + spin_lock_irqsave(&t150->settings.access_lock, flags); + t150->settings.gain = ff_change->gain; + spin_unlock_irqrestore(&t150->settings.access_lock, flags); + + urb->complete = t150_ff_free_urb; + errno = usb_submit_urb(urb, GFP_KERNEL); + if(errno) + hid_err(t150->hid_device, "unable to send URB to set gain, errno %i\n", errno); +} diff --git a/drivers/custom/t150/hid-t150/forcefeedback.h b/drivers/custom/t150/hid-t150/forcefeedback.h new file mode 100644 index 000000000000..8865ad4a7f23 --- /dev/null +++ b/drivers/custom/t150/hid-t150/forcefeedback.h @@ -0,0 +1,173 @@ +#define T150_FF_BLIND_COMPUTE_EFFECT false +#define T150_FF_BLIND_UPLOAD false + +#define T150_FF_FIRST_CODE_CONSTANT 0x02 +#define T150_FF_FIRST_CODE_PERIODIC 0x02 +#define T150_FF_FIRST_CODE_CONDITION 0x05 + +#define T150_FF_UPDATE_CODE_CONSTANT 0x03 +#define T150_FF_UPDATE_CODE_PERIODIC 0x04 +#define T150_FF_UPDATE_CODE_CONDITION 0x05 + +#define T150_FF_COMMIT_CODE_CONSTANT 0x4000 +#define T150_FF_COMMIT_CODE_SINE 0x4022 +#define T150_FF_COMMIT_CODE_SAW_UP 0x4023 +#define T150_FF_COMMIT_CODE_SAW_DOWN 0x4024 +#define T150_FF_COMMIT_CODE_SPRING 0x4040 +#define T150_FF_COMMIT_CODE_DAMPER 0x4041 + + +struct __packed ff_periodic +{ + /** Truncked value of maginute on 16 bit */ + int8_t magnitude; + /** Trunked value of offset */ + int8_t offset; + /** Phase where 0x00 = 0° and 0xff = 360° */ + uint8_t phase; + /** Period in milliseconds */ + uint16_t period; +}; + +struct __packed ff_constant +{ + /** the level of the effect */ + int8_t level; +}; + +struct __packed ff_condition +{ + /** between [-100, +100] = [0x9c, 0x64] */ + int8_t right_coeff; + /** between [-100, +100] = [0x9c, 0x64] */ + int8_t left_coeff; + /** between [-500, +500] = [0xfe0c, 0x01f4] */ + int16_t center; + /** between [0, +1000] = [0x0000, 0x03e8] */ + int16_t deadband; + /** between if spring [0x00, 0x54]; if damper [0x00, 0x64] */ + uint8_t right_sat; + /** between if spring [0x00, 0x54]; if damper [0x00, 0x64] **/ + uint8_t left_sat; + +}; + +/** This is the rappresentation of the packet used to start + * loading a new effect to the wheel. + * + * On the Windows's driver is sent before all the others + */ +struct __packed ff_first +{ + uint8_t f0; + /** Seems is effect_id * 0x1c + 0x1c */ + uint8_t pk_id0; + uint8_t f1; + /** Attack length in milliseconds */ + uint16_t attack_length; + /** Do not know how it works */ + uint8_t attack_level; + /** Fade length in milliseconds */ + uint16_t fade_length; + /** Do not know how it works */ + uint8_t fade_level; + /** Always 0x46 ? */ + uint8_t f2; + /** Always 0x54 ?*/ + uint8_t f3; +}; + +/** This is the rappresentation of the packet used to load the + * informations of updatable effects. + * + * On the Windows's driver is sent after ff_first and before ff_commit + * when uploading a new effect; it's sent alone when uploading an already + * existing effect + */ +struct __packed ff_update +{ + /** 0x04 for periodic, 0x03 for const*/ + uint8_t effect_class; + /** seems is effect_id * 0x1c + 0x0e */ + uint8_t pk_id1; + uint8_t f1; + + /** Fields specific to effect class */ + union { + struct ff_periodic periodic; + struct ff_constant constant; + struct ff_condition condition; + } effect; +}; + +/** This is the rappresentation of the packet used to commit a new + * effect into the wheel. + * The wheel can associate the others packtest to this packet by + * using the pk_id1 and pk_id0 keys + * + * On the Windows's driver is sent after ff_first and ff_update + */ +struct __packed ff_commit +{ + uint8_t f0; + uint8_t id; + /** Effect code */ + uint16_t effect_type; + /** Length of the effect in milliseconds */ + uint16_t length; + /** Do not know how it works */ + uint16_t f1; + uint8_t f2; + /** seems is effect_id * 0x1c + 0x0e */ + uint8_t pk_id1; + uint8_t f3; + /** Seems is effect_id * 0x1c + 0x1c */ + uint8_t pk_id0; + uint8_t f4; + /** Delay in milliseconds */ + uint8_t delay; + uint8_t f5; +}; + +struct __packed ff_change_effect_status +{ + uint8_t f0; + uint8_t id; + uint8_t mode; + uint8_t times; +}; + +struct __packed ff_change_gain +{ + uint8_t f0; + uint16_t gain; +}; + +union __packed ff_change +{ + struct ff_change_effect_status effect; + struct ff_change_gain gain; +}; + +static int t150_init_ffb(struct t150 *t150); +static void t150_free_ffb(struct t150 *t150); + +static int t150_ff_upload(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old); +static int t150_ff_erase(struct input_dev *dev, int effect_id); +static int t150_ff_play(struct input_dev *dev, int effect_id, int value); +static void t150_ff_set_gain(struct input_dev *dev, uint16_t gain); + +static uint8_t t150_ffb_effects_length = 8; +static const int16_t t150_ffb_effects[] = { + FF_GAIN, + + FF_PERIODIC, + FF_SINE, + FF_SAW_UP, + FF_SAW_DOWN, + + FF_CONSTANT, + + FF_SPRING, + FF_DAMPER +}; diff --git a/drivers/custom/t150/hid-t150/hid-t150.c b/drivers/custom/t150/hid-t150/hid-t150.c new file mode 100644 index 000000000000..e155196db628 --- /dev/null +++ b/drivers/custom/t150/hid-t150/hid-t150.c @@ -0,0 +1,220 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hid-t150.h" +#include "input.h" +#include "attributes.h" +#include "settings.h" +#include "forcefeedback.h" +#include "packet.h" + +static void donothing_callback(struct urb *urb) {} + +/** Init for a t150 data struct + * @param t150 pointer to the t150 structor to init + * @param interface pointer to usb interface which the wheel is connected to + */ +static inline int t150_constructor(struct t150 *t150,struct hid_device *hid_device) +{ + int i, error_code = 0; + struct usb_endpoint_descriptor *ep, *ep_irq_in = 0, *ep_irq_out = 0; + struct usb_interface *interface = to_usb_interface(hid_device->dev.parent); + + t150->usb_device = interface_to_usbdev(interface); + t150->hid_device = hid_device; + + // Saving ref to t150 + dev_set_drvdata(&t150->usb_device->dev, t150); + hid_set_drvdata(hid_device, t150); + + error_code = hid_parse(hid_device); + if (error_code) { + hid_err(hid_device, "hid_parse() failed\n"); + return error_code; + } + + error_code = hid_hw_start(hid_device, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); + if (error_code) { + hid_err(hid_device, "hid_hw_start() failed\n"); + return error_code; + } + + mutex_init(&t150->lock); + spin_lock_init(&t150->settings.access_lock); + + // Path used for the input subsystem + usb_make_path(t150->usb_device, t150->dev_path, sizeof(t150->dev_path)); + strlcat(t150->dev_path, "/input0", sizeof(t150->dev_path)); + + // From xpad.c + for (i = 0; i < 2; i++) { + ep = &interface->cur_altsetting->endpoint[i].desc; + + if (usb_endpoint_xfer_int(ep)) + if (usb_endpoint_dir_in(ep)) + ep_irq_in = ep; + else + ep_irq_out = ep; + } + + if (!ep_irq_in || !ep_irq_out) { + error_code = -ENODEV; + goto error3; + } + + t150->pipe_in = usb_rcvintpipe(t150->usb_device, ep_irq_in->bEndpointAddress); + t150->pipe_out= usb_sndintpipe(t150->usb_device, ep_irq_out->bEndpointAddress); + + t150->bInterval_in = ep_irq_in->bInterval; + t150->bInterval_out = ep_irq_out->bInterval; + + error_code = t150_init_input(t150); + if(error_code) + goto error4; + + error_code = t150_init_ffb(t150); + if(error_code) + goto error5; + + error_code = t150_init_attributes(t150); + if(error_code) + goto error6; + + return 0; + +error6: t150_free_ffb(t150); +error5: t150_free_input(t150); +error4: ; +error3: hid_hw_stop(hid_device); + return error_code; +} + +static int t150_probe(struct hid_device *hid_device, const struct hid_device_id *id) +{ + int error_code = 0; + struct t150 *t150; + + // Create new t150 struct + t150 = kzalloc(sizeof(struct t150), GFP_KERNEL); + if(!t150) + return -ENOMEM; + + error_code = t150_constructor(t150, hid_device); + if(error_code) + goto error0; + + return 0; + +error0: kfree(t150); + return error_code; +} + +static void t150_remove(struct hid_device *hid_device) +{ + struct t150 *t150 = hid_get_drvdata(hid_device);; + + hid_info(t150->hid_device, "T150RS Wheel removed. Bye\n"); + + // Force feedback + t150_free_ffb(t150); + + // input deregister + t150_free_input(t150); + + // sysf free + t150_free_attributes(t150); + + // Stop hid + hid_hw_close(hid_device); + hid_hw_stop(hid_device); + + // t150 free + kfree(t150); +} + +#include "attributes.c" +#include "input.c" +#include "settings.c" +#include "forcefeedback.c" + + +/******************************************************************** + * MODULE STUFF + * + *******************************************************************/ + +static struct hid_device_id t150_table[] = +{ + { HID_USB_DEVICE(USB_THRUSTMASTER_VENDOR_ID, USB_T150_PRODUCT_ID) }, + { HID_USB_DEVICE(USB_THRUSTMASTER_VENDOR_ID, USB_TMX_PRODUCT_ID) }, + {} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE (hid, t150_table); + +static struct hid_driver t150_driver = +{ + .name = "hid-t150", + .id_table = t150_table, + .probe = t150_probe, + .remove = t150_remove, + .raw_event = t150_update_input +}; + +static int __init t150_init(void) +{ + int errno = -ENOMEM; + packet_input_open = kzalloc(sizeof(uint16_t), GFP_KERNEL); + if(!packet_input_open) + goto err0; + + packet_input_what = kzalloc(sizeof(uint16_t), GFP_KERNEL); + if(!packet_input_what) + goto err1; + + packet_input_close = kzalloc(sizeof(uint16_t), GFP_KERNEL); + if(!packet_input_close) + goto err2; + + *packet_input_open = cpu_to_le16(0x0442); + *packet_input_what = cpu_to_le16(0x0542); + *packet_input_close = cpu_to_le16(0x0042); + + errno = hid_register_driver(&t150_driver); + if(errno) + goto err3; + else + return 0; + +err3: kfree(packet_input_close); +err2: kfree(packet_input_what); +err1: kfree(packet_input_open); +err0: return errno; +} + +static void __exit t150_exit(void) +{ + kfree(packet_input_open); + kfree(packet_input_what); + kfree(packet_input_close); + + hid_unregister_driver(&t150_driver); +} + +module_init(t150_init); +module_exit(t150_exit); + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dario Pagani <>"); +MODULE_DESCRIPTION("ThrustMaster T150 steering wheel driver"); diff --git a/drivers/custom/t150/hid-t150/hid-t150.h b/drivers/custom/t150/hid-t150/hid-t150.h new file mode 100644 index 000000000000..d4fba46f8d07 --- /dev/null +++ b/drivers/custom/t150/hid-t150/hid-t150.h @@ -0,0 +1,81 @@ +#include + +#define USB_THRUSTMASTER_VENDOR_ID 0x044f +#define USB_T150_PRODUCT_ID 0xb677 +#define USB_TMX_PRODUCT_ID 0xb67f + +struct joy_state_packet; +struct ff_first; +struct ff_second; +struct ff_third; +union ff_change; + +struct t150 +{ + struct usb_device *usb_device; + struct hid_device *hid_device; + + // Stuff to read from the wheel + int pipe_in; + uint8_t bInterval_in; + + // Stuff to write to the wheel + int pipe_out; + uint8_t bInterval_out; + + // Input api stuff + char dev_path[128]; + struct input_dev *joystick; + + struct urb *update_ffb_urbs[FF_MAX_EFFECTS][3]; + unsigned update_ffb_free_slot; + + struct mutex lock; + + struct { + spinlock_t access_lock; + + uint16_t autocenter_force; + bool autocenter_enabled; + uint16_t range; + uint16_t gain; + + uint8_t firmware_version; + } settings; +}; + +/** + * Simple macro to make a word from two bytes + * @low the low part of a word + * @high the high part of a word + */ +static inline uint16_t make_word(const uint8_t low, const uint8_t high) +{ + return ((uint16_t)low | ((uint8_t)(high) << 8)); +} + +static inline uint8_t word_high(const uint16_t word) +{ + return word >> 8; +} + +static inline uint8_t word_low(const uint16_t word) +{ + return word; +} + +static inline void printP(uint8_t const *const bytes, const size_t length) +{ + int i; + char printstr[202] = "t150: "; + + if(length > 64) // Packet too big :/ + return; + + for(i = 0; i < length; i++) + sprintf( printstr + (6 + i*3),"%02hhX ", bytes[i]); + + sprintf(printstr + (6 + length*3),"\n"); + + printk(printstr); +} diff --git a/drivers/custom/t150/hid-t150/input.c b/drivers/custom/t150/hid-t150/input.c new file mode 100644 index 000000000000..aae66338f6f4 --- /dev/null +++ b/drivers/custom/t150/hid-t150/input.c @@ -0,0 +1,83 @@ +/** + * This function initializes the input system for + * @t150 pointer to our device + */ +static inline int t150_init_input(struct t150 *t150) +{ + struct hid_input *hidinput = list_entry(t150->hid_device->inputs.next, struct hid_input, list); + t150->joystick = hidinput->input; + + input_set_drvdata(t150->joystick, t150); + + t150->joystick->open = t150_input_open; + t150->joystick->close = t150_input_close; + + return 0; +} + +static inline void t150_free_input(struct t150 *t150) +{ +} + +static int t150_input_open(struct input_dev *dev) +{ + struct t150 *t150 = input_get_drvdata(dev); + int boh, ret; + + ret = usb_interrupt_msg( + t150->usb_device, + t150->pipe_out, + packet_input_open, 2, &boh, + 8 + ); + + if(ret) + return ret; + + ret = hid_hw_open(t150->hid_device); + + return ret; +} + +static void t150_input_close(struct input_dev *dev) +{ + struct t150 *t150 = input_get_drvdata(dev); + int boh, i; + + hid_hw_close(t150->hid_device); + + // Send magic codes + for(i = 0; i < 2; i++) + usb_interrupt_msg( + t150->usb_device, + t150->pipe_out, + packet_input_what, 2, &boh, + 8 + ); + + usb_interrupt_msg( + t150->usb_device, + t150->pipe_out, + packet_input_close, 2, &boh, + 8 + ); +} + +/** + * This function updates the current input status of the joystick + * @t150 target wheel + * @ss new status to register + */ +static int t150_update_input(struct hid_device *hdev, struct hid_report *report, uint8_t *packet_raw, int size) +{ + struct t150_state_packet *packet = (struct t150_state_packet*)packet_raw; + + if(packet->type != STATE_PACKET_INPUT) + { + hid_warn(hdev, "recived a packet that is not an input state :/\n"); + printP(packet_raw, size); + return -1; // @TODO + } + + return 0; +} diff --git a/drivers/custom/t150/hid-t150/input.h b/drivers/custom/t150/hid-t150/input.h new file mode 100644 index 000000000000..b0140e95a07f --- /dev/null +++ b/drivers/custom/t150/hid-t150/input.h @@ -0,0 +1,12 @@ +struct t150_state_packet; + +static inline int t150_init_input(struct t150 *t150); +static inline void t150_free_input(struct t150 *t150); +static int t150_input_open(struct input_dev *dev); +static void t150_input_close(struct input_dev *dev); +static int t150_update_input(struct hid_device *hdev, struct hid_report *report, uint8_t *packet_raw, int size); + +static uint16_t *packet_input_open = 0; +/** It seems it's used to purge all uploaded effects from the wheel, not sure */ +static uint16_t *packet_input_what = 0; +static uint16_t *packet_input_close = 0; diff --git a/drivers/custom/t150/hid-t150/packet.h b/drivers/custom/t150/hid-t150/packet.h new file mode 100644 index 000000000000..6022fd0fb057 --- /dev/null +++ b/drivers/custom/t150/hid-t150/packet.h @@ -0,0 +1,10 @@ +#define STATE_PACKET_INPUT 0x07 + +/** + * All data is stored in little endian + */ +struct __packed t150_state_packet +{ + /** 0x07 if this packet contains the wheel current input status */ + uint8_t type; +}; \ No newline at end of file diff --git a/drivers/custom/t150/hid-t150/settings.c b/drivers/custom/t150/hid-t150/settings.c new file mode 100644 index 000000000000..0024e5796766 --- /dev/null +++ b/drivers/custom/t150/hid-t150/settings.c @@ -0,0 +1,195 @@ +/** + * @param t150 ptr to t150 + * @param gain a value between 0x00 and 0xffff where 0xffff is 100% gain + * @return 0 on success @see usb_interrupt_msg for return codes + */ +static int t150_set_gain(struct t150 *t150, uint16_t gain) +{ + int boh, errno; + uint8_t *buffer = kzalloc(2, GFP_KERNEL); + unsigned long flags; + + buffer[0] = 0x43; + buffer[1] = gain; + + mutex_lock(&t150->lock); + + // Send to the wheel desidered return force + errno = usb_interrupt_msg( + t150->usb_device, + t150->pipe_out, + buffer, + 2, &boh, + SETTINGS_TIMEOUT + ); + + if(!errno) { + spin_lock_irqsave(&t150->settings.access_lock, flags); + t150->settings.gain = gain; + spin_unlock_irqrestore(&t150->settings.access_lock, flags); + } else { + hid_err(t150->hid_device, "Operation set gain failed with code %d", errno); + } + + mutex_unlock(&t150->lock); + + kfree(buffer); + + return errno; +} + +/** + * @param autocenter_force a value between 0 and 65535, is the strength of the autocenter effect + */ +static __always_inline int t150_set_autocenter(struct t150 *t150, uint16_t autocenter_force) +{ + uint8_t *buffer = kzalloc(4, GFP_KERNEL); + int errno; + unsigned long flags; + + mutex_lock(&t150->lock); + + errno = t150_settings_set40(t150, SET40_RETURN_FORCE, DIV_ROUND_CLOSEST((autocenter_force * 100), 0xffff), buffer); + + if(!errno) { + spin_lock_irqsave(&t150->settings.access_lock, flags); + t150->settings.autocenter_force = autocenter_force; + spin_unlock_irqrestore(&t150->settings.access_lock, flags); + } + + mutex_unlock(&t150->lock); + + kfree(buffer); + return errno; +} + +/** + * @param enable true if the autocenter effect is to be kept enabled when the input + * is opened. The autocentering effect is always active while no input are open + */ +static __always_inline int t150_set_enable_autocenter(struct t150 *t150, bool enable) +{ + uint8_t *buffer = kzalloc(4, GFP_KERNEL); + int errno; + unsigned long flags; + + mutex_lock(&t150->lock); + + errno = t150_settings_set40(t150, SET40_USE_RETURN_FORCE, enable, buffer); + + if(!errno) { + spin_lock_irqsave(&t150->settings.access_lock, flags); + t150->settings.autocenter_enabled = enable; + spin_unlock_irqrestore(&t150->settings.access_lock, flags); + } + + mutex_unlock(&t150->lock); + + kfree(buffer); + return errno; +} + +/** + * @param range a value between 0x0000 and 0xffff where 0xffff is 1080° + * wheel range + */ +static __always_inline int t150_set_range(struct t150 *t150, uint16_t range) +{ + uint8_t *buffer = kzalloc(4, GFP_KERNEL); + int errno; + unsigned long flags; + + mutex_lock(&t150->lock); + + errno = t150_settings_set40(t150, SET40_RANGE, range, buffer); + + if(!errno) { + spin_lock_irqsave(&t150->settings.access_lock, flags); + t150->settings.range = range; + spin_unlock_irqrestore(&t150->settings.access_lock, flags); + } + + mutex_unlock(&t150->lock); + + kfree(buffer); + return errno; +} + + +/** + * @t150 pointer to t150 + * @operation number of operation + * @argument the argument to pass with the request + * @buffer a buffer of at least 4 BYTES! + * @return 0 on success @see usb_interrupt_msg for return codes + */ +static int t150_settings_set40( + struct t150 *t150, operation_t operation, uint16_t argument, void *buffer_ +) +{ + int boh, errno; + struct operation40 *buffer = buffer_; + buffer->code = 0x40; + buffer->operation = operation; + buffer->argument = cpu_to_le16(argument); + + // Send to the wheel desidered return force + errno = usb_interrupt_msg( + t150->usb_device, + t150->pipe_out, + buffer, + sizeof(struct operation40), &boh, + SETTINGS_TIMEOUT + ); + + if(errno) + hid_err(t150->hid_device, "errno %d during operation 0x40 0x%02hhX with argument (big endian) %04hhX", + errno, operation, argument); + + return errno; +} + +static int t150_setup_task(struct t150 *t150) +{ + int errno = 0; + uint8_t *fw_version; + + fw_version = kzalloc(8, GFP_KERNEL); + + // Retrive current version + mutex_lock(&t150->lock); + + errno = usb_control_msg( + t150->usb_device, + usb_rcvctrlpipe(t150->usb_device, 0), + 86, 0xc1, 0, 0, fw_version, 8, SETTINGS_TIMEOUT + ); + + mutex_unlock(&t150->lock); + + if(errno < 0) + hid_err(t150->hid_device, "Error %d while sending the control URB to retrive firmware version\n", errno); + else + t150->settings.firmware_version = fw_version[1]; + + errno = t150_set_gain(t150, 0xbffe); // ~75% + if(errno) + hid_err(t150->hid_device, "Error %d while setting the t150 default gain\n", errno); + + errno = t150_set_enable_autocenter(t150, false); + if(errno) + hid_err(t150->hid_device, "Error %d while setting the t150 default enable_autocenter\n", errno); + + errno = t150_set_autocenter(t150, 0x7fff); + if(errno) + hid_err(t150->hid_device, "Error %d while setting the t150 default autocenter\n", errno); + + errno = t150_set_range(t150, 0xffff); + if(errno) + hid_err(t150->hid_device, "Error %d while setting the t150 default range\n", errno); + + hid_info(t150->hid_device, "Setup completed! Firmware version is %d\n", t150->settings.firmware_version); + + kfree(fw_version); + return errno; +} diff --git a/drivers/custom/t150/hid-t150/settings.h b/drivers/custom/t150/hid-t150/settings.h new file mode 100644 index 000000000000..c85654a7346e --- /dev/null +++ b/drivers/custom/t150/hid-t150/settings.h @@ -0,0 +1,36 @@ +#define SETTINGS_TIMEOUT 10 + +typedef uint8_t operation_t; +#define SET40_RETURN_FORCE 0x03 +#define SET40_USE_RETURN_FORCE 0x04 +#define SET40_RANGE 0x11 + +struct operation40 +{ + uint8_t code; // MUST BE 0x40 + operation_t operation; + uint16_t argument; +}; + +/** [SEEMS LIKE IT'S NOT NEEDED :P] Packet to send to the Wheel when we do not want to edit its settings anymore **/ +#define SET42_STOP_WHEEL_SETTINGS 0x00 +/** [SEEMS LIKE IT'S NOT NEEDED :P] Packet to send to the Wheel when we want to edit its settings **/ +#define SET42_START_WHEEL_SETTINGS 0x04 +/** [SEEMS LIKE IT'S NOT NEEDED :P] Packet to send to the Wheel when we want to apply the new settings **/ +#define SET42_APPLY_WHEEL_SETTINGS 0x05 + +struct opertation42 +{ + uint8_t code; // MUST BE 0x42 + operation_t operation; +}; + +static int t150_settings_set40(struct t150 *t150, operation_t operation, + uint16_t argument, void *buffer); + +static int t150_set_gain(struct t150 *t150, uint16_t gain); +static __always_inline int t150_set_autocenter(struct t150 *t150, uint16_t autocenter_force); +static __always_inline int t150_set_enable_autocenter(struct t150 *t150, bool enable); +static __always_inline int t150_set_range(struct t150 *t150, uint16_t range); + +static int t150_setup_task(struct t150 *t150); # ---------------------------------------- # Module: v4l2loopback # Version: v0.15.3 # ---------------------------------------- diff --git a/drivers/custom/v4l2loopback/Kbuild b/drivers/custom/v4l2loopback/Kbuild new file mode 100644 index 000000000000..9aad4eb75294 --- /dev/null +++ b/drivers/custom/v4l2loopback/Kbuild @@ -0,0 +1 @@ +obj-m := v4l2loopback.o diff --git a/drivers/custom/v4l2loopback/Makefile b/drivers/custom/v4l2loopback/Makefile new file mode 100644 index 000000000000..6d86646d5d04 --- /dev/null +++ b/drivers/custom/v4l2loopback/Makefile @@ -0,0 +1,139 @@ +ifneq ($(wildcard .gitversion),) +# building a snapshot version +V4L2LOOPBACK_SNAPSHOT_VERSION=$(patsubst v%,%,$(shell git describe --always --dirty 2>/dev/null || shell git describe --always 2>/dev/null || echo snapshot)) +override KCPPFLAGS += -DSNAPSHOT_VERSION='"$(V4L2LOOPBACK_SNAPSHOT_VERSION)"' +endif + +include Kbuild +ifeq ($(KBUILD_MODULES),) + +KERNELRELEASE ?= `uname -r` +KERNEL_DIR ?= /lib/modules/$(KERNELRELEASE)/build +PWD := $(shell pwd) + +PREFIX ?= /usr/local +BINDIR = $(PREFIX)/bin +INCLUDEDIR = $(PREFIX)/include +MANDIR = $(PREFIX)/share/man +MAN1DIR = $(MANDIR)/man1 +INSTALL = install +INSTALL_PROGRAM = $(INSTALL) -p -m 755 +INSTALL_DIR = $(INSTALL) -p -m 755 -d +INSTALL_DATA = $(INSTALL) -m 644 + +MODULE_OPTIONS = devices=2 + +########################################## +# note on build targets +# +# module-assistant makes some assumptions about targets, namely +# : must be present and build the module +# .ko is not enough +# install: must be present (and should only install the module) +# +# we therefore make a .PHONY alias to .ko +# and remove utils-installation from 'install' +# call 'make install-all' if you want to install everything +########################################## + + +.PHONY: all install clean distclean +.PHONY: install-all install-extra install-utils install-man install-headers +.PHONY: modprobe v4l2loopback + +# we don't control the .ko file dependencies, as it is done by kernel +# makefiles. therefore v4l2loopback.ko is a phony target actually +.PHONY: v4l2loopback.ko utils + +all: v4l2loopback.ko utils + +v4l2loopback: v4l2loopback.ko +v4l2loopback.ko: + @echo "Building v4l2-loopback driver..." + $(MAKE) -C $(KERNEL_DIR) M=$(PWD) KCPPFLAGS="$(KCPPFLAGS)" modules + +install-all: install install-extra +install: + $(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install + @echo "" + @echo "SUCCESS (if you got 'SSL errors' above, you can safely ignore them)" + @echo "" + +install-extra: install-utils install-man install-headers +install-utils: utils/v4l2loopback-ctl + $(INSTALL_DIR) "$(DESTDIR)$(BINDIR)" + $(INSTALL_PROGRAM) $< "$(DESTDIR)$(BINDIR)" + +install-man: man/v4l2loopback-ctl.1 + $(INSTALL_DIR) "$(DESTDIR)$(MAN1DIR)" + $(INSTALL_DATA) $< "$(DESTDIR)$(MAN1DIR)" + +install-headers: v4l2loopback.h + $(INSTALL_DIR) "$(DESTDIR)$(INCLUDEDIR)/linux" + $(INSTALL_DATA) $< "$(DESTDIR)$(INCLUDEDIR)/linux" + +clean: + rm -f *~ + rm -f Module.symvers Module.markers modules.order + $(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean + -$(MAKE) -C utils clean + +distclean: clean + rm -f man/v4l2loopback-ctl.1 + +modprobe: v4l2loopback.ko + -sudo chmod a+r $< + -sudo modprobe videodev + -sudo rmmod $< + sudo insmod ./$< $(MODULE_OPTIONS) + +man/v4l2loopback-ctl.1: utils/v4l2loopback-ctl + help2man -N --name "control v4l2 loopback devices" \ + --no-discard-stderr --help-option=-h --version-option=-v \ + $^ > $@ + +utils: utils/v4l2loopback-ctl +utils/v4l2loopback-ctl: utils/v4l2loopback-ctl.c v4l2loopback.h + $(MAKE) -C utils V4L2LOOPBACK_SNAPSHOT_VERSION=$(V4L2LOOPBACK_SNAPSHOT_VERSION) + +.clang-format: + curl "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/.clang-format" > $@ + +.PHONY: clang-format +clang-format: .clang-format + clang-format -i *.c *.h utils/*.c + +.PHONY: sign +# try to read the default certificate/key from the dkms config +dkms_framework=/etc/dkms/framework.conf /etc/dkms/framework.conf.d/*.conf +KBUILD_SIGN_KEY=$(patsubst mok_signing_key=%,%,$(lastword $(shell grep -h -E "^[[:space:]]*mok_signing_key=" $(dkms_framework)))) +KBUILD_SIGN_CERT=$(patsubst mok_certificate=%,%,$(lastword $(shell grep -h -E "^[[:space:]]*mok_certificate=" $(dkms_framework)))) + +ifeq ($(KBUILD_SIGN_PIN),) +define usage_kbuildsignpin +$(info ) +$(info ++++++ If your certificate requires a password, pass it via the KBUILD_SIGN_PIN env-var!) +$(info ++++++ E.g. using 'export KBUILD_SIGN_PIN; read -s -p "Passphrase for signing key $(KBUILD_SIGN_KEY): " KBUILD_SIGN_PIN; sudo --preserve-env=KBUILD_SIGN_PIN make sign') +$(info ) +endef +endif + +define usage_kbuildsign +sign: v4l2loopback.ko + $(info ) + $(info ++++++ To sign the $< module, you must set KBUILD_SIGN_KEY/KBUILD_SIGN_CERT to point to the signing key/certificate!) + $(info ++++++ For your convenience, we try to read these variables as 'mok_signing_key' resp. 'mok_certificate' from $(dkms_framework)) + $(call usage_kbuildsignpin) +endef + +ifeq ($(wildcard $(KBUILD_SIGN_KEY)),) +$(call usage_kbuildsign) +else ifeq ($(wildcard $(KBUILD_SIGN_CERT)),) +$(call usage_kbuildsign) +else +sign: v4l2loopback.ko + $(call usage_kbuildsignpin) + "$(KERNEL_DIR)"/scripts/sign-file sha256 $(KBUILD_SIGN_KEY) $(KBUILD_SIGN_CERT) $< +endif + +endif # !kbuild diff --git a/drivers/custom/v4l2loopback/doc/missingformats.h b/drivers/custom/v4l2loopback/doc/missingformats.h new file mode 100644 index 000000000000..89de33cbe2b8 --- /dev/null +++ b/drivers/custom/v4l2loopback/doc/missingformats.h @@ -0,0 +1,291 @@ +#ifdef V4L2_PIX_FMT_Y10BPACK + }, { + .name = "10 bpp Greyscale bit-packed", + .fourcc = V4L2_PIX_FMT_Y10BPACK, + .depth = 10, + .flags = 0, +#endif /* V4L2_PIX_FMT_Y10BPACK */ +#ifdef V4L2_PIX_FMT_PAL8 + }, { + .name = "8 bpp 8-bit palette", + .fourcc = V4L2_PIX_FMT_PAL8, + .depth = 8, + .flags = 0, +#endif /* V4L2_PIX_FMT_PAL8 */ +#ifdef V4L2_PIX_FMT_UV8 + }, { + .name = "8 bpp UV 4:4", + .fourcc = V4L2_PIX_FMT_UV8, + .depth = 8, + .flags = 0, +#endif /* V4L2_PIX_FMT_UV8 */ + +#ifdef V4L2_PIX_FMT_HI240 + }, { + .name = "8 bpp 8-bit color ", + .fourcc = V4L2_PIX_FMT_HI240, + .depth = 8, + .flags = FORMAT_FLAGS_PLANAR, +#endif /* V4L2_PIX_FMT_HI240 */ +#ifdef V4L2_PIX_FMT_HM12 + }, { + .name = "8 bpp YUV 4:2:0 16x16 macroblocks", + .fourcc = V4L2_PIX_FMT_HM12, + .depth = 8, + .flags = FORMAT_FLAGS_PLANAR, +#endif /* V4L2_PIX_FMT_HM12 */ +#ifdef V4L2_PIX_FMT_M420 + }, { + .name = "12 bpp YUV 4:2:0 2 lines y, 1 line uv interleaved", + .fourcc = V4L2_PIX_FMT_M420, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, +#endif /* V4L2_PIX_FMT_M420 */ + + + +#ifdef V4L2_PIX_FMT_NV12 + }, { + .name = "12 bpp Y/CbCr 4:2:0 ", + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 12, + .flags = 0, +#endif /* V4L2_PIX_FMT_NV12 */ +#ifdef V4L2_PIX_FMT_NV21 + }, { + .name = "12 bpp Y/CrCb 4:2:0 ", + .fourcc = V4L2_PIX_FMT_NV21, + .depth = 12, + .flags = 0, +#endif /* V4L2_PIX_FMT_NV21 */ +#ifdef V4L2_PIX_FMT_NV16 + }, { + .name = "16 bpp Y/CbCr 4:2:2 ", + .fourcc = V4L2_PIX_FMT_NV16, + .depth = 16, + .flags = 0, +#endif /* V4L2_PIX_FMT_NV16 */ +#ifdef V4L2_PIX_FMT_NV61 + }, { + .name = "16 bpp Y/CrCb 4:2:2 ", + .fourcc = V4L2_PIX_FMT_NV61, + .depth = 16, + .flags = 0, +#endif /* V4L2_PIX_FMT_NV61 */ +#ifdef V4L2_PIX_FMT_NV24 + }, { + .name = "24 bpp Y/CbCr 4:4:4 ", + .fourcc = V4L2_PIX_FMT_NV24, + .depth = 24, + .flags = 0, +#endif /* V4L2_PIX_FMT_NV24 */ +#ifdef V4L2_PIX_FMT_NV42 + }, { + .name = "24 bpp Y/CrCb 4:4:4 ", + .fourcc = V4L2_PIX_FMT_NV42, + .depth = 24, + .flags = 0, +#endif /* V4L2_PIX_FMT_NV42 */ +#ifdef V4L2_PIX_FMT_NV12M + }, { + .name = "12 bpp Y/CbCr 4:2:0 ", + .fourcc = V4L2_PIX_FMT_NV12M, + .depth = 12, + .flags = 0, +#endif /* V4L2_PIX_FMT_NV12M */ +#ifdef V4L2_PIX_FMT_NV21M + }, { + .name = "21 bpp Y/CrCb 4:2:0 ", + .fourcc = V4L2_PIX_FMT_NV21M, + .depth = 21, + .flags = 0, +#endif /* V4L2_PIX_FMT_NV21M */ +#ifdef V4L2_PIX_FMT_NV16M + }, { + .name = "16 bpp Y/CbCr 4:2:2 ", + .fourcc = V4L2_PIX_FMT_NV16M, + .depth = 16, + .flags = 0, +#endif /* V4L2_PIX_FMT_NV16M */ +#ifdef V4L2_PIX_FMT_NV61M + }, { + .name = "16 bpp Y/CrCb 4:2:2 ", + .fourcc = V4L2_PIX_FMT_NV61M, + .depth = 16, + .flags = 0, +#endif /* V4L2_PIX_FMT_NV61M */ +#ifdef V4L2_PIX_FMT_NV12MT + }, { + .name = "12 bpp Y/CbCr 4:2:0 64x32 macroblocks", + .fourcc = V4L2_PIX_FMT_NV12MT, + .depth = 12, + .flags = 0, +#endif /* V4L2_PIX_FMT_NV12MT */ +#ifdef V4L2_PIX_FMT_NV12MT_16X16 + }, { + .name = "12 bpp Y/CbCr 4:2:0 16x16 macroblocks", + .fourcc = V4L2_PIX_FMT_NV12MT_16X16, + .depth = 12, + .flags = 0, +#endif /* V4L2_PIX_FMT_NV12MT_16X16 */ +#ifdef V4L2_PIX_FMT_YUV420M + }, { + .name = "12 bpp YUV420 planar", + .fourcc = V4L2_PIX_FMT_YUV420M, + .depth = 12, + .flags = 0, +#endif /* V4L2_PIX_FMT_YUV420M */ +#ifdef V4L2_PIX_FMT_YVU420M + }, { + .name = "12 bpp YVU420 planar", + .fourcc = V4L2_PIX_FMT_YVU420M, + .depth = 12, + .flags = 0, +#endif /* V4L2_PIX_FMT_YVU420M */ +#ifdef V4L2_PIX_FMT_SBGGR8 + }, { + .name = "8 bpp BGBG.. GRGR..", + .fourcc = V4L2_PIX_FMT_SBGGR8, + .depth = 8, + .flags = 0, +#endif /* V4L2_PIX_FMT_SBGGR8 */ +#ifdef V4L2_PIX_FMT_SGBRG8 + }, { + .name = "8 bpp GBGB.. RGRG..", + .fourcc = V4L2_PIX_FMT_SGBRG8, + .depth = 8, + .flags = 0, +#endif /* V4L2_PIX_FMT_SGBRG8 */ +#ifdef V4L2_PIX_FMT_SGRBG8 + }, { + .name = "8 bpp GRGR.. BGBG..", + .fourcc = V4L2_PIX_FMT_SGRBG8, + .depth = 8, + .flags = 0, +#endif /* V4L2_PIX_FMT_SGRBG8 */ +#ifdef V4L2_PIX_FMT_SRGGB8 + }, { + .name = "8 bpp RGRG.. GBGB..", + .fourcc = V4L2_PIX_FMT_SRGGB8, + .depth = 8, + .flags = 0, +#endif /* V4L2_PIX_FMT_SRGGB8 */ +#ifdef V4L2_PIX_FMT_SBGGR10 + }, { + .name = "10 bpp BGBG.. GRGR..", + .fourcc = V4L2_PIX_FMT_SBGGR10, + .depth = 10, + .flags = 0, +#endif /* V4L2_PIX_FMT_SBGGR10 */ +#ifdef V4L2_PIX_FMT_SGBRG10 + }, { + .name = "10 bpp GBGB.. RGRG..", + .fourcc = V4L2_PIX_FMT_SGBRG10, + .depth = 10, + .flags = 0, +#endif /* V4L2_PIX_FMT_SGBRG10 */ +#ifdef V4L2_PIX_FMT_SGRBG10 + }, { + .name = "10 bpp GRGR.. BGBG..", + .fourcc = V4L2_PIX_FMT_SGRBG10, + .depth = 10, + .flags = 0, +#endif /* V4L2_PIX_FMT_SGRBG10 */ +#ifdef V4L2_PIX_FMT_SRGGB10 + }, { + .name = "10 bpp RGRG.. GBGB..", + .fourcc = V4L2_PIX_FMT_SRGGB10, + .depth = 10, + .flags = 0, +#endif /* V4L2_PIX_FMT_SRGGB10 */ +#ifdef V4L2_PIX_FMT_SBGGR12 + }, { + .name = "12 bpp BGBG.. GRGR..", + .fourcc = V4L2_PIX_FMT_SBGGR12, + .depth = 12, + .flags = 0, +#endif /* V4L2_PIX_FMT_SBGGR12 */ +#ifdef V4L2_PIX_FMT_SGBRG12 + }, { + .name = "12 bpp GBGB.. RGRG..", + .fourcc = V4L2_PIX_FMT_SGBRG12, + .depth = 12, + .flags = 0, +#endif /* V4L2_PIX_FMT_SGBRG12 */ +#ifdef V4L2_PIX_FMT_SGRBG12 + }, { + .name = "12 bpp GRGR.. BGBG..", + .fourcc = V4L2_PIX_FMT_SGRBG12, + .depth = 12, + .flags = 0, +#endif /* V4L2_PIX_FMT_SGRBG12 */ +#ifdef V4L2_PIX_FMT_SRGGB12 + }, { + .name = "12 bpp RGRG.. GBGB..", + .fourcc = V4L2_PIX_FMT_SRGGB12, + .depth = 12, + .flags = 0, +#endif /* V4L2_PIX_FMT_SRGGB12 */ +#ifdef V4L2_PIX_FMT_SBGGR10ALAW8 + }, { + .name = "", + .fourcc = V4L2_PIX_FMT_SBGGR10ALAW8, + .depth = 0, + .flags = 0, +#endif /* V4L2_PIX_FMT_SBGGR10ALAW8 */ +#ifdef V4L2_PIX_FMT_SGBRG10ALAW8 + }, { + .name = "", + .fourcc = V4L2_PIX_FMT_SGBRG10ALAW8, + .depth = 0, + .flags = 0, +#endif /* V4L2_PIX_FMT_SGBRG10ALAW8 */ +#ifdef V4L2_PIX_FMT_SGRBG10ALAW8 + }, { + .name = "", + .fourcc = V4L2_PIX_FMT_SGRBG10ALAW8, + .depth = 0, + .flags = 0, +#endif /* V4L2_PIX_FMT_SGRBG10ALAW8 */ +#ifdef V4L2_PIX_FMT_SRGGB10ALAW8 + }, { + .name = "", + .fourcc = V4L2_PIX_FMT_SRGGB10ALAW8, + .depth = 0, + .flags = 0, +#endif /* V4L2_PIX_FMT_SRGGB10ALAW8 */ +#ifdef V4L2_PIX_FMT_SBGGR10DPCM8 + }, { + .name = "", + .fourcc = V4L2_PIX_FMT_SBGGR10DPCM8, + .depth = 0, + .flags = 0, +#endif /* V4L2_PIX_FMT_SBGGR10DPCM8 */ +#ifdef V4L2_PIX_FMT_SGBRG10DPCM8 + }, { + .name = "", + .fourcc = V4L2_PIX_FMT_SGBRG10DPCM8, + .depth = 0, + .flags = 0, +#endif /* V4L2_PIX_FMT_SGBRG10DPCM8 */ +#ifdef V4L2_PIX_FMT_SGRBG10DPCM8 + }, { + .name = "", + .fourcc = V4L2_PIX_FMT_SGRBG10DPCM8, + .depth = 0, + .flags = 0, +#endif /* V4L2_PIX_FMT_SGRBG10DPCM8 */ +#ifdef V4L2_PIX_FMT_SRGGB10DPCM8 + }, { + .name = "", + .fourcc = V4L2_PIX_FMT_SRGGB10DPCM8, + .depth = 0, + .flags = 0, +#endif /* V4L2_PIX_FMT_SRGGB10DPCM8 */ +#ifdef V4L2_PIX_FMT_SBGGR16 + }, { + .name = "16 bpp BGBG.. GRGR..", + .fourcc = V4L2_PIX_FMT_SBGGR16, + .depth = 16, + .flags = 0, +#endif /* V4L2_PIX_FMT_SBGGR16 */ diff --git a/drivers/custom/v4l2loopback/examples/ondemandcam.c b/drivers/custom/v4l2loopback/examples/ondemandcam.c new file mode 100644 index 000000000000..eae56bb7658f --- /dev/null +++ b/drivers/custom/v4l2loopback/examples/ondemandcam.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include /* low-level i/o */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char *v4l2dev = "/dev/video1"; +static int v4l2sink = -1; +static int width = 80; //640; // Default for Flash +static int height = 60; //480; // Default for Flash +static char *vidsendbuf = NULL; +static int vidsendsiz = 0; + +static void init_device() { + +} + +static void grab_frame() { + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + memset( vidsendbuf, 0, 3); + switch( ts.tv_sec & 3 ) { + case 0: + vidsendbuf[0] = 255; + break; + case 1: + vidsendbuf[0] = 255; + vidsendbuf[1] = 255; + break; + case 2: + vidsendbuf[1] = 255; + break; + case 3: + vidsendbuf[2] = 255; + break; + } + memcpy( vidsendbuf+3, vidsendbuf, vidsendsiz-3 ); +} + +static void stop_device() { + +} + +static void open_vpipe() +{ + v4l2sink = open(v4l2dev, O_WRONLY); + if (v4l2sink < 0) { + fprintf(stderr, "Failed to open v4l2sink device. (%s)\n", strerror(errno)); + exit(-2); + } + // setup video for proper format + struct v4l2_format v; + int t; + v.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + t = ioctl(v4l2sink, VIDIOC_G_FMT, &v); + if( t < 0 ) + exit(t); + v.fmt.pix.width = width; + v.fmt.pix.height = height; + v.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; + vidsendsiz = width * height * 3; + v.fmt.pix.sizeimage = vidsendsiz; + t = ioctl(v4l2sink, VIDIOC_S_FMT, &v); + if( t < 0 ) + exit(t); + vidsendbuf = malloc( vidsendsiz ); +} + +static pthread_t sender; +static sem_t lock1,lock2; +static void *sendvid(void *v) +{ + for (;;) { + sem_wait(&lock1); + if (vidsendsiz != write(v4l2sink, vidsendbuf, vidsendsiz)) + exit(-1); + sem_post(&lock2); + } +} + +int main(int argc, char **argv) +{ + struct timespec ts; + + if( argc == 2 ) + v4l2dev = argv[1]; + + open_vpipe(); + + // open and lock response + if (sem_init(&lock2, 0, 1) == -1) + exit(-1); + sem_wait(&lock2); + + if (sem_init(&lock1, 0, 1) == -1) + exit(-1); + pthread_create(&sender, NULL, sendvid, NULL); + + for (;;) { + // wait until a frame can be written + fprintf( stderr, "Waiting for sink\n" ); + sem_wait(&lock2); + // setup source + init_device(); // open and setup SPI + for (;;) { + grab_frame(); + // push it out + sem_post(&lock1); + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 2; + // wait for it to get written (or is blocking) + if (sem_timedwait(&lock2, &ts)) + break; + } + stop_device(); // close SPI + } + close(v4l2sink); + return 0; +} diff --git a/drivers/custom/v4l2loopback/examples/test.c b/drivers/custom/v4l2loopback/examples/test.c new file mode 100644 index 000000000000..ca0b4f549305 --- /dev/null +++ b/drivers/custom/v4l2loopback/examples/test.c @@ -0,0 +1,188 @@ +/* + * How to test v4l2loopback: + * 1. launch this test program (even in background), it will initialize the + * loopback device and keep it open so it won't loose the settings. + * 2. Feed the video device with data according to the settings specified + * below: size, pixelformat, etc. + * For instance, you can try the default settings with this command: + * mencoder video.avi -ovc raw -nosound -vf scale=640:480,format=yuy2 -o /dev/video1 + * TODO: a command that limits the fps would be better :) + * + * Test the video in your favourite viewer, for instance: + * luvcview -d /dev/video1 -f yuyv + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ROUND_UP_2(num) (((num)+1)&~1) +#define ROUND_UP_4(num) (((num)+3)&~3) +#define ROUND_UP_8(num) (((num)+7)&~7) +#define ROUND_UP_16(num) (((num)+15)&~15) +#define ROUND_UP_32(num) (((num)+31)&~31) +#define ROUND_UP_64(num) (((num)+63)&~63) + + + + +#if 0 +# define CHECK_REREAD +#endif + +#define VIDEO_DEVICE "/dev/video0" +#if 1 +# define FRAME_WIDTH 640 +# define FRAME_HEIGHT 480 +#else +# define FRAME_WIDTH 512 +# define FRAME_HEIGHT 512 +#endif + +#if 0 +# define FRAME_FORMAT V4L2_PIX_FMT_YUYV +#else +# define FRAME_FORMAT V4L2_PIX_FMT_YVU420 +#endif + +static int debug = 0; + + +int format_properties(const unsigned int format, + const unsigned int width, + const unsigned int height, + size_t*linewidth, + size_t*framewidth) { +size_t lw, fw; + switch(format) { + case V4L2_PIX_FMT_YUV420: case V4L2_PIX_FMT_YVU420: + lw = width; /* ??? */ + fw = ROUND_UP_4 (width) * ROUND_UP_2 (height); + fw += 2 * ((ROUND_UP_8 (width) / 2) * (ROUND_UP_2 (height) / 2)); + break; + case V4L2_PIX_FMT_UYVY: case V4L2_PIX_FMT_Y41P: case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_YVYU: + lw = (ROUND_UP_2 (width) * 2); + fw = lw * height; + break; + default: + return 0; + } + + if(linewidth)*linewidth=lw; + if(framewidth)*framewidth=fw; + + return 1; +} + + +void print_format(struct v4l2_format*vid_format) { + printf(" vid_format->type =%d\n", vid_format->type ); + printf(" vid_format->fmt.pix.width =%d\n", vid_format->fmt.pix.width ); + printf(" vid_format->fmt.pix.height =%d\n", vid_format->fmt.pix.height ); + printf(" vid_format->fmt.pix.pixelformat =%d\n", vid_format->fmt.pix.pixelformat); + printf(" vid_format->fmt.pix.sizeimage =%d\n", vid_format->fmt.pix.sizeimage ); + printf(" vid_format->fmt.pix.field =%d\n", vid_format->fmt.pix.field ); + printf(" vid_format->fmt.pix.bytesperline=%d\n", vid_format->fmt.pix.bytesperline ); + printf(" vid_format->fmt.pix.colorspace =%d\n", vid_format->fmt.pix.colorspace ); +} + +int main(int argc, char**argv) +{ + struct v4l2_capability vid_caps; + struct v4l2_format vid_format; + + size_t framesize = 0; + size_t linewidth = 0; + + __u8*buffer; + __u8*check_buffer; + + const char*video_device=VIDEO_DEVICE; + int fdwr = 0; + int ret_code = 0; + + int i; + + if(argc>1) { + video_device=argv[1]; + printf("using output device: %s\n", video_device); + } + + fdwr = open(video_device, O_RDWR); + assert(fdwr >= 0); + + ret_code = ioctl(fdwr, VIDIOC_QUERYCAP, &vid_caps); + assert(ret_code != -1); + + memset(&vid_format, 0, sizeof(vid_format)); + + ret_code = ioctl(fdwr, VIDIOC_G_FMT, &vid_format); + if(debug)print_format(&vid_format); + + vid_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + vid_format.fmt.pix.width = FRAME_WIDTH; + vid_format.fmt.pix.height = FRAME_HEIGHT; + vid_format.fmt.pix.pixelformat = FRAME_FORMAT; + vid_format.fmt.pix.sizeimage = framesize; + vid_format.fmt.pix.field = V4L2_FIELD_NONE; + vid_format.fmt.pix.bytesperline = linewidth; + vid_format.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + + if(debug)print_format(&vid_format); + ret_code = ioctl(fdwr, VIDIOC_S_FMT, &vid_format); + + assert(ret_code != -1); + + if(debug)printf("frame: format=%d\tsize=%lu\n", FRAME_FORMAT, framesize); + print_format(&vid_format); + + if(!format_properties(vid_format.fmt.pix.pixelformat, + vid_format.fmt.pix.width, vid_format.fmt.pix.height, + &linewidth, + &framesize)) { + printf("unable to guess correct settings for format '%d'\n", FRAME_FORMAT); + } + buffer=(__u8*)malloc(sizeof(__u8)*framesize); + check_buffer=(__u8*)malloc(sizeof(__u8)*framesize); + + memset(buffer, 0, framesize); + memset(check_buffer, 0, framesize); + for (i = 0; i < framesize; ++i) { + //buffer[i] = i % 2; + check_buffer[i] = 0; + } + + + + + + write(fdwr, buffer, framesize); + +#ifdef CHECK_REREAD + do { + /* check if we get the same data on output */ + int fdr = open(video_device, O_RDONLY); + read(fdr, check_buffer, framesize); + for (i = 0; i < framesize; ++i) { + if (buffer[i] != check_buffer[i]) + assert(0); + } + close(fdr); + } while(0); +#endif + + pause(); + + close(fdwr); + + free(buffer); + free(check_buffer); + + return 0; +} diff --git a/drivers/custom/v4l2loopback/examples/yuv420_infiniteloop.c b/drivers/custom/v4l2loopback/examples/yuv420_infiniteloop.c new file mode 100644 index 000000000000..78a30c0be7e8 --- /dev/null +++ b/drivers/custom/v4l2loopback/examples/yuv420_infiniteloop.c @@ -0,0 +1,121 @@ +/* Read a yuv file directly and play with infinite loop + * + * Example: + * $ ./yuv420_infiniteloop /dev/video1 akiyo_qcif.yuv 176 144 30 + + * This will loop a yuv file named akiyo_qcif.yuv over video 1 + * + * Modified by T. Xu from yuv4mpeg_to_v4l2 example, + * original Copyright (C) 2011 Eric C. Cooper + * Released under the GNU General Public License + */ + +#include +#include +#include +#include +#include +#include +#include + +char *prog; + +struct yuv_setup { + char *device; + char *file_name; + int frame_width; + int frame_height; + int frame_bytes; + float fps; +}; + +void +fail(char *msg) +{ + fprintf(stderr, "%s: %s\n", prog, msg); + exit(1); +} + +struct yuv_setup +process_args(int argc, char **argv) +{ + prog = argv[0]; + struct yuv_setup setup; + if (argc != 6) { + fail("invalid argument"); + } else { + setup.device = argv[1]; + setup.file_name = argv[2]; + setup.frame_width = atoi(argv[3]); + setup.frame_height = atoi(argv[4]); + setup.frame_bytes = 3 * setup.frame_height * setup.frame_width / 2; + setup.fps = atof(argv[5]); + } + return setup; +} + +void +copy_frames(struct yuv_setup setup, int dev_fd) +{ + + FILE * yuv_file = fopen (setup.file_name,"rb"); + if (yuv_file == NULL) { + fail("can not open yuv file"); + } + + char *frame = malloc(setup.frame_bytes); + + if (frame == NULL) { + fail("cannot malloc frame"); + } + + while (1) { + int read_size = fread(frame, 1, setup.frame_bytes, yuv_file); + usleep(1.0f/setup.fps * 1000000.0f); + if (read_size == setup.frame_bytes) { + write(dev_fd, frame, setup.frame_bytes); + } else if (read_size == 0) { + fclose(yuv_file); + yuv_file = fopen (setup.file_name,"rb"); + } else { + free(frame); + fail("invalid frame size or file ending"); + } + } + + free(frame); +} + +int +open_video(struct yuv_setup setup) +{ + struct v4l2_format v; + + int dev_fd = open(setup.device, O_RDWR); + if (dev_fd == -1) { + fail("cannot open video device"); + } + v.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + if (ioctl(dev_fd, VIDIOC_G_FMT, &v) == -1) { + fail("cannot setup video device"); + } + v.fmt.pix.width = setup.frame_width; + v.fmt.pix.height = setup.frame_height; + v.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + v.fmt.pix.sizeimage = setup.frame_bytes; + v.fmt.pix.field = V4L2_FIELD_NONE; + if (ioctl(dev_fd, VIDIOC_S_FMT, &v) == -1) { + fail("cannot setup video device"); + } + + return dev_fd; +} + +int +main(int argc, char **argv) +{ + struct yuv_setup loc_setup = process_args(argc, argv); + int loc_dev = open_video(loc_setup); + copy_frames(loc_setup, loc_dev); + return 0; +} diff --git a/drivers/custom/v4l2loopback/examples/yuv4mpeg_to_v4l2.c b/drivers/custom/v4l2loopback/examples/yuv4mpeg_to_v4l2.c new file mode 100644 index 000000000000..d1d9bdfc0430 --- /dev/null +++ b/drivers/custom/v4l2loopback/examples/yuv4mpeg_to_v4l2.c @@ -0,0 +1,184 @@ +/* + * Copy a YUV4MPEG stream to a v4l2 output device. + * The stream is read from standard input. + * The device can be specified as argument; it defaults to /dev/video0. + * + * Example using mplayer as a producer for the v4l2loopback driver: + * + * $ mkfifo /tmp/pipe + * $ ./yuv4mpeg_to_v4l2 < /tmp/pipe & + * $ mplayer movie.mp4 -vo yuv4mpeg:file=/tmp/pipe + * + * Copyright (C) 2011 Eric C. Cooper + * Released under the GNU General Public License + */ + +#include +#include +#include +#include +#include +#include +#include + +char *prog; + +char *device; +int dev_fd; + +int frame_width; +int frame_height; +int frame_bytes; + +void +usage(void) +{ + fprintf(stderr, "Usage: %s [/dev/videoN]\n", prog); + exit(1); +} + +void +process_args(int argc, char **argv) +{ + prog = argv[0]; + switch (argc) { + case 1: + device = "/dev/video0"; + break; + case 2: + device = argv[1]; + break; + default: + usage(); + break; + } +} + +void +sysfail(char *msg) +{ + perror(msg); + exit(1); +} + +void +fail(char *msg) +{ + fprintf(stderr, "%s: %s\n", prog, msg); + exit(1); +} + +void +bad_header(char *kind) +{ + char msg[64]; + + sprintf(msg, "malformed %s header", kind); + fail(msg); +} + +void +do_tag(char tag, char *value) +{ + switch (tag) { + case 'W': + frame_width = strtoul(value, NULL, 10); + break; + case 'H': + frame_height = strtoul(value, NULL, 10); + break; + } +} + +int +read_header(char *magic) +{ + char *p, *q, *p0; + size_t n; + int first, done; + + p0 = NULL; + if (getline(&p0, &n, stdin) == -1) { + free(p0); + return 0; + } + + q = p = p0; + first = 1; + done = 0; + while (!done) { + while (*q != ' ' && *q != '\n') + if (*q++ == '\0') bad_header(magic); + done = (*q == '\n'); + *q = '\0'; + if (first) + if (strcmp(p, magic) == 0) first = 0; + else bad_header(magic); + else + do_tag(*p, p + 1); + p = ++q; + } + + free(p0); + return 1; +} + +void +process_header(void) +{ + if (!read_header("YUV4MPEG2")) fail("missing YUV4MPEG2 header"); + frame_bytes = 3 * frame_width * frame_height / 2; + if (frame_bytes == 0) fail("frame width or height is missing"); +} + +void +copy_frames(void) +{ + char *frame; + + frame = malloc(frame_bytes); + if (frame == NULL) fail("cannot malloc frame"); + while (read_header("FRAME")) { + if (fread(frame, 1, frame_bytes, stdin) != frame_bytes) { + free(frame); + fail("malformed frame"); + } + else if (write(dev_fd, frame, frame_bytes) != frame_bytes) { + free(frame); + sysfail("write"); + } + } + + free(frame); +} + +#define vidioc(op, arg) \ + if (ioctl(dev_fd, VIDIOC_##op, arg) == -1) \ + sysfail(#op); \ + else + +void +open_video(void) +{ + struct v4l2_format v; + + dev_fd = open(device, O_RDWR); + if (dev_fd == -1) sysfail(device); + v.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + vidioc(G_FMT, &v); + v.fmt.pix.width = frame_width; + v.fmt.pix.height = frame_height; + v.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + v.fmt.pix.sizeimage = frame_bytes; + vidioc(S_FMT, &v); +} + +int +main(int argc, char **argv) +{ + process_args(argc, argv); + process_header(); + open_video(); + copy_frames(); + return 0; +} diff --git a/drivers/custom/v4l2loopback/tests/common.h b/drivers/custom/v4l2loopback/tests/common.h new file mode 100644 index 000000000000..3878deabe30b --- /dev/null +++ b/drivers/custom/v4l2loopback/tests/common.h @@ -0,0 +1,154 @@ +/* -*- c-file-style: "linux" -*- */ +/* + * common.h -- some commong functions + * + * Copyright (C) 2023 IOhannes m zmoelnig (zmoelnig@iem.at) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include + +static char *fourcc2str(unsigned int fourcc, char buf[4]) +{ + buf[0] = (fourcc >> 0) & 0xFF; + buf[1] = (fourcc >> 8) & 0xFF; + buf[2] = (fourcc >> 16) & 0xFF; + buf[3] = (fourcc >> 24) & 0xFF; + + return buf; +} +static const char *field2str(unsigned int field) +{ + switch (field) { + case V4L2_FIELD_ANY: + return "any"; + case V4L2_FIELD_NONE: + return "none"; + case V4L2_FIELD_TOP: + return "top"; + case V4L2_FIELD_BOTTOM: + return "bottom"; + case V4L2_FIELD_INTERLACED: + return "interlaced"; + case V4L2_FIELD_SEQ_TB: + return "seq/topbottom"; + case V4L2_FIELD_SEQ_BT: + return "seq/bottomtop"; + case V4L2_FIELD_ALTERNATE: + return "alternate"; + case V4L2_FIELD_INTERLACED_TB: + return "interlaced/topbottom"; + case V4L2_FIELD_INTERLACED_BT: + return "interlaced/bottomtop"; + + default: + break; + } + return "unknown"; +} + +static const char *buftype2str(unsigned int type) +{ + switch (type) { + default: + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return "CAPTURE"; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + return "CAPTURE(planar)"; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return "OUTPUT"; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return "OUTPUT(planar)"; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + return "OUTPUT(overlay)"; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + return "OVERLAY"; + case V4L2_BUF_TYPE_VBI_CAPTURE: + return "VBI(capture)"; + case V4L2_BUF_TYPE_VBI_OUTPUT: + return "VBI(output)"; + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + return "SlicedVBI(capture)"; + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + return "SlicedVBI(output)"; + case V4L2_BUF_TYPE_SDR_CAPTURE: + return "SDR(capture)"; + case V4L2_BUF_TYPE_SDR_OUTPUT: + return "SDR(output)"; + case V4L2_BUF_TYPE_META_CAPTURE: + return "META(capture)"; + case V4L2_BUF_TYPE_META_OUTPUT: + return "META(output)"; + case V4L2_BUF_TYPE_PRIVATE: + return "private"; + } + return "unknown"; +} + +static const char *bufmemory2str(unsigned int mem) +{ + switch (mem) { + case V4L2_MEMORY_MMAP: + return "MMAP"; + case V4L2_MEMORY_USERPTR: + return "USERPTR"; + case V4L2_MEMORY_OVERLAY: + return "OVERLAY"; + case V4L2_MEMORY_DMABUF: + return "DMABUF"; + default: + break; + } + return "unknown"; +} + +static const char *snprintf_format(char *buf, size_t size, + struct v4l2_format *fmt) +{ + char fourcc[5]; + fourcc[4] = 0; + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + snprintf(buf, size, + "%s:%dx%d:%s bytes/line=%u sizeimage=%u field=%s", + buftype2str(fmt->type), fmt->fmt.pix.width, + fmt->fmt.pix.height, + fourcc2str(fmt->fmt.pix.pixelformat, fourcc), + fmt->fmt.pix.bytesperline, fmt->fmt.pix.sizeimage, + field2str(fmt->fmt.pix.field)); + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + snprintf(buf, size, "%s:%dx%d:%s (%d planes) field=%s", + buftype2str(fmt->type), fmt->fmt.pix_mp.width, + fmt->fmt.pix_mp.height, + fourcc2str(fmt->fmt.pix_mp.pixelformat, fourcc), + fmt->fmt.pix_mp.num_planes, + field2str(fmt->fmt.pix_mp.field)); + default: + snprintf(buf, size, "TODO: %s(type=%d)", __FUNCTION__, + (fmt->type)); + } + return buf; +} + +static const char *snprintf_buffer(char *strbuf, size_t size, + struct v4l2_buffer *buf) +{ + snprintf( + strbuf, size, + "buffer#%d @ %p %s bytesused=%d, length=%d flags=0x%08X field=%s timestamp=%ld.%06ld memory=%s (offset=%d)", + buf->index, buf, buftype2str(buf->type), buf->bytesused, + buf->length, buf->flags, field2str(buf->field), + buf->timestamp.tv_sec, buf->timestamp.tv_usec, + bufmemory2str(buf->memory), buf->m.offset); + return strbuf; +} diff --git a/drivers/custom/v4l2loopback/tests/consumer.c b/drivers/custom/v4l2loopback/tests/consumer.c new file mode 100644 index 000000000000..434b1d27b8af --- /dev/null +++ b/drivers/custom/v4l2loopback/tests/consumer.c @@ -0,0 +1,632 @@ +/* + * V4L2 video capture example + * + * This program can be used and distributed without restrictions. + * + * This program is provided with the V4L2 API + * see http://linuxtv.org/docs.php for more information + */ + +#include +#include +#include +#include + +#include /* getopt_long() */ + +#include /* low-level i/o */ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" + +#define CLEAR(x) memset(&(x), 0, sizeof(x)) + +enum io_method { + IO_METHOD_READ, + IO_METHOD_MMAP, + IO_METHOD_USERPTR, +}; + +struct buffer { + void *start; + size_t length; +}; + +static char *dev_name; +static enum io_method io = IO_METHOD_MMAP; +static int fd = -1; +struct buffer *buffers; +static unsigned int n_buffers; +static int frame_count = 70; + +static void errno_exit(const char *s) +{ + fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno)); + exit(EXIT_FAILURE); +} + +static int xioctl(int fh, unsigned long int request, void *arg) +{ + int r; + + do { + r = ioctl(fh, request, arg); + } while (-1 == r && EINTR == errno); + + return r; +} + +static int read_frame(void) +{ + char strbuf[1024]; + struct v4l2_buffer buf; + unsigned int i; + + switch (io) { + case IO_METHOD_READ: + if (-1 == read(fd, buffers[0].start, buffers[0].length)) { + switch (errno) { + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + errno_exit("read"); + } + } + printf("READ\t%lu@%p\n", buffers[0].length, buffers[0].start); + break; + + case IO_METHOD_MMAP: + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { + switch (errno) { + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + errno_exit("VIDIOC_DQBUF"); + } + } + + printf("MMAP\t%s\n", + snprintf_buffer(strbuf, sizeof(strbuf), &buf)); + assert(buf.index < n_buffers); + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + break; + + case IO_METHOD_USERPTR: + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + + if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { + switch (errno) { + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + errno_exit("VIDIOC_DQBUF"); + } + } + + for (i = 0; i < n_buffers; ++i) + if (buf.m.userptr == (unsigned long)buffers[i].start && + buf.length == buffers[i].length) + break; + + printf("USERPTR\t%s\n", + snprintf_buffer(strbuf, sizeof(strbuf), &buf)); + assert(i < n_buffers); + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + break; + } + + return 1; +} + +static void mainloop(void) +{ + unsigned int count; + int keep_running = 1; + + count = frame_count; + + while (1) { + if (count < 1) + break; + if (frame_count >= 0) { + count--; + } + + for (;;) { + fd_set fds; + struct timeval tv; + int r; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + /* Timeout. */ + tv.tv_sec = 2; + tv.tv_usec = 0; + + r = select(fd + 1, &fds, NULL, NULL, &tv); + + if (-1 == r) { + if (EINTR == errno) + continue; + errno_exit("select"); + } + + if (0 == r) { + fprintf(stderr, "select timeout\n"); + exit(EXIT_FAILURE); + } + + if (read_frame()) + break; + /* EAGAIN - continue select loop. */ + } + } +} + +static void stop_capturing(void) +{ + enum v4l2_buf_type type; + + switch (io) { + case IO_METHOD_READ: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type)) + errno_exit("VIDIOC_STREAMOFF"); + break; + } +} + +static void start_capturing(void) +{ + unsigned int i; + enum v4l2_buf_type type; + + switch (io) { + case IO_METHOD_READ: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + for (i = 0; i < n_buffers; ++i) { + struct v4l2_buffer buf; + + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + } + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) + errno_exit("VIDIOC_STREAMON"); + break; + + case IO_METHOD_USERPTR: + for (i = 0; i < n_buffers; ++i) { + struct v4l2_buffer buf; + + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + buf.index = i; + buf.m.userptr = (unsigned long)buffers[i].start; + buf.length = buffers[i].length; + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + } + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) + errno_exit("VIDIOC_STREAMON"); + break; + } +} + +static void uninit_device(void) +{ + unsigned int i; + + switch (io) { + case IO_METHOD_READ: + free(buffers[0].start); + break; + + case IO_METHOD_MMAP: + for (i = 0; i < n_buffers; ++i) + if (-1 == munmap(buffers[i].start, buffers[i].length)) + errno_exit("munmap"); + break; + + case IO_METHOD_USERPTR: + for (i = 0; i < n_buffers; ++i) + free(buffers[i].start); + break; + } + + free(buffers); +} + +static void init_read(unsigned int buffer_size) +{ + buffers = calloc(1, sizeof(*buffers)); + + if (!buffers) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + + buffers[0].length = buffer_size; + buffers[0].start = malloc(buffer_size); + + if (!buffers[0].start) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } +} + +static void init_mmap(void) +{ + char strbuf[1024]; + const int count = 4; + struct v4l2_requestbuffers req; + + CLEAR(req); + + req.count = count; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + fprintf(stderr, + "%s does not support " + "memory mapping\n", + dev_name); + exit(EXIT_FAILURE); + } else { + errno_exit("VIDIOC_REQBUFS"); + } + } + printf("requested %d buffers, got %d\n", count, req.count); + + if (req.count < 2) { + fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name); + exit(EXIT_FAILURE); + } + + buffers = calloc(req.count, sizeof(*buffers)); + + if (!buffers) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + + for (n_buffers = 0; n_buffers < req.count; ++n_buffers) { + struct v4l2_buffer buf; + + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = n_buffers; + + if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) + errno_exit("VIDIOC_QUERYBUF"); + printf("requested buffer %d/%d: %s\n", n_buffers, count, + snprintf_buffer(strbuf, sizeof(strbuf), &buf)); + + buffers[n_buffers].length = buf.length; + buffers[n_buffers].start = + mmap(NULL /* start anywhere */, buf.length, + PROT_READ | PROT_WRITE /* required */, + MAP_SHARED /* recommended */, fd, buf.m.offset); + + if (MAP_FAILED == buffers[n_buffers].start) + errno_exit("mmap"); + } +} + +static void init_userp(unsigned int buffer_size) +{ + struct v4l2_requestbuffers req; + + CLEAR(req); + + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_USERPTR; + + if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + fprintf(stderr, + "%s does not support " + "user pointer i/o\n", + dev_name); + exit(EXIT_FAILURE); + } else { + errno_exit("VIDIOC_REQBUFS"); + } + } + + buffers = calloc(4, sizeof(*buffers)); + + if (!buffers) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + + for (n_buffers = 0; n_buffers < 4; ++n_buffers) { + buffers[n_buffers].length = buffer_size; + buffers[n_buffers].start = malloc(buffer_size); + + if (!buffers[n_buffers].start) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + } +} + +static void init_device(void) +{ + char strbuf[1024]; + struct v4l2_capability cap; + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + struct v4l2_format fmt; + + if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { + if (EINVAL == errno) { + fprintf(stderr, "%s is no V4L2 device\n", dev_name); + exit(EXIT_FAILURE); + } else { + errno_exit("VIDIOC_QUERYCAP"); + } + } + + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + fprintf(stderr, "%s is no video capture device\n", dev_name); + exit(EXIT_FAILURE); + } + + switch (io) { + case IO_METHOD_READ: + if (!(cap.capabilities & V4L2_CAP_READWRITE)) { + fprintf(stderr, "%s does not support read i/o\n", + dev_name); + exit(EXIT_FAILURE); + } + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + fprintf(stderr, "%s does not support streaming i/o\n", + dev_name); + exit(EXIT_FAILURE); + } + break; + } + + /* Select video input, video standard and tune here. */ + + CLEAR(cropcap); + + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) { + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; /* reset to default */ + + if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) { + switch (errno) { + case EINVAL: + /* Cropping not supported. */ + break; + default: + /* Errors ignored. */ + break; + } + } + } else { + /* Errors ignored. */ + } + + CLEAR(fmt); + + /* get the current format */ + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt)) + errno_exit("VIDIOC_G_FMT"); + printf("got format: %s\n", + snprintf_format(strbuf, sizeof(strbuf), &fmt)); + + /* try to set the current format (no-change should always succeed) */ + if (xioctl(fd, VIDIOC_S_FMT, &fmt) < 0) { + const char *s = "VIDIOC_S_FMT"; + fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno)); + //errno_exit("VIDIOC_S_FMT"); + } + printf("set format: %s\n", + snprintf_format(strbuf, sizeof(strbuf), &fmt)); + + switch (io) { + case IO_METHOD_READ: + init_read(fmt.fmt.pix.sizeimage); + break; + + case IO_METHOD_MMAP: + init_mmap(); + break; + + case IO_METHOD_USERPTR: + init_userp(fmt.fmt.pix.sizeimage); + break; + } +} + +static void close_device(void) +{ + if (-1 == close(fd)) + errno_exit("close"); + + fd = -1; +} + +static void open_device(void) +{ + struct stat st; + + if (-1 == stat(dev_name, &st)) { + fprintf(stderr, "Cannot identify '%s': %d, %s\n", dev_name, + errno, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (!S_ISCHR(st.st_mode)) { + fprintf(stderr, "%s is no device\n", dev_name); + exit(EXIT_FAILURE); + } + + fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0); + + if (-1 == fd) { + fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_name, errno, + strerror(errno)); + exit(EXIT_FAILURE); + } +} + +static void usage(FILE *fp, int argc, char **argv) +{ + fprintf(fp, + "Usage: %s [options]\n\n" + "Version 1.3\n" + "Options:\n" + "-d | --device name Video device name [%s]\n" + "-h | --help Print this message\n" + "-m | --mmap Use memory mapped buffers [default]\n" + "-r | --read Use read() calls\n" + "-u | --userp Use application allocated buffers\n" + "-c | --count Number of frames to grab [%i] (negative numbers: no limit)\n" + "", + argv[0], dev_name, frame_count); +} + +static const char short_options[] = "d:hmruofc:"; + +static const struct option long_options[] = { + { "device", required_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "mmap", no_argument, NULL, 'm' }, + { "read", no_argument, NULL, 'r' }, + { "userp", no_argument, NULL, 'u' }, + { "count", required_argument, NULL, 'c' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char **argv) +{ + dev_name = "/dev/video0"; + + for (;;) { + int idx; + int c; + + c = getopt_long(argc, argv, short_options, long_options, &idx); + + if (-1 == c) + break; + + switch (c) { + case 0: /* getopt_long() flag */ + break; + + case 'd': + dev_name = optarg; + break; + + case 'h': + usage(stdout, argc, argv); + exit(EXIT_SUCCESS); + + case 'm': + io = IO_METHOD_MMAP; + break; + + case 'r': + io = IO_METHOD_READ; + break; + + case 'u': + io = IO_METHOD_USERPTR; + break; + + case 'c': + errno = 0; + frame_count = strtol(optarg, NULL, 0); + if (errno) + errno_exit(optarg); + break; + + default: + usage(stderr, argc, argv); + exit(EXIT_FAILURE); + } + } + + open_device(); + init_device(); + start_capturing(); + mainloop(); + stop_capturing(); + uninit_device(); + close_device(); + fprintf(stderr, "\n"); + return 0; +} diff --git a/drivers/custom/v4l2loopback/tests/producer.c b/drivers/custom/v4l2loopback/tests/producer.c new file mode 100644 index 000000000000..03c73e4acc37 --- /dev/null +++ b/drivers/custom/v4l2loopback/tests/producer.c @@ -0,0 +1,720 @@ +/* + * V4L2 video output example + * + * This program can be used and distributed without restrictions. + * + * This program is provided with the V4L2 API + * see http://linuxtv.org/docs.php for more information + */ + +#include +#include +#include +#include + +#include /* clock_gettime() */ +#include /* getopt_long() */ + +#include /* low-level i/o */ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" + +#define CLEAR(x) memset(&(x), 0, sizeof(x)) + +#define SET_QUEUED(buffer) ((buffer).flags |= V4L2_BUF_FLAG_QUEUED) + +#define IS_QUEUED(buffer) \ + ((buffer).flags & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE)) + +enum io_method { + IO_METHOD_WRITE, + IO_METHOD_MMAP, + IO_METHOD_USERPTR, +}; + +struct buffer { + void *start; + size_t length; + size_t bytesused; +}; + +static char *dev_name; +static enum io_method io = IO_METHOD_MMAP; +static int fd = -1; +struct buffer *buffers; +static unsigned int n_buffers; +static int frame_count = 70; +static unsigned int width = 640; +static unsigned int height = 480; +static unsigned int pixelformat = V4L2_PIX_FMT_YUYV; +static int set_timestamp = 0; +static char strbuf[1024]; + +static void errno_exit(const char *s) +{ + fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno)); + exit(EXIT_FAILURE); +} +static unsigned int str2fourcc(char buf[4]) +{ + return (buf[0]) + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); +} + +static int xioctl(int fh, unsigned long int request, void *arg) +{ + int r; + + do { + r = ioctl(fh, request, arg); + } while (-1 == r && EINTR == errno); + + return r; +} + +static unsigned int random_nextseed = 148985372; +static unsigned char randombyte(void) +{ + random_nextseed = (random_nextseed * 472940017) + 832416023; + return ((random_nextseed >> 16) & 0xFF); +} +static void process_image(unsigned char *data, size_t length) +{ + size_t i; + for (i = 0; i < length; i++) { + data[i] = randombyte(); + } +} + +static int framenum = 0; +static int write_frame(void) +{ + struct v4l2_buffer buf; + unsigned int i; + + switch (io) { + case IO_METHOD_WRITE: + process_image(buffers[0].start, buffers[0].bytesused); + if (-1 == write(fd, buffers[0].start, buffers[0].length)) { + switch (errno) { + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + errno_exit("write"); + } + } + printf("WRITE %p: %lu/%lu\n", buffers[0].start, + buffers[0].bytesused, buffers[0].length); + break; + + case IO_METHOD_MMAP: + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + buf.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { + switch (errno) { + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + errno_exit("VIDIOC_DQBUF"); + } + } + if (set_timestamp) { + struct timespec curTime; + clock_gettime(CLOCK_MONOTONIC, &curTime); + buf.timestamp.tv_sec = curTime.tv_sec; + buf.timestamp.tv_usec = curTime.tv_nsec / 1000ULL; + } else { + buf.timestamp.tv_sec = 0; + buf.timestamp.tv_usec = 0; + } + printf("MMAP\t%s\n", + snprintf_buffer(strbuf, sizeof(strbuf), &buf)); + fflush(stdout); + assert(buf.index < n_buffers); + process_image(buffers[buf.index].start, buf.bytesused); + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + if (!IS_QUEUED(buf)) { + printf("driver pretends buffer is not queued even if queue succeeded\n"); + SET_QUEUED(buf); + } + break; + + case IO_METHOD_USERPTR: + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + buf.memory = V4L2_MEMORY_USERPTR; + + if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { + switch (errno) { + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + errno_exit("VIDIOC_DQBUF"); + } + } + + for (i = 0; i < n_buffers; ++i) + if (buf.m.userptr == (unsigned long)buffers[i].start && + buf.bytesused == buffers[i].bytesused) + break; + + assert(i < n_buffers); + printf("USERPTR\t%s\n", + snprintf_buffer(strbuf, sizeof(strbuf), &buf)); + process_image(buffers[buf.index].start, buf.bytesused); + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + break; + } + + return 1; +} + +static void mainloop(void) +{ + unsigned int count; + int keep_running = 1; + + count = frame_count; + + while (1) { + if (count < 1) + break; + if (frame_count >= 0) { + count--; + } + + for (;;) { + if (write_frame()) + break; + /* EAGAIN - continue select loop. */ + } + usleep(33000); + } +} + +static void stop_capturing(void) +{ + enum v4l2_buf_type type; + + switch (io) { + case IO_METHOD_WRITE: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type)) + errno_exit("VIDIOC_STREAMOFF"); + break; + } +} + +static void start_capturing(void) +{ + unsigned int i; + enum v4l2_buf_type type; + + switch (io) { + case IO_METHOD_WRITE: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + for (i = 0; i < n_buffers; ++i) { + struct v4l2_buffer buf; + + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + buf.length = buffers[i].length; + buf.bytesused = buffers[i].bytesused; + + printf("MMAP init qbuf %d/%d (length=%d): %s\n", i, + n_buffers, buffers[i].length, + snprintf_buffer(strbuf, sizeof(strbuf), &buf)); + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + } + type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) + errno_exit("VIDIOC_STREAMON"); + break; + + case IO_METHOD_USERPTR: + for (i = 0; i < n_buffers; ++i) { + struct v4l2_buffer buf; + + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + buf.memory = V4L2_MEMORY_USERPTR; + buf.index = i; + buf.m.userptr = (unsigned long)buffers[i].start; + buf.bytesused = buffers[i].bytesused; + buf.length = buffers[i].length; + + printf("USERPTR init qbuf %d/%d: %s\n", i, n_buffers, + snprintf_buffer(strbuf, sizeof(strbuf), &buf)); + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + } + type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) + errno_exit("VIDIOC_STREAMON"); + break; + } +} + +static void uninit_device(void) +{ + unsigned int i; + + switch (io) { + case IO_METHOD_WRITE: + free(buffers[0].start); + break; + + case IO_METHOD_MMAP: + for (i = 0; i < n_buffers; ++i) + if (-1 == munmap(buffers[i].start, buffers[i].length)) + errno_exit("munmap"); + break; + + case IO_METHOD_USERPTR: + for (i = 0; i < n_buffers; ++i) + free(buffers[i].start); + break; + } + + free(buffers); +} + +static void init_write(unsigned int buffer_size) +{ + buffers = calloc(1, sizeof(*buffers)); + + if (!buffers) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + + buffers[0].length = buffer_size; + buffers[0].bytesused = buffer_size; + buffers[0].start = malloc(buffer_size); + + if (!buffers[0].start) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } +} + +static void init_mmap(void) +{ + const int count = 4; + struct v4l2_requestbuffers req; + + CLEAR(req); + + req.count = count; + req.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + fprintf(stderr, + "%s does not support " + "memory mapping\n", + dev_name); + exit(EXIT_FAILURE); + } else { + errno_exit("VIDIOC_REQBUFS"); + } + } + printf("requested %d buffers, got %d\n", count, req.count); + + if (req.count < 2) { + fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name); + exit(EXIT_FAILURE); + } + + buffers = calloc(req.count, sizeof(*buffers)); + + if (!buffers) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + + for (n_buffers = 0; n_buffers < req.count; ++n_buffers) { + struct v4l2_buffer buf; + + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = n_buffers; + + if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) + errno_exit("VIDIOC_QUERYBUF"); + printf("requested buffer %d/%d: %s\n", n_buffers, count, + snprintf_buffer(strbuf, sizeof(strbuf), &buf)); + + buffers[n_buffers].length = buf.length; + buffers[n_buffers].bytesused = buf.bytesused; + buffers[n_buffers].start = + mmap(NULL /* start anywhere */, buf.length, + PROT_READ | PROT_WRITE /* required */, + MAP_SHARED /* recommended */, fd, buf.m.offset); + + if (MAP_FAILED == buffers[n_buffers].start) + errno_exit("mmap"); + printf("buffer#%d @%p of %d bytes\n", n_buffers, + buffers[n_buffers].start, buffers[n_buffers].length); + } +} + +static void init_userp(unsigned int buffer_size) +{ + struct v4l2_requestbuffers req; + + CLEAR(req); + + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + req.memory = V4L2_MEMORY_USERPTR; + + if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + fprintf(stderr, + "%s does not support " + "user pointer i/o\n", + dev_name); + exit(EXIT_FAILURE); + } else { + errno_exit("VIDIOC_REQBUFS"); + } + } + + buffers = calloc(4, sizeof(*buffers)); + + if (!buffers) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + + for (n_buffers = 0; n_buffers < 4; ++n_buffers) { + buffers[n_buffers].length = buffer_size; + buffers[n_buffers].start = malloc(buffer_size); + buffers[n_buffers].bytesused = buffer_size; + + if (!buffers[n_buffers].start) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + } +} + +static void init_device(void) +{ + struct v4l2_capability cap; + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + struct v4l2_format fmt; + + if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { + if (EINVAL == errno) { + fprintf(stderr, "%s is no V4L2 device\n", dev_name); + exit(EXIT_FAILURE); + } else { + errno_exit("VIDIOC_QUERYCAP"); + } + } + + if (!(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)) { + fprintf(stderr, "%s is no video output device\n", dev_name); + exit(EXIT_FAILURE); + } + + switch (io) { + case IO_METHOD_WRITE: + if (!(cap.capabilities & V4L2_CAP_READWRITE)) { + fprintf(stderr, "%s does not support write i/o\n", + dev_name); + exit(EXIT_FAILURE); + } + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + fprintf(stderr, "%s does not support streaming i/o\n", + dev_name); + exit(EXIT_FAILURE); + } + break; + } + + /* Select video input, video standard and tune here. */ + + CLEAR(cropcap); + + cropcap.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + + if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) { + crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + crop.c = cropcap.defrect; /* reset to default */ + + if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) { + switch (errno) { + case EINVAL: + /* Cropping not supported. */ + break; + default: + /* Errors ignored. */ + break; + } + } + } else { + /* Errors ignored. */ + } + + CLEAR(fmt); + + /* get the current format */ + fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt)) + errno_exit("VIDIOC_G_FMT"); + printf("get format: %s\n", + snprintf_format(strbuf, sizeof(strbuf), &fmt)); + + /* try to set the current format (no-change should always succeed) */ + if (xioctl(fd, VIDIOC_TRY_FMT, &fmt) < 0) + errno_exit("VIDIOC_TRY_FMT"); + printf("tried format: %s\n", + snprintf_format(strbuf, sizeof(strbuf), &fmt)); + /* and get the format again */ + if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt)) + errno_exit("VIDIOC_G_FMT"); + printf("got format: %s\n", + snprintf_format(strbuf, sizeof(strbuf), &fmt)); + + /* try to set the current format (no-change should always succeed) */ + if (xioctl(fd, VIDIOC_S_FMT, &fmt) < 0) + errno_exit("VIDIOC_S_FMT"); + printf("set format: %s\n", + snprintf_format(strbuf, sizeof(strbuf), &fmt)); + + switch (fmt.type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + fmt.fmt.pix.width = width; + fmt.fmt.pix.height = height; + fmt.fmt.pix.pixelformat = pixelformat; + break; + default: + printf("unable to set format for anything but output/single-plane\n"); + break; + } + printf("finalizing format: %s\n", + snprintf_format(strbuf, sizeof(strbuf), &fmt)); + if (xioctl(fd, VIDIOC_S_FMT, &fmt) < 0) { + fprintf(stderr, "VIDIOC_S_FMT error %d, %s\n", errno, + strerror(errno)); + } + printf("final format: %s\n", + snprintf_format(strbuf, sizeof(strbuf), &fmt)); + + switch (io) { + case IO_METHOD_WRITE: + init_write(fmt.fmt.pix.sizeimage); + break; + + case IO_METHOD_MMAP: + init_mmap(); + break; + + case IO_METHOD_USERPTR: + init_userp(fmt.fmt.pix.sizeimage); + break; + } +} + +static void close_device(void) +{ + if (-1 == close(fd)) + errno_exit("close"); + + fd = -1; +} + +static void open_device(void) +{ + struct stat st; + + if (-1 == stat(dev_name, &st)) { + fprintf(stderr, "Cannot identify '%s': %d, %s\n", dev_name, + errno, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (!S_ISCHR(st.st_mode)) { + fprintf(stderr, "%s is no device\n", dev_name); + exit(EXIT_FAILURE); + } + + fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0); + + if (-1 == fd) { + fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_name, errno, + strerror(errno)); + exit(EXIT_FAILURE); + } +} + +static void usage(FILE *fp, int argc, char **argv) +{ + char fourccstr[5]; + fourccstr[4] = 0; + fprintf(fp, + "Usage: %s [options]\n\n" + "Version 1.3\n" + "Options:\n" + "-d | --device name Video device name [%s]\n" + "-h | --help Print this message\n" + "-m | --mmap Use memory mapped buffers [default]\n" + "-w | --write Use write() calls\n" + "-u | --userp Use application allocated buffers\n" + "-c | --count Number of frames to create [%i] (negative numbers: no limit)\n" + "-f | --format Use format [%dx%d@%s]\n" + "-t | --timestamp Set timestamp\n" + "", + argv[0], dev_name, frame_count, width, height, + fourcc2str(pixelformat, fourccstr)); +} + +static const char short_options[] = "d:hmwuc:f:t"; + +static const struct option long_options[] = { + { "device", required_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "mmap", no_argument, NULL, 'm' }, + { "write", no_argument, NULL, 'w' }, + { "userp", no_argument, NULL, 'u' }, + { "count", required_argument, NULL, 'c' }, + { "format", required_argument, NULL, 'f' }, + { "timestamp", no_argument, NULL, 't' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char **argv) +{ + dev_name = "/dev/video0"; + + for (;;) { + int idx; + int c; + + c = getopt_long(argc, argv, short_options, long_options, &idx); + + if (-1 == c) + break; + + switch (c) { + case 0: /* getopt_long() flag */ + break; + + case 'd': + dev_name = optarg; + break; + + case 'h': + usage(stdout, argc, argv); + exit(EXIT_SUCCESS); + + case 'm': + io = IO_METHOD_MMAP; + break; + + case 'w': + io = IO_METHOD_WRITE; + break; + + case 'u': + io = IO_METHOD_USERPTR; + break; + + case 'c': + errno = 0; + frame_count = strtol(optarg, NULL, 0); + if (errno) + errno_exit(optarg); + break; + + case 'f': { + int n; + int w = 0, h = 0; + char col[5]; + n = sscanf(optarg, "%dx%d@%4c", &w, &h, col); + if (n == 3) { + width = (w > 0) ? w : 0; + height = (h > 0) ? h : 0; + pixelformat = str2fourcc(col); + col[4] = 0; + } else { + errno_exit(optarg); + } + break; + case 't': + set_timestamp = 1; + break; + } + + default: + usage(stderr, argc, argv); + exit(EXIT_FAILURE); + } + } + + open_device(); + init_device(); + start_capturing(); + mainloop(); + stop_capturing(); + uninit_device(); + close_device(); + fprintf(stderr, "\n"); + return 0; +} diff --git a/drivers/custom/v4l2loopback/tests/test_dqbuf.c b/drivers/custom/v4l2loopback/tests/test_dqbuf.c new file mode 100644 index 000000000000..b67cf0438a9e --- /dev/null +++ b/drivers/custom/v4l2loopback/tests/test_dqbuf.c @@ -0,0 +1,134 @@ +/* + * v4l2loopback.c -- video4linux2 loopback driver + * + * Copyright (C) 2014 Nicolas Dufresne + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define COUNT 4 +#define sysfail(msg) \ + { \ + printf("%s failed: %s\n", (msg), strerror(errno)); \ + return -1; \ + } + +void usage(const char *progname) +{ + printf("usage: %s \n", progname); + exit(1); +} + +int main(int argc, char **argv) +{ + struct v4l2_format fmt = { 0 }; + struct v4l2_requestbuffers breq = { 0 }; + struct v4l2_buffer bufs[COUNT]; + void *data[COUNT] = { 0 }; + int fd; + int i; + + if (argc < 2) + usage(argv[0]); + + fd = open(argv[1], O_RDWR); + if (fd < 0) + sysfail("open"); + + fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + fmt.fmt.pix.width = 320; + fmt.fmt.pix.height = 240; + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; + + if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) + sysfail("S_FMT"); + + breq.count = COUNT; + breq.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + breq.memory = V4L2_MEMORY_MMAP; + + if (ioctl(fd, VIDIOC_REQBUFS, &breq) < 0) + sysfail("REQBUFS"); + + assert(breq.count == COUNT); + + memset(bufs, 0, sizeof(bufs)); + + for (i = 0; i < COUNT; i++) { + int p; + + bufs[i].index = i; + bufs[i].type = breq.type; + bufs[i].memory = breq.memory; + + if (ioctl(fd, VIDIOC_QUERYBUF, &bufs[i]) < 0) + sysfail("QUERYBUF"); + + data[i] = mmap(NULL, bufs[i].length, PROT_WRITE, MAP_SHARED, fd, + bufs[i].m.offset); + if (data[i] == MAP_FAILED) + sysfail("mmap"); + + for (p = 0; p < (bufs[i].bytesused >> 2); p++) + ((unsigned int *)data[i])[p] = 0xFF00FF00; + } + + if (ioctl(fd, VIDIOC_QBUF, &bufs[0]) < 0) + sysfail("QBUF"); + + if ((bufs[0].flags & V4L2_BUF_FLAG_QUEUED) == 0) { + printf("BUG #1: Driver should set the QUEUED flag before returning from QBUF\n"); + bufs[0].flags |= V4L2_BUF_FLAG_QUEUED; + } + + if (ioctl(fd, VIDIOC_STREAMON, &fmt.type) < 0) + sysfail("STREAMON"); + + i = 1; + while (1) { + struct v4l2_buffer buf = { 0 }; + + if (ioctl(fd, VIDIOC_QBUF, &bufs[i]) < 0) + sysfail("QBUF"); + + printf("\tQUEUED=%d\tDONE=%d\n", + bufs[i].flags & V4L2_BUF_FLAG_QUEUED, + bufs[i].flags & V4L2_BUF_FLAG_DONE); + + if ((bufs[i].flags & V4L2_BUF_FLAG_QUEUED) == 0) { + printf("BUG #1: Driver should set the QUEUED flag before returning from QBUF\n"); + bufs[i].flags |= V4L2_BUF_FLAG_QUEUED; + } + + buf.type = breq.type; + buf.memory = breq.memory; + + if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) + sysfail("DBBUF"); + + i = buf.index; + + if ((bufs[i].flags & V4L2_BUF_FLAG_QUEUED) == 0) { + printf("BUG #2: Driver should not dequeue a buffer that was not initially queued\n"); + } + +#if 0 + assert (bufs[i].flags & V4L2_BUF_FLAG_QUEUED); + assert (!(buf.flags & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE))); +#endif + bufs[i] = buf; + } + + return 0; +} diff --git a/drivers/custom/v4l2loopback/utils/Makefile b/drivers/custom/v4l2loopback/utils/Makefile new file mode 100644 index 000000000000..99c68620b16f --- /dev/null +++ b/drivers/custom/v4l2loopback/utils/Makefile @@ -0,0 +1,28 @@ +CPPFLAGS += -I.. +ifneq ($(V4L2LOOPBACK_SNAPSHOT_VERSION),) +CPPFLAGS += -DSNAPSHOT_VERSION='"$(V4L2LOOPBACK_SNAPSHOT_VERSION)"' +endif + +prefix?=/usr +exec_prefix = ${prefix} +bindir = ${exec_prefix}/bin +INSTALL = /usr/bin/install -c +INSTALL_PROGRAM = ${INSTALL} +MKDIR_P = /usr/bin/mkdir -p + +.PHONY: default clean + +programs = v4l2loopback-ctl + +default: $(programs) + +clean: + -rm $(programs) + -rm $(programs:%=%.o) + +install: + $(MKDIR_P) $(DESTDIR)$(bindir) + $(INSTALL_PROGRAM) $(programs) $(DESTDIR)$(bindir) + +v4l2loopback-ctl.o: v4l2loopback-ctl.c ../v4l2loopback.h +v4l2loopback-ctl: v4l2loopback-ctl.o diff --git a/drivers/custom/v4l2loopback/utils/v4l2loopback-ctl.c b/drivers/custom/v4l2loopback/utils/v4l2loopback-ctl.c new file mode 100644 index 000000000000..ba996021baab --- /dev/null +++ b/drivers/custom/v4l2loopback/utils/v4l2loopback-ctl.c @@ -0,0 +1,1579 @@ +/* -*- c-file-style: "linux" -*- */ +/* + * v4l2loopback-ctl -- An application to control v4l2loopback devices driver + * + * Copyright (C) 2020-2023 IOhannes m zmoelnig (zmoelnig@iem.at) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "v4l2loopback.h" + +#ifndef GLOB_ONLYDIR +/* Fix for musl libc and other libcs missing GLOB_ONLYDIR at glob.h */ +/* (GLOB_ONLYDIR is not required by POSIX) */ +#define GLOB_ONLYDIR 0 +#endif + +#define CONTROLDEVICE "/dev/v4l2loopback" + +#if 0 +#define MARK() dprintf(2, "%s:%d @ %s\n", __FILE__, __LINE__, __func__) +#else +#define MARK() +#endif + +struct v4l2l_format { + char *name; + int fourcc; /* video4linux 2 */ + int depth; /* bit/pixel */ + int flags; +}; +#define FORMAT_FLAGS_PLANAR 0x01 +#define FORMAT_FLAGS_COMPRESSED 0x02 +#include "../v4l2loopback_formats.h" + +/********************/ +/* helper functions */ + +/* running externals programs */ +static char *which(char *outbuf, size_t bufsize, const char *filename) +{ + struct stat statbuf; + char *paths, *saveptr = NULL; + if (filename && '/' == *filename) { + /* an absolute filename */ + int err = stat(filename, &statbuf); + if (!err) { + snprintf(outbuf, bufsize, "%s", filename); + return outbuf; + } + return NULL; + } + for (paths = getenv("PATH");; paths = NULL) { + char *path = strtok_r(paths, ":", &saveptr); + int err; + if (path == NULL) + return NULL; + snprintf(outbuf, bufsize, "%s/%s", path, filename); + err = stat(outbuf, &statbuf); + if (!err) + return outbuf; + } + return NULL; +} + +static pid_t pid; +void exec_cleanup(int signal) +{ + if (pid) { + switch (signal) { + default: + break; + case SIGINT: + kill(pid, SIGTERM); + break; + } + } + + while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) { + } +} +static int my_execv(char *const *cmdline) +{ + char exe[1024]; + int res = 0; + if (!which(exe, 1024, cmdline[0])) { + dprintf(2, "cannot find %s - is it installed???\n", cmdline[0]); + return 1; + } +#if 0 + do { + char *const *argp = cmdline; + dprintf(2, "%s:", exe); + while (*argp) { + dprintf(2, " %s", *argp++); + }; + dprintf(2, "\n"); + } while(0); +#endif + + pid = fork(); + if (pid == 0) { /* this is the child-process */ + res = execv(exe, cmdline); + if (res < 0) { + dprintf(2, "ERROR running helper program (%d, %d)", res, + errno); + dprintf(2, "failed program was:\n\t"); + while (*cmdline) + dprintf(2, " %s", *cmdline++); + dprintf(2, "\n"); + exit(0); + } + exit(0); + } else if (pid > 0) { /* we are parent: wait for child */ + int status = 0; + int waitoptions = 0; + signal(SIGCHLD, exec_cleanup); + signal(SIGINT, exec_cleanup); + waitpid(pid, &status, waitoptions); + pid = 0; + if (WIFEXITED(status)) + return WEXITSTATUS(status); + return 0; + } else { /* pid < 0, error */ + dprintf(2, "ERROR: child fork failed\n"); + exit(1); + } + return 0; +} + +/* misc */ +static int my_atoi(const char *name, const char *s) +{ + char *endptr = 0; + int n = strtol(s, &endptr, 10); + if (*endptr) { + dprintf(2, "%s must be a number (got: '%s')\n", name, s); + exit(1); + } + return n; +} + +static void printf_raw(const char *str, int escape_level) +{ + const char *backslash = (escape_level > 1) ? "\\\\" : "\\"; + if (escape_level > 0) + while (*str) { + char c = *str++; + switch (c) { + case '\"': + printf("%s\"", backslash); + break; + case '\'': + printf("%s\'", backslash); + break; + case '\\': + printf("%s\\", backslash); + break; + case '\a': + printf("%sa", backslash); + break; + case '\b': + printf("%sb", backslash); + break; + case '\n': + printf("%sn", backslash); + break; + case '\t': + printf("%st", backslash); + break; + // and so on + default: + if (iscntrl(c)) + printf("%s%03o", backslash, c); + else + printf("%c", c); + } + } + else + printf("%s", str); +} + +static char *fourcc2str(unsigned int fourcc, char buf[4]) +{ + buf[0] = (fourcc >> 0) & 0xFF; + buf[1] = (fourcc >> 8) & 0xFF; + buf[2] = (fourcc >> 16) & 0xFF; + buf[3] = (fourcc >> 24) & 0xFF; + + return buf; +} +unsigned int str2fourcc(char buf[4]) +{ + return (buf[0]) + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); +} + +/* helper functions */ +/********************/ + +static unsigned int _get_control_id(int fd, const char *control) +{ + const size_t length = strnlen(control, 1024); + const unsigned next = V4L2_CTRL_FLAG_NEXT_CTRL; + struct v4l2_queryctrl qctrl; + int id; + + memset(&qctrl, 0, sizeof(qctrl)); + while (ioctl(fd, VIDIOC_QUERYCTRL, &qctrl) == 0) { + if (!strncmp(qctrl.name, control, length)) + return qctrl.id; + qctrl.id |= next; + } + for (id = V4L2_CID_USER_BASE; id < V4L2_CID_LASTP1; id++) { + qctrl.id = id; + if (ioctl(fd, VIDIOC_QUERYCTRL, &qctrl) == 0) { + if (!strncmp(qctrl.name, control, length)) + return qctrl.id; + } + } + for (qctrl.id = V4L2_CID_PRIVATE_BASE; + ioctl(fd, VIDIOC_QUERYCTRL, &qctrl) == 0; qctrl.id++) { + if (!strncmp(qctrl.name, control, length)) { + unsigned int id = qctrl.id; + return id; + } + } + return 0; +} + +static int set_control_i(int fd, const char *control, int value) +{ + struct v4l2_control ctrl; + memset(&ctrl, 0, sizeof(ctrl)); + ctrl.id = _get_control_id(fd, control); + ctrl.value = value; + if (ctrl.id && ioctl(fd, VIDIOC_S_CTRL, &ctrl) == 0) { + int value = ctrl.value; + return value; + } + return 0; +} +static int get_control_i(int fd, const char *control) +{ + struct v4l2_control ctrl; + memset(&ctrl, 0, sizeof(ctrl)); + ctrl.id = _get_control_id(fd, control); + + if (ctrl.id && ioctl(fd, VIDIOC_G_CTRL, &ctrl) == 0) { + int value = ctrl.value; + return value; + } + return 0; +} + +/********************/ +/* main logic */ +typedef enum { + VERSION, + HELP, + ADD, + DELETE, + LIST, + QUERY, + SET_FPS, + GET_FPS, + SET_CAPS, + GET_CAPS, + SET_TIMEOUTIMAGE, + MOO, + _UNKNOWN +} t_command; + +static void _help(int detail, const char *section, const char *program, + const char *verb, const char *argstring, + const char *description, const char *options) +{ + (void)section; + if (!detail) { + dprintf(2, "%s%s%s %s\n", program ? program : "", + program ? " " : "", verb, argstring); + return; + } + + dprintf(2, "\n%s%s%s %s:\n", program ? program : "", program ? " " : "", + verb, argstring); + + dprintf(2, " %s\n ", description); + + if (options) { + dprintf(2, "%s\n", options); + //dprintf(2, "\n\n'%s' OPTIONS:%s\n", verb, options); + } +} + +static void help_list(const char *program, int detail) +{ + _help(detail, "Listing Devices", program, "list", "[OPTIONS]", + "list all available loopback-devices", + "\n\t-e, --escape escape control-characters in (device) names" + "\n\t-h, --help print this help and exit" + ""); +} +static void help_add(const char *program, int detail) +{ + _help(detail, "Adding Devices", program, "add", + "[OPTIONS] [ []]", + "create/add a new loopback-device", + "\n\t-b , --buffers buffers to queue" + "\n\t-h , --max-height maximum allowed frame height" + "\n\t-n , --name pretty name for the device" + "\n\t-o , --max-openers maximum allowed concurrent openers" + "\n\t-v, --verbose verbose mode (print properties of device after successfully creating it)" + "\n\t-w , --max-width maximum allowed frame width" + "\n\t-x , --exclusive-caps whether to announce OUTPUT/CAPTURE capabilities exclusively" + "\n\t--min-width minimum allowed frame width" + "\n\t--min-height minimum allowed frame height" + "\n\t-?, --help print this help and exit" + "\n" + "\n \tif given, create a specific device (otherwise just create a free one)." + "\n \teither specify a device name (e.g. '/dev/video1') or a device number ('1')." + "\n \tif given, use separate output & capture devices (otherwise they are the same)."); +} +static void help_delete(const char *program, int detail) +{ + _help(detail, "Deleting Devices", program, "delete", "", + "delete/remove an unused loopback device", + "\n \tcan be given one more more times (to delete multiple devices at once)." + "\n \teither specify a device name (e.g. '/dev/video1') or a device number ('1')."); +} +static void help_query(const char *program, int detail) +{ + _help(detail, "Querying Devices", program, "query", + "[OPTIONS] ", "query information about a loopback device", + "\n\t-e, --escape escape control-characters in (device) names" + "\n\t-h, --help print this help and exit" + "\n" + "\n \tcan be given one more more times (to query multiple devices at once)." + "\n \teither specify a device name (e.g. '/dev/video1') or a device number ('1')."); +} +static void help_setfps(const char *program, int detail) +{ + _help(detail, "Setting Framerate", program, "set-fps", " ", + "set the default framerate for a loopback device", + "\n \teither specify a device name (e.g. '/dev/video1') or a device number ('1')." + "\n \tframes per second, either as integer ('30') or fraction ('50/2')."); +} +static void help_getfps(const char *program, int detail) +{ + _help(detail, "Getting Framerate", program, "get-fps", "", + "query the framerate of a loopback device", 0); +} +static void help_setcaps(const char *program, int detail) +{ + _help(detail, "Setting Capabilities", program, "set-caps", + " ", + "set format/dimension/framerate of a loopback device", + "\n \teither specify a device name (e.g. '/dev/video1') or a device number ('1')." + "\n \tformat specification as ':x@' (e.g. 'UYVY:1024x768@60/1')" + "\n \tunset the current caps with the special value 'any'"); + if (detail > 1) { + dprintf(2, "\nknown fourcc-codes" + "\n==================" + "\nFOURCC\thex \tdec \tdescription" + "\n------\t----------\t------------\t-----------" + ""); + char fourcc[5]; + const size_t num_formats = sizeof(formats) / sizeof(*formats); + size_t i = 0; + for (i = 0; i < num_formats; i++) { + const struct v4l2l_format *fmt = formats + i; + memset(fourcc, 0, 5); + dprintf(2, "'%4s'\t0x%08X\t%12d\t%s\n", + fourcc2str(fmt->fourcc, fourcc), fmt->fourcc, + fmt->fourcc, fmt->name); + } + } +} +static void help_getcaps(const char *program, int detail) +{ + _help(detail, "Getting Capabilities", program, "get-caps", "", + "get current format/dimension/framerate of a loopback device", 0); +} +static void help_settimeoutimage(const char *program, int detail) +{ + _help(detail, "Setting Timeout Image", program, "set-timeout-image", + "[OPTIONS] ", + "set a fallback image to be used if a video producer does not send new frames in time.", + "\n \tany of the following flags may be present" + "\n\t-h, --help print this help and exit" + "\n\t-t , --timeout timeout (in ms)" + "\n\t-v, --verbose raise verbosity (print what is being done)" + "\n" + "\n \teither specify a device name (e.g. '/dev/video1') or a device number ('1')." + "\n \timage file"); +} +static void help_none(const char *program, int detail) +{ +} +typedef void (*t_help)(const char *, int); +static t_help get_help(t_command cmd) +{ + switch (cmd) { + default: + break; + case ADD: + return help_add; + case DELETE: + return help_delete; + case LIST: + return help_list; + case QUERY: + return help_query; + case SET_FPS: + return help_setfps; + case GET_FPS: + return help_getfps; + case SET_CAPS: + return help_setcaps; + case GET_CAPS: + return help_getcaps; + case SET_TIMEOUTIMAGE: + return help_settimeoutimage; + } + return help_none; +} + +static void help(const char *name, int status) +{ + t_command cmd; + dprintf(2, "Usage: %s [OPTIONS]\n", name); + for (cmd = ADD; cmd < _UNKNOWN; cmd++) { + t_help hlp = get_help(cmd); + if (help_none == hlp) + continue; + dprintf(2, " or: %s ", name); + hlp(0, 0); + } + + dprintf(2, "\nManage v4l2 loopback devices."); + dprintf(2, + "\n" + "\nThe general invocation uses a verb (like 'add' or 'delete') that defines" + "\nan action to be executed. Each verb has their own options and arguments." + "\n" + "\nOptions:" + "\n\t-h, -?, --help: print this help and exit" + "\n\t-v, --version: print version and exit" + "\n\n"); + + /* long helps */ + dprintf(2, "*Verbs and their arguments*\n"); + for (cmd = ADD; cmd < _UNKNOWN; cmd++) { + t_help hlp = get_help(cmd); + if (help_none == hlp) + continue; + hlp("v4l2loopback-ctl", 1); + dprintf(2, "\n\n"); + } + + dprintf(2, + "*Reporting Bugs*\n" + "\nIssue tracker: https://github.com/umlaeute/v4l2loopback/issues" + "\nSecurity Issue tracker: https://git.iem.at/zmoelnig/v4l2loopback/-/issues" + "\n\n"); + exit(status); +} +static void usage(const char *name) +{ + help(name, 1); +} +static void usage_topic(const char *name, t_command cmd, int argc, char **argv) +{ + t_help hlp = get_help(cmd); + if (help_none == hlp) + usage(name); + else + hlp(name, 2); + dprintf(2, "\n"); + exit(1); +} + +static const char *my_realpath(const char *path, char *resolved_path) +{ + char *str = realpath(path, resolved_path); + return str ? str : path; +} +static int parse_device(const char *devicename_) +{ + char devicenamebuf[4096]; + const char *devicename = my_realpath(devicename_, devicenamebuf); + int ret = strncmp(devicename, "/dev/video", 10); + const char *device = (ret) ? devicename : (devicename + 10); + char *endptr = 0; + int dev = strtol(device, &endptr, 10); + if (!*endptr) + return dev; + + return -1; +} + +static void print_conf(struct v4l2_loopback_config *cfg, int escape_level) +{ + int output_nr, capture_nr; + MARK(); + if (!cfg) { + printf("configuration: %p\n", cfg); + return; + } + output_nr = capture_nr = cfg->output_nr; +#ifdef SPLIT_DEVICES + capture_nr = cfg->capture_nr; +#endif + MARK(); + printf("\tcapture_device# : %d" + "\n\toutput_device# : %d" + "\n\tcard_label : ", + capture_nr, output_nr); + printf_raw(cfg->card_label, escape_level); + printf("\n\tmin_width : %d" + "\n\tmax_width : %d" + "\n\tmin_height : %d" + "\n\tmax_height : %d" + "\n\tannounce_all_caps: %d" + "\n\tmax_buffers : %d" + "\n\tmax_openers : %d" + "\n\tdebug : %d" + "\n", + cfg->min_width, cfg->max_width, cfg->min_height, cfg->max_height, + cfg->announce_all_caps, cfg->max_buffers, cfg->max_openers, + cfg->debug); + MARK(); +} + +static struct v4l2_loopback_config * +make_conf(struct v4l2_loopback_config *cfg, const char *label, int min_width, + int max_width, int min_height, int max_height, int exclusive_caps, + int buffers, int openers, int capture_device, int output_device) +{ + if (!cfg) + return 0; + /* check if at least one of the args are non-default */ + if (!label && min_width <= 0 && max_width <= 0 && min_height <= 0 && + max_height <= 0 && exclusive_caps < 0 && buffers <= 0 && + openers <= 0 && capture_device < 0 && output_device < 0) + return 0; +#ifdef SPLIT_DEVICES + cfg->capture_nr = capture_device; +#endif + cfg->output_nr = output_device; + cfg->card_label[0] = 0; + if (label) + snprintf(cfg->card_label, 32, "%s", label); + cfg->min_width = (min_width < 0) ? 0 : min_width; + cfg->max_width = (max_width < 0) ? 0 : max_width; + cfg->min_height = (min_height < 0) ? 0 : min_height; + cfg->max_height = (max_height < 0) ? 0 : max_height; + cfg->announce_all_caps = (exclusive_caps < 0) ? -1 : !exclusive_caps; + cfg->max_buffers = buffers; + cfg->max_openers = openers; + cfg->debug = 0; + return cfg; +} + +static int add_device(int fd, struct v4l2_loopback_config *cfg, int verbose) +{ + int err = 0; + MARK(); + int ret = ioctl(fd, V4L2LOOPBACK_CTL_ADD, cfg); + MARK(); + if (ret < 0) { + err = errno; + perror("failed to create device"); + return err; + } + MARK(); + + printf("/dev/video%d\n", ret); + + if (verbose > 0) { + MARK(); + struct v4l2_loopback_config config; + memset(&config, 0, sizeof(config)); + config.output_nr = ret; +#ifdef SPLIT_DEVICES + config.capture_nr = ret; +#endif + ret = ioctl(fd, V4L2LOOPBACK_CTL_QUERY, &config); + if (ret < 0) { + err = errno; + perror("failed querying newly added device"); + } + MARK(); + print_conf(&config, 0); + MARK(); + } + return err; +} + +static int delete_device(int fd, const char *devicename) +{ + int err = 0; + int dev = parse_device(devicename); + if (dev < 0) { + dprintf(2, "ignoring illegal devicename '%s'\n", devicename); + return 1; + } + if (ioctl(fd, V4L2LOOPBACK_CTL_REMOVE, dev) < 0) { + err = errno; + perror(devicename); + } + + return err; +} + +static int query_device(int fd, const char *devicename, int escape) +{ + int err; + struct v4l2_loopback_config config; + int dev = parse_device(devicename); + if (dev < 0) { + dprintf(2, "ignoring illegal devicename '%s'\n", devicename); + return 1; + } + + memset(&config, 0, sizeof(config)); + config.output_nr = dev; +#ifdef SPLIT_DEVICES + config.capture_nr = dev; +#endif + err = ioctl(fd, V4L2LOOPBACK_CTL_QUERY, &config); + if (err) + perror("query failed"); + else { + printf("%s\n", devicename); + print_conf(&config, escape); + return 0; + } + return err; +} +static int list_devices(int fd, int escape) +{ + struct devnode_ { + int output, capture; + char name[32]; + } *devices = 0; + size_t numdevices = 0, i; + glob_t globbuf = { 0 }; + int output_nr, capture_nr; + glob("/sys/devices/virtual/video4linux/video*", GLOB_ONLYDIR, 0, + &globbuf); + if (globbuf.gl_pathc) { + devices = malloc(globbuf.gl_pathc * sizeof(*devices)); + } + for (i = 0; i < globbuf.gl_pathc; i++) { + size_t j; + struct v4l2_loopback_config config = { 0 }; + int dev = -1; + char *endptr; + struct stat sb; + const char *path = globbuf.gl_pathv[i]; + if (lstat(path, &sb)) { + //perror("stat"); + continue; + } + if (!S_ISDIR(sb.st_mode)) { + //dprintf(2, "not a directory\n"); + continue; + } + dev = strtol(path + 38, &endptr, 10); + if (*endptr) { + //dprintf(2, "unable to parse device-name\n"); + continue; + } + /* check if this is a loopback device */ + config.output_nr = dev; +#ifdef SPLIT_DEVICES + config.capture_nr = -1; + if (ioctl(fd, V4L2LOOPBACK_CTL_QUERY, &config)) { + memset(&config, 0, sizeof(config)); + config.output_nr = -1; + config.capture_nr = dev; + if (ioctl(fd, V4L2LOOPBACK_CTL_QUERY, &config)) { + //dprintf(2, "not a loopback device\n"); + continue; + } + } + capture_nr = config.capture_nr; +#else + if (ioctl(fd, V4L2LOOPBACK_CTL_QUERY, &config)) { + //dprintf(2, "not a loopback device\n"); + continue; + } + capture_nr = config.output_nr; +#endif + output_nr = config.output_nr; + /* check if we already have this device */ + for (j = 0; j < numdevices; j++) { + if ((devices[j].output == output_nr) && + (devices[j].capture == capture_nr)) { + //dprintf(2, "duplicate device\n"); + output_nr = capture_nr = -1; + break; + } + } + if ((output_nr < 0) || (capture_nr < 0)) + continue; + + devices[numdevices].output = output_nr; + devices[numdevices].capture = capture_nr; + snprintf(devices[numdevices].name, + sizeof(devices[numdevices].name), "%s", + config.card_label); + numdevices++; + } + if (numdevices) { + dprintf(2, "OUTPUT \tCAPTURE \tNAME\n"); + } else { + dprintf(2, "no loopback devices found\n"); + } + for (i = 0; i < numdevices; i++) { + const char *str = devices[i].name; + printf("/dev/video%-3d\t/dev/video%-3d\t", devices[i].output, + devices[i].capture); + printf_raw(str, escape); + printf("\n"); + } + globfree(&globbuf); + free(devices); + return 0; +} +static int open_videodevice(const char *devicename, int mode) +{ + int fd = open(devicename, mode); + if (fd < 0) { + int devnr = parse_device(devicename); + if (devnr >= 0) { + char devname[100]; + snprintf(devname, 99, "/dev/video%d", devnr); + devname[99] = 0; + fd = open(devname, mode); + } + } + return fd; +} +static int open_controldevice() +{ + int fd = open(CONTROLDEVICE, 0); + if (fd < 0) { + perror("unable to open control device '" CONTROLDEVICE "'"); + exit(1); + } + return fd; +} + +static int open_sysfs_file(const char *devicename, const char *filename, + int flags) +{ + int fd = -1; + char sysdev[100]; + int dev = parse_device(devicename); + if (dev < 0) { + dprintf(2, "ignoring illegal devicename '%s'\n", devicename); + return -1; + } + snprintf(sysdev, sizeof(sysdev) - 1, + "/sys/devices/virtual/video4linux/video%d/%s", dev, filename); + sysdev[sizeof(sysdev) - 1] = 0; + fd = open(sysdev, flags); + if (fd < 0) { + perror(sysdev); + return -1; + } + //dprintf(2, "%s\n", sysdev); + return fd; +} + +static int parse_fps(const char *fps, int *numerator, int *denominator) +{ + int num = 0; + int denom = 1; + if (sscanf(fps, "%d/%d", &num, &denom) <= 0) { + return 1; + } + if (numerator) + *numerator = num; + if (denominator) + *denominator = denom; + return 0; +} +static int is_fps(const char *fps) +{ + return parse_fps(fps, 0, 0); +} + +static int set_fps(const char *devicename, const char *fps) +{ + int result = 1; + char _fps[100]; + int fd = open_sysfs_file(devicename, "format", O_WRONLY); + if (fd < 0) + return 1; + snprintf(_fps, sizeof(_fps) - 1, "@%s", fps); + _fps[sizeof(_fps) - 1] = 0; + + if (write(fd, _fps, strnlen(_fps, sizeof(_fps))) < 0) { + perror("failed to set fps"); + goto done; + } + + result = 0; +done: + close(fd); + return result; +} + +typedef struct _caps { + unsigned int fourcc; + int width, height; + int fps_num, fps_denom; +} t_caps; + +static void print_caps(t_caps *caps) +{ + char fourcc[4]; + if (!caps) { + dprintf(2, "no caps\n"); + return; + } + dprintf(2, "FOURCC : %.4s\n", fourcc2str(caps->fourcc, fourcc)); + dprintf(2, "dimen : %dx%d\n", caps->width, caps->height); + dprintf(2, "fps : %d/%d\n", caps->fps_num, caps->fps_denom); +} +static int parse_caps(const char *buffer, t_caps *caps) +{ + char fourcc[5]; + memset(caps, 0, sizeof(*caps)); + memset(fourcc, 0, sizeof(*fourcc)); + caps->fps_denom = 1; + + if (!(buffer && *buffer)) + return 1; + + if (sscanf(buffer, "%4c:%dx%d@%d/%d", fourcc, &caps->width, + &caps->height, &caps->fps_num, &caps->fps_denom) <= 0) { + } + caps->fourcc = str2fourcc(fourcc); + return (0 == caps->fourcc); +} +static int read_caps(const char *devicename, t_caps *caps) +{ + int result = 1; + char _caps[100]; + int len; + int fd = open_sysfs_file(devicename, "format", O_RDONLY); + if (fd < 0) + return 1; + + len = read(fd, _caps, 100); + if (len <= 0) { + if (len) + perror("failed to read fps"); + goto done; + } + _caps[100 - 1] = 0; + if (caps) { + if (parse_caps(_caps, caps)) { + dprintf(2, "unable to parse format '%s'\n", _caps); + goto done; + } + } + result = 0; +done: + close(fd); + return result; +} + +static int get_fps(const char *devicename) +{ + t_caps caps; + struct v4l2_streamparm param; + int fd = -1; + int num = -1, denom = -1; + int ret = 0; + + if (!read_caps(devicename, &caps)) { + num = caps.fps_num; + denom = caps.fps_denom; + goto done; + } + + /* get the framerate via ctls */ + fd = open_videodevice(devicename, O_RDWR); + if (fd < 0) { + ret = 1; + goto done; + } + + memset(¶m, 0, sizeof(param)); + param.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + if (ioctl(fd, VIDIOC_G_PARM, ¶m) == 0) { + const struct v4l2_fract *tf = ¶m.parm.output.timeperframe; + num = tf->numerator; + denom = tf->denominator; + goto done; + } + + memset(¶m, 0, sizeof(param)); + param.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(fd, VIDIOC_G_PARM, ¶m) == 0) { + const struct v4l2_fract *tf = ¶m.parm.output.timeperframe; + num = tf->numerator; + denom = tf->denominator; + goto done; + } + + ret = 1; +done: + if (fd >= 0) + close(fd); + printf("%d/%d\n", num, denom); + return ret; +} +static int set_caps(const char *devicename, const char *capsstring) +{ + int result = 1; + int fd = open_videodevice(devicename, O_RDWR); + struct v4l2_format vid_format; + struct v4l2_capability vid_caps; + t_caps caps; + + /* now open up the device */ + if (fd < 0) + goto done; + + if (!strncmp("any", capsstring, 4)) { + /* skip caps-parsing */ + } else if (!strncmp("video/", capsstring, 6)) { + dprintf(2, + "ERROR: GStreamer-style caps are no longer supported!\n"); + dprintf(2, + "ERROR: use ':x[@] instead\n"); + dprintf(2, + " e.g. 'UYVY:640x480@30/1' or 'RGBA:1024x768'\n"); + goto done; + } else if (parse_caps(capsstring, &caps)) { + dprintf(2, "unable to parse format '%s'\n", capsstring); + goto done; + } + //print_caps(&caps); + + /* check whether this is actually a video-device */ + if (ioctl(fd, VIDIOC_QUERYCAP, &vid_caps) == -1) { + perror("VIDIOC_QUERYCAP"); + goto done; + } + + if (!strncmp("any", capsstring, 4)) { + set_control_i(fd, "keep_format", 0); + //set_control_i(fd, "sustain_framerate", 0); + result = 0; + goto done; + } + + /* try to get the default values for the format first */ + memset(&vid_format, 0, sizeof(vid_format)); + + vid_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + if (ioctl(fd, VIDIOC_G_FMT, &vid_format) == -1) { + perror("VIDIOC_G_FMT"); + } + + /* and set those caps that we have */ + if (caps.width) + vid_format.fmt.pix.width = caps.width; + if (caps.height) + vid_format.fmt.pix.height = caps.height; + if (caps.fourcc) + vid_format.fmt.pix.pixelformat = caps.fourcc; + + if (ioctl(fd, VIDIOC_S_FMT, &vid_format) == -1) { + perror("unable to set requested format"); + goto done; + } + + set_control_i(fd, "keep_format", 1); + + /* finally, try setting the fps */ + if (caps.fps_num && caps.fps_denom) { + char fps[100]; + int didit; + snprintf(fps, 100, "%d/%d", caps.fps_num, caps.fps_denom); + didit = set_fps(devicename, fps); + if (!didit) { + set_control_i(fd, "sustain_framerate", 1); + } + close(fd); + fd = -1; + return didit; + } + + result = 0; +done: + if (fd >= 0) + close(fd); + return result; +} +static int get_caps(const char *devicename) +{ + int format = 0; + t_caps caps; + char fourcc[4]; + if (read_caps(devicename, &caps)) + return 1; + switch (format) { + default: + printf("%.4s:%dx%d@%d/%d\n", fourcc2str(caps.fourcc, fourcc), + caps.width, caps.height, caps.fps_num, caps.fps_denom); + break; + case 1: /* GStreamer-1.0 */ + + /* FOURCC is different everywhere... */ + switch (caps.fourcc) { + default: + break; + case 0x56595559: /* YUYV */ + caps.fourcc = str2fourcc("YUY2"); + break; + } + + printf("video/x-raw,format=%.4s,width=%d,height=%d,framerate=%d/%d\n", + fourcc2str(caps.fourcc, fourcc), caps.width, caps.height, + caps.fps_num, caps.fps_denom); + break; + } + return 0; +} +static int set_timeoutimage(const char *devicename, const char *imagefile, + int timeout, int verbose) +{ + int err = 0; + int fd = -1; + char imagearg[4096], imagefile2[4096], devicearg[4096]; + char *args[] = { "gst-launch-1.0", + "uridecodebin", + 0, + "!", + "videoconvert", + "!", + "videoscale", + "!", + "imagefreeze", + "!", + "identity", + "eos-after=2", + "drop-allocation=1", + "!", + "v4l2sink", + "show-preroll-frame=false", + 0, + 0 }; + if (verbose) + printf("set-timeout-image '%s' for '%s' with %dms timeout\n", + imagefile, devicename, timeout); + + snprintf(imagearg, 4096, "uri=file://%s", + realpath(imagefile, imagefile2)); + snprintf(devicearg, 4096, "device=%s", devicename); + imagearg[4095] = devicearg[4095] = 0; + args[2] = imagearg; + args[16] = devicearg; + + fd = open_videodevice(devicename, O_RDWR); + if (fd >= 0) { + dprintf(2, "v4l2-ctl -d %s -c timeout_image_io=1\n", + devicename); + set_control_i(fd, "timeout_image_io", 1); + close(fd); + } else { + err = errno; + } + + if (verbose > 1) { + char **ap = args; + while (*ap) { + dprintf(2, "%s", *ap); + if (*ap++) + dprintf(2, " "); + else + dprintf(2, "\n"); + } + } + + dprintf(2, + "v======================================================================v\n"); + if (my_execv(args)) { + dprintf(2, "ERROR: setting time-out image failed\n"); + err = 1; + } + dprintf(2, + "^======================================================================^\n"); + + fd = open_videodevice(devicename, O_RDWR); + if (fd >= 0) { + /* finally check the timeout */ + if (timeout < 0) { + timeout = get_control_i(fd, "timeout"); + } else { + dprintf(2, "v4l2-ctl -d %s -c timeout=%d\n", devicename, + timeout); + timeout = set_control_i(fd, "timeout", timeout); + } + if (timeout <= 0) { + dprintf(2, + "Timeout is currently disabled; you can set it to some positive value, e.g.:\n"); + dprintf(2, " $ v4l2-ctl -d %s -c timeout=3000\n", + devicename); + } + + close(fd); + } else { + err = errno; + } + return err; +} + +static t_command get_command(const char *command) +{ + if (!strncmp(command, "-h", 3)) + return HELP; + if (!strncmp(command, "-?", 3)) + return HELP; + if (!strncmp(command, "--help", 7)) + return HELP; + if (!strncmp(command, "-v", 3)) + return VERSION; + if (!strncmp(command, "--version", 10)) + return VERSION; + if (!strncmp(command, "list", 5)) + return LIST; + if (!strncmp(command, "add", 4)) + return ADD; + if (!strncmp(command, "del", 3)) /* also allow delete */ + return DELETE; + if (!strncmp(command, "query", 6)) + return QUERY; + if (!strncmp(command, "set-fps", 8)) + return SET_FPS; + if (!strncmp(command, "get-fps", 8)) + return GET_FPS; + if (!strncmp(command, "set-caps", 9)) + return SET_CAPS; + if (!strncmp(command, "get-caps", 9)) + return GET_CAPS; + if (!strncmp(command, "set-timeout-image", 18)) + return SET_TIMEOUTIMAGE; + if (!strncmp(command, "moo", 10)) + return MOO; + return _UNKNOWN; +} + +typedef int (*t_argcheck)(const char *); +static int called_deprecated(const char *device, const char *argument, + const char *programname, const char *cmdname, + const char *argname, t_argcheck argcheck) +{ + /* check if does not look like a device, but does + * if so, assume that the user swapped the two */ + /* if the looks about right, optionally do some extra + * -check, to see if it can be used + */ + + int deviceswapped = 0; + int argswapped = 0; + + if (argcheck) + argswapped = + ((argcheck(argument) != 0) && (argcheck(device) == 0)); + + if (!argswapped) + deviceswapped = (parse_device(device) < 0 && + parse_device(argument) >= 0); + + if (argswapped || deviceswapped) { + dprintf(2, "WARNING: '%s %s <%s> ' is deprecated!\n", + programname, cmdname, argname); + dprintf(2, "WARNING: use '%s %s <%s>' instead.\n", + programname, cmdname, argname); + return 1; + } + return 0; +} + +static int do_defaultargs(const char *progname, t_command cmd, int argc, + char **argv) +{ + static const char options_short[] = "?h"; + static const struct option options_long[] = { + { "help", no_argument, NULL, 'h' }, { 0, 0, 0, 0 } + }; + for (;;) { + int c; + int idx; + c = getopt_long(argc - 1, argv + 1, options_short, options_long, + &idx); + if (-1 == c) + break; + switch (c) { + case 'h': + usage_topic(argv[0], cmd, argc - 1, argv + 1); + exit(0); + default: + usage_topic(argv[0], cmd, argc - 1, argv + 1); + exit(1); + } + } + return optind; +} + +int main(int argc, char **argv) +{ + const char *progname = argv[0]; + int i; + int fd = -1; + int verbose = 0; + t_command cmd; + + char *label = 0; + int min_width = -1; + int max_width = -1; + int min_height = -1; + int max_height = -1; + int exclusive_caps = -1; + int buffers = -1; + int openers = -1; + int escape_strings = 0; + + int ret = 0; + + static const char add_options_short[] = "?vn:w:h:x:b:o:"; + static const struct option add_options_long[] = { + { "help", no_argument, NULL, '?' }, + { "verbose", no_argument, NULL, 'v' }, + { "name", required_argument, NULL, 'n' }, + { "min-width", required_argument, NULL, 'w' + 0xFFFF }, + { "max-width", required_argument, NULL, 'w' }, + { "min-height", required_argument, NULL, 'h' + 0xFFFF }, + { "max-height", required_argument, NULL, 'h' }, + { "exclusive-caps", required_argument, NULL, 'x' }, + { "buffers", required_argument, NULL, 'b' }, + { "max-openers", required_argument, NULL, 'o' }, + { 0, 0, 0, 0 } + }; + static const char list_options_short[] = "?he"; + static const struct option list_options_long[] = { + { "help", no_argument, NULL, 'h' }, + { "escape", no_argument, NULL, 'e' }, + { 0, 0, 0, 0 } + }; + static const char timeoutimg_options_short[] = "?ht:v"; + static const struct option timeoutimg_options_long[] = { + { "help", no_argument, NULL, 'h' }, + { "timeout", required_argument, NULL, 't' }, + { "verbose", no_argument, NULL, 'v' }, + { 0, 0, 0, 0 } + }; + + if (argc < 2) + usage(progname); + cmd = get_command(argv[1]); + if (_UNKNOWN == cmd) { + dprintf(2, "unknown command '%s'\n\n", argv[1]); + usage(progname); + return 1; + } + argc--; + argv++; + switch (cmd) { + case HELP: + help(progname, 0); + break; + case LIST: + for (;;) { + int c; + int idx; + c = getopt_long(argc, argv, list_options_short, + list_options_long, &idx); + if (-1 == c) + break; + switch (c) { + case 'e': + escape_strings++; + break; + default: + usage_topic(progname, cmd, argc - 1, argv + 1); + return 1; + } + } + argc -= optind; + argv += optind; + + if (argc) { + dprintf(2, "'list' does not take any arguments\n"); + return 1; + } + fd = open_controldevice(); + if (fd >= 0) + list_devices(fd, escape_strings); + else + return 1; + break; + case ADD: + for (;;) { + int c; + int idx; + c = getopt_long(argc, argv, add_options_short, + add_options_long, &idx); + if (-1 == c) + break; + switch (c) { + case 'v': + verbose++; + break; + case 'n': + label = optarg; + break; + case 'w' + 0xFFFF: + min_width = my_atoi("min_width", optarg); + break; + case 'h' + 0xFFFF: + min_height = my_atoi("min_height", optarg); + break; + case 'w': + max_width = my_atoi("max_width", optarg); + break; + case 'h': + max_height = my_atoi("max_height", optarg); + break; + case 'x': + exclusive_caps = + my_atoi("exclusive_caps", optarg); + break; + case 'b': + buffers = my_atoi("buffers", optarg); + break; + case 'o': + openers = my_atoi("openers", optarg); + break; + default: + usage_topic(progname, cmd, argc - 1, argv + 1); + return 1; + } + } + argc -= optind; + argv += optind; + fd = open_controldevice(); + if (min_width > max_width && max_width > 0) { + dprintf(2, + "min_width (%d) must not be greater than max_width (%d)\n", + min_width, max_width); + return 1; + } + if (min_height > max_height && max_height > 0) { + dprintf(2, + "min_height (%d) must not be greater than max_height (%d)\n", + min_height, max_height); + return 1; + } + do { + struct v4l2_loopback_config cfg; + int capture_nr = -1, output_nr = -1; + switch (argc) { + case 0: + /* no device given: pick some */ + break; + case 2: + /* two devices given: capture_device and output_device */ + output_nr = parse_device(argv[0]); + capture_nr = parse_device(argv[1]); +#ifndef SPLIT_DEVICES + if (capture_nr != output_nr) + dprintf(2, + "split output/capture devices currently not supported...ignoring capture device\n"); + capture_nr = output_nr; +#endif + break; + case 1: + /* single device given: use it for both input and output */ + capture_nr = output_nr = parse_device(argv[0]); + break; + default: + usage_topic(progname, cmd, argc, argv); + return 1; + } + ret = add_device(fd, + make_conf(&cfg, label, min_width, + max_width, min_height, + max_height, exclusive_caps, + buffers, openers, capture_nr, + output_nr), + verbose); + } while (0); + break; + case DELETE: + optind = do_defaultargs(progname, cmd, argc, argv); + argc -= optind; + argv += optind; + + if (!argc) + usage_topic(progname, cmd, argc, argv); + fd = open_controldevice(); + for (i = 0; i < argc; i++) { + int err = delete_device(fd, argv[i]); + if (err) + ret = err; + } + break; + case QUERY: + for (;;) { + int c; + int idx; + c = getopt_long(argc, argv, list_options_short, + list_options_long, &idx); + if (-1 == c) + break; + switch (c) { + case 'e': + escape_strings++; + break; + default: + usage_topic(progname, cmd, argc - 1, argv + 1); + return 1; + } + } + argc -= optind; + argv += optind; + + if (!argc) + usage_topic(progname, cmd, argc, argv); + fd = open_controldevice(); + for (i = 0; i < argc; i++) { + ret += query_device(fd, argv[i], escape_strings); + } + ret = (ret > 0); + break; + case SET_FPS: + optind = do_defaultargs(progname, cmd, argc, argv); + argc -= optind; + argv += optind; + if (argc != 2) + usage_topic(progname, cmd, argc, argv); + if (called_deprecated(argv[0], argv[1], progname, "set-fps", + "fps", is_fps)) { + ret = set_fps(argv[1], argv[0]); + } else + ret = set_fps(argv[0], argv[1]); + break; + case GET_FPS: + optind = do_defaultargs(progname, cmd, argc, argv); + argc -= optind; + argv += optind; + if (argc != 1) + usage_topic(progname, cmd, argc, argv); + ret = get_fps(argv[0]); + break; + case SET_CAPS: + optind = do_defaultargs(progname, cmd, argc, argv); + argc -= optind; + argv += optind; + if (argc != 2) + usage_topic(progname, cmd, argc, argv); + if (called_deprecated(argv[0], argv[1], progname, "set-caps", + "caps", 0)) { + ret = set_caps(argv[1], argv[0]); + } else { + ret = set_caps(argv[0], argv[1]); + } + break; + case GET_CAPS: + optind = do_defaultargs(progname, cmd, argc, argv); + argc -= optind; + argv += optind; + if (argc != 1) + usage_topic(progname, cmd, argc, argv); + ret = get_caps(argv[0]); + break; + case SET_TIMEOUTIMAGE: + if ((3 == argc) && (strncmp("-t", argv[1], 3)) && + (strncmp("--timeout", argv[1], 10)) && + (called_deprecated(argv[1], argv[2], progname, + "set-timeout-image", "image", 0))) { + ret = set_timeoutimage(argv[2], argv[1], -1, verbose); + } else { + int timeout = -1; + for (;;) { + int c, idx; + c = getopt_long(argc, argv, + timeoutimg_options_short, + timeoutimg_options_long, &idx); + if (-1 == c) + break; + switch (c) { + case 't': + timeout = my_atoi("timeout", optarg); + break; + case 'v': + verbose++; + break; + default: + usage_topic(progname, cmd, argc, argv); + } + } + argc -= optind; + argv += optind; + if (argc != 2) + usage_topic(progname, cmd, argc, argv); + ret = set_timeoutimage(argv[0], argv[1], timeout, + verbose); + } + break; + case VERSION: +#ifdef SNAPSHOT_VERSION + printf("%s v%s\n", progname, SNAPSHOT_VERSION); +#else + printf("%s v%d.%d.%d\n", progname, V4L2LOOPBACK_VERSION_MAJOR, + V4L2LOOPBACK_VERSION_MINOR, V4L2LOOPBACK_VERSION_BUGFIX); +#endif + fd = open("/sys/module/v4l2loopback/version", O_RDONLY); + if (fd >= 0) { + char buf[1024]; + int len = read(fd, buf, sizeof(buf)); + if (len > 0) { + if (len < sizeof(buf)) + buf[len] = 0; + printf("v4l2loopback sysfs v%s", buf); + } + close(fd); + } + fd = open_controldevice(); + if (fd >= 0) { + __u32 version = 0; + if (ioctl(fd, V4L2LOOPBACK_CTL_VERSION, &version) == + 0) { + printf("v4l2loopback module v%d.%d.%d\n", + (version >> 16) & 0xff, + (version >> 8) & 0xff, + (version >> 0) & 0xff); + } + } + break; + default: + dprintf(2, "not implemented: '%s'\n", argv[0]); + break; + } + + if (fd >= 0) + close(fd); + + return ret; +} diff --git a/drivers/custom/v4l2loopback/v4l2loopback.c b/drivers/custom/v4l2loopback/v4l2loopback.c new file mode 100644 index 000000000000..22e270717191 --- /dev/null +++ b/drivers/custom/v4l2loopback/v4l2loopback.c @@ -0,0 +1,3331 @@ +/* -*- c-file-style: "linux" -*- */ +/* + * v4l2loopback.c -- video4linux2 loopback driver + * + * Copyright (C) 2005-2009 Vasily Levin (vasaka@gmail.com) + * Copyright (C) 2010-2023 IOhannes m zmoelnig (zmoelnig@iem.at) + * Copyright (C) 2011 Stefan Diewald (stefan.diewald@mytum.de) + * Copyright (C) 2012 Anton Novikov (random.plant@gmail.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "v4l2loopback.h" + +#define V4L2LOOPBACK_CTL_ADD_legacy 0x4C80 +#define V4L2LOOPBACK_CTL_REMOVE_legacy 0x4C81 +#define V4L2LOOPBACK_CTL_QUERY_legacy 0x4C82 + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) +#error This module is not supported on kernels before 4.0.0. +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0) +#define strscpy strlcpy +#endif + +#if defined(timer_setup) +#define HAVE_TIMER_SETUP +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0) +#define VFL_TYPE_VIDEO VFL_TYPE_GRABBER +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) +#define timer_delete_sync del_timer_sync +#endif + +#define fh_to_opener(ptr) container_of((ptr), struct v4l2_loopback_opener, fh) +#define file_to_opener(ptr) \ + container_of(file_to_v4l2_fh(ptr), struct v4l2_loopback_opener, fh) + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 18, 0) +#define v4l2l_f_to_opener(_file, _fh) fh_to_opener(_fh) +#define v4l2_fh_add(fh, filp) v4l2_fh_add(fh) +#define v4l2_fh_del(fh, filp) v4l2_fh_del(fh) +#else +#define v4l2l_f_to_opener(_file, _fh) file_to_opener(_file) +#endif + +#define V4L2LOOPBACK_VERSION_CODE \ + KERNEL_VERSION(V4L2LOOPBACK_VERSION_MAJOR, V4L2LOOPBACK_VERSION_MINOR, \ + V4L2LOOPBACK_VERSION_BUGFIX) + +MODULE_DESCRIPTION("V4L2 loopback video device"); +MODULE_AUTHOR("Vasily Levin, " + "IOhannes m zmoelnig ," + "Stefan Diewald," + "Anton Novikov" + "et al."); +#ifdef SNAPSHOT_VERSION +MODULE_VERSION(__stringify(SNAPSHOT_VERSION)); +#else +MODULE_VERSION("" __stringify(V4L2LOOPBACK_VERSION_MAJOR) "." __stringify( + V4L2LOOPBACK_VERSION_MINOR) "." __stringify(V4L2LOOPBACK_VERSION_BUGFIX)); +#endif +MODULE_LICENSE("GPL"); + +/* + * helpers + */ +#define dprintk(fmt, args...) \ + do { \ + if (debug > 0) { \ + printk(KERN_INFO "v4l2-loopback[" __stringify( \ + __LINE__) "], pid(%d): " fmt, \ + task_pid_nr(current), ##args); \ + } \ + } while (0) + +#define MARK() \ + do { \ + if (debug > 1) { \ + printk(KERN_INFO "%s:%d[%s], pid(%d)\n", __FILE__, \ + __LINE__, __func__, task_pid_nr(current)); \ + } \ + } while (0) + +#define dprintkrw(fmt, args...) \ + do { \ + if (debug > 2) { \ + printk(KERN_INFO "v4l2-loopback[" __stringify( \ + __LINE__) "], pid(%d): " fmt, \ + task_pid_nr(current), ##args); \ + } \ + } while (0) + +static inline void v4l2l_get_timestamp(struct v4l2_buffer *b) +{ + struct timespec64 ts; + ktime_get_ts64(&ts); + + b->timestamp.tv_sec = ts.tv_sec; + b->timestamp.tv_usec = (ts.tv_nsec / NSEC_PER_USEC); + b->flags |= V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + b->flags &= ~V4L2_BUF_FLAG_TIMESTAMP_COPY; +} + +#if BITS_PER_LONG == 32 +#include /* do_div() for 64bit division */ +static inline int v4l2l_mod64(const s64 A, const u32 B) +{ + u64 a = (u64)A; + u32 b = B; + + if (A > 0) + return do_div(a, b); + a = -A; + return -do_div(a, b); +} +#else +static inline int v4l2l_mod64(const s64 A, const u32 B) +{ + return A % B; +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0) +typedef unsigned __poll_t; +#endif + +/* module constants + * can be overridden during he build process using something like + * make KCPPFLAGS="-DMAX_DEVICES=100" + */ + +/* maximum number of v4l2loopback devices that can be created */ +#ifndef MAX_DEVICES +#define MAX_DEVICES 8 +#endif + +/* whether the default is to announce capabilities exclusively or not */ +#ifndef V4L2LOOPBACK_DEFAULT_EXCLUSIVECAPS +#define V4L2LOOPBACK_DEFAULT_EXCLUSIVECAPS 0 +#endif + +/* when a producer is considered to have gone stale */ +#ifndef MAX_TIMEOUT +#define MAX_TIMEOUT (100 * 1000) /* in msecs */ +#endif + +/* max buffers that can be mapped, actually they + * are all mapped to max_buffers buffers */ +#ifndef MAX_BUFFERS +#define MAX_BUFFERS 32 +#endif + +/* module parameters */ +static int debug = 0; +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "debugging level (higher values == more verbose)"); + +#define V4L2LOOPBACK_DEFAULT_MAX_BUFFERS 2 +static int max_buffers = V4L2LOOPBACK_DEFAULT_MAX_BUFFERS; +module_param(max_buffers, int, S_IRUGO); +MODULE_PARM_DESC(max_buffers, + "how many buffers should be allocated [DEFAULT: " __stringify( + V4L2LOOPBACK_DEFAULT_MAX_BUFFERS) "]"); + +/* how many times a device can be opened + * the per-module default value can be overridden on a per-device basis using + * the /sys/devices interface + * + * note that max_openers should be at least 2 in order to get a working system: + * one opener for the producer and one opener for the consumer + * however, we leave that to the user + */ +#define V4L2LOOPBACK_DEFAULT_MAX_OPENERS 10 +static int max_openers = V4L2LOOPBACK_DEFAULT_MAX_OPENERS; +module_param(max_openers, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC( + max_openers, + "how many users can open the loopback device [DEFAULT: " __stringify( + V4L2LOOPBACK_DEFAULT_MAX_OPENERS) "]"); + +static int devices = -1; +module_param(devices, int, 0); +MODULE_PARM_DESC(devices, "how many devices should be created"); + +static int video_nr[MAX_DEVICES] = { [0 ...(MAX_DEVICES - 1)] = -1 }; +module_param_array(video_nr, int, NULL, 0444); +MODULE_PARM_DESC(video_nr, + "video device numbers (-1=auto, 0=/dev/video0, etc.)"); + +static char *card_label[MAX_DEVICES]; +module_param_array(card_label, charp, NULL, 0000); +MODULE_PARM_DESC(card_label, "card labels for each device"); + +static bool exclusive_caps[MAX_DEVICES] = { + [0 ...(MAX_DEVICES - 1)] = V4L2LOOPBACK_DEFAULT_EXCLUSIVECAPS +}; +module_param_array(exclusive_caps, bool, NULL, 0444); +/* FIXXME: wording */ +MODULE_PARM_DESC( + exclusive_caps, + "whether to announce OUTPUT/CAPTURE capabilities exclusively or not [DEFAULT: " __stringify( + V4L2LOOPBACK_DEFAULT_EXCLUSIVECAPS) "]"); + +/* format specifications */ +#define V4L2LOOPBACK_SIZE_MIN_WIDTH 2 +#define V4L2LOOPBACK_SIZE_MIN_HEIGHT 1 +#define V4L2LOOPBACK_SIZE_DEFAULT_MAX_WIDTH 8192 +#define V4L2LOOPBACK_SIZE_DEFAULT_MAX_HEIGHT 8192 + +#define V4L2LOOPBACK_SIZE_DEFAULT_WIDTH 640 +#define V4L2LOOPBACK_SIZE_DEFAULT_HEIGHT 480 + +static int max_width = V4L2LOOPBACK_SIZE_DEFAULT_MAX_WIDTH; +module_param(max_width, int, S_IRUGO); +MODULE_PARM_DESC(max_width, + "maximum allowed frame width [DEFAULT: " __stringify( + V4L2LOOPBACK_SIZE_DEFAULT_MAX_WIDTH) "]"); +static int max_height = V4L2LOOPBACK_SIZE_DEFAULT_MAX_HEIGHT; +module_param(max_height, int, S_IRUGO); +MODULE_PARM_DESC(max_height, + "maximum allowed frame height [DEFAULT: " __stringify( + V4L2LOOPBACK_SIZE_DEFAULT_MAX_HEIGHT) "]"); + +static DEFINE_IDR(v4l2loopback_index_idr); +static DEFINE_MUTEX(v4l2loopback_ctl_mutex); + +/* frame intervals */ +#define V4L2LOOPBACK_FRAME_INTERVAL_MAX __UINT32_MAX__ +#define V4L2LOOPBACK_FPS_DEFAULT 30 +#define V4L2LOOPBACK_FPS_MAX 1000 + +/* control IDs */ +#define V4L2LOOPBACK_CID_BASE (V4L2_CID_USER_BASE | 0xf000) +#define CID_KEEP_FORMAT (V4L2LOOPBACK_CID_BASE + 0) +#define CID_SUSTAIN_FRAMERATE (V4L2LOOPBACK_CID_BASE + 1) +#define CID_TIMEOUT (V4L2LOOPBACK_CID_BASE + 2) +#define CID_TIMEOUT_IMAGE_IO (V4L2LOOPBACK_CID_BASE + 3) + +static int v4l2loopback_s_ctrl(struct v4l2_ctrl *ctrl); +static const struct v4l2_ctrl_ops v4l2loopback_ctrl_ops = { + .s_ctrl = v4l2loopback_s_ctrl, +}; +static const struct v4l2_ctrl_config v4l2loopback_ctrl_keepformat = { + // clang-format off + .ops = &v4l2loopback_ctrl_ops, + .id = CID_KEEP_FORMAT, + .name = "keep_format", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 0, + // clang-format on +}; +static const struct v4l2_ctrl_config v4l2loopback_ctrl_sustainframerate = { + // clang-format off + .ops = &v4l2loopback_ctrl_ops, + .id = CID_SUSTAIN_FRAMERATE, + .name = "sustain_framerate", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 0, + // clang-format on +}; +static const struct v4l2_ctrl_config v4l2loopback_ctrl_timeout = { + // clang-format off + .ops = &v4l2loopback_ctrl_ops, + .id = CID_TIMEOUT, + .name = "timeout", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = MAX_TIMEOUT, + .step = 1, + .def = 0, + // clang-format on +}; +static const struct v4l2_ctrl_config v4l2loopback_ctrl_timeoutimageio = { + // clang-format off + .ops = &v4l2loopback_ctrl_ops, + .id = CID_TIMEOUT_IMAGE_IO, + .name = "timeout_image_io", + .type = V4L2_CTRL_TYPE_BUTTON, + .min = 0, + .max = 0, + .step = 0, + .def = 0, + // clang-format on +}; + +/* module structures */ +struct v4l2loopback_private { + int device_nr; +}; + +/* TODO(vasaka) use typenames which are common to kernel, but first find out if + * it is needed */ +/* struct keeping state and settings of loopback device */ + +struct v4l2l_buffer { + struct v4l2_buffer buffer; + struct list_head list_head; + atomic_t use_count; +}; + +struct v4l2_loopback_device { + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler ctrl_handler; + struct video_device *vdev; + + /* loopback device-specific parameters */ + char card_label[32]; + bool announce_all_caps; /* announce both OUTPUT and CAPTURE capabilities + * when true; else announce OUTPUT when no + * writer is streaming, otherwise CAPTURE. */ + int max_openers; /* how many times can this device be opened */ + int min_width, max_width; + int min_height, max_height; + + /* pixel and stream format */ + struct v4l2_pix_format pix_format; + bool pix_format_has_valid_sizeimage; + struct v4l2_captureparm capture_param; + unsigned long frame_jiffies; + + /* ctrls */ + int keep_format; /* CID_KEEP_FORMAT; lock the format, do not free + * on close(), and when `!announce_all_caps` do NOT + * fall back to OUTPUT when no writers attached (clear + * `keep_format` to attach a new writer) */ + int sustain_framerate; /* CID_SUSTAIN_FRAMERATE; duplicate frames to maintain + (close to) nominal framerate */ + unsigned long timeout_jiffies; /* CID_TIMEOUT; 0 means disabled */ + int timeout_image_io; /* CID_TIMEOUT_IMAGE_IO; next opener will + * queue/dequeue the timeout image buffer */ + + /* buffers for OUTPUT and CAPTURE */ + u8 *image; /* pointer to actual buffers data */ + unsigned long image_size; /* number of bytes alloc'd for all buffers */ + struct v4l2l_buffer buffers[MAX_BUFFERS]; /* inner driver buffers */ + u32 buffer_count; /* should not be big, 4 is a good choice */ + u32 buffer_size; /* number of bytes alloc'd per buffer */ + u32 used_buffer_count; /* number of buffers allocated to openers */ + struct list_head outbufs_list; /* FIFO queue for OUTPUT buffers */ + u32 bufpos2index[MAX_BUFFERS]; /* mapping of `(position % used_buffers)` + * to `buffers[index]` */ + s64 write_position; /* sequence number of last 'displayed' buffer plus + * one */ + + /* synchronization between openers */ + atomic_t open_count; + struct mutex image_mutex; /* mutex for allocating image(s) and + * exchanging format tokens */ + spinlock_t lock; /* lock for the timeout and framerate timers */ + spinlock_t list_lock; /* lock for the OUTPUT buffer queue */ + wait_queue_head_t read_event; + u32 format_tokens; /* tokens to 'set format' for OUTPUT, CAPTURE, or + * timeout buffers */ + u32 stream_tokens; /* tokens to 'start' OUTPUT, CAPTURE, or timeout + * stream */ + + /* sustain framerate */ + struct timer_list sustain_timer; + unsigned int reread_count; + + /* timeout */ + u8 *timeout_image; /* copied to outgoing buffers when timeout passes */ + struct v4l2l_buffer timeout_buffer; + u32 timeout_buffer_size; /* number bytes alloc'd for timeout buffer */ + struct timer_list timeout_timer; + int timeout_happened; +}; + +enum v4l2l_io_method { + V4L2L_IO_NONE = 0, + V4L2L_IO_MMAP = 1, + V4L2L_IO_FILE = 2, + V4L2L_IO_TIMEOUT = 3, +}; + +/* struct keeping state and type of opener */ +struct v4l2_loopback_opener { + u32 format_token; /* token (if any) for type used in call to S_FMT or + * REQBUFS */ + u32 stream_token; /* token (if any) for type used in call to STREAMON */ + u32 buffer_count; /* number of buffers (if any) that opener acquired via + * REQBUFS */ + s64 read_position; /* sequence number of the next 'captured' frame */ + unsigned int reread_count; + enum v4l2l_io_method io_method; + + struct v4l2_fh fh; +}; + +/* this is heavily inspired by the bttv driver found in the linux kernel */ +struct v4l2l_format { + char *name; + int fourcc; /* video4linux 2 */ + int depth; /* bit/pixel */ + int flags; +}; +/* set the v4l2l_format.flags to PLANAR for non-packed formats */ +#define FORMAT_FLAGS_PLANAR 0x01 +#define FORMAT_FLAGS_COMPRESSED 0x02 + +#include "v4l2loopback_formats.h" + +#ifndef V4L2_TYPE_IS_CAPTURE +#define V4L2_TYPE_IS_CAPTURE(type) \ + ((type) == V4L2_BUF_TYPE_VIDEO_CAPTURE || \ + (type) == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) +#endif /* V4L2_TYPE_IS_CAPTURE */ +#ifndef V4L2_TYPE_IS_OUTPUT +#define V4L2_TYPE_IS_OUTPUT(type) \ + ((type) == V4L2_BUF_TYPE_VIDEO_OUTPUT || \ + (type) == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) +#endif /* V4L2_TYPE_IS_OUTPUT */ + +/* token values for privilege to set format or start/stop stream */ +#define V4L2L_TOKEN_CAPTURE 0x01 +#define V4L2L_TOKEN_OUTPUT 0x02 +#define V4L2L_TOKEN_TIMEOUT 0x04 +#define V4L2L_TOKEN_MASK \ + (V4L2L_TOKEN_CAPTURE | V4L2L_TOKEN_OUTPUT | V4L2L_TOKEN_TIMEOUT) + +/* helpers for token exchange and token status */ +#define token_from_type(type) \ + (V4L2_TYPE_IS_CAPTURE(type) ? V4L2L_TOKEN_CAPTURE : V4L2L_TOKEN_OUTPUT) +#define acquire_token(dev, opener, label, token) \ + do { \ + (opener)->label##_token = token; \ + (dev)->label##_tokens &= ~token; \ + } while (0) +#define release_token(dev, opener, label) \ + do { \ + (dev)->label##_tokens |= (opener)->label##_token; \ + (opener)->label##_token = 0; \ + } while (0) +#define has_output_token(token) (token & V4L2L_TOKEN_OUTPUT) +#define has_capture_token(token) (token & V4L2L_TOKEN_CAPTURE) +#define has_no_owners(dev) ((~((dev)->format_tokens) & V4L2L_TOKEN_MASK) == 0) +#define has_other_owners(opener, dev) \ + (~((dev)->format_tokens ^ (opener)->format_token) & V4L2L_TOKEN_MASK) +#define need_timeout_buffer(dev, token) \ + ((dev)->timeout_jiffies > 0 || (token) & V4L2L_TOKEN_TIMEOUT) + +static const unsigned int FORMATS = ARRAY_SIZE(formats); + +static char *fourcc2str(unsigned int fourcc, char buf[5]) +{ + buf[0] = (fourcc >> 0) & 0xFF; + buf[1] = (fourcc >> 8) & 0xFF; + buf[2] = (fourcc >> 16) & 0xFF; + buf[3] = (fourcc >> 24) & 0xFF; + buf[4] = 0; + + return buf; +} + +static const struct v4l2l_format *format_by_fourcc(int fourcc) +{ + unsigned int i; + char buf[5]; + + for (i = 0; i < FORMATS; i++) { + if (formats[i].fourcc == fourcc) + return formats + i; + } + + dprintk("unsupported format '%4s'\n", fourcc2str(fourcc, buf)); + return NULL; +} + +static void pix_format_set_size(struct v4l2_pix_format *f, + const struct v4l2l_format *fmt, + unsigned int width, unsigned int height) +{ + f->width = width; + f->height = height; + + if (fmt->flags & FORMAT_FLAGS_PLANAR) { + f->bytesperline = width; /* Y plane */ + f->sizeimage = (width * height * fmt->depth) >> 3; + } else if (fmt->flags & FORMAT_FLAGS_COMPRESSED) { + /* doesn't make sense for compressed formats */ + f->bytesperline = 0; + f->sizeimage = (width * height * fmt->depth) >> 3; + } else { + f->bytesperline = (width * fmt->depth) >> 3; + f->sizeimage = height * f->bytesperline; + } +} + +static int v4l2l_fill_format(struct v4l2_format *fmt, const u32 minwidth, + const u32 maxwidth, const u32 minheight, + const u32 maxheight) +{ + u32 width = fmt->fmt.pix.width, height = fmt->fmt.pix.height; + u32 pixelformat = fmt->fmt.pix.pixelformat; + struct v4l2_format fmt0 = *fmt; + u32 bytesperline = 0, sizeimage = 0; + + if (!width) + width = V4L2LOOPBACK_SIZE_DEFAULT_WIDTH; + if (!height) + height = V4L2LOOPBACK_SIZE_DEFAULT_HEIGHT; + width = clamp_val(width, minwidth, maxwidth); + height = clamp_val(height, minheight, maxheight); + + /* sets: width,height,pixelformat,bytesperline,sizeimage */ + if (!(V4L2_TYPE_IS_MULTIPLANAR(fmt0.type))) { + fmt0.fmt.pix.bytesperline = 0; + fmt0.fmt.pix.sizeimage = 0; + } + + if (0) { + ; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0) + } else if (!v4l2_fill_pixfmt(&fmt0.fmt.pix, pixelformat, width, + height)) { + ; + } else if (!v4l2_fill_pixfmt_mp(&fmt0.fmt.pix_mp, pixelformat, width, + height)) { + ; +#endif + } else { + const struct v4l2l_format *format = + format_by_fourcc(pixelformat); + if (!format) + return -EINVAL; + pix_format_set_size(&fmt0.fmt.pix, format, width, height); + fmt0.fmt.pix.pixelformat = format->fourcc; + } + + if (V4L2_TYPE_IS_MULTIPLANAR(fmt0.type)) { + *fmt = fmt0; + + if ((fmt->fmt.pix_mp.colorspace == V4L2_COLORSPACE_DEFAULT) || + (fmt->fmt.pix_mp.colorspace > V4L2_COLORSPACE_DCI_P3)) + fmt->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SRGB; + if (V4L2_FIELD_ANY == fmt->fmt.pix_mp.field) + fmt->fmt.pix_mp.field = V4L2_FIELD_NONE; + } else { + bytesperline = fmt->fmt.pix.bytesperline; + sizeimage = fmt->fmt.pix.sizeimage; + + *fmt = fmt0; + + if (!fmt->fmt.pix.bytesperline) + fmt->fmt.pix.bytesperline = bytesperline; + if (!fmt->fmt.pix.sizeimage) + fmt->fmt.pix.sizeimage = sizeimage; + + if ((fmt->fmt.pix.colorspace == V4L2_COLORSPACE_DEFAULT) || + (fmt->fmt.pix.colorspace > V4L2_COLORSPACE_DCI_P3)) + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + if (V4L2_FIELD_ANY == fmt->fmt.pix.field) + fmt->fmt.pix.field = V4L2_FIELD_NONE; + } + + return 0; +} + +/* Checks if v4l2l_fill_format() has set a valid, fixed sizeimage val. */ +static bool v4l2l_pix_format_has_valid_sizeimage(struct v4l2_format *fmt) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0) + const struct v4l2_format_info *info; + + info = v4l2_format_info(fmt->fmt.pix.pixelformat); + if (info && info->mem_planes == 1) + return true; +#endif + + return false; +} + +static int pix_format_eq(const struct v4l2_pix_format *ref, + const struct v4l2_pix_format *tgt, int strict) +{ + /* check if the two formats are equivalent. + * ANY fields are handled gracefully + */ +#define _pix_format_eq0(x) \ + if (ref->x != tgt->x) \ + result = 0 +#define _pix_format_eq1(x, def) \ + do { \ + if ((def != tgt->x) && (ref->x != tgt->x)) { \ + printk(KERN_INFO #x " failed"); \ + result = 0; \ + } \ + } while (0) + int result = 1; + _pix_format_eq0(width); + _pix_format_eq0(height); + _pix_format_eq0(pixelformat); + if (!strict) + return result; + _pix_format_eq1(field, V4L2_FIELD_ANY); + _pix_format_eq0(bytesperline); + _pix_format_eq0(sizeimage); + _pix_format_eq1(colorspace, V4L2_COLORSPACE_DEFAULT); + return result; +} + +static void set_timeperframe(struct v4l2_loopback_device *dev, + struct v4l2_fract *tpf) +{ + if (!tpf->denominator && !tpf->numerator) { + tpf->numerator = 1; + tpf->denominator = V4L2LOOPBACK_FPS_DEFAULT; + } else if (tpf->numerator > + V4L2LOOPBACK_FRAME_INTERVAL_MAX * tpf->denominator) { + /* divide-by-zero or greater than maximum interval => min FPS */ + tpf->numerator = V4L2LOOPBACK_FRAME_INTERVAL_MAX; + tpf->denominator = 1; + } else if (tpf->numerator * V4L2LOOPBACK_FPS_MAX < tpf->denominator) { + /* zero or lower than minimum interval => max FPS */ + tpf->numerator = 1; + tpf->denominator = V4L2LOOPBACK_FPS_MAX; + } + + dev->capture_param.timeperframe = *tpf; + dev->frame_jiffies = + max(1UL, (msecs_to_jiffies(1000) * tpf->numerator) / + tpf->denominator); +} + +static struct v4l2_loopback_device *v4l2loopback_cd2dev(struct device *cd); + +/* device attributes */ +/* available via sysfs: /sys/devices/virtual/video4linux/video* */ + +static ssize_t attr_show_format(struct device *cd, + struct device_attribute *attr, char *buf) +{ + /* gets the current format as "FOURCC:WxH@f/s", e.g. "YUYV:320x240@1000/30" */ + struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd); + const struct v4l2_fract *tpf; + char buf4cc[5], buf_fps[32]; + + if (!dev || (has_no_owners(dev) && !dev->keep_format)) + return 0; + tpf = &dev->capture_param.timeperframe; + + fourcc2str(dev->pix_format.pixelformat, buf4cc); + if (tpf->numerator == 1) + snprintf(buf_fps, sizeof(buf_fps), "%u", tpf->denominator); + else + snprintf(buf_fps, sizeof(buf_fps), "%u/%u", tpf->denominator, + tpf->numerator); + return sprintf(buf, "%4s:%ux%u@%s\n", buf4cc, dev->pix_format.width, + dev->pix_format.height, buf_fps); +} + +static ssize_t attr_store_format(struct device *cd, + struct device_attribute *attr, const char *buf, + size_t len) +{ + struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd); + int fps_num = 0, fps_den = 1; + + if (!dev) + return -ENODEV; + + /* only fps changing is supported */ + if (sscanf(buf, "@%u/%u", &fps_num, &fps_den) > 0) { + struct v4l2_fract f = { .numerator = fps_den, + .denominator = fps_num }; + set_timeperframe(dev, &f); + return len; + } + return -EINVAL; +} + +static DEVICE_ATTR(format, S_IRUGO | S_IWUSR, attr_show_format, + attr_store_format); + +static ssize_t attr_show_buffers(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd); + + if (!dev) + return -ENODEV; + + return sprintf(buf, "%u\n", dev->used_buffer_count); +} + +static DEVICE_ATTR(buffers, S_IRUGO, attr_show_buffers, NULL); + +static ssize_t attr_show_maxopeners(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd); + + if (!dev) + return -ENODEV; + + return sprintf(buf, "%d\n", dev->max_openers); +} + +static ssize_t attr_store_maxopeners(struct device *cd, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct v4l2_loopback_device *dev = NULL; + unsigned long curr = 0; + + if (kstrtoul(buf, 0, &curr)) + return -EINVAL; + + dev = v4l2loopback_cd2dev(cd); + if (!dev) + return -ENODEV; + + if (dev->max_openers == curr) + return len; + + if (curr > __INT_MAX__ || dev->open_count.counter > curr) { + /* request to limit to less openers as are currently attached to us */ + return -EINVAL; + } + + dev->max_openers = (int)curr; + + return len; +} + +static DEVICE_ATTR(max_openers, S_IRUGO | S_IWUSR, attr_show_maxopeners, + attr_store_maxopeners); + +static ssize_t attr_show_state(struct device *cd, struct device_attribute *attr, + char *buf) +{ + struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd); + + if (!dev) + return -ENODEV; + + if (!has_output_token(dev->stream_tokens) || dev->keep_format) { + return sprintf(buf, "capture\n"); + } else + return sprintf(buf, "output\n"); + + return -EAGAIN; +} + +static DEVICE_ATTR(state, S_IRUGO, attr_show_state, NULL); + +static void v4l2loopback_remove_sysfs(struct video_device *vdev) +{ +#define V4L2_SYSFS_DESTROY(x) device_remove_file(&vdev->dev, &dev_attr_##x) + + if (vdev) { + V4L2_SYSFS_DESTROY(format); + V4L2_SYSFS_DESTROY(buffers); + V4L2_SYSFS_DESTROY(max_openers); + V4L2_SYSFS_DESTROY(state); + /* ... */ + } +} + +static void v4l2loopback_create_sysfs(struct video_device *vdev) +{ + int res = 0; + +#define V4L2_SYSFS_CREATE(x) \ + res = device_create_file(&vdev->dev, &dev_attr_##x); \ + if (res < 0) \ + break + if (!vdev) + return; + do { + V4L2_SYSFS_CREATE(format); + V4L2_SYSFS_CREATE(buffers); + V4L2_SYSFS_CREATE(max_openers); + V4L2_SYSFS_CREATE(state); + /* ... */ + } while (0); + + if (res >= 0) + return; + dev_err(&vdev->dev, "%s error: %d\n", __func__, res); +} + +/* Event APIs */ + +#define V4L2LOOPBACK_EVENT_BASE (V4L2_EVENT_PRIVATE_START) +#define V4L2LOOPBACK_EVENT_OFFSET 0x08E00000 +#define V4L2_EVENT_PRI_CLIENT_USAGE \ + (V4L2LOOPBACK_EVENT_BASE + V4L2LOOPBACK_EVENT_OFFSET + 1) + +struct v4l2_event_client_usage { + __u32 count; +}; + +/* global module data */ +/* find a device based on it's device-number (e.g. '3' for /dev/video3) */ +struct v4l2loopback_lookup_cb_data { + int device_nr; + struct v4l2_loopback_device *device; +}; +static int v4l2loopback_lookup_cb(int id, void *ptr, void *data) +{ + struct v4l2_loopback_device *device = ptr; + struct v4l2loopback_lookup_cb_data *cbdata = data; + if (cbdata && device && device->vdev) { + if (device->vdev->num == cbdata->device_nr) { + cbdata->device = device; + cbdata->device_nr = id; + return 1; + } + } + return 0; +} +static int v4l2loopback_lookup(int device_nr, + struct v4l2_loopback_device **device) +{ + struct v4l2loopback_lookup_cb_data data = { + .device_nr = device_nr, + .device = NULL, + }; + int err = idr_for_each(&v4l2loopback_index_idr, &v4l2loopback_lookup_cb, + &data); + if (1 == err) { + if (device) + *device = data.device; + return data.device_nr; + } + return -ENODEV; +} +#define v4l2loopback_get_vdev_nr(vdev) \ + ((struct v4l2loopback_private *)video_get_drvdata(vdev))->device_nr +static struct v4l2_loopback_device *v4l2loopback_cd2dev(struct device *cd) +{ + struct video_device *loopdev = to_video_device(cd); + int device_nr = v4l2loopback_get_vdev_nr(loopdev); + + return idr_find(&v4l2loopback_index_idr, device_nr); +} + +static struct v4l2_loopback_device *v4l2loopback_getdevice(struct file *f) +{ + struct v4l2loopback_private *ptr = video_drvdata(f); + int nr = ptr->device_nr; + + return idr_find(&v4l2loopback_index_idr, nr); +} + +/* forward declarations */ +static void client_usage_queue_event(struct video_device *vdev); +static bool any_buffers_mapped(struct v4l2_loopback_device *dev); +static int allocate_buffers(struct v4l2_loopback_device *dev, + struct v4l2_pix_format *pix_format); +static void init_buffers(struct v4l2_loopback_device *dev, u32 bytes_used, + u32 buffer_size); +static void free_buffers(struct v4l2_loopback_device *dev); +static int allocate_timeout_buffer(struct v4l2_loopback_device *dev); +static void free_timeout_buffer(struct v4l2_loopback_device *dev); +static void check_timers(struct v4l2_loopback_device *dev); +static const struct v4l2_file_operations v4l2_loopback_fops; +static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops; + +/* V4L2 ioctl caps and params calls */ +/* returns device capabilities + * called on VIDIOC_QUERYCAP + */ +static int vidioc_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + int device_nr = v4l2loopback_get_vdev_nr(dev->vdev); + __u32 capabilities = V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; + + strscpy(cap->driver, "v4l2 loopback", sizeof(cap->driver)); + snprintf(cap->card, sizeof(cap->card), "%s", dev->card_label); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:v4l2loopback-%03d", device_nr); + + if (dev->announce_all_caps) { + capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; + } else { + if (opener->io_method == V4L2L_IO_TIMEOUT || + (has_output_token(dev->stream_tokens) && + !dev->keep_format)) { + capabilities |= V4L2_CAP_VIDEO_OUTPUT; + } else + capabilities |= V4L2_CAP_VIDEO_CAPTURE; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + dev->vdev->device_caps = +#endif /* >=linux-4.7.0 */ + cap->device_caps = cap->capabilities = capabilities; + + cap->capabilities |= V4L2_CAP_DEVICE_CAPS; + + memset(cap->reserved, 0, sizeof(cap->reserved)); + return 0; +} + +static int vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *argp) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + + /* there can be only one... */ + if (argp->index) + return -EINVAL; + + if (dev->keep_format || has_other_owners(opener, dev)) { + /* only current frame size supported */ + if (argp->pixel_format != dev->pix_format.pixelformat) + return -EINVAL; + + argp->type = V4L2_FRMSIZE_TYPE_DISCRETE; + + argp->discrete.width = dev->pix_format.width; + argp->discrete.height = dev->pix_format.height; + } else { + /* return continuous sizes if pixel format is supported */ + if (NULL == format_by_fourcc(argp->pixel_format)) + return -EINVAL; + + if (dev->min_width == dev->max_width && + dev->min_height == dev->max_height) { + argp->type = V4L2_FRMSIZE_TYPE_DISCRETE; + + argp->discrete.width = dev->min_width; + argp->discrete.height = dev->min_height; + } else { + argp->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + + argp->stepwise.min_width = dev->min_width; + argp->stepwise.min_height = dev->min_height; + + argp->stepwise.max_width = dev->max_width; + argp->stepwise.max_height = dev->max_height; + + argp->stepwise.step_width = 1; + argp->stepwise.step_height = 1; + } + } + return 0; +} + +/* Test if the device is currently 'capable' of the buffer (stream) type when + * the `exclusive_caps` parameter is set. `keep_format` should lock the format + * and prevent free of buffers */ +static int check_buffer_capability(struct v4l2_loopback_device *dev, + struct v4l2_loopback_opener *opener, + enum v4l2_buf_type type) +{ + /* short-circuit for (non-compliant) timeout image mode */ + if (opener->io_method == V4L2L_IO_TIMEOUT) + return 0; + if (dev->announce_all_caps) + return (type == V4L2_BUF_TYPE_VIDEO_CAPTURE || + type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? + 0 : + -EINVAL; + /* CAPTURE if opener has a capture format or a writer is streaming; + * else OUTPUT. */ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (!(has_capture_token(opener->format_token) || + !has_output_token(dev->stream_tokens))) + return -EINVAL; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (!(has_output_token(opener->format_token) || + has_output_token(dev->stream_tokens))) + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} +/* returns frameinterval (fps) for the set resolution + * called on VIDIOC_ENUM_FRAMEINTERVALS + */ +static int vidioc_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *argp) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + + /* there can be only one... */ + if (argp->index) + return -EINVAL; + + if (dev->keep_format || has_other_owners(opener, dev)) { + /* keep_format also locks the frame rate */ + if (argp->width != dev->pix_format.width || + argp->height != dev->pix_format.height || + argp->pixel_format != dev->pix_format.pixelformat) + return -EINVAL; + + argp->type = V4L2_FRMIVAL_TYPE_DISCRETE; + argp->discrete = dev->capture_param.timeperframe; + } else { + if (argp->width < dev->min_width || + argp->width > dev->max_width || + argp->height < dev->min_height || + argp->height > dev->max_height || + !format_by_fourcc(argp->pixel_format)) + return -EINVAL; + + argp->type = V4L2_FRMIVAL_TYPE_CONTINUOUS; + argp->stepwise.min.numerator = 1; + argp->stepwise.min.denominator = V4L2LOOPBACK_FPS_MAX; + argp->stepwise.max.numerator = V4L2LOOPBACK_FRAME_INTERVAL_MAX; + argp->stepwise.max.denominator = 1; + argp->stepwise.step.numerator = 1; + argp->stepwise.step.denominator = 1; + } + + return 0; +} + +/* Enumerate device formats + * Returns: + * - EINVAL the index is out of bounds; or if non-zero when format is fixed + * - EFAULT unexpected null pointer */ +static int vidioc_enum_fmt_vid(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + int fixed = dev->keep_format || has_other_owners(opener, dev); + const struct v4l2l_format *fmt; + + if (check_buffer_capability(dev, opener, f->type) < 0) + return -EINVAL; + + if (!(f->index < FORMATS)) + return -EINVAL; + /* TODO: Support 6.14 V4L2_FMTDESC_FLAG_ENUM_ALL */ + if (fixed && f->index) + return -EINVAL; + + fmt = fixed ? format_by_fourcc(dev->pix_format.pixelformat) : + &formats[f->index]; + if (!fmt) + return -EFAULT; + + f->flags = 0; + if (fmt->flags & FORMAT_FLAGS_COMPRESSED) + f->flags |= V4L2_FMT_FLAG_COMPRESSED; + snprintf(f->description, sizeof(f->description), fmt->name); + f->pixelformat = fmt->fourcc; + return 0; +} + +/* Tests (or tries) the format. + * Returns: + * - EINVAL if the buffer type or format is not supported + */ +static int vidioc_try_fmt_vid(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + + if (check_buffer_capability(dev, opener, f->type) < 0) + return -EINVAL; + if (v4l2l_fill_format(f, dev->min_width, dev->max_width, + dev->min_height, dev->max_height) != 0) + return -EINVAL; + if (dev->keep_format || has_other_owners(opener, dev)) + /* use existing format - including colorspace info */ + f->fmt.pix = dev->pix_format; + + return 0; +} + +/* Sets new format. Fills 'f' argument with the requested or existing format. + * Side-effect: buffers are allocated for the (returned) format. + * Returns: + * - EINVAL if the type is not supported + * - EBUSY if buffers are already allocated + * TODO: (vasaka) set subregions of input + */ +static int vidioc_s_fmt_vid(struct file *file, void *fh, struct v4l2_format *f) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + u32 token = opener->io_method == V4L2L_IO_TIMEOUT ? + V4L2L_TOKEN_TIMEOUT : + token_from_type(f->type); + int changed, result; + char buf[5]; + + result = vidioc_try_fmt_vid(file, fh, f); + if (result < 0) + return result; + + if (opener->buffer_count > 0) + /* must free buffers before format can be set */ + return -EBUSY; + + result = mutex_lock_killable(&dev->image_mutex); + if (result < 0) + return result; + + if (opener->format_token) + release_token(dev, opener, format); + if (!(dev->format_tokens & token)) { + result = -EBUSY; + goto exit_s_fmt_unlock; + } + + dprintk("S_FMT[%s] %4s:%ux%u size=%u\n", + V4L2_TYPE_IS_CAPTURE(f->type) ? "CAPTURE" : "OUTPUT", + fourcc2str(f->fmt.pix.pixelformat, buf), f->fmt.pix.width, + f->fmt.pix.height, f->fmt.pix.sizeimage); + changed = !pix_format_eq(&dev->pix_format, &f->fmt.pix, 0); + if (changed || has_no_owners(dev)) { + result = allocate_buffers(dev, &f->fmt.pix); + if (result < 0) + goto exit_s_fmt_unlock; + } + if ((dev->timeout_image && changed) || + (!dev->timeout_image && need_timeout_buffer(dev, token))) { + result = allocate_timeout_buffer(dev); + if (result < 0) + goto exit_s_fmt_free; + } + if (changed) { + dev->pix_format = f->fmt.pix; + dev->pix_format_has_valid_sizeimage = + v4l2l_pix_format_has_valid_sizeimage(f); + } + acquire_token(dev, opener, format, token); + if (opener->io_method == V4L2L_IO_TIMEOUT) + dev->timeout_image_io = 0; + goto exit_s_fmt_unlock; +exit_s_fmt_free: + free_buffers(dev); +exit_s_fmt_unlock: + mutex_unlock(&dev->image_mutex); + return result; +} + +/* ------------------ CAPTURE ----------------------- */ +/* ioctl for VIDIOC_ENUM_FMT, _G_FMT, _S_FMT, and _TRY_FMT when buffer type + * is V4L2_BUF_TYPE_VIDEO_CAPTURE */ + +static int vidioc_enum_fmt_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + return vidioc_enum_fmt_vid(file, fh, f); +} + +static int vidioc_g_fmt_cap(struct file *file, void *fh, struct v4l2_format *f) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + if (check_buffer_capability(dev, opener, f->type) < 0) + return -EINVAL; + f->fmt.pix = dev->pix_format; + return 0; +} + +static int vidioc_try_fmt_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + return vidioc_try_fmt_vid(file, fh, f); +} + +static int vidioc_s_fmt_cap(struct file *file, void *fh, struct v4l2_format *f) +{ + return vidioc_s_fmt_vid(file, fh, f); +} + +/* ------------------ OUTPUT ----------------------- */ +/* ioctl for VIDIOC_ENUM_FMT, _G_FMT, _S_FMT, and _TRY_FMT when buffer type + * is V4L2_BUF_TYPE_VIDEO_OUTPUT */ + +static int vidioc_enum_fmt_out(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + return vidioc_enum_fmt_vid(file, fh, f); +} + +static int vidioc_g_fmt_out(struct file *file, void *fh, struct v4l2_format *f) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + if (check_buffer_capability(dev, opener, f->type) < 0) + return -EINVAL; + /* + * LATER: this should return the currently valid format + * gstreamer doesn't like it, if this returns -EINVAL, as it + * then concludes that there is _no_ valid format + * CHECK whether this assumption is wrong, + * or whether we have to always provide a valid format + */ + f->fmt.pix = dev->pix_format; + return 0; +} + +static int vidioc_try_fmt_out(struct file *file, void *fh, + struct v4l2_format *f) +{ + return vidioc_try_fmt_vid(file, fh, f); +} + +static int vidioc_s_fmt_out(struct file *file, void *fh, struct v4l2_format *f) +{ + return vidioc_s_fmt_vid(file, fh, f); +} + +// #define V4L2L_OVERLAY +#ifdef V4L2L_OVERLAY +/* ------------------ OVERLAY ----------------------- */ +/* currently unsupported */ +/* GSTreamer's v4l2sink is buggy, as it requires the overlay to work + * while it should only require it, if overlay is requested + * once the gstreamer element is fixed, remove the overlay dummies + */ +#warning OVERLAY dummies +static int vidioc_g_fmt_overlay(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + return 0; +} + +static int vidioc_s_fmt_overlay(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + return 0; +} +#endif /* V4L2L_OVERLAY */ + +/* ------------------ PARAMs ----------------------- */ + +/* get some data flow parameters, only capability, fps and readbuffers has + * effect on this driver + * called on VIDIOC_G_PARM + */ +static int vidioc_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *parm) +{ + /* do not care about type of opener, hope these enums would always be + * compatible */ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + if (check_buffer_capability(dev, opener, parm->type) < 0) + return -EINVAL; + parm->parm.capture = dev->capture_param; + return 0; +} + +/* get some data flow parameters, only capability, fps and readbuffers has + * effect on this driver + * called on VIDIOC_S_PARM + */ +static int vidioc_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *parm) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + + dprintk("S_PARM(frame-time=%u/%u)\n", + parm->parm.capture.timeperframe.numerator, + parm->parm.capture.timeperframe.denominator); + if (check_buffer_capability(dev, opener, parm->type) < 0) + return -EINVAL; + + switch (parm->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + set_timeperframe(dev, &parm->parm.capture.timeperframe); + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + set_timeperframe(dev, &parm->parm.output.timeperframe); + break; + default: + return -EINVAL; + } + + parm->parm.capture = dev->capture_param; + return 0; +} + +#ifdef V4L2LOOPBACK_WITH_STD +/* sets a tv standard, actually we do not need to handle this any special way + * added to support effecttv + * called on VIDIOC_S_STD + */ +static int vidioc_s_std(struct file *file, void *fh, v4l2_std_id *_std) +{ + v4l2_std_id req_std = 0, supported_std = 0; + const v4l2_std_id all_std = V4L2_STD_ALL, no_std = 0; + + if (_std) { + req_std = *_std; + *_std = all_std; + } + + /* we support everything in V4L2_STD_ALL, but not more... */ + supported_std = (all_std & req_std); + if (no_std == supported_std) + return -EINVAL; + + return 0; +} + +/* gets a fake video standard + * called on VIDIOC_G_STD + */ +static int vidioc_g_std(struct file *file, void *fh, v4l2_std_id *norm) +{ + if (norm) + *norm = V4L2_STD_ALL; + return 0; +} +/* gets a fake video standard + * called on VIDIOC_QUERYSTD + */ +static int vidioc_querystd(struct file *file, void *fh, v4l2_std_id *norm) +{ + if (norm) + *norm = V4L2_STD_ALL; + return 0; +} +#endif /* V4L2LOOPBACK_WITH_STD */ + +static int v4l2loopback_set_ctrl(struct v4l2_loopback_device *dev, u32 id, + s64 val) +{ + int result = 0; + switch (id) { + case CID_KEEP_FORMAT: + if (val < 0 || val > 1) + return -EINVAL; + dev->keep_format = val; + result = mutex_lock_killable(&dev->image_mutex); + if (result < 0) + return result; + if (!dev->keep_format) { + if (has_no_owners(dev) && !any_buffers_mapped(dev)) + free_buffers(dev); + } + mutex_unlock(&dev->image_mutex); + break; + case CID_SUSTAIN_FRAMERATE: + if (val < 0 || val > 1) + return -EINVAL; + spin_lock_bh(&dev->lock); + dev->sustain_framerate = val; + check_timers(dev); + spin_unlock_bh(&dev->lock); + break; + case CID_TIMEOUT: + if (val < 0 || val > MAX_TIMEOUT) + return -EINVAL; + if (val > 0) { + result = mutex_lock_killable(&dev->image_mutex); + if (result < 0) + return result; + /* on-the-fly allocate if device is owned; else + * allocate occurs on next S_FMT or REQBUFS */ + if (!has_no_owners(dev)) + result = allocate_timeout_buffer(dev); + mutex_unlock(&dev->image_mutex); + if (result < 0) { + /* disable timeout as buffer not alloc'd */ + spin_lock_bh(&dev->lock); + dev->timeout_jiffies = 0; + spin_unlock_bh(&dev->lock); + return result; + } + } + spin_lock_bh(&dev->lock); + dev->timeout_jiffies = msecs_to_jiffies(val); + check_timers(dev); + spin_unlock_bh(&dev->lock); + break; + case CID_TIMEOUT_IMAGE_IO: + dev->timeout_image_io = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static int v4l2loopback_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_loopback_device *dev = container_of( + ctrl->handler, struct v4l2_loopback_device, ctrl_handler); + return v4l2loopback_set_ctrl(dev, ctrl->id, ctrl->val); +} + +/* returns set of device outputs, in our case there is only one + * called on VIDIOC_ENUMOUTPUT + */ +static int vidioc_enum_output(struct file *file, void *fh, + struct v4l2_output *outp) +{ + __u32 index = outp->index; + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + + if (check_buffer_capability(dev, opener, V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -ENOTTY; + if (index) + return -EINVAL; + + /* clear all data (including the reserved fields) */ + memset(outp, 0, sizeof(*outp)); + + outp->index = index; + strscpy(outp->name, "loopback in", sizeof(outp->name)); + outp->type = V4L2_OUTPUT_TYPE_ANALOG; + outp->audioset = 0; + outp->modulator = 0; +#ifdef V4L2LOOPBACK_WITH_STD + outp->std = V4L2_STD_ALL; +#ifdef V4L2_OUT_CAP_STD + outp->capabilities |= V4L2_OUT_CAP_STD; +#endif /* V4L2_OUT_CAP_STD */ +#endif /* V4L2LOOPBACK_WITH_STD */ + + return 0; +} + +/* which output is currently active, + * called on VIDIOC_G_OUTPUT + */ +static int vidioc_g_output(struct file *file, void *fh, unsigned int *index) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + if (check_buffer_capability(dev, opener, V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -ENOTTY; + if (index) + *index = 0; + return 0; +} + +/* set output, can make sense if we have more than one video src, + * called on VIDIOC_S_OUTPUT + */ +static int vidioc_s_output(struct file *file, void *fh, unsigned int index) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + if (check_buffer_capability(dev, opener, V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -ENOTTY; + return index == 0 ? index : -EINVAL; +} + +/* returns set of device inputs, in our case there is only one, + * but later I may add more + * called on VIDIOC_ENUMINPUT + */ +static int vidioc_enum_input(struct file *file, void *fh, + struct v4l2_input *inp) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + __u32 index = inp->index; + + if (check_buffer_capability(dev, opener, V4L2_BUF_TYPE_VIDEO_CAPTURE)) + return -ENOTTY; + if (index) + return -EINVAL; + + /* clear all data (including the reserved fields) */ + memset(inp, 0, sizeof(*inp)); + + inp->index = index; + strscpy(inp->name, "loopback", sizeof(inp->name)); + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->audioset = 0; + inp->tuner = 0; + inp->status = 0; + +#ifdef V4L2LOOPBACK_WITH_STD + inp->std = V4L2_STD_ALL; +#ifdef V4L2_IN_CAP_STD + inp->capabilities |= V4L2_IN_CAP_STD; +#endif +#endif /* V4L2LOOPBACK_WITH_STD */ + + if (has_output_token(dev->stream_tokens) && !dev->keep_format) + /* if no outputs attached; pretend device is powered off */ + inp->status |= V4L2_IN_ST_NO_SIGNAL; + + return 0; +} + +/* which input is currently active, + * called on VIDIOC_G_INPUT + */ +static int vidioc_g_input(struct file *file, void *fh, unsigned int *index) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + if (check_buffer_capability(dev, opener, V4L2_BUF_TYPE_VIDEO_CAPTURE)) + return -ENOTTY; /* NOTE: -EAGAIN might be more informative */ + if (index) + *index = 0; + return 0; +} + +/* set input, can make sense if we have more than one video src, + * called on VIDIOC_S_INPUT + */ +static int vidioc_s_input(struct file *file, void *fh, unsigned int index) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + if (index != 0) + return -EINVAL; + if (check_buffer_capability(dev, opener, V4L2_BUF_TYPE_VIDEO_CAPTURE)) + return -ENOTTY; /* NOTE: -EAGAIN might be more informative */ + return 0; +} + +/* --------------- V4L2 ioctl buffer related calls ----------------- */ + +#define is_allocated(opener, type, index) \ + (opener->format_token & (opener->io_method == V4L2L_IO_TIMEOUT ? \ + V4L2L_TOKEN_TIMEOUT : \ + token_from_type(type)) && \ + (index) < (opener)->buffer_count) +#define BUFFER_DEBUG_FMT_STR \ + "buffer#%u @ %p type=%u bytesused=%u length=%u flags=%x " \ + "field=%u timestamp= %lld.%06lldsequence=%u\n" +#define BUFFER_DEBUG_FMT_ARGS(buf) \ + (buf)->index, (buf), (buf)->type, (buf)->bytesused, (buf)->length, \ + (buf)->flags, (buf)->field, \ + (long long)(buf)->timestamp.tv_sec, \ + (long long)(buf)->timestamp.tv_usec, (buf)->sequence +/* Buffer flag helpers */ +#define unset_flags(flags) \ + do { \ + flags &= ~V4L2_BUF_FLAG_QUEUED; \ + flags &= ~V4L2_BUF_FLAG_DONE; \ + } while (0) +#define set_queued(flags) \ + do { \ + flags |= V4L2_BUF_FLAG_QUEUED; \ + flags &= ~V4L2_BUF_FLAG_DONE; \ + } while (0) +#define set_done(flags) \ + do { \ + flags &= ~V4L2_BUF_FLAG_QUEUED; \ + flags |= V4L2_BUF_FLAG_DONE; \ + } while (0) + +static bool any_buffers_mapped(struct v4l2_loopback_device *dev) +{ + u32 index; + for (index = 0; index < dev->buffer_count; ++index) + if (dev->buffers[index].buffer.flags & V4L2_BUF_FLAG_MAPPED) + return true; + return false; +} + +static void prepare_buffer_queue(struct v4l2_loopback_device *dev, int count) +{ + struct v4l2l_buffer *bufd, *n; + u32 pos; + + spin_lock_bh(&dev->list_lock); + + /* ensure sufficient number of buffers in queue */ + for (pos = 0; pos < count; ++pos) { + bufd = &dev->buffers[pos]; + if (list_empty(&bufd->list_head)) + list_add_tail(&bufd->list_head, &dev->outbufs_list); + } + if (list_empty(&dev->outbufs_list)) + goto exit_prepare_queue_unlock; + + /* remove any excess buffers */ + list_for_each_entry_safe(bufd, n, &dev->outbufs_list, list_head) { + if (bufd->buffer.index >= count) + list_del_init(&bufd->list_head); + } + + /* buffers are no longer queued; and `write_position` will correspond + * to the first item of `outbufs_list`. */ + pos = v4l2l_mod64(dev->write_position, count); + list_for_each_entry(bufd, &dev->outbufs_list, list_head) { + unset_flags(bufd->buffer.flags); + dev->bufpos2index[pos % count] = bufd->buffer.index; + ++pos; + } +exit_prepare_queue_unlock: + spin_unlock_bh(&dev->list_lock); +} + +/* forward declaration */ +static int vidioc_streamoff(struct file *file, void *fh, + enum v4l2_buf_type type); +/* negotiate buffer type + * only mmap streaming supported + * called on VIDIOC_REQBUFS + */ +static int vidioc_reqbufs(struct file *file, void *fh, + struct v4l2_requestbuffers *reqbuf) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + u32 token = opener->io_method == V4L2L_IO_TIMEOUT ? + V4L2L_TOKEN_TIMEOUT : + token_from_type(reqbuf->type); + u32 req_count = reqbuf->count; + int result = 0; + + dprintk("REQBUFS(memory=%u, req_count=%u) and device-bufs=%u/%u " + "[used/max]\n", + reqbuf->memory, req_count, dev->used_buffer_count, + dev->buffer_count); + + switch (reqbuf->memory) { + case V4L2_MEMORY_MMAP: +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) + reqbuf->capabilities = 0; /* only guarantee MMAP support */ +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0) + reqbuf->flags = 0; /* no memory consistency support */ +#endif + break; + default: + return -EINVAL; + } + + if (opener->format_token & ~token) + /* different (buffer) type already assigned to descriptor by + * S_FMT or REQBUFS */ + return -EINVAL; + + MARK(); + result = mutex_lock_killable(&dev->image_mutex); + if (result < 0) + return result; /* -EINTR */ + + /* CASE queue/dequeue timeout-buffer only: */ + if (opener->format_token & V4L2L_TOKEN_TIMEOUT) { + opener->buffer_count = req_count; + if (req_count == 0) + release_token(dev, opener, format); + goto exit_reqbufs_unlock; + } + + MARK(); + /* CASE count is zero: streamoff, free buffers, release their token */ + if (req_count == 0) { + if (dev->format_tokens & token) { + acquire_token(dev, opener, format, token); + opener->io_method = V4L2L_IO_MMAP; + } + result = vidioc_streamoff(file, fh, reqbuf->type); + opener->buffer_count = 0; + /* undocumented requirement - REQBUFS with count zero should + * ALSO release lock on logical stream */ + if (opener->format_token) + release_token(dev, opener, format); + if (has_no_owners(dev)) + dev->used_buffer_count = 0; + goto exit_reqbufs_unlock; + } + + /* CASE count non-zero: allocate buffers and acquire token for them */ + MARK(); + switch (reqbuf->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (!(dev->format_tokens & token || + opener->format_token & token)) + /* only exclusive ownership for each stream */ + result = -EBUSY; + break; + default: + result = -EINVAL; + } + if (result < 0) + goto exit_reqbufs_unlock; + + if (has_other_owners(opener, dev) && dev->used_buffer_count > 0) { + /* allow 'allocation' of existing number of buffers */ + req_count = dev->used_buffer_count; + } else if (any_buffers_mapped(dev)) { + /* do not allow re-allocation if buffers are mapped */ + result = -EBUSY; + goto exit_reqbufs_unlock; + } + + MARK(); + opener->buffer_count = 0; + + if (req_count > dev->buffer_count) + req_count = dev->buffer_count; + + if (has_no_owners(dev)) { + result = allocate_buffers(dev, &dev->pix_format); + if (result < 0) + goto exit_reqbufs_unlock; + } + if (!dev->timeout_image && need_timeout_buffer(dev, token)) { + result = allocate_timeout_buffer(dev); + if (result < 0) + goto exit_reqbufs_unlock; + } + acquire_token(dev, opener, format, token); + + MARK(); + switch (opener->io_method) { + case V4L2L_IO_TIMEOUT: + dev->timeout_image_io = 0; + opener->buffer_count = req_count; + break; + default: + opener->io_method = V4L2L_IO_MMAP; + prepare_buffer_queue(dev, req_count); + dev->used_buffer_count = opener->buffer_count = req_count; + } +exit_reqbufs_unlock: + mutex_unlock(&dev->image_mutex); + reqbuf->count = opener->buffer_count; + return result; +} + +/* returns buffer asked for; + * give app as many buffers as it wants, if it less than MAX, + * but map them in our inner buffers + * called on VIDIOC_QUERYBUF + */ +static int vidioc_querybuf(struct file *file, void *fh, struct v4l2_buffer *buf) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + u32 type = buf->type; + u32 index = buf->index; + + if ((type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (type != V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -EINVAL; + if (!is_allocated(opener, type, index)) + return -EINVAL; + + if (opener->format_token & V4L2L_TOKEN_TIMEOUT) { + *buf = dev->timeout_buffer.buffer; + buf->index = index; + } else + *buf = dev->buffers[index].buffer; + + buf->type = type; + + if (!(buf->flags & (V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_QUEUED))) { + /* v4l2-compliance requires these to be zero */ + buf->sequence = 0; + buf->timestamp.tv_sec = buf->timestamp.tv_usec = 0; + } else if (V4L2_TYPE_IS_CAPTURE(type)) { + /* guess flags based on sequence values */ + if (buf->sequence >= opener->read_position) { + set_done(buf->flags); + } else if (buf->flags & V4L2_BUF_FLAG_DONE) { + set_queued(buf->flags); + } + } + dprintkrw("QUERYBUF(%s, index=%u) -> " BUFFER_DEBUG_FMT_STR, + V4L2_TYPE_IS_CAPTURE(type) ? "CAPTURE" : "OUTPUT", index, + BUFFER_DEBUG_FMT_ARGS(buf)); + return 0; +} + +static void buffer_written(struct v4l2_loopback_device *dev, + struct v4l2l_buffer *buf) +{ + timer_delete_sync(&dev->sustain_timer); + timer_delete_sync(&dev->timeout_timer); + + spin_lock_bh(&dev->list_lock); + list_move_tail(&buf->list_head, &dev->outbufs_list); + spin_unlock_bh(&dev->list_lock); + + spin_lock_bh(&dev->lock); + dev->bufpos2index[v4l2l_mod64(dev->write_position, + dev->used_buffer_count)] = + buf->buffer.index; + ++dev->write_position; + dev->reread_count = 0; + + check_timers(dev); + spin_unlock_bh(&dev->lock); +} + +/* put buffer to queue + * called on VIDIOC_QBUF + */ +static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + struct v4l2l_buffer *bufd; + u32 index = buf->index; + u32 type = buf->type; + + if (!is_allocated(opener, type, index)) + return -EINVAL; + bufd = &dev->buffers[index]; + + switch (buf->memory) { + case V4L2_MEMORY_MMAP: + if (!(bufd->buffer.flags & V4L2_BUF_FLAG_MAPPED)) + dprintkrw("QBUF() unmapped buffer [index=%u]\n", index); + break; + default: + return -EINVAL; + } + + if (opener->format_token & V4L2L_TOKEN_TIMEOUT) { + set_queued(buf->flags); + return 0; + } + + switch (type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + dprintkrw("QBUF(CAPTURE, index=%u) -> " BUFFER_DEBUG_FMT_STR, + index, BUFFER_DEBUG_FMT_ARGS(buf)); + set_queued(buf->flags); + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + dprintkrw("QBUF(OUTPUT, index=%u) -> " BUFFER_DEBUG_FMT_STR, + index, BUFFER_DEBUG_FMT_ARGS(buf)); + if (!(bufd->buffer.flags & V4L2_BUF_FLAG_TIMESTAMP_COPY) && + (buf->timestamp.tv_sec == 0 && + buf->timestamp.tv_usec == 0)) { + v4l2l_get_timestamp(&bufd->buffer); + } else { + bufd->buffer.timestamp = buf->timestamp; + bufd->buffer.flags |= V4L2_BUF_FLAG_TIMESTAMP_COPY; + bufd->buffer.flags &= + ~V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + } + if (dev->pix_format_has_valid_sizeimage) { + if (buf->bytesused >= dev->pix_format.sizeimage) { + bufd->buffer.bytesused = + dev->pix_format.sizeimage; + } else { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + dev_warn_ratelimited( + &dev->vdev->dev, +#else + dprintkrw( +#endif + "warning queued output buffer bytesused too small %u < %u\n", + buf->bytesused, + dev->pix_format.sizeimage); + bufd->buffer.bytesused = buf->bytesused; + } + } else { + bufd->buffer.bytesused = buf->bytesused; + } + bufd->buffer.sequence = dev->write_position; + set_queued(bufd->buffer.flags); + *buf = bufd->buffer; + buffer_written(dev, bufd); + set_done(bufd->buffer.flags); + wake_up_all(&dev->read_event); + break; + default: + return -EINVAL; + } + buf->type = type; + return 0; +} + +static int can_read(struct v4l2_loopback_device *dev, + struct v4l2_loopback_opener *opener) +{ + int ret; + + spin_lock_bh(&dev->lock); + check_timers(dev); + ret = dev->write_position > opener->read_position || + dev->reread_count > opener->reread_count || dev->timeout_happened; + spin_unlock_bh(&dev->lock); + return ret; +} + +static int get_capture_buffer(struct file *file) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = + v4l2l_f_to_opener(file, file->private_data); + int pos, timeout_happened; + u32 index; + + if ((file->f_flags & O_NONBLOCK) && + (dev->write_position <= opener->read_position && + dev->reread_count <= opener->reread_count && + !dev->timeout_happened)) + return -EAGAIN; + wait_event_interruptible(dev->read_event, can_read(dev, opener)); + + spin_lock_bh(&dev->lock); + if (dev->write_position == opener->read_position) { + if (dev->reread_count > opener->reread_count + 2) + opener->reread_count = dev->reread_count - 1; + ++opener->reread_count; + pos = v4l2l_mod64(opener->read_position + + dev->used_buffer_count - 1, + dev->used_buffer_count); + } else { + opener->reread_count = 0; + if (dev->write_position > + opener->read_position + dev->used_buffer_count) + opener->read_position = dev->write_position - 1; + pos = v4l2l_mod64(opener->read_position, + dev->used_buffer_count); + ++opener->read_position; + } + timeout_happened = dev->timeout_happened && (dev->timeout_jiffies > 0); + dev->timeout_happened = 0; + spin_unlock_bh(&dev->lock); + + index = dev->bufpos2index[pos]; + if (timeout_happened) { + if (index >= dev->used_buffer_count) { + dprintkrw("get_capture_buffer() read position is at " + "an unallocated buffer [index=%u]\n", + index); + return -EFAULT; + } + /* although allocated on-demand, timeout_image is freed only + * in free_buffers(), so we don't need to worry about it being + * deallocated suddenly */ + memcpy(dev->image + dev->buffers[index].buffer.m.offset, + dev->timeout_image, dev->buffer_size); + } + return (int)index; +} + +/* put buffer to dequeue + * called on VIDIOC_DQBUF + */ +static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + u32 type = buf->type; + int index; + struct v4l2l_buffer *bufd; + + if (buf->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + if (opener->format_token & V4L2L_TOKEN_TIMEOUT) { + *buf = dev->timeout_buffer.buffer; + buf->type = type; + unset_flags(buf->flags); + return 0; + } + if ((opener->buffer_count == 0) || + !(opener->format_token & token_from_type(type))) + return -EINVAL; + + switch (type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + index = get_capture_buffer(file); + if (index < 0) + return index; + *buf = dev->buffers[index].buffer; + unset_flags(buf->flags); + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + spin_lock_bh(&dev->list_lock); + + bufd = list_first_entry_or_null(&dev->outbufs_list, + struct v4l2l_buffer, list_head); + if (bufd) + list_move_tail(&bufd->list_head, &dev->outbufs_list); + + spin_unlock_bh(&dev->list_lock); + if (!bufd) + return -EFAULT; + unset_flags(bufd->buffer.flags); + *buf = bufd->buffer; + break; + default: + return -EINVAL; + } + + buf->type = type; + dprintkrw("DQBUF(%s, index=%u) -> " BUFFER_DEBUG_FMT_STR, + V4L2_TYPE_IS_CAPTURE(type) ? "CAPTURE" : "OUTPUT", index, + BUFFER_DEBUG_FMT_ARGS(buf)); + return 0; +} + +/* ------------- STREAMING ------------------- */ + +/* start streaming + * called on VIDIOC_STREAMON + */ +static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type type) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + u32 token = token_from_type(type); + + /* short-circuit when using timeout buffer set */ + if (opener->format_token & V4L2L_TOKEN_TIMEOUT) + return 0; + /* opener must have claimed (same) buffer set via REQBUFS */ + if (!opener->buffer_count || !(opener->format_token & token)) + return -EINVAL; + + switch (type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (has_output_token(dev->stream_tokens) && !dev->keep_format) + return -EIO; + if (dev->stream_tokens & token) { + acquire_token(dev, opener, stream, token); + client_usage_queue_event(dev->vdev); + } + return 0; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (dev->stream_tokens & token) + acquire_token(dev, opener, stream, token); + return 0; + default: + return -EINVAL; + } +} + +/* stop streaming + * called on VIDIOC_STREAMOFF + */ +static int vidioc_streamoff(struct file *file, void *fh, + enum v4l2_buf_type type) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + u32 token = token_from_type(type); + + /* short-circuit when using timeout buffer set */ + if (opener->format_token & V4L2L_TOKEN_TIMEOUT) + return 0; + /* short-circuit when buffer set has no owner */ + if (dev->format_tokens & token) + return 0; + /* opener needs a claim to buffer set */ + if (!opener->format_token) + return -EBUSY; + if (opener->format_token & ~token) + return -EINVAL; + + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (opener->stream_token & token) + release_token(dev, opener, stream); + /* reset output queue */ + if (dev->used_buffer_count > 0) + prepare_buffer_queue(dev, dev->used_buffer_count); + return 0; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (opener->stream_token & token) { + release_token(dev, opener, stream); + client_usage_queue_event(dev->vdev); + } + return 0; + default: + return -EINVAL; + } +} + +#ifdef CONFIG_VIDEO_V4L1_COMPAT +static int vidiocgmbuf(struct file *file, void *fh, struct video_mbuf *p) +{ + struct v4l2_loopback_device *dev; + MARK(); + + dev = v4l2loopback_getdevice(file); + p->frames = dev->buffer_count; + p->offsets[0] = 0; + p->offsets[1] = 0; + p->size = dev->buffer_size; + return 0; +} +#endif + +static void client_usage_queue_event(struct video_device *vdev) +{ + struct v4l2_event ev; + struct v4l2_loopback_device *dev; + + dev = container_of(vdev->v4l2_dev, struct v4l2_loopback_device, + v4l2_dev); + + memset(&ev, 0, sizeof(ev)); + ev.type = V4L2_EVENT_PRI_CLIENT_USAGE; + ((struct v4l2_event_client_usage *)&ev.u)->count = + !has_capture_token(dev->stream_tokens); + + v4l2_event_queue(vdev, &ev); +} + +static int client_usage_ops_add(struct v4l2_subscribed_event *sev, + unsigned elems) +{ + if (!(sev->flags & V4L2_EVENT_SUB_FL_SEND_INITIAL)) + return 0; + + client_usage_queue_event(sev->fh->vdev); + return 0; +} + +static void client_usage_ops_replace(struct v4l2_event *old, + const struct v4l2_event *new) +{ + *((struct v4l2_event_client_usage *)&old->u) = + *((struct v4l2_event_client_usage *)&new->u); +} + +static void client_usage_ops_merge(const struct v4l2_event *old, + struct v4l2_event *new) +{ + *((struct v4l2_event_client_usage *)&new->u) = + *((struct v4l2_event_client_usage *)&old->u); +} + +const struct v4l2_subscribed_event_ops client_usage_ops = { + .add = client_usage_ops_add, + .replace = client_usage_ops_replace, + .merge = client_usage_ops_merge, +}; + +static int vidioc_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + case V4L2_EVENT_PRI_CLIENT_USAGE: + return v4l2_event_subscribe(fh, sub, 0, &client_usage_ops); + } + + return -EINVAL; +} + +/* file operations */ +static void vm_open(struct vm_area_struct *vma) +{ + struct v4l2l_buffer *buf; + MARK(); + + buf = vma->vm_private_data; + atomic_inc(&buf->use_count); + buf->buffer.flags |= V4L2_BUF_FLAG_MAPPED; +} + +static void vm_close(struct vm_area_struct *vma) +{ + struct v4l2l_buffer *buf; + MARK(); + + buf = vma->vm_private_data; + if (atomic_dec_and_test(&buf->use_count)) + buf->buffer.flags &= ~V4L2_BUF_FLAG_MAPPED; +} + +static struct vm_operations_struct vm_ops = { + .open = vm_open, + .close = vm_close, +}; + +static int v4l2_loopback_mmap(struct file *file, struct vm_area_struct *vma) +{ + u8 *addr; + unsigned long start, size, offset; + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = + v4l2l_f_to_opener(file, file->private_data); + struct v4l2l_buffer *buffer = NULL; + int result = 0; + MARK(); + + offset = (unsigned long)vma->vm_pgoff << PAGE_SHIFT; + start = (unsigned long)vma->vm_start; + size = (unsigned long)(vma->vm_end - vma->vm_start); /* always != 0 */ + + /* ensure buffer size, count, and allocated image(s) are not altered by + * other file descriptors */ + result = mutex_lock_killable(&dev->image_mutex); + if (result < 0) + return result; + + if (size > dev->buffer_size) { + dprintk("mmap() attempt to map %lubytes when %ubytes are " + "allocated to buffers\n", + size, dev->buffer_size); + result = -EINVAL; + goto exit_mmap_unlock; + } + if (offset % dev->buffer_size != 0) { + dprintk("mmap() offset does not match start of any buffer\n"); + result = -EINVAL; + goto exit_mmap_unlock; + } + switch (opener->format_token) { + case V4L2L_TOKEN_TIMEOUT: + if (offset != (unsigned long)dev->buffer_size * MAX_BUFFERS) { + dprintk("mmap() incorrect offset for timeout image\n"); + result = -EINVAL; + goto exit_mmap_unlock; + } + buffer = &dev->timeout_buffer; + addr = dev->timeout_image; + break; + default: + if (offset >= dev->image_size) { + dprintk("mmap() attempt to map beyond all buffers\n"); + result = -EINVAL; + goto exit_mmap_unlock; + } + u32 index = offset / dev->buffer_size; + buffer = &dev->buffers[index]; + addr = dev->image + offset; + break; + } + + while (size > 0) { + struct page *page = vmalloc_to_page(addr); + + result = vm_insert_page(vma, start, page); + if (result < 0) + goto exit_mmap_unlock; + + start += PAGE_SIZE; + addr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + vma->vm_ops = &vm_ops; + vma->vm_private_data = buffer; + + vm_open(vma); +exit_mmap_unlock: + mutex_unlock(&dev->image_mutex); + return result; +} + +static unsigned int v4l2_loopback_poll(struct file *file, + struct poll_table_struct *pts) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = + v4l2l_f_to_opener(file, file->private_data); + __poll_t req_events = poll_requested_events(pts); + int ret_mask = 0; + + /* call poll_wait in first call, regardless, to ensure that the + * wait-queue is not null */ + poll_wait(file, &dev->read_event, pts); + poll_wait(file, &opener->fh.wait, pts); + + if (req_events & POLLPRI) { + if (v4l2_event_pending(&opener->fh)) { + ret_mask |= POLLPRI; + if (!(req_events & DEFAULT_POLLMASK)) + return ret_mask; + } + } + + switch (opener->format_token) { + case V4L2L_TOKEN_OUTPUT: + if (opener->stream_token != 0 || + opener->io_method == V4L2L_IO_NONE) + ret_mask |= POLLOUT | POLLWRNORM; + break; + case V4L2L_TOKEN_CAPTURE: + if ((opener->io_method == V4L2L_IO_NONE || + opener->stream_token != 0) && + can_read(dev, opener)) + ret_mask |= POLLIN | POLLWRNORM; + break; + case V4L2L_TOKEN_TIMEOUT: + ret_mask |= POLLOUT | POLLWRNORM; + break; + default: + break; + } + + return ret_mask; +} + +/* do not want to limit device opens, it can be as many readers as user want, + * writers are limited by means of setting writer field */ +static int v4l2_loopback_open(struct file *file) +{ + struct v4l2_loopback_device *dev; + struct v4l2_loopback_opener *opener; + + dev = v4l2loopback_getdevice(file); + if (dev->open_count.counter >= dev->max_openers) + return -EBUSY; + /* kfree on close */ + opener = kzalloc(sizeof(*opener), GFP_KERNEL); + if (opener == NULL) + return -ENOMEM; + + atomic_inc(&dev->open_count); + if (dev->timeout_image_io && dev->format_tokens & V4L2L_TOKEN_TIMEOUT) + /* will clear timeout_image_io once buffer set acquired */ + opener->io_method = V4L2L_IO_TIMEOUT; + + v4l2_fh_init(&opener->fh, video_devdata(file)); + file->private_data = &opener->fh; + + v4l2_fh_add(&opener->fh, file); + dprintk("open() -> dev@%p with image@%p\n", dev, + dev ? dev->image : NULL); + return 0; +} + +static int v4l2_loopback_close(struct file *file) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = + v4l2l_f_to_opener(file, file->private_data); + int result = 0; + dprintk("close() -> dev@%p with image@%p\n", dev, + dev ? dev->image : NULL); + + if (opener->format_token) { + struct v4l2_requestbuffers reqbuf = { + .count = 0, .memory = V4L2_MEMORY_MMAP, .type = 0 + }; + switch (opener->format_token) { + case V4L2L_TOKEN_CAPTURE: + reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + break; + case V4L2L_TOKEN_OUTPUT: + case V4L2L_TOKEN_TIMEOUT: + reqbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + break; + } + if (reqbuf.type) + result = vidioc_reqbufs(file, file->private_data, + &reqbuf); + if (result < 0) + dprintk("failed to free buffers REQBUFS(count=0) " + " returned %d\n", + result); + mutex_lock(&dev->image_mutex); + release_token(dev, opener, format); + mutex_unlock(&dev->image_mutex); + } + + if (atomic_dec_and_test(&dev->open_count)) { + timer_delete_sync(&dev->sustain_timer); + timer_delete_sync(&dev->timeout_timer); + if (!dev->keep_format) { + mutex_lock(&dev->image_mutex); + free_buffers(dev); + mutex_unlock(&dev->image_mutex); + } + } + + v4l2_fh_del(&opener->fh, file); + v4l2_fh_exit(&opener->fh); + + kfree(opener); + return 0; +} + +static int start_fileio(struct file *file, void *fh, enum v4l2_buf_type type) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_opener *opener = v4l2l_f_to_opener(file, fh); + struct v4l2_requestbuffers reqbuf = { .count = dev->buffer_count, + .memory = V4L2_MEMORY_MMAP, + .type = type }; + int token = token_from_type(type); + int result; + + if (opener->format_token & V4L2L_TOKEN_TIMEOUT || + opener->format_token & ~token) + return -EBUSY; /* NOTE: -EBADF might be more informative */ + + /* short-circuit if already have stream token */ + if (opener->stream_token && opener->io_method == V4L2L_IO_FILE) + return 0; + + /* otherwise attempt to acquire stream token and assign IO method */ + if (!(dev->stream_tokens & token) || opener->io_method != V4L2L_IO_NONE) + return -EBUSY; + + result = vidioc_reqbufs(file, fh, &reqbuf); + if (result < 0) + return result; + result = vidioc_streamon(file, fh, type); + if (result < 0) + return result; + + opener->io_method = V4L2L_IO_FILE; + return 0; +} + +static ssize_t v4l2_loopback_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_buffer *b; + int index, result; + + dprintkrw("read() %zu bytes\n", count); + result = start_fileio(file, file->private_data, + V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (result < 0) + return result; + + index = get_capture_buffer(file); + if (index < 0) + return index; + b = &dev->buffers[index].buffer; + if (count > b->bytesused) + count = b->bytesused; + if (copy_to_user((void *)buf, (void *)(dev->image + b->m.offset), + count)) { + printk(KERN_ERR "v4l2-loopback read() failed copy_to_user()\n"); + return -EFAULT; + } + return count; +} + +static ssize_t v4l2_loopback_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_buffer *b; + int index, result; + + dprintkrw("write() %zu bytes\n", count); + result = start_fileio(file, file->private_data, + V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (result < 0) + return result; + + if (count > dev->buffer_size) + count = dev->buffer_size; + index = v4l2l_mod64(dev->write_position, dev->used_buffer_count); + b = &dev->buffers[index].buffer; + + if (copy_from_user((void *)(dev->image + b->m.offset), (void *)buf, + count)) { + printk(KERN_ERR + "v4l2-loopback write() failed copy_from_user()\n"); + return -EFAULT; + } + b->bytesused = count; + + v4l2l_get_timestamp(b); + b->sequence = dev->write_position; + set_queued(b->flags); + buffer_written(dev, &dev->buffers[index]); + set_done(b->flags); + wake_up_all(&dev->read_event); + + return count; +} + +/* init functions */ +/* frees buffers, if allocated */ +static void free_buffers(struct v4l2_loopback_device *dev) +{ + dprintk("free_buffers() with image@%p\n", dev->image); + if (!dev->image) + return; + if (!has_no_owners(dev) || any_buffers_mapped(dev)) + /* maybe an opener snuck in before image_mutex was acquired */ + printk(KERN_WARNING + "v4l2-loopback free_buffers() buffers of video device " + "#%u freed while still mapped to userspace\n", + dev->vdev->num); + vfree(dev->image); + dev->image = NULL; + dev->image_size = 0; + dev->buffer_size = 0; +} + +static void free_timeout_buffer(struct v4l2_loopback_device *dev) +{ + dprintk("free_timeout_buffer() with timeout_image@%p\n", + dev->timeout_image); + if (!dev->timeout_image) + return; + + if ((dev->timeout_jiffies > 0 && !has_no_owners(dev)) || + dev->timeout_buffer.buffer.flags & V4L2_BUF_FLAG_MAPPED) + printk(KERN_WARNING + "v4l2-loopback free_timeout_buffer() timeout image " + "of device #%u freed while still mapped to userspace\n", + dev->vdev->num); + + vfree(dev->timeout_image); + dev->timeout_image = NULL; + dev->timeout_buffer_size = 0; +} +/* allocates buffers if no (other) openers are already using them */ +static int allocate_buffers(struct v4l2_loopback_device *dev, + struct v4l2_pix_format *pix_format) +{ + u32 buffer_size = PAGE_ALIGN(pix_format->sizeimage); + unsigned long image_size = + (unsigned long)buffer_size * (unsigned long)dev->buffer_count; + /* vfree on close file operation in case no open handles left */ + + if (buffer_size == 0 || dev->buffer_count == 0 || + buffer_size < pix_format->sizeimage) + return -EINVAL; + + if ((__LONG_MAX__ / buffer_size) < dev->buffer_count) + return -ENOSPC; + + dprintk("allocate_buffers() size %lubytes = %ubytes x %ubuffers\n", + image_size, buffer_size, dev->buffer_count); + if (dev->image) { + /* check that no buffers are expected in user-space */ + if (!has_no_owners(dev) || any_buffers_mapped(dev)) + return -EBUSY; + dprintk("allocate_buffers() existing size=%lubytes\n", + dev->image_size); + /* FIXME: prevent double allocation more intelligently! */ + if (image_size == dev->image_size) { + dprintk("allocate_buffers() keep existing\n"); + return 0; + } + free_buffers(dev); + } + + /* FIXME: set buffers to 0 */ + dev->image = vmalloc(image_size); + if (dev->image == NULL) { + dev->buffer_size = dev->image_size = 0; + return -ENOMEM; + } + init_buffers(dev, pix_format->sizeimage, buffer_size); + dev->buffer_size = buffer_size; + dev->image_size = image_size; + dprintk("allocate_buffers() -> vmalloc'd %lubytes\n", dev->image_size); + return 0; +} +static int allocate_timeout_buffer(struct v4l2_loopback_device *dev) +{ + /* device's `buffer_size` and `buffers` must be initialised in + * allocate_buffers() */ + + dprintk("allocate_timeout_buffer() size %ubytes\n", dev->buffer_size); + if (dev->buffer_size == 0) + return -EINVAL; + + if (dev->timeout_image) { + if (dev->timeout_buffer.buffer.flags & V4L2_BUF_FLAG_MAPPED) + return -EBUSY; + if (dev->buffer_size == dev->timeout_buffer_size) + return 0; + free_timeout_buffer(dev); + } + + dev->timeout_image = vzalloc(dev->buffer_size); + if (!dev->timeout_image) { + dev->timeout_buffer_size = 0; + return -ENOMEM; + } + dev->timeout_buffer_size = dev->buffer_size; + return 0; +} +/* init inner buffers, they are capture mode and flags are set as for capture + * mode buffers */ +static void init_buffers(struct v4l2_loopback_device *dev, u32 bytes_used, + u32 buffer_size) +{ + u32 i; + + for (i = 0; i < dev->buffer_count; ++i) { + struct v4l2_buffer *b = &dev->buffers[i].buffer; + b->index = i; + b->bytesused = bytes_used; + b->length = buffer_size; + b->field = V4L2_FIELD_NONE; + b->flags = 0; + b->m.offset = i * buffer_size; + b->memory = V4L2_MEMORY_MMAP; + b->sequence = 0; + b->timestamp.tv_sec = 0; + b->timestamp.tv_usec = 0; + b->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + v4l2l_get_timestamp(b); + } + dev->timeout_buffer = dev->buffers[0]; + dev->timeout_buffer.buffer.m.offset = MAX_BUFFERS * buffer_size; +} + +/* fills and register video device */ +static void init_vdev(struct video_device *vdev, int nr) +{ +#ifdef V4L2LOOPBACK_WITH_STD + vdev->tvnorms = V4L2_STD_ALL; +#endif /* V4L2LOOPBACK_WITH_STD */ + + vdev->vfl_type = VFL_TYPE_VIDEO; + vdev->fops = &v4l2_loopback_fops; + vdev->ioctl_ops = &v4l2_loopback_ioctl_ops; + vdev->release = &video_device_release; + vdev->minor = -1; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + vdev->device_caps = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; +#endif + + if (debug > 1) + vdev->dev_debug = V4L2_DEV_DEBUG_IOCTL | + V4L2_DEV_DEBUG_IOCTL_ARG; + + vdev->vfl_dir = VFL_DIR_M2M; +} + +/* init default capture parameters, only fps may be changed in future */ +static void init_capture_param(struct v4l2_captureparm *capture_param) +{ + capture_param->capability = V4L2_CAP_TIMEPERFRAME; /* since 2.16 */ + capture_param->capturemode = 0; + capture_param->extendedmode = 0; + capture_param->readbuffers = max_buffers; + capture_param->timeperframe.numerator = 1; + capture_param->timeperframe.denominator = V4L2LOOPBACK_FPS_DEFAULT; +} + +static void check_timers(struct v4l2_loopback_device *dev) +{ + if (has_output_token(dev->stream_tokens)) + return; + + if (dev->timeout_jiffies > 0 && !timer_pending(&dev->timeout_timer)) + mod_timer(&dev->timeout_timer, jiffies + dev->timeout_jiffies); + if (dev->sustain_framerate && !timer_pending(&dev->sustain_timer)) + mod_timer(&dev->sustain_timer, + jiffies + dev->frame_jiffies * 3 / 2); +} +#ifdef HAVE_TIMER_SETUP +static void sustain_timer_clb(struct timer_list *t) +{ + struct v4l2_loopback_device *dev = + container_of(t, struct v4l2_loopback_device, sustain_timer); +#else +static void sustain_timer_clb(unsigned long nr) +{ + struct v4l2_loopback_device *dev = + idr_find(&v4l2loopback_index_idr, nr); +#endif + spin_lock(&dev->lock); + if (dev->sustain_framerate) { + dev->reread_count++; + dprintkrw("sustain_timer_clb() write_pos=%lld reread=%u\n", + (long long)dev->write_position, dev->reread_count); + if (dev->reread_count == 1) + mod_timer(&dev->sustain_timer, + jiffies + max(1UL, dev->frame_jiffies / 2)); + else + mod_timer(&dev->sustain_timer, + jiffies + dev->frame_jiffies); + wake_up_all(&dev->read_event); + } + spin_unlock(&dev->lock); +} +#ifdef HAVE_TIMER_SETUP +static void timeout_timer_clb(struct timer_list *t) +{ + struct v4l2_loopback_device *dev = + container_of(t, struct v4l2_loopback_device, timeout_timer); +#else +static void timeout_timer_clb(unsigned long nr) +{ + struct v4l2_loopback_device *dev = + idr_find(&v4l2loopback_index_idr, nr); +#endif + spin_lock(&dev->lock); + if (dev->timeout_jiffies > 0) { + dev->timeout_happened = 1; + mod_timer(&dev->timeout_timer, jiffies + dev->timeout_jiffies); + wake_up_all(&dev->read_event); + } + spin_unlock(&dev->lock); +} + +/* init loopback main structure */ +#define DEFAULT_FROM_CONF(confmember, default_condition, default_value) \ + ((conf) ? \ + ((conf->confmember default_condition) ? (default_value) : \ + (conf->confmember)) : \ + default_value) + +static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) +{ + struct v4l2_loopback_device *dev; + struct v4l2_ctrl_handler *hdl; + struct v4l2loopback_private *vdev_priv = NULL; + int err; + + u32 _width = V4L2LOOPBACK_SIZE_DEFAULT_WIDTH; + u32 _height = V4L2LOOPBACK_SIZE_DEFAULT_HEIGHT; + + u32 _min_width = DEFAULT_FROM_CONF(min_width, + < V4L2LOOPBACK_SIZE_MIN_WIDTH, + V4L2LOOPBACK_SIZE_MIN_WIDTH); + u32 _min_height = DEFAULT_FROM_CONF(min_height, + < V4L2LOOPBACK_SIZE_MIN_HEIGHT, + V4L2LOOPBACK_SIZE_MIN_HEIGHT); + u32 _max_width = DEFAULT_FROM_CONF(max_width, < _min_width, max_width); + u32 _max_height = + DEFAULT_FROM_CONF(max_height, < _min_height, max_height); + bool _announce_all_caps = (conf && conf->announce_all_caps >= 0) ? + (bool)(conf->announce_all_caps) : + !(V4L2LOOPBACK_DEFAULT_EXCLUSIVECAPS); + int _max_buffers = DEFAULT_FROM_CONF(max_buffers, <= 0, max_buffers); + int _max_openers = DEFAULT_FROM_CONF(max_openers, <= 0, max_openers); + struct v4l2_format _fmt; + + int nr = -1; + + if (conf) { + const int output_nr = conf->output_nr; +#ifdef SPLIT_DEVICES + const int capture_nr = conf->capture_nr; +#else + const int capture_nr = output_nr; +#endif + if (capture_nr >= 0 && output_nr == capture_nr) { + nr = output_nr; + } else if (capture_nr < 0 && output_nr < 0) { + nr = -1; + } else if (capture_nr < 0) { + nr = output_nr; + } else if (output_nr < 0) { + nr = capture_nr; + } else { + printk(KERN_ERR + "v4l2-loopback add() split OUTPUT and CAPTURE " + "devices not yet supported.\n"); + printk(KERN_INFO + "v4l2-loopback add() both devices must have the " + "same number (%d != %d).\n", + output_nr, capture_nr); + return -EINVAL; + } + } + + if (idr_find(&v4l2loopback_index_idr, nr)) + return -EEXIST; + + /* initialisation of a new device */ + dprintk("add() creating device #%d\n", nr); + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* allocate id, if @id >= 0, we're requesting that specific id */ + if (nr >= 0) { + err = idr_alloc(&v4l2loopback_index_idr, dev, nr, nr + 1, + GFP_KERNEL); + if (err == -ENOSPC) + err = -EEXIST; + } else { + err = idr_alloc(&v4l2loopback_index_idr, dev, 0, 0, GFP_KERNEL); + } + if (err < 0) + goto out_free_dev; + + /* register new device */ + MARK(); + nr = err; + + if (conf && conf->card_label[0]) { + snprintf(dev->card_label, sizeof(dev->card_label), "%s", + conf->card_label); + } else { + snprintf(dev->card_label, sizeof(dev->card_label), + "Dummy video device (0x%04X)", nr); + } + snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), + "v4l2loopback-%03d", nr); + + err = v4l2_device_register(NULL, &dev->v4l2_dev); + if (err) + goto out_free_idr; + + /* initialise the _video_ device */ + MARK(); + err = -ENOMEM; + dev->vdev = video_device_alloc(); + if (dev->vdev == NULL) + goto out_unregister; + + vdev_priv = kzalloc(sizeof(struct v4l2loopback_private), GFP_KERNEL); + if (vdev_priv == NULL) + goto out_unregister; + + video_set_drvdata(dev->vdev, vdev_priv); + if (video_get_drvdata(dev->vdev) == NULL) + goto out_unregister; + + snprintf(dev->vdev->name, sizeof(dev->vdev->name), "%s", + dev->card_label); + vdev_priv->device_nr = nr; + init_vdev(dev->vdev, nr); + dev->vdev->v4l2_dev = &dev->v4l2_dev; + + /* initialise v4l2-loopback specific parameters */ + MARK(); + dev->announce_all_caps = _announce_all_caps; + dev->min_width = _min_width; + dev->min_height = _min_height; + dev->max_width = _max_width; + dev->max_height = _max_height; + dev->max_openers = _max_openers; + + /* set (initial) pixel and stream format */ + _width = clamp_val(_width, _min_width, _max_width); + _height = clamp_val(_height, _min_height, _max_height); + _fmt = (struct v4l2_format){ + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .fmt.pix = { .width = _width, + .height = _height, + .pixelformat = formats[0].fourcc, + .colorspace = V4L2_COLORSPACE_DEFAULT, + .field = V4L2_FIELD_NONE } + }; + + err = v4l2l_fill_format(&_fmt, _min_width, _max_width, _min_height, + _max_height); + if (err) + /* highly unexpected failure to assign default format */ + goto out_unregister; + dev->pix_format = _fmt.fmt.pix; + init_capture_param(&dev->capture_param); + set_timeperframe(dev, &dev->capture_param.timeperframe); + + /* ctrls parameters */ + dev->keep_format = 0; + dev->sustain_framerate = 0; + dev->timeout_jiffies = 0; + dev->timeout_image_io = 0; + + /* initialise OUTPUT and CAPTURE buffer values */ + dev->image = NULL; + dev->image_size = 0; + dev->buffer_count = _max_buffers; + dev->buffer_size = 0; + dev->used_buffer_count = 0; + INIT_LIST_HEAD(&dev->outbufs_list); + do { + u32 index; + for (index = 0; index < dev->buffer_count; ++index) + INIT_LIST_HEAD(&dev->buffers[index].list_head); + + } while (0); + memset(dev->bufpos2index, 0, sizeof(dev->bufpos2index)); + dev->write_position = 0; + + /* initialise synchronisation data */ + atomic_set(&dev->open_count, 0); + mutex_init(&dev->image_mutex); + spin_lock_init(&dev->lock); + spin_lock_init(&dev->list_lock); + init_waitqueue_head(&dev->read_event); + dev->format_tokens = V4L2L_TOKEN_MASK; + dev->stream_tokens = V4L2L_TOKEN_MASK; + + /* initialise sustain frame rate and timeout parameters, and timers */ + dev->reread_count = 0; + dev->timeout_image = NULL; + dev->timeout_happened = 0; +#ifdef HAVE_TIMER_SETUP + timer_setup(&dev->sustain_timer, sustain_timer_clb, 0); + timer_setup(&dev->timeout_timer, timeout_timer_clb, 0); +#else + setup_timer(&dev->sustain_timer, sustain_timer_clb, nr); + setup_timer(&dev->timeout_timer, timeout_timer_clb, nr); +#endif + + /* initialise the control handler and add controls */ + MARK(); + hdl = &dev->ctrl_handler; + err = v4l2_ctrl_handler_init(hdl, 4); + if (err) + goto out_unregister; + v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_keepformat, NULL); + v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_sustainframerate, NULL); + v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_timeout, NULL); + v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_timeoutimageio, NULL); + if (hdl->error) { + err = hdl->error; + goto out_free_handler; + } + dev->v4l2_dev.ctrl_handler = hdl; + + err = v4l2_ctrl_handler_setup(hdl); + if (err) + goto out_free_handler; + + /* register the device (creates /dev/video*) */ + MARK(); + if (video_register_device(dev->vdev, VFL_TYPE_VIDEO, nr) < 0) { + printk(KERN_ERR + "v4l2-loopback add() failed video_register_device()\n"); + err = -EFAULT; + goto out_free_device; + } + v4l2loopback_create_sysfs(dev->vdev); + /* NOTE: ambivalent if sysfs entries fail */ + + if (ret_nr) + *ret_nr = dev->vdev->num; + return 0; + +out_free_device: +out_free_handler: + v4l2_ctrl_handler_free(&dev->ctrl_handler); +out_unregister: + if (dev->vdev) + video_set_drvdata(dev->vdev, NULL); + video_device_release(dev->vdev); + if (vdev_priv != NULL) + kfree(vdev_priv); + v4l2_device_unregister(&dev->v4l2_dev); +out_free_idr: + idr_remove(&v4l2loopback_index_idr, nr); +out_free_dev: + kfree(dev); + return err; +} + +static void v4l2_loopback_remove(struct v4l2_loopback_device *dev) +{ + int device_nr = v4l2loopback_get_vdev_nr(dev->vdev); + mutex_lock(&dev->image_mutex); + free_buffers(dev); + free_timeout_buffer(dev); + mutex_unlock(&dev->image_mutex); + v4l2loopback_remove_sysfs(dev->vdev); + v4l2_ctrl_handler_free(&dev->ctrl_handler); + kfree(video_get_drvdata(dev->vdev)); + video_unregister_device(dev->vdev); + v4l2_device_unregister(&dev->v4l2_dev); + idr_remove(&v4l2loopback_index_idr, device_nr); + kfree(dev); +} + +static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd, + unsigned long parm) +{ + struct v4l2_loopback_device *dev; + struct v4l2_loopback_config conf; + struct v4l2_loopback_config *confptr = &conf; + int device_nr, capture_nr, output_nr; + int ret; + const __u32 version = V4L2LOOPBACK_VERSION_CODE; + + ret = mutex_lock_killable(&v4l2loopback_ctl_mutex); + if (ret) + return ret; + + ret = -EINVAL; + switch (cmd) { + default: + ret = -ENOSYS; + break; + /* add a v4l2loopback device (pair), based on the user-provided specs */ + case V4L2LOOPBACK_CTL_ADD: + case V4L2LOOPBACK_CTL_ADD_legacy: + if (parm) { + if ((ret = copy_from_user(&conf, (void *)parm, + sizeof(conf))) < 0) + break; + } else + confptr = NULL; + ret = v4l2_loopback_add(confptr, &device_nr); + if (ret >= 0) + ret = device_nr; + break; + /* remove a v4l2loopback device (both capture and output) */ + case V4L2LOOPBACK_CTL_REMOVE: + case V4L2LOOPBACK_CTL_REMOVE_legacy: + ret = v4l2loopback_lookup((__u32)parm, &dev); + if (ret >= 0 && dev) { + ret = -EBUSY; + if (dev->open_count.counter > 0) + break; + v4l2_loopback_remove(dev); + ret = 0; + }; + break; + /* get information for a loopback device. + * this is mostly about limits (which cannot be queried directly with VIDIOC_G_FMT and friends + */ + case V4L2LOOPBACK_CTL_QUERY: + case V4L2LOOPBACK_CTL_QUERY_legacy: + if (!parm) + break; + if ((ret = copy_from_user(&conf, (void *)parm, sizeof(conf))) < + 0) + break; + capture_nr = output_nr = conf.output_nr; +#ifdef SPLIT_DEVICES + capture_nr = conf.capture_nr; +#endif + device_nr = (output_nr < 0) ? capture_nr : output_nr; + MARK(); + /* get the device from either capture_nr or output_nr (whatever is valid) */ + if ((ret = v4l2loopback_lookup(device_nr, &dev)) < 0) + break; + MARK(); + /* if we got the device from output_nr and there is a valid capture_nr, + * make sure that both refer to the same device (or bail out) + */ + if ((device_nr != capture_nr) && (capture_nr >= 0) && + ((ret = v4l2loopback_lookup(capture_nr, 0)) < 0)) + break; + MARK(); + /* if otoh, we got the device from capture_nr and there is a valid output_nr, + * make sure that both refer to the same device (or bail out) + */ + if ((device_nr != output_nr) && (output_nr >= 0) && + ((ret = v4l2loopback_lookup(output_nr, 0)) < 0)) + break; + + /* v4l2_loopback_config identified a single device, so fetch the data */ + snprintf(conf.card_label, sizeof(conf.card_label), "%s", + dev->card_label); + + conf.output_nr = dev->vdev->num; +#ifdef SPLIT_DEVICES + conf.capture_nr = dev->vdev->num; +#endif + conf.min_width = dev->min_width; + conf.min_height = dev->min_height; + conf.max_width = dev->max_width; + conf.max_height = dev->max_height; + conf.announce_all_caps = dev->announce_all_caps; + conf.max_buffers = dev->buffer_count; + conf.max_openers = dev->max_openers; + conf.debug = debug; + MARK(); + if (copy_to_user((void *)parm, &conf, sizeof(conf))) { + ret = -EFAULT; + break; + } + ret = 0; + break; + case V4L2LOOPBACK_CTL_VERSION: + if (!parm) + break; + if (copy_to_user((void *)parm, &version, sizeof(version))) { + ret = -EFAULT; + break; + } + ret = 0; + break; + } + + mutex_unlock(&v4l2loopback_ctl_mutex); + MARK(); + return ret; +} + +/* LINUX KERNEL */ + +static const struct file_operations v4l2loopback_ctl_fops = { + // clang-format off + .owner = THIS_MODULE, + .open = nonseekable_open, + .unlocked_ioctl = v4l2loopback_control_ioctl, + .compat_ioctl = v4l2loopback_control_ioctl, + .llseek = noop_llseek, + // clang-format on +}; + +static struct miscdevice v4l2loopback_misc = { + // clang-format off + .minor = MISC_DYNAMIC_MINOR, + .name = "v4l2loopback", + .fops = &v4l2loopback_ctl_fops, + // clang-format on +}; + +static const struct v4l2_file_operations v4l2_loopback_fops = { + // clang-format off + .owner = THIS_MODULE, + .open = v4l2_loopback_open, + .release = v4l2_loopback_close, + .read = v4l2_loopback_read, + .write = v4l2_loopback_write, + .poll = v4l2_loopback_poll, + .mmap = v4l2_loopback_mmap, + .unlocked_ioctl = video_ioctl2, + // clang-format on +}; + +static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops = { + // clang-format off + .vidioc_querycap = &vidioc_querycap, + .vidioc_enum_framesizes = &vidioc_enum_framesizes, + .vidioc_enum_frameintervals = &vidioc_enum_frameintervals, + + .vidioc_enum_output = &vidioc_enum_output, + .vidioc_g_output = &vidioc_g_output, + .vidioc_s_output = &vidioc_s_output, + + .vidioc_enum_input = &vidioc_enum_input, + .vidioc_g_input = &vidioc_g_input, + .vidioc_s_input = &vidioc_s_input, + + .vidioc_enum_fmt_vid_cap = &vidioc_enum_fmt_cap, + .vidioc_g_fmt_vid_cap = &vidioc_g_fmt_cap, + .vidioc_s_fmt_vid_cap = &vidioc_s_fmt_cap, + .vidioc_try_fmt_vid_cap = &vidioc_try_fmt_cap, + + .vidioc_enum_fmt_vid_out = &vidioc_enum_fmt_out, + .vidioc_s_fmt_vid_out = &vidioc_s_fmt_out, + .vidioc_g_fmt_vid_out = &vidioc_g_fmt_out, + .vidioc_try_fmt_vid_out = &vidioc_try_fmt_out, + +#ifdef V4L2L_OVERLAY + .vidioc_s_fmt_vid_overlay = &vidioc_s_fmt_overlay, + .vidioc_g_fmt_vid_overlay = &vidioc_g_fmt_overlay, +#endif + +#ifdef V4L2LOOPBACK_WITH_STD + .vidioc_s_std = &vidioc_s_std, + .vidioc_g_std = &vidioc_g_std, + .vidioc_querystd = &vidioc_querystd, +#endif /* V4L2LOOPBACK_WITH_STD */ + + .vidioc_g_parm = &vidioc_g_parm, + .vidioc_s_parm = &vidioc_s_parm, + + .vidioc_reqbufs = &vidioc_reqbufs, + .vidioc_querybuf = &vidioc_querybuf, + .vidioc_qbuf = &vidioc_qbuf, + .vidioc_dqbuf = &vidioc_dqbuf, + + .vidioc_streamon = &vidioc_streamon, + .vidioc_streamoff = &vidioc_streamoff, + +#ifdef CONFIG_VIDEO_V4L1_COMPAT + .vidiocgmbuf = &vidiocgmbuf, +#endif + + .vidioc_subscribe_event = &vidioc_subscribe_event, + .vidioc_unsubscribe_event = &v4l2_event_unsubscribe, + // clang-format on +}; + +static int free_device_cb(int id, void *ptr, void *data) +{ + struct v4l2_loopback_device *dev = ptr; + v4l2_loopback_remove(dev); + return 0; +} +static void free_devices(void) +{ + idr_for_each(&v4l2loopback_index_idr, &free_device_cb, NULL); + idr_destroy(&v4l2loopback_index_idr); +} + +static int __init v4l2loopback_init_module(void) +{ + const u32 min_width = V4L2LOOPBACK_SIZE_MIN_WIDTH; + const u32 min_height = V4L2LOOPBACK_SIZE_MIN_HEIGHT; + int err; + int i; + MARK(); + + err = misc_register(&v4l2loopback_misc); + if (err < 0) + return err; + + if (devices < 0) { + devices = 1; + + /* try guessing the devices from the "video_nr" parameter */ + for (i = MAX_DEVICES - 1; i >= 0; i--) { + if (video_nr[i] >= 0) { + devices = i + 1; + break; + } + } + } + + if (devices > MAX_DEVICES) { + devices = MAX_DEVICES; + printk(KERN_INFO + "v4l2-loopback init() number of initial devices is " + "limited to: %d\n", + MAX_DEVICES); + } + + if (max_buffers > MAX_BUFFERS) { + max_buffers = MAX_BUFFERS; + printk(KERN_INFO + "v4l2-loopback init() number of buffers is limited " + "to: %d\n", + MAX_BUFFERS); + } + + if (max_openers < 0) { + printk(KERN_INFO + "v4l2-loopback init() allowing %d openers rather " + "than %d\n", + 2, max_openers); + max_openers = 2; + } + + if (max_width < min_width) { + max_width = V4L2LOOPBACK_SIZE_DEFAULT_MAX_WIDTH; + printk(KERN_INFO "v4l2-loopback init() using max_width %d\n", + max_width); + } + if (max_height < min_height) { + max_height = V4L2LOOPBACK_SIZE_DEFAULT_MAX_HEIGHT; + printk(KERN_INFO "v4l2-loopback init() using max_height %d\n", + max_height); + } + + for (i = 0; i < devices; i++) { + struct v4l2_loopback_config cfg = { + // clang-format off + .output_nr = video_nr[i], +#ifdef SPLIT_DEVICES + .capture_nr = video_nr[i], +#endif + .min_width = min_width, + .min_height = min_height, + .max_width = max_width, + .max_height = max_height, + .announce_all_caps = (!exclusive_caps[i]), + .max_buffers = max_buffers, + .max_openers = max_openers, + .debug = debug, + // clang-format on + }; + cfg.card_label[0] = 0; + if (card_label[i]) + snprintf(cfg.card_label, sizeof(cfg.card_label), "%s", + card_label[i]); + err = v4l2_loopback_add(&cfg, 0); + if (err) { + free_devices(); + goto error; + } + } + + dprintk("module installed\n"); + + printk(KERN_INFO "v4l2-loopback driver version %d.%d.%d%s loaded\n", + // clang-format off + (V4L2LOOPBACK_VERSION_CODE >> 16) & 0xff, + (V4L2LOOPBACK_VERSION_CODE >> 8) & 0xff, + (V4L2LOOPBACK_VERSION_CODE ) & 0xff, +#ifdef SNAPSHOT_VERSION + " (" __stringify(SNAPSHOT_VERSION) ")" +#else + "" +#endif + ); + // clang-format on + + return 0; +error: + misc_deregister(&v4l2loopback_misc); + return err; +} + +static void v4l2loopback_cleanup_module(void) +{ + MARK(); + /* unregister the device -> it deletes /dev/video* */ + free_devices(); + /* and get rid of /dev/v4l2loopback */ + misc_deregister(&v4l2loopback_misc); + dprintk("module removed\n"); +} + +MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR); + +module_init(v4l2loopback_init_module); +module_exit(v4l2loopback_cleanup_module); diff --git a/drivers/custom/v4l2loopback/v4l2loopback.h b/drivers/custom/v4l2loopback/v4l2loopback.h new file mode 100644 index 000000000000..8961a98eff58 --- /dev/null +++ b/drivers/custom/v4l2loopback/v4l2loopback.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * v4l2loopback.h + * + * Written by IOhannes m zmölnig, 7/1/20. + * + * Copyright 2020 by IOhannes m zmölnig. Redistribution of this file is + * permitted under the GNU General Public License. + */ +#ifndef _V4L2LOOPBACK_H +#define _V4L2LOOPBACK_H + +#define V4L2LOOPBACK_VERSION_MAJOR 0 +#define V4L2LOOPBACK_VERSION_MINOR 15 +#define V4L2LOOPBACK_VERSION_BUGFIX 3 + +/* /dev/v4l2loopback interface */ + +struct v4l2_loopback_config { + /** + * the device-number (/dev/video) + * V4L2LOOPBACK_CTL_ADD: + * setting this to a value<0, will allocate an available one + * if nr>=0 and the device already exists, the ioctl will EEXIST + * if output_nr and capture_nr are the same, only a single device will be created + * NOTE: currently split-devices (where output_nr and capture_nr differ) + * are not implemented yet. + * until then, requesting different device-IDs will result in EINVAL. + * + * V4L2LOOPBACK_CTL_QUERY: + * either both output_nr and capture_nr must refer to the same loopback, + * or one (and only one) of them must be -1 + * + */ + __s32 output_nr; + __s32 unused; /*capture_nr;*/ + + /** + * a nice name for your device + * if (*card_label)==0, an automatic name is assigned + */ + char card_label[32]; + + /** + * allowed frame size + * if too low, default values are used + */ + __u32 min_width; + __u32 max_width; + __u32 min_height; + __u32 max_height; + + /** + * number of buffers to allocate for the queue + * if set to <=0, default values are used + */ + __s32 max_buffers; + + /** + * how many consumers are allowed to open this device concurrently + * if set to <=0, default values are used + */ + __s32 max_openers; + + /** + * set the debugging level for this device + */ + __s32 debug; + + /** + * whether to announce OUTPUT/CAPTURE capabilities exclusively + * for this device or not + * (!exclusive_caps) + * NOTE: this is going to be removed once separate output/capture + * devices are implemented + */ + __s32 announce_all_caps; +}; + +#define V4L2LOOPBACK_CTL_IOCTLMAGIC '~' + +/* a pointer to an (unsigned int) that - on success - will hold + * the version code of the v4l2loopback module + * as returned by KERNEL_VERSION(MAJOR, MINOR, BUGFIX) + */ +#define V4L2LOOPBACK_CTL_VERSION _IOR(V4L2LOOPBACK_CTL_IOCTLMAGIC, 0, __u32) + +/* a pointer to a (struct v4l2_loopback_config) that has all values you wish to impose on the + * to-be-created device set. + * if the ptr is NULL, a new device is created with default values at the driver's discretion. + * + * returns the device_nr of the OUTPUT device (which can be used with V4L2LOOPBACK_CTL_QUERY, + * to get more information on the device) + */ +#define V4L2LOOPBACK_CTL_ADD \ + _IOW(V4L2LOOPBACK_CTL_IOCTLMAGIC, 1, struct v4l2_loopback_config) + +/* the device-number (either CAPTURE or OUTPUT) associated with the loopback-device */ +#define V4L2LOOPBACK_CTL_REMOVE _IOW(V4L2LOOPBACK_CTL_IOCTLMAGIC, 2, __u32) + +/* a pointer to a (struct v4l2_loopback_config) that has output_nr and/or capture_nr set + * (the two values must either refer to video-devices associated with the same loopback device + * or exactly one of them must be <0 + */ +#define V4L2LOOPBACK_CTL_QUERY \ + _IOWR(V4L2LOOPBACK_CTL_IOCTLMAGIC, 3, struct v4l2_loopback_config) + +#endif /* _V4L2LOOPBACK_H */ diff --git a/drivers/custom/v4l2loopback/v4l2loopback_formats.h b/drivers/custom/v4l2loopback/v4l2loopback_formats.h new file mode 100644 index 000000000000..ac2f1cbc062c --- /dev/null +++ b/drivers/custom/v4l2loopback/v4l2loopback_formats.h @@ -0,0 +1,453 @@ +static const struct v4l2l_format formats[] = { +#ifndef V4L2_PIX_FMT_VP9 +#define V4L2_PIX_FMT_VP9 v4l2_fourcc('V', 'P', '9', '0') +#endif +#ifndef V4L2_PIX_FMT_HEVC +#define V4L2_PIX_FMT_HEVC v4l2_fourcc('H', 'E', 'V', 'C') +#endif + + /* here come the packed formats */ + { + .name = "32 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = 32, + .flags = 0, + }, + { + .name = "32 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .flags = 0, + }, + { + .name = "24 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .flags = 0, + }, + { + .name = "24 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 24, + .flags = 0, + }, +#ifdef V4L2_PIX_FMT_ABGR32 + { + .name = "32 bpp RGBA, le", + .fourcc = V4L2_PIX_FMT_ABGR32, + .depth = 32, + .flags = 0, + }, +#endif +#ifdef V4L2_PIX_FMT_XBGR32 + { + .name = "32 bpp BGRX-8-8-8-8", + .fourcc = V4L2_PIX_FMT_XBGR32, + .depth = 32, + .flags = 0, + }, +#endif +#ifdef V4L2_PIX_FMT_RGBA32 + { + .name = "32 bpp RGBA", + .fourcc = V4L2_PIX_FMT_RGBA32, + .depth = 32, + .flags = 0, + }, +#endif +#ifdef V4L2_PIX_FMT_RGB332 + { + .name = "8 bpp RGB-3-3-2", + .fourcc = V4L2_PIX_FMT_RGB332, + .depth = 8, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_RGB332 */ +#ifdef V4L2_PIX_FMT_RGB444 + { + .name = "16 bpp RGB (xxxxrrrr ggggbbbb)", + .fourcc = V4L2_PIX_FMT_RGB444, + .depth = 16, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_RGB444 */ +#ifdef V4L2_PIX_FMT_RGB555 + { + .name = "16 bpp RGB-5-5-5", + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_RGB555 */ +#ifdef V4L2_PIX_FMT_RGB565 + { + .name = "16 bpp RGB-5-6-5", + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_RGB565 */ +#ifdef V4L2_PIX_FMT_RGB555X + { + .name = "16 bpp RGB-5-5-5 BE", + .fourcc = V4L2_PIX_FMT_RGB555X, + .depth = 16, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_RGB555X */ +#ifdef V4L2_PIX_FMT_RGB565X + { + .name = "16 bpp RGB-5-6-5 BE", + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_RGB565X */ +#ifdef V4L2_PIX_FMT_BGR666 + { + .name = "18 bpp BGR-6-6-6", + .fourcc = V4L2_PIX_FMT_BGR666, + .depth = 18, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_BGR666 */ + { + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .flags = 0, + }, + { + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .flags = 0, + }, +#ifdef V4L2_PIX_FMT_YVYU + { + .name = "4:2:2, packed YVYU", + .fourcc = V4L2_PIX_FMT_YVYU, + .depth = 16, + .flags = 0, + }, +#endif +#ifdef V4L2_PIX_FMT_VYUY + { + .name = "4:2:2, packed VYUY", + .fourcc = V4L2_PIX_FMT_VYUY, + .depth = 16, + .flags = 0, + }, +#endif + { + .name = "4:2:2, packed YYUV", + .fourcc = V4L2_PIX_FMT_YYUV, + .depth = 16, + .flags = 0, + }, + { + .name = "YUV-8-8-8-8", + .fourcc = V4L2_PIX_FMT_YUV32, + .depth = 32, + .flags = 0, + }, + { + .name = "8 bpp, Greyscale", + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .flags = 0, + }, +#ifdef V4L2_PIX_FMT_Y4 + { + .name = "4 bpp Greyscale", + .fourcc = V4L2_PIX_FMT_Y4, + .depth = 4, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_Y4 */ +#ifdef V4L2_PIX_FMT_Y6 + { + .name = "6 bpp Greyscale", + .fourcc = V4L2_PIX_FMT_Y6, + .depth = 6, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_Y6 */ +#ifdef V4L2_PIX_FMT_Y10 + { + .name = "10 bpp Greyscale", + .fourcc = V4L2_PIX_FMT_Y10, + .depth = 10, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_Y10 */ +#ifdef V4L2_PIX_FMT_Y12 + { + .name = "12 bpp Greyscale", + .fourcc = V4L2_PIX_FMT_Y12, + .depth = 12, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_Y12 */ + { + .name = "16 bpp, Greyscale", + .fourcc = V4L2_PIX_FMT_Y16, + .depth = 16, + .flags = 0, + }, +#ifdef V4L2_PIX_FMT_YUV444 + { + .name = "16 bpp xxxxyyyy uuuuvvvv", + .fourcc = V4L2_PIX_FMT_YUV444, + .depth = 16, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_YUV444 */ +#ifdef V4L2_PIX_FMT_YUV555 + { + .name = "16 bpp YUV-5-5-5", + .fourcc = V4L2_PIX_FMT_YUV555, + .depth = 16, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_YUV555 */ +#ifdef V4L2_PIX_FMT_YUV565 + { + .name = "16 bpp YUV-5-6-5", + .fourcc = V4L2_PIX_FMT_YUV565, + .depth = 16, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_YUV565 */ + +/* bayer formats */ +#ifdef V4L2_PIX_FMT_SRGGB8 + { + .name = "Bayer RGGB 8bit", + .fourcc = V4L2_PIX_FMT_SRGGB8, + .depth = 8, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_SRGGB8 */ +#ifdef V4L2_PIX_FMT_SGRBG8 + { + .name = "Bayer GRBG 8bit", + .fourcc = V4L2_PIX_FMT_SGRBG8, + .depth = 8, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_SGRBG8 */ +#ifdef V4L2_PIX_FMT_SGBRG8 + { + .name = "Bayer GBRG 8bit", + .fourcc = V4L2_PIX_FMT_SGBRG8, + .depth = 8, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_SGBRG8 */ +#ifdef V4L2_PIX_FMT_SBGGR8 + { + .name = "Bayer BA81 8bit", + .fourcc = V4L2_PIX_FMT_SBGGR8, + .depth = 8, + .flags = 0, + }, +#endif /* V4L2_PIX_FMT_SBGGR8 */ + + /* here come the planar formats */ + { + .name = "4:1:0, planar, Y-Cr-Cb", + .fourcc = V4L2_PIX_FMT_YVU410, + .depth = 9, + .flags = FORMAT_FLAGS_PLANAR, + }, + { + .name = "4:2:0, planar, Y-Cr-Cb", + .fourcc = V4L2_PIX_FMT_YVU420, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, + }, + { + .name = "4:1:0, planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YUV410, + .depth = 9, + .flags = FORMAT_FLAGS_PLANAR, + }, + { + .name = "4:2:0, planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, + }, +#ifdef V4L2_PIX_FMT_YUV422P + { + .name = "16 bpp YVU422 planar", + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = 16, + .flags = FORMAT_FLAGS_PLANAR, + }, +#endif /* V4L2_PIX_FMT_YUV422P */ +#ifdef V4L2_PIX_FMT_YUV411P + { + .name = "16 bpp YVU411 planar", + .fourcc = V4L2_PIX_FMT_YUV411P, + .depth = 16, + .flags = FORMAT_FLAGS_PLANAR, + }, +#endif /* V4L2_PIX_FMT_YUV411P */ +#ifdef V4L2_PIX_FMT_Y41P + { + .name = "12 bpp YUV 4:1:1", + .fourcc = V4L2_PIX_FMT_Y41P, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, + }, +#endif /* V4L2_PIX_FMT_Y41P */ +#ifdef V4L2_PIX_FMT_NV12 + { + .name = "12 bpp Y/CbCr 4:2:0 ", + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, + }, +#endif /* V4L2_PIX_FMT_NV12 */ + +/* here come the compressed formats */ + +#ifdef V4L2_PIX_FMT_MJPEG + { + .name = "Motion-JPEG", + .fourcc = V4L2_PIX_FMT_MJPEG, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_MJPEG */ +#ifdef V4L2_PIX_FMT_JPEG + { + .name = "JFIF JPEG", + .fourcc = V4L2_PIX_FMT_JPEG, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_JPEG */ +#ifdef V4L2_PIX_FMT_DV + { + .name = "DV1394", + .fourcc = V4L2_PIX_FMT_DV, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_DV */ +#ifdef V4L2_PIX_FMT_MPEG + { + .name = "MPEG-1/2/4 Multiplexed", + .fourcc = V4L2_PIX_FMT_MPEG, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_MPEG */ +#ifdef V4L2_PIX_FMT_H264 + { + .name = "H264 with start codes", + .fourcc = V4L2_PIX_FMT_H264, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_H264 */ +#ifdef V4L2_PIX_FMT_H264_NO_SC + { + .name = "H264 without start codes", + .fourcc = V4L2_PIX_FMT_H264_NO_SC, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_H264_NO_SC */ +#ifdef V4L2_PIX_FMT_H264_MVC + { + .name = "H264 MVC", + .fourcc = V4L2_PIX_FMT_H264_MVC, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_H264_MVC */ +#ifdef V4L2_PIX_FMT_H263 + { + .name = "H263", + .fourcc = V4L2_PIX_FMT_H263, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_H263 */ +#ifdef V4L2_PIX_FMT_MPEG1 + { + .name = "MPEG-1 ES", + .fourcc = V4L2_PIX_FMT_MPEG1, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_MPEG1 */ +#ifdef V4L2_PIX_FMT_MPEG2 + { + .name = "MPEG-2 ES", + .fourcc = V4L2_PIX_FMT_MPEG2, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_MPEG2 */ +#ifdef V4L2_PIX_FMT_MPEG4 + { + .name = "MPEG-4 part 2 ES", + .fourcc = V4L2_PIX_FMT_MPEG4, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_MPEG4 */ +#ifdef V4L2_PIX_FMT_XVID + { + .name = "Xvid", + .fourcc = V4L2_PIX_FMT_XVID, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_XVID */ +#ifdef V4L2_PIX_FMT_VC1_ANNEX_G + { + .name = "SMPTE 421M Annex G compliant stream", + .fourcc = V4L2_PIX_FMT_VC1_ANNEX_G, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_VC1_ANNEX_G */ +#ifdef V4L2_PIX_FMT_VC1_ANNEX_L + { + .name = "SMPTE 421M Annex L compliant stream", + .fourcc = V4L2_PIX_FMT_VC1_ANNEX_L, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_VC1_ANNEX_L */ +#ifdef V4L2_PIX_FMT_VP8 + { + .name = "VP8", + .fourcc = V4L2_PIX_FMT_VP8, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_VP8 */ +#ifdef V4L2_PIX_FMT_VP9 + { + .name = "VP9", + .fourcc = V4L2_PIX_FMT_VP9, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_VP9 */ +#ifdef V4L2_PIX_FMT_HEVC + { + .name = "HEVC", + .fourcc = V4L2_PIX_FMT_HEVC, + .depth = 32, + .flags = FORMAT_FLAGS_COMPRESSED, + }, +#endif /* V4L2_PIX_FMT_HEVC */ +}; # ---------------------------------------- # Module: xonedo # Version: 6e9491b5296c # ---------------------------------------- diff --git a/drivers/custom/xonedo/Kbuild b/drivers/custom/xonedo/Kbuild new file mode 100644 index 000000000000..9a45c80af092 --- /dev/null +++ b/drivers/custom/xonedo/Kbuild @@ -0,0 +1,17 @@ +xone_gip-y := bus/bus.o bus/protocol.o auth/auth.o auth/crypto.o driver/common.o +xone_dongle-y := transport/dongle.o transport/mt76.o +xone_gip_gamepad-y := driver/gamepad.o +xone_gip_headset-y := driver/headset.o +xone_gip_chatpad-y := driver/chatpad.o +xone_gip_madcatz_strat-y := driver/madcatz_strat.o +xone_gip_madcatz_glam-y := driver/madcatz_glam.o +xone_gip_pdp_jaguar-y := driver/pdp_jaguar.o + +obj-m := xone_gip.o \ + xone_dongle.o \ + xone_gip_gamepad.o \ + xone_gip_headset.o \ + xone_gip_chatpad.o \ + xone_gip_madcatz_strat.o \ + xone_gip_madcatz_glam.o \ + xone_gip_pdp_jaguar.o diff --git a/drivers/custom/xonedo/Makefile b/drivers/custom/xonedo/Makefile new file mode 100644 index 000000000000..fead9075836e --- /dev/null +++ b/drivers/custom/xonedo/Makefile @@ -0,0 +1,34 @@ +KVERSION := $(shell uname -r) +KDIR := /lib/modules/${KVERSION}/build +MAKEFLAGS+="-j $(shell nproc)" + +default: clean + $(MAKE) -C $(KDIR) M=$$PWD + +debug: clean + $(MAKE) -C $(KDIR) M=$$PWD ccflags-y="-Og -g3 -DDEBUG" + +clean: + $(MAKE) -C $(KDIR) M=$$PWD clean + +unload: + ./modules_load.sh unload + +load: unload + ./modules_load.sh + +test: + $(MAKE) debug &&\ + $(MAKE) load + $(MAKE) clean + +remove: clean + ./uninstall.sh + +install: clean + ./install.sh + ./install/firmware.sh --skip-disclaimer + +install-debug: clean + ./install.sh --debug + ./install/firmware.sh --skip-disclaimer diff --git a/drivers/custom/xonedo/auth/auth.c b/drivers/custom/xonedo/auth/auth.c new file mode 100644 index 000000000000..307225e436f3 --- /dev/null +++ b/drivers/custom/xonedo/auth/auth.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Severin von Wnuck-Lipinski + */ + +#include +#include + +#include "auth.h" +#include "crypto.h" +#include "../bus/bus.h" + +enum gip_auth_context { + GIP_AUTH_CTX_HANDSHAKE = 0x00, + GIP_AUTH_CTX_CONTROL = 0x01, +}; + +enum gip_auth_command_handshake { + GIP_AUTH_CMD_HOST_HELLO = 0x01, + GIP_AUTH_CMD_CLIENT_HELLO = 0x02, + GIP_AUTH_CMD_CLIENT_CERTIFICATE = 0x03, + GIP_AUTH_CMD_HOST_SECRET = 0x05, + GIP_AUTH_CMD_HOST_FINISH = 0x07, + GIP_AUTH_CMD_CLIENT_FINISH = 0x08, + + GIP_AUTH2_CMD_HOST_HELLO = 0x21, + GIP_AUTH2_CMD_CLIENT_HELLO = 0x22, + GIP_AUTH2_CMD_CLIENT_CERTIFICATE = 0x23, + GIP_AUTH2_CMD_CLIENT_PUBKEY = 0x24, + GIP_AUTH2_CMD_HOST_PUBKEY = 0x25, + GIP_AUTH2_CMD_HOST_FINISH = 0x26, + GIP_AUTH2_CMD_CLIENT_FINISH = 0x27, +}; + +enum gip_auth_command_control { + GIP_AUTH_CTRL_COMPLETE = 0x00, + GIP_AUTH_CTRL_RESET = 0x01, +}; + +enum gip_auth_option { + GIP_AUTH_OPT_ACKNOWLEDGE = BIT(0), + GIP_AUTH_OPT_REQUEST = BIT(1), + GIP_AUTH_OPT_FROM_HOST = BIT(6), + GIP_AUTH_OPT_FROM_CLIENT = BIT(6) | BIT(7), +}; + +struct gip_auth_header_handshake { + u8 context; + u8 options; + u8 error; + u8 command; + __be16 length; +} __packed; + +struct gip_auth_header_data { + u8 command; + u8 version; + __be16 length; +} __packed; + +struct gip_auth_header_full { + struct gip_auth_header_handshake handshake; + struct gip_auth_header_data data; +} __packed; + +struct gip_auth_header_control { + u8 context; + u8 control; +} __packed; + +struct gip_auth_request { + struct gip_auth_header_handshake header; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth_pkt_host_hello { + struct gip_auth_header_full header; + + u8 random[GIP_AUTH_RANDOM_LEN]; + u8 unknown1[4]; + u8 unknown2[4]; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth_pkt_host_secret { + struct gip_auth_header_full header; + + u8 encrypted_pms[GIP_AUTH_ENCRYPTED_PMS_LEN]; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth_pkt_host_finish { + struct gip_auth_header_full header; + + u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth_pkt_client_hello { + u8 random[GIP_AUTH_RANDOM_LEN]; + u8 unknown[48]; +} __packed; + +struct gip_auth_pkt_client_finish { + u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; + u8 unknown[32]; +} __packed; + +struct gip_auth2_pkt_host_hello { + struct gip_auth_header_full header; + + u8 random[GIP_AUTH_RANDOM_LEN]; + u8 unknown[4]; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth2_pkt_host_pubkey { + struct gip_auth_header_full header; + + u8 pubkey[GIP_AUTH2_PUBKEY_LEN]; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth2_pkt_host_finish { + struct gip_auth_header_full header; + + u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth2_pkt_client_hello { + u8 random[GIP_AUTH_RANDOM_LEN]; + u8 unknown1[108]; + u8 unknown2[32]; +} __packed; + +struct gip_auth2_pkt_client_cert { + char header[4]; + u8 unknown1[136]; + char chip[32]; + char revision[20]; + u8 unknown2[576]; +} __packed; + +struct gip_auth2_pkt_client_pubkey { + u8 pubkey[GIP_AUTH2_PUBKEY_LEN]; + u8 unknown[64]; +} __packed; + +struct gip_auth2_pkt_client_finish { + u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; + u8 unknown[32]; +} __packed; + +static int gip_auth_send_pkt(struct gip_auth *auth, + enum gip_auth_command_handshake cmd, + void *pkt, u16 len) +{ + struct gip_auth_header_full *hdr = pkt; + u16 data_len = len - sizeof(hdr->handshake) - GIP_AUTH_TRAILER_LEN; + + hdr->handshake.context = GIP_AUTH_CTX_HANDSHAKE; + hdr->handshake.options = GIP_AUTH_OPT_ACKNOWLEDGE | + GIP_AUTH_OPT_FROM_HOST; + hdr->handshake.command = cmd; + hdr->handshake.length = cpu_to_be16(data_len); + + hdr->data.command = cmd; + hdr->data.version = cmd >= GIP_AUTH2_CMD_HOST_HELLO ? 0x02 : 0x01; + hdr->data.length = cpu_to_be16(data_len - sizeof(hdr->data)); + + auth->last_sent_command = cmd; + crypto_shash_update(auth->shash_transcript, + pkt + sizeof(hdr->handshake), data_len); + + return gip_send_authenticate(auth->client, pkt, len, true); +} + +static int gip_auth_request_pkt(struct gip_auth *auth, + enum gip_auth_command_handshake cmd, u16 len) +{ + struct gip_auth_request req = {}; + u16 data_len = len + sizeof(struct gip_auth_header_data); + + req.header.context = GIP_AUTH_CTX_HANDSHAKE; + req.header.options = GIP_AUTH_OPT_REQUEST | GIP_AUTH_OPT_FROM_HOST; + req.header.command = cmd; + req.header.length = cpu_to_be16(data_len); + + return gip_send_authenticate(auth->client, &req, sizeof(req), true); +} + +static int gip_auth2_send_hello(struct gip_auth *auth) +{ + struct gip_auth2_pkt_host_hello pkt = {}; + + get_random_bytes(auth->random_host, sizeof(auth->random_host)); + memcpy(pkt.random, auth->random_host, sizeof(pkt.random)); + + return gip_auth_send_pkt(auth, GIP_AUTH2_CMD_HOST_HELLO, + &pkt, sizeof(pkt)); +} + +static int gip_auth2_handle_pkt_hello(struct gip_auth *auth, + void *data, u32 len) +{ + struct gip_auth2_pkt_client_hello *pkt = data; + + if (len < sizeof(*pkt)) + return -EINVAL; + + memcpy(auth->random_client, pkt->random, sizeof(auth->random_client)); + + return gip_auth_request_pkt(auth, GIP_AUTH2_CMD_CLIENT_CERTIFICATE, + sizeof(struct gip_auth2_pkt_client_cert)); +} + +static int gip_auth2_handle_pkt_certificate(struct gip_auth *auth, + void *data, u32 len) +{ + struct gip_auth2_pkt_client_cert *pkt = data; + + if (len < sizeof(*pkt)) + return -EINVAL; + + dev_dbg(&auth->client->dev, + "%s: header=%.*s, chip=%.*s, revision=%.*s\n", __func__, + (int)sizeof(pkt->header), pkt->header, + (int)sizeof(pkt->chip), pkt->chip, + (int)sizeof(pkt->revision), pkt->revision); + + return gip_auth_request_pkt(auth, GIP_AUTH2_CMD_CLIENT_PUBKEY, + sizeof(struct gip_auth2_pkt_client_pubkey)); +} + +static int gip_auth2_handle_pkt_pubkey(struct gip_auth *auth, + void *data, u32 len) +{ + struct gip_auth2_pkt_client_pubkey *pkt = data; + + if (len < sizeof(*pkt)) + return -EINVAL; + + memcpy(auth->pubkey_client2, pkt->pubkey, sizeof(pkt->pubkey)); + schedule_work(&auth->work_exchange_ecdh); + + return 0; +} + +static void gip_auth2_exchange_ecdh(struct work_struct *work) +{ + struct gip_auth *auth = container_of(work, typeof(*auth), + work_exchange_ecdh); + struct gip_auth2_pkt_host_pubkey pkt = {}; + u8 random[GIP_AUTH_RANDOM_LEN * 2]; + u8 secret[GIP_AUTH2_SECRET_LEN]; + int err; + + memcpy(random, auth->random_host, sizeof(auth->random_host)); + memcpy(random + sizeof(auth->random_host), auth->random_client, + sizeof(auth->random_client)); + + err = gip_auth_compute_ecdh(auth->pubkey_client2, pkt.pubkey, + sizeof(pkt.pubkey), secret); + if (err) { + dev_err(&auth->client->dev, "%s: compute ECDH failed: %d\n", + __func__, err); + return; + } + + err = gip_auth_compute_prf(auth->shash_prf, "Master Secret", + secret, sizeof(secret), + random, sizeof(random), + auth->master_secret, + sizeof(auth->master_secret)); + if (err) { + dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", + __func__, err); + return; + } + + err = gip_auth_send_pkt(auth, GIP_AUTH2_CMD_HOST_PUBKEY, + &pkt, sizeof(pkt)); + if (err) + dev_err(&auth->client->dev, "%s: send pkt failed: %d\n", + __func__, err); +} + +static int gip_auth_send_pkt_hello(struct gip_auth *auth) +{ + struct gip_auth_pkt_host_hello pkt = {}; + + get_random_bytes(auth->random_host, sizeof(auth->random_host)); + memcpy(pkt.random, auth->random_host, sizeof(pkt.random)); + + return gip_auth_send_pkt(auth, GIP_AUTH_CMD_HOST_HELLO, + &pkt, sizeof(pkt)); +} + +static int gip_auth_send_pkt_finish(struct gip_auth *auth, + enum gip_auth_command_handshake cmd) +{ + struct gip_auth_pkt_host_finish pkt = {}; + u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; + int err; + + err = gip_auth_get_transcript(auth->shash_transcript, transcript); + if (err) { + dev_err(&auth->client->dev, "%s: get transcript failed: %d\n", + __func__, err); + return err; + } + + err = gip_auth_compute_prf(auth->shash_prf, "Host Finished", + auth->master_secret, + sizeof(auth->master_secret), + transcript, sizeof(transcript), + pkt.transcript, sizeof(pkt.transcript)); + if (err) { + dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", + __func__, err); + return err; + } + + return gip_auth_send_pkt(auth, cmd, &pkt, sizeof(pkt)); +} + +static int gip_auth_handle_pkt_acknowledge(struct gip_auth *auth) +{ + switch (auth->last_sent_command) { + case GIP_AUTH2_CMD_HOST_HELLO: + return gip_auth_request_pkt(auth, GIP_AUTH2_CMD_CLIENT_HELLO, + sizeof(struct gip_auth2_pkt_client_hello)); + case GIP_AUTH2_CMD_HOST_PUBKEY: + return gip_auth_send_pkt_finish(auth, GIP_AUTH2_CMD_HOST_FINISH); + case GIP_AUTH2_CMD_HOST_FINISH: + return gip_auth_request_pkt(auth, GIP_AUTH2_CMD_CLIENT_FINISH, + sizeof(struct gip_auth2_pkt_client_finish)); + case GIP_AUTH_CMD_HOST_HELLO: + return gip_auth_request_pkt(auth, GIP_AUTH_CMD_CLIENT_HELLO, + sizeof(struct gip_auth_pkt_client_hello)); + case GIP_AUTH_CMD_HOST_SECRET: + return gip_auth_send_pkt_finish(auth, GIP_AUTH_CMD_HOST_FINISH); + case GIP_AUTH_CMD_HOST_FINISH: + return gip_auth_request_pkt(auth, GIP_AUTH_CMD_CLIENT_FINISH, + sizeof(struct gip_auth_pkt_client_finish)); + default: + return -EPROTO; + } +} + +static int gip_auth_handle_pkt_hello(struct gip_auth *auth, + void *data, u32 len) +{ + struct gip_auth_pkt_client_hello *pkt = data; + + if (len < sizeof(*pkt)) + return -EINVAL; + + memcpy(auth->random_client, pkt->random, sizeof(pkt->random)); + + return gip_auth_request_pkt(auth, GIP_AUTH_CMD_CLIENT_CERTIFICATE, + GIP_AUTH_CERTIFICATE_MAX_LEN); +} + +static int gip_auth_handle_pkt_certificate(struct gip_auth *auth, + void *data, u32 len) +{ + /* ASN.1 SEQUENCE (len = 0x04 + 0x010a) */ + u8 asn1_seq[] = { 0x30, 0x82, 0x01, 0x0a }; + int i; + + if (len > GIP_AUTH_CERTIFICATE_MAX_LEN) + return -EINVAL; + + /* + * Poor way of extracting a pubkey from an X.509 certificate. + * The certificates issued by Microsoft do not comply with RFC 5280. + * They have an empty subject and no subjectAltName. + * This is explicitly forbidden by section 4.2.1.6 of the RFC. + * The kernel's ASN.1 parser will fail when using x509_cert_parse. + */ + for (i = 0; i + sizeof(asn1_seq) <= len; i++) { + if (memcmp(data + i, asn1_seq, sizeof(asn1_seq))) + continue; + + if (i + GIP_AUTH_PUBKEY_LEN > len) + return -EINVAL; + + memcpy(auth->pubkey_client, data + i, GIP_AUTH_PUBKEY_LEN); + schedule_work(&auth->work_exchange_rsa); + + return 0; + } + + return -EPROTO; +} + +static int gip_auth_handle_pkt_finish(struct gip_auth *auth, + void *data, u32 len) +{ + struct gip_auth_pkt_client_finish *pkt = data; + u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; + u8 finished[GIP_AUTH_TRANSCRIPT_LEN]; + int err; + + if (len < sizeof(*pkt)) + return -EINVAL; + + err = gip_auth_get_transcript(auth->shash_transcript, transcript); + if (err) { + dev_err(&auth->client->dev, "%s: get transcript failed: %d\n", + __func__, err); + return err; + } + + err = gip_auth_compute_prf(auth->shash_prf, "Device Finished", + auth->master_secret, + sizeof(auth->master_secret), + transcript, sizeof(transcript), + finished, sizeof(finished)); + if (err) { + dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", + __func__, err); + return err; + } + + if (memcmp(pkt->transcript, finished, sizeof(finished))) { + dev_err(&auth->client->dev, "%s: transcript mismatch\n", + __func__); + return -EPROTO; + } + + schedule_work(&auth->work_complete); + + return 0; +} + +static void gip_auth_exchange_rsa(struct work_struct *work) +{ + struct gip_auth *auth = container_of(work, typeof(*auth), + work_exchange_rsa); + struct gip_auth_pkt_host_secret pkt = {}; + u8 random[GIP_AUTH_RANDOM_LEN * 2]; + int err; + + memcpy(random, auth->random_host, sizeof(auth->random_host)); + memcpy(random + sizeof(auth->random_host), auth->random_client, + sizeof(auth->random_client)); + + /* get random premaster secret */ + get_random_bytes(auth->pms, sizeof(auth->pms)); + + err = gip_auth_encrypt_rsa(auth->pubkey_client, + sizeof(auth->pubkey_client), + auth->pms, sizeof(auth->pms), + pkt.encrypted_pms, + sizeof(pkt.encrypted_pms)); + if (err) { + dev_err(&auth->client->dev, "%s: encrypt RSA failed: %d\n", + __func__, err); + return; + } + + err = gip_auth_compute_prf(auth->shash_prf, "Master Secret", + auth->pms, sizeof(auth->pms), + random, sizeof(random), + auth->master_secret, + sizeof(auth->master_secret)); + if (err) { + dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", + __func__, err); + return; + } + + err = gip_auth_send_pkt(auth, GIP_AUTH_CMD_HOST_SECRET, + &pkt, sizeof(pkt)); + if (err) + dev_err(&auth->client->dev, "%s: send pkt failed: %d\n", + __func__, err); +} + +int gip_auth_send_complete(struct gip_client *client) +{ + struct gip_auth_header_control hdr = {}; + + hdr.context = GIP_AUTH_CTX_CONTROL; + hdr.control = GIP_AUTH_CTRL_COMPLETE; + + return gip_send_authenticate(client, &hdr, sizeof(hdr), false); +} +EXPORT_SYMBOL_GPL(gip_auth_send_complete); + +static void gip_auth_complete_handshake(struct work_struct *work) +{ + struct gip_auth *auth = container_of(work, typeof(*auth), + work_complete); + u8 random[GIP_AUTH_RANDOM_LEN * 2]; + u8 key[GIP_AUTH_SESSION_KEY_LEN]; + int err; + + memcpy(random, auth->random_host, sizeof(auth->random_host)); + memcpy(random + sizeof(auth->random_host), auth->random_client, + sizeof(auth->random_client)); + + err = gip_auth_compute_prf(auth->shash_prf, + "EXPORTER DAWN data channel session key for controller", + auth->master_secret, + sizeof(auth->master_secret), + random, sizeof(random), + key, sizeof(key)); + if (err) { + dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", + __func__, err); + return; + } + + dev_dbg(&auth->client->dev, "%s: key=%*phD\n", __func__, + (int)sizeof(key), key); + + err = gip_auth_send_complete(auth->client); + if (err) { + dev_err(&auth->client->dev, "%s: send complete failed: %d\n", + __func__, err); + return; + } + + err = gip_set_encryption_key(auth->client, key, sizeof(key)); + if (err) + dev_err(&auth->client->dev, + "%s: set encryption key failed: %d\n", __func__, err); +} + +static int gip_auth_dispatch_pkt(struct gip_auth *auth, + enum gip_auth_command_handshake cmd, + void *data, u32 len) +{ + switch (cmd) { + case GIP_AUTH2_CMD_CLIENT_HELLO: + return gip_auth2_handle_pkt_hello(auth, data, len); + case GIP_AUTH2_CMD_CLIENT_CERTIFICATE: + return gip_auth2_handle_pkt_certificate(auth, data, len); + case GIP_AUTH2_CMD_CLIENT_PUBKEY: + return gip_auth2_handle_pkt_pubkey(auth, data, len); + case GIP_AUTH2_CMD_CLIENT_FINISH: + return gip_auth_handle_pkt_finish(auth, data, len); + case GIP_AUTH_CMD_CLIENT_HELLO: + return gip_auth_handle_pkt_hello(auth, data, len); + case GIP_AUTH_CMD_CLIENT_CERTIFICATE: + return gip_auth_handle_pkt_certificate(auth, data, len); + case GIP_AUTH_CMD_CLIENT_FINISH: + return gip_auth_handle_pkt_finish(auth, data, len); + default: + return -EPROTO; + } +} + +static int gip_auth_process_pkt_data(struct gip_auth *auth, void *data, u32 len) +{ + struct gip_auth_header_full *hdr = data; + int err; + + if (len < sizeof(*hdr)) + return -EINVAL; + + /* client uses auth v2 */ + if (hdr->handshake.command != hdr->data.command) { + /* reset transcript hash and restart handshake */ + dev_dbg(&auth->client->dev, "%s: protocol upgrade\n", __func__); + crypto_shash_init(auth->shash_transcript); + return gip_auth2_send_hello(auth); + } + + err = gip_auth_dispatch_pkt(auth, hdr->data.command, + data + sizeof(*hdr), len - sizeof(*hdr)); + if (err) + return err; + + return crypto_shash_update(auth->shash_transcript, + data + sizeof(hdr->handshake), + len - sizeof(hdr->handshake)); +} + +int gip_auth_process_pkt(struct gip_auth *auth, void *data, u32 len) +{ + struct gip_auth_header_handshake *hdr = data; + + if (!auth->client) + return -ENODEV; + + if (len < sizeof(*hdr)) + return -EINVAL; + + if (hdr->error) + return -EPROTO; + + if (hdr->options & GIP_AUTH_OPT_ACKNOWLEDGE) { + if (hdr->command == 0x01) + return gip_auth_handle_pkt_acknowledge(auth); + + dev_err(&auth->client->dev, "%s: handshake failed: 0x%02x\n", + __func__, hdr->command); + return -EPROTO; + } + + return gip_auth_process_pkt_data(auth, data, len); +} +EXPORT_SYMBOL_GPL(gip_auth_process_pkt); + +static void gip_auth_release(void *res) +{ + struct gip_auth *auth = res; + + cancel_work_sync(&auth->work_exchange_rsa); + cancel_work_sync(&auth->work_exchange_ecdh); + cancel_work_sync(&auth->work_complete); + + crypto_free_shash(auth->shash_transcript->tfm); + crypto_free_shash(auth->shash_prf->tfm); + kfree(auth->shash_transcript); + kfree(auth->shash_prf); + + auth->client = NULL; + auth->shash_transcript = NULL; + auth->shash_prf = NULL; +} + +int gip_auth_start_handshake(struct gip_auth *auth, struct gip_client *client) +{ + struct shash_desc *shash_transcript, *shash_prf; + int err; + + shash_transcript = gip_auth_alloc_shash("sha256"); + if (IS_ERR(shash_transcript)) + return PTR_ERR(shash_transcript); + + shash_prf = gip_auth_alloc_shash("hmac(sha256)"); + if (IS_ERR(shash_prf)) { + crypto_free_shash(shash_transcript->tfm); + kfree(shash_transcript); + return PTR_ERR(shash_prf); + } + + auth->client = client; + auth->shash_transcript = shash_transcript; + auth->shash_prf = shash_prf; + + INIT_WORK(&auth->work_exchange_rsa, gip_auth_exchange_rsa); + INIT_WORK(&auth->work_exchange_ecdh, gip_auth2_exchange_ecdh); + INIT_WORK(&auth->work_complete, gip_auth_complete_handshake); + + err = devm_add_action_or_reset(&client->dev, gip_auth_release, auth); + if (err) + return err; + + return gip_auth_send_pkt_hello(auth); +} +EXPORT_SYMBOL_GPL(gip_auth_start_handshake); diff --git a/drivers/custom/xonedo/auth/auth.h b/drivers/custom/xonedo/auth/auth.h new file mode 100644 index 000000000000..85d2c8165fb0 --- /dev/null +++ b/drivers/custom/xonedo/auth/auth.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023 Severin von Wnuck-Lipinski + */ + +#pragma once + +#include +#include + +/* trailer is required for v1 clients */ +#define GIP_AUTH_TRAILER_LEN 8 +#define GIP_AUTH_RANDOM_LEN 32 +#define GIP_AUTH_CERTIFICATE_MAX_LEN 1024 +#define GIP_AUTH_PUBKEY_LEN 270 +#define GIP_AUTH_SECRET_LEN 48 +#define GIP_AUTH_ENCRYPTED_PMS_LEN 256 +#define GIP_AUTH_TRANSCRIPT_LEN 32 +#define GIP_AUTH_SESSION_KEY_LEN 16 + +#define GIP_AUTH2_PUBKEY_LEN 64 +#define GIP_AUTH2_SECRET_LEN 32 + +struct gip_client; + +struct gip_auth { + struct gip_client *client; + + struct shash_desc *shash_transcript; + struct shash_desc *shash_prf; + + struct work_struct work_exchange_rsa; + struct work_struct work_exchange_ecdh; + struct work_struct work_complete; + + u8 last_sent_command; + + u8 random_host[GIP_AUTH_RANDOM_LEN]; + u8 random_client[GIP_AUTH_RANDOM_LEN]; + + u8 pubkey_client[GIP_AUTH_PUBKEY_LEN]; + u8 pubkey_client2[GIP_AUTH2_PUBKEY_LEN]; + + u8 pms[GIP_AUTH_SECRET_LEN]; + u8 master_secret[GIP_AUTH_SECRET_LEN]; +}; + +int gip_auth_send_complete(struct gip_client *client); +int gip_auth_process_pkt(struct gip_auth *auth, void *data, u32 len); +int gip_auth_start_handshake(struct gip_auth *auth, struct gip_client *client); diff --git a/drivers/custom/xonedo/auth/crypto.c b/drivers/custom/xonedo/auth/crypto.c new file mode 100644 index 000000000000..64dd7388a4aa --- /dev/null +++ b/drivers/custom/xonedo/auth/crypto.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Severin von Wnuck-Lipinski + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "crypto.h" + +#define GIP_AUTH_ECDH_SECRET_LEN 32 + +struct shash_desc *gip_auth_alloc_shash(const char *alg) +{ + struct crypto_shash *tfm; + struct shash_desc *desc; + + tfm = crypto_alloc_shash(alg, 0, 0); + if (IS_ERR(tfm)) + return ERR_CAST(tfm); + + desc = kzalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL); + if (!desc) { + crypto_free_shash(tfm); + return ERR_PTR(-ENOMEM); + } + + desc->tfm = tfm; + crypto_shash_init(desc); + + return desc; +} + +int gip_auth_get_transcript(struct shash_desc *desc, void *transcript) +{ + void *state = kzalloc(crypto_shash_descsize(desc->tfm), GFP_KERNEL); + int err; + + err = crypto_shash_export(desc, state); + if (err) + goto get_transcript_error; + + err = crypto_shash_final(desc, transcript); + if (err) + goto get_transcript_error; + + err = crypto_shash_import(desc, state); + +get_transcript_error: + kfree(state); + return err; +} + +int gip_auth_compute_prf(struct shash_desc *desc, const char *label, + u8 *key, int key_len, + u8 *seed, int seed_len, + u8 *out, int out_len) +{ + u8 hash[SHA256_DIGEST_SIZE], hash_out[SHA256_DIGEST_SIZE]; + int err; + + err = crypto_shash_setkey(desc->tfm, key, key_len); + if (err) + return err; + + crypto_shash_init(desc); + crypto_shash_update(desc, label, strlen(label)); + crypto_shash_update(desc, seed, seed_len); + crypto_shash_final(desc, hash); + + while (out_len > 0) { + crypto_shash_init(desc); + crypto_shash_update(desc, hash, sizeof(hash)); + crypto_shash_update(desc, label, strlen(label)); + crypto_shash_update(desc, seed, seed_len); + crypto_shash_final(desc, hash_out); + + memcpy(out, hash_out, min_t(int, out_len, sizeof(hash))); + out += sizeof(hash); + out_len -= sizeof(hash); + + crypto_shash_digest(desc, hash, sizeof(hash), hash); + } + + return 0; +} + +int gip_auth_encrypt_rsa(u8 *key, int key_len, + u8 *in, int in_len, + u8 *out, int out_len) +{ + struct crypto_akcipher *tfm; + int err; + + tfm = crypto_alloc_akcipher("pkcs1pad(rsa)", 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + err = crypto_akcipher_set_pub_key(tfm, key, key_len); + if (err) + goto err_free_tfm; + + err = crypto_akcipher_sync_encrypt(tfm, in, in_len, out, out_len); + +err_free_tfm: + crypto_free_akcipher(tfm); + + return err; +} + +static int gip_auth_ecdh_get_pubkey(struct crypto_kpp *tfm, + u8 *out, int len) +{ + struct kpp_request *req; + struct scatterlist dest; + struct ecdh key = {}; + DECLARE_CRYPTO_WAIT(wait); + void *privkey, *pubkey; + unsigned int privkey_len; + int err = 0; + + privkey_len = crypto_ecdh_key_len(&key); + privkey = kzalloc(privkey_len, GFP_KERNEL); + if (!privkey) + return -ENOMEM; + + pubkey = kzalloc(len, GFP_KERNEL); + if (!pubkey){ + err = -ENOMEM; + goto err_free_privkey; + } + + /* generate private key */ + err = crypto_ecdh_encode_key(privkey, privkey_len, &key); + if (err) + goto err_free_pubkey; + + err = crypto_kpp_set_secret(tfm, privkey, privkey_len); + if (err) + goto err_free_pubkey; + + req = kpp_request_alloc(tfm, GFP_KERNEL); + if (!req) { + err = -ENOMEM; + goto err_free_pubkey; + } + + sg_init_one(&dest, pubkey, len); + + kpp_request_set_input(req, NULL, 0); + kpp_request_set_output(req, &dest, len); + kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, + crypto_req_done, &wait); + err = crypto_wait_req(crypto_kpp_generate_public_key(req), &wait); + if (!err) + memcpy(out, pubkey, len); + + kpp_request_free(req); + +err_free_pubkey: + kfree(pubkey); +err_free_privkey: + kfree(privkey); + + return err; +} + +static int gip_auth_ecdh_get_secret(struct crypto_kpp *tfm, + u8 *pubkey, int pubkey_len, + u8 *secret, int secret_len) +{ + struct kpp_request *req; + struct scatterlist src, dest; + DECLARE_CRYPTO_WAIT(wait); + int err; + + req = kpp_request_alloc(tfm, GFP_KERNEL); + if (!req) + return -ENOMEM; + + sg_init_one(&src, pubkey, pubkey_len); + sg_init_one(&dest, secret, secret_len); + + kpp_request_set_input(req, &src, pubkey_len); + kpp_request_set_output(req, &dest, secret_len); + kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, + crypto_req_done, &wait); + err = crypto_wait_req(crypto_kpp_compute_shared_secret(req), &wait); + + kpp_request_free(req); + + return err; +} + +int gip_auth_compute_ecdh(u8 *pubkey_in, u8 *pubkey_out, + int pubkey_len, u8 *secret_hash) +{ + struct crypto_kpp *tfm_ecdh; + struct crypto_shash *tfm_sha; + u8 *secret; + int err; + + secret = kzalloc(GIP_AUTH_ECDH_SECRET_LEN, GFP_KERNEL); + if (!secret) + return -ENOMEM; + + tfm_ecdh = crypto_alloc_kpp("ecdh-nist-p256", 0, 0); + if (IS_ERR(tfm_ecdh)) { + err = PTR_ERR(tfm_ecdh); + goto err_free_secret; + } + + tfm_sha = crypto_alloc_shash("sha256", 0, 0); + if (IS_ERR(tfm_sha)) { + err = PTR_ERR(tfm_sha); + goto err_free_ecdh; + } + + err = gip_auth_ecdh_get_pubkey(tfm_ecdh, pubkey_out, pubkey_len); + if (err) + goto err_free_sha; + + err = gip_auth_ecdh_get_secret(tfm_ecdh, pubkey_in, pubkey_len, + secret, GIP_AUTH_ECDH_SECRET_LEN); + if (err) + goto err_free_sha; + + crypto_shash_tfm_digest(tfm_sha, secret, GIP_AUTH_ECDH_SECRET_LEN, + secret_hash); + +err_free_sha: + crypto_free_shash(tfm_sha); +err_free_ecdh: + crypto_free_kpp(tfm_ecdh); +err_free_secret: + kfree(secret); + + return err; +} diff --git a/drivers/custom/xonedo/auth/crypto.h b/drivers/custom/xonedo/auth/crypto.h new file mode 100644 index 000000000000..a34972e35b28 --- /dev/null +++ b/drivers/custom/xonedo/auth/crypto.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023 Severin von Wnuck-Lipinski + */ + +#pragma once + +#include + +struct shash_desc *gip_auth_alloc_shash(const char *alg); +int gip_auth_get_transcript(struct shash_desc *desc, void *transcript); +int gip_auth_compute_prf(struct shash_desc *desc, const char *label, + u8 *key, int key_len, + u8 *seed, int seed_len, + u8 *out, int out_len); + +int gip_auth_encrypt_rsa(u8 *key, int key_len, + u8 *in, int in_len, + u8 *out, int out_len); +int gip_auth_compute_ecdh(u8 *pubkey_in, u8 *pubkey_out, + int pubkey_len, u8 *secret_hash); diff --git a/drivers/custom/xonedo/bus/bus.c b/drivers/custom/xonedo/bus/bus.c new file mode 100644 index 000000000000..8baa71ea97ef --- /dev/null +++ b/drivers/custom/xonedo/bus/bus.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck-Lipinski + */ + +#include +#include +#include +#include +#include + +#include "bus.h" + +#define to_gip_adapter(d) container_of(d, struct gip_adapter, dev) +#define to_gip_client(d) container_of(d, struct gip_client, dev) +#define to_gip_driver(d) container_of(d, struct gip_driver, drv) + +static DEFINE_IDA(gip_adapter_ida); + +static void gip_adapter_release(struct device *dev) +{ + kfree(to_gip_adapter(dev)); +} + +static struct device_type gip_adapter_type = { + .release = gip_adapter_release, +}; + +static int gip_client_uevent(const struct device *dev, + struct kobj_uevent_env *env) +{ + struct gip_client *client = to_gip_client(dev); + struct gip_classes *classes = client->classes; + + if (!classes || !classes->count) + return -EINVAL; + + return add_uevent_var(env, "MODALIAS=gip:%s", classes->strings[0]); +} + +static void gip_client_release(struct device *dev) +{ + struct gip_client *client = to_gip_client(dev); + + gip_free_client_info(client); + kfree(client->chunk_buf_out); + kfree(client->chunk_buf_in); + kfree(client); +} + +static struct device_type gip_client_type = { + .uevent = gip_client_uevent, + .release = gip_client_release, +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) +static int gip_bus_match(struct device *dev, struct device_driver *driver) +#else +static int gip_bus_match(struct device *dev, const struct device_driver *driver) +#endif +{ + struct gip_client *client; + struct gip_driver *drv; + int i; + + if (dev->type != &gip_client_type) + return false; + + client = to_gip_client(dev); + drv = to_gip_driver(driver); + + for (i = 0; i < client->classes->count; i++) + if (!strcmp(client->classes->strings[i], drv->class)) + return true; + + return false; +} + +static int gip_bus_probe(struct device *dev) +{ + struct gip_client *client = to_gip_client(dev); + struct gip_driver *drv = to_gip_driver(dev->driver); + int err = 0; + + if (down_interruptible(&client->drv_lock)) + return -EINTR; + + if (!client->drv) { + err = drv->probe(client); + if (!err) + client->drv = drv; + } + + up(&client->drv_lock); + + return err; +} + +static void gip_bus_remove(struct device *dev) +{ + struct gip_client *client = to_gip_client(dev); + struct gip_driver *drv; + + down(&client->drv_lock); + + drv = client->drv; + if (drv) { + client->drv = NULL; + if (drv->remove) + drv->remove(client); + } + + up(&client->drv_lock); +} + +static struct bus_type gip_bus_type = { + .name = "xone-gip", + .match = gip_bus_match, + .probe = gip_bus_probe, + .remove = gip_bus_remove, +}; + +struct gip_adapter *gip_create_adapter(struct device *parent, + struct gip_adapter_ops *ops, + int audio_pkts) +{ + struct gip_adapter *adap; + int err; + + adap = kzalloc(sizeof(*adap), GFP_KERNEL); + if (!adap) + return ERR_PTR(-ENOMEM); + + adap->id = ida_alloc(&gip_adapter_ida, GFP_KERNEL); + if (adap->id < 0) { + err = adap->id; + goto err_put_device; + } + + adap->clients_wq = alloc_ordered_workqueue("gip%d", 0, adap->id); + if (!adap->clients_wq) { + err = -ENOMEM; + goto err_remove_ida; + } + + adap->dev.parent = parent; + adap->dev.type = &gip_adapter_type; + adap->dev.bus = &gip_bus_type; + adap->ops = ops; + adap->audio_packet_count = audio_pkts; + dev_set_name(&adap->dev, "gip%d", adap->id); + spin_lock_init(&adap->send_lock); + + err = device_register(&adap->dev); + if (err) + goto err_destroy_queue; + + dev_dbg(&adap->dev, "%s: registered\n", __func__); + + return adap; + +err_destroy_queue: + destroy_workqueue(adap->clients_wq); + +err_remove_ida: + ida_free(&gip_adapter_ida, adap->id); + +err_put_device: + put_device(&adap->dev); + + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(gip_create_adapter); + +int gip_power_off_adapter(struct gip_adapter *adap) +{ + struct gip_client *client = adap->clients[0]; + + if (!client) + return 0; + + /* power off main client */ + return gip_set_power_mode(client, GIP_PWR_OFF); +} +EXPORT_SYMBOL_GPL(gip_power_off_adapter); + +void gip_destroy_adapter(struct gip_adapter *adap) +{ + struct gip_client *client; + int i; + + /* ensure all state changes have been processed */ + flush_workqueue(adap->clients_wq); + + for (i = GIP_MAX_CLIENTS - 1; i >= 0; i--) { + client = adap->clients[i]; + if (!client || !device_is_registered(&client->dev)) + continue; + + device_unregister(&client->dev); + } + + ida_free(&gip_adapter_ida, adap->id); + destroy_workqueue(adap->clients_wq); + + dev_dbg(&adap->dev, "%s: unregistered\n", __func__); + device_unregister(&adap->dev); +} +EXPORT_SYMBOL_GPL(gip_destroy_adapter); + +static void gip_register_client(struct work_struct *work) +{ + struct gip_client *client = container_of(work, typeof(*client), + work_register); + int err; + + /* + * Sometimes, after wakeup from sleep gamepads are unresponsive, Turns + * out, it has to do something with how fast this function is performed. + * Our best guess for now is that some kind of data is just not getting + * there fast enough with some hubs that lose power and some timeouts + * that normally asre used to init the device, aren't there to help. + * + * The unfortunate workaround, that at least works reliably is to add a + * delay here. Since this is for human input device, one second is fine. + */ + msleep(700); + + client->dev.parent = &client->adapter->dev; + client->dev.type = &gip_client_type; + client->dev.bus = &gip_bus_type; + sema_init(&client->drv_lock, 1); + dev_set_name(&client->dev, "gip%d.%u", client->adapter->id, client->id); + + err = device_register(&client->dev); + if (err) + dev_err(&client->dev, "%s: register failed: %d\n", + __func__, err); + else + dev_dbg(&client->dev, "%s: registered\n", __func__); +} + +static void gip_unregister_client(struct work_struct *work) +{ + struct gip_client *client = container_of(work, typeof(*client), + work_unregister); + + if (!device_is_registered(&client->dev)) + return; + + dev_dbg(&client->dev, "%s: unregistered\n", __func__); + device_unregister(&client->dev); +} + +struct gip_client *gip_get_client(struct gip_adapter *adap, u8 id) +{ + struct gip_client *client; + + client = adap->clients[id]; + if (client) + return client; + + client = kzalloc(sizeof(*client), GFP_ATOMIC); + if (!client) + return ERR_PTR(-ENOMEM); + + client->id = id; + client->adapter = adap; + sema_init(&client->drv_lock, 1); + INIT_WORK(&client->work_register, gip_register_client); + INIT_WORK(&client->work_unregister, gip_unregister_client); + + adap->clients[id] = client; + + dev_dbg(&client->adapter->dev, "%s: initialized client %u\n", + __func__, id); + + return client; +} + +void gip_add_client(struct gip_client *client) +{ + queue_work(client->adapter->clients_wq, &client->work_register); +} + +void gip_remove_client(struct gip_client *client) +{ + client->adapter->clients[client->id] = NULL; + queue_work(client->adapter->clients_wq, &client->work_unregister); +} + +void gip_free_client_info(struct gip_client *client) +{ + int i; + + kfree(client->client_commands); + kfree(client->firmware_versions); + kfree(client->audio_formats); + kfree(client->capabilities_out); + kfree(client->capabilities_in); + + if (client->classes) + for (i = 0; i < client->classes->count; i++) + kfree(client->classes->strings[i]); + + kfree(client->classes); + kfree(client->interfaces); + kfree(client->hid_descriptor); + + client->client_commands = NULL; + client->audio_formats = NULL; + client->capabilities_out = NULL; + client->capabilities_in = NULL; + client->classes = NULL; + client->interfaces = NULL; + client->hid_descriptor = NULL; +} + +int __gip_register_driver(struct gip_driver *drv, struct module *owner, + const char *mod_name) +{ + drv->drv.name = drv->name; + drv->drv.bus = &gip_bus_type; + drv->drv.owner = owner; + drv->drv.mod_name = mod_name; + + return driver_register(&drv->drv); +} +EXPORT_SYMBOL_GPL(__gip_register_driver); + +void gip_unregister_driver(struct gip_driver *drv) +{ + driver_unregister(&drv->drv); +} +EXPORT_SYMBOL_GPL(gip_unregister_driver); + +static int __init gip_bus_init(void) +{ + return bus_register(&gip_bus_type); +} + +static void __exit gip_bus_exit(void) +{ + bus_unregister(&gip_bus_type); +} + +module_init(gip_bus_init); +module_exit(gip_bus_exit); + +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); +MODULE_DESCRIPTION("xone GIP driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/custom/xonedo/bus/bus.h b/drivers/custom/xonedo/bus/bus.h new file mode 100644 index 000000000000..95c483fb131c --- /dev/null +++ b/drivers/custom/xonedo/bus/bus.h @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 Severin von Wnuck-Lipinski + */ + +#pragma once + +#include +#include +#include + +#include "protocol.h" + +#define GIP_MAX_CLIENTS 16 + +#define gip_register_driver(drv) \ + __gip_register_driver(drv, THIS_MODULE, KBUILD_MODNAME) + +#define module_gip_driver(drv) \ + module_driver(drv, gip_register_driver, gip_unregister_driver) + +struct gip_adapter_buffer { + enum gip_adapter_buffer_type { + GIP_BUF_DATA, + GIP_BUF_AUDIO, + } type; + + void *context; + void *data; + int length; +}; + +struct gip_adapter_ops { + int (*get_buffer)(struct gip_adapter *adap, + struct gip_adapter_buffer *buf); + int (*submit_buffer)(struct gip_adapter *adap, + struct gip_adapter_buffer *buf); + int (*set_encryption_key)(struct gip_adapter *adap, u8 *key, int len); + int (*enable_audio)(struct gip_adapter *adap); + int (*init_audio_in)(struct gip_adapter *adap); + int (*init_audio_out)(struct gip_adapter *adap, int pkt_len); + int (*disable_audio)(struct gip_adapter *adap); +}; + +struct gip_adapter { + struct device dev; + int id; + + struct gip_adapter_ops *ops; + int audio_packet_count; + + struct gip_client *clients[GIP_MAX_CLIENTS]; + struct workqueue_struct *clients_wq; + + /* serializes access to data sequence number */ + spinlock_t send_lock; + + u8 data_sequence; + u8 audio_sequence; +}; + +struct gip_client { + struct device dev; + u8 id; + + struct gip_adapter *adapter; + struct gip_driver *drv; + struct semaphore drv_lock; + + struct work_struct work_register; + struct work_struct work_unregister; + + struct gip_chunk_buffer *chunk_buf_out; + struct gip_chunk_buffer *chunk_buf_in; + struct gip_hardware hardware; + + struct gip_info_element *client_commands; + struct gip_info_element *firmware_versions; + struct gip_info_element *audio_formats; + struct gip_info_element *capabilities_out; + struct gip_info_element *capabilities_in; + struct gip_classes *classes; + struct gip_info_element *interfaces; + struct gip_info_element *hid_descriptor; + + struct gip_audio_config audio_config_in; + struct gip_audio_config audio_config_out; + + struct gip_serial_number serial; +}; + +struct gip_driver_ops { + int (*battery)(struct gip_client *client, + enum gip_battery_type type, + enum gip_battery_level level); + int (*authenticate)(struct gip_client *client, void *data, u32 len); + int (*authenticated)(struct gip_client *client); + int (*guide_button)(struct gip_client *client, bool down); + int (*audio_ready)(struct gip_client *client); + int (*audio_volume)(struct gip_client *client, u8 in, u8 out); + int (*hid_report)(struct gip_client *client, void *data, u32 len); + int (*input)(struct gip_client *client, void *data, u32 len); + int (*firmware)(struct gip_client *client, void *data, u32 len); + int (*audio_samples)(struct gip_client *client, void *data, u32 len); +}; + +struct gip_driver { + struct device_driver drv; + const char *name; + const char *class; + + struct gip_driver_ops ops; + + int (*probe)(struct gip_client *client); + void (*remove)(struct gip_client *client); +}; + +struct gip_adapter *gip_create_adapter(struct device *parent, + struct gip_adapter_ops *ops, + int audio_pkts); +int gip_power_off_adapter(struct gip_adapter *adap); +void gip_destroy_adapter(struct gip_adapter *adap); + +struct gip_client *gip_get_client(struct gip_adapter *adap, u8 id); +void gip_add_client(struct gip_client *client); +void gip_remove_client(struct gip_client *client); +void gip_free_client_info(struct gip_client *client); + +int __gip_register_driver(struct gip_driver *drv, struct module *owner, + const char *mod_name); +void gip_unregister_driver(struct gip_driver *drv); diff --git a/drivers/custom/xonedo/bus/protocol.c b/drivers/custom/xonedo/bus/protocol.c new file mode 100644 index 000000000000..9152be5cae56 --- /dev/null +++ b/drivers/custom/xonedo/bus/protocol.c @@ -0,0 +1,1714 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck-Lipinski + */ + +#include +#include +#include + +#include "bus.h" + +#define GIP_HDR_CLIENT_ID GENMASK(3, 0) +#define GIP_HDR_MIN_LENGTH 3 + +/* max length, even for wireless packets (except audio) */ +#define GIP_PKT_MAX_LENGTH 58 + +/* reliable packet transmission coalesce count */ +#define GIP_PKT_COALESCE_COUNT 5 + +#define GIP_CHUNK_BUF_MAX_LENGTH 0xffff + +#define GIP_BATT_LEVEL GENMASK(1, 0) +#define GIP_BATT_TYPE GENMASK(3, 2) +#define GIP_STATUS_CONNECTED BIT(7) + +#define GIP_VKEY_LEFT_WIN 0x5b + +#define gip_dbg(client, ...) dev_dbg(&(client)->adapter->dev, __VA_ARGS__) +#define gip_warn(client, ...) dev_warn(&(client)->adapter->dev, __VA_ARGS__) +#define gip_err(client, ...) dev_err(&(client)->adapter->dev, __VA_ARGS__) + +enum gip_command_core { + GIP_CMD_ACKNOWLEDGE = 0x01, + GIP_CMD_ANNOUNCE = 0x02, + GIP_CMD_STATUS = 0x03, + GIP_CMD_IDENTIFY = 0x04, + GIP_CMD_POWER = 0x05, + GIP_CMD_AUTHENTICATE = 0x06, + GIP_CMD_VIRTUAL_KEY = 0x07, + GIP_CMD_AUDIO_CONTROL = 0x08, + GIP_CMD_LED = 0x0a, + GIP_CMD_HID_REPORT = 0x0b, + GIP_CMD_FIRMWARE = 0x0c, + GIP_CMD_EXTENDED = 0x1e, + GIP_CMD_AUDIO_SAMPLES = 0x60, +}; + +enum gip_command_client { + GIP_CMD_RUMBLE = 0x09, + GIP_CMD_INPUT = 0x20, +}; + +enum gip_option { + GIP_OPT_ACKNOWLEDGE = BIT(4), + GIP_OPT_INTERNAL = BIT(5), + GIP_OPT_CHUNK_START = BIT(6), + GIP_OPT_CHUNK = BIT(7), +}; + +enum gip_audio_control { + GIP_AUD_CTRL_VOLUME_CHAT = 0x00, + GIP_AUD_CTRL_FORMAT_CHAT = 0x01, + GIP_AUD_CTRL_FORMAT = 0x02, + GIP_AUD_CTRL_VOLUME = 0x03, +}; + +enum gip_audio_volume_mute { + GIP_AUD_VOLUME_UNMUTED = 0x04, + GIP_AUD_VOLUME_MIC_MUTED = 0x05, +}; + +static uint gip_audio_sample_rate[] = { + 0, + 8000, + 8000, + 12000, + 12000, + 16000, + 16000, + 20000, + 20000, + 24000, + 24000, + 32000, + 32000, + 40000, + 40000, + 48000, + 48000, +}; + +enum gip_extended_command { + GIP_EXT_GET_CAPABILITIES = 0x00, + GIP_EXT_GET_TELEMETRY = 0x01, + GIP_EXT_GET_SERIAL_NUMBER = 0x04, +}; + +enum gip_extended_status { + GIP_EXT_STATUS_OK, + GIP_EXT_STATUS_NOT_SUPPORTED, + GIP_EXT_STATUS_NOT_READY, + GIP_EXT_STATUS_ACCESS_DENIED, + GIP_EXT_STATUS_COMMAND_FAILED, +}; + +struct gip_pkt_acknowledge { + u8 unknown; + u8 command; + u8 options; + __le16 length; + u8 padding[2]; + __le16 remaining; +} __packed; + +struct gip_pkt_announce { + u8 address[6]; + __le16 unknown; + __le16 vendor_id; + __le16 product_id; + struct gip_version { + __le16 major; + __le16 minor; + __le16 build; + __le16 revision; + } __packed fw_version, hw_version; +} __packed; + +struct gip_pkt_status { + u8 status; + u8 unknown[3]; +} __packed; + +struct gip_pkt_identify { + u8 unknown[16]; + __le16 client_commands_offset; + __le16 firmware_versions_offset; + __le16 audio_formats_offset; + __le16 capabilities_out_offset; + __le16 capabilities_in_offset; + __le16 classes_offset; + __le16 interfaces_offset; + __le16 hid_descriptor_offset; +} __packed; + +struct gip_pkt_power { + u8 mode; +} __packed; + +struct gip_pkt_virtual_key { + u8 down; + u8 key; +} __packed; + +struct gip_pkt_audio_control { + u8 subcommand; +} __packed; + +struct gip_pkt_audio_volume_chat { + struct gip_pkt_audio_control control; + u8 mute; + u8 gain_out; + u8 out; + u8 in; +} __packed; + +struct gip_pkt_audio_format_chat { + struct gip_pkt_audio_control control; + u8 in_out; +} __packed; + +struct gip_pkt_audio_format { + struct gip_pkt_audio_control control; + u8 in; + u8 out; +} __packed; + +struct gip_pkt_audio_volume { + struct gip_pkt_audio_control control; + u8 mute; + u8 out; + u8 chat; + u8 in; + u8 unknown1; + u8 unknown2[2]; +} __packed; + +struct gip_pkt_led { + u8 unknown; + u8 mode; + u8 brightness; +} __packed; + +struct gip_pkt_get_serial_number { + u8 command; +} __packed; + +struct gip_pkt_serial_number { + u8 command; + u8 status; + char serial[GIP_PKT_MAX_SERIAL_LENGTH]; +} __packed; + +struct gip_pkt_audio_samples { + __le16 flow_rate; + u8 samples[]; +} __packed; + +struct gip_command_descriptor { + u8 marker; + u8 unknown1; + u8 command; + u8 length; + u8 unknown2[3]; + u8 options; + u8 unknown3[15]; +} __packed; + +struct gip_firmware_version { + __le16 major; + __le16 minor; +} __packed; + +static int gip_encode_varint(u8 *buf, u32 val) +{ + int i; + + /* encode variable-length integer */ + for (i = 0; i < sizeof(val); i++) { + buf[i] = val; + if (val > GENMASK(6, 0)) + buf[i] |= BIT(7); + + val >>= 7; + if (!val) + break; + } + + return i + 1; +} + +static int gip_decode_varint(u8 *data, int len, u32 *val) +{ + int i; + + /* decode variable-length integer */ + for (i = 0; i < sizeof(*val) && i < len; i++) { + *val |= (data[i] & GENMASK(6, 0)) << (i * 7); + + if (!(data[i] & BIT(7))) + break; + } + + return i + 1; +} + +static int gip_get_actual_header_length(struct gip_header *hdr) +{ + u32 pkt_len = hdr->packet_length; + u32 chunk_offset = hdr->chunk_offset; + int len = GIP_HDR_MIN_LENGTH; + + do { + len++; + pkt_len >>= 7; + } while (pkt_len); + + if (hdr->options & GIP_OPT_CHUNK) { + while (chunk_offset) { + len++; + chunk_offset >>= 7; + } + } + + return len; +} + +static int gip_get_header_length(struct gip_header *hdr) +{ + int len = gip_get_actual_header_length(hdr); + + /* round up to nearest even length */ + return len + (len % 2); +} + +static void gip_encode_header(struct gip_header *hdr, u8 *buf) +{ + int hdr_len = 0; + + buf[hdr_len++] = hdr->command; + buf[hdr_len++] = hdr->options; + buf[hdr_len++] = hdr->sequence; + + hdr_len += gip_encode_varint(buf + hdr_len, hdr->packet_length); + + /* header length must be even */ + if (gip_get_actual_header_length(hdr) % 2) { + buf[hdr_len - 1] |= BIT(7); + buf[hdr_len++] = 0; + } + + if (hdr->options & GIP_OPT_CHUNK) + gip_encode_varint(buf + hdr_len, hdr->chunk_offset); +} + +static int gip_decode_header(struct gip_header *hdr, u8 *data, int len) +{ + int hdr_len = 0; + + hdr->command = data[hdr_len++]; + hdr->options = data[hdr_len++]; + hdr->sequence = data[hdr_len++]; + hdr->packet_length = 0; + hdr->chunk_offset = 0; + + hdr_len += gip_decode_varint(data + hdr_len, len - hdr_len, + &hdr->packet_length); + + if (hdr->options & GIP_OPT_CHUNK) + hdr_len += gip_decode_varint(data + hdr_len, len - hdr_len, + &hdr->chunk_offset); + + return hdr_len; +} + +static int gip_init_chunk_buffer(struct gip_client *client, + struct gip_header *hdr, + struct gip_chunk_buffer **buf, + bool is_input) +{ + /* offset is total length of all chunks */ + if (hdr->chunk_offset > GIP_CHUNK_BUF_MAX_LENGTH) + return -EINVAL; + + if (*buf) { + gip_err(client, "%s: already initialized (%s)\n", __func__, + is_input ? "in" : "out"); + kfree(*buf); + *buf = NULL; + } + + *buf = kzalloc(sizeof(**buf) + hdr->chunk_offset, GFP_ATOMIC); + if (!*buf) + return -ENOMEM; + + (*buf)->header = *hdr; + /* clear GIP_OPT_ACKNOWLEDGE and GIP_OPT_CHUNK_START */ + (*buf)->header.options &= ~(GIP_OPT_ACKNOWLEDGE | GIP_OPT_CHUNK_START); + (*buf)->length = hdr->chunk_offset; + gip_dbg(client, "%s[%s]: command=0x%02x, length=0x%04x\n", __func__, + is_input ? "in" : "out", + (*buf)->header.command, (*buf)->length); + + return 0; +} + +static int gip_send_pkt_simple(struct gip_client *client, + struct gip_header *hdr, void *data) +{ + struct gip_adapter *adap = client->adapter; + struct gip_adapter_buffer buf = {}; + int hdr_len, err; + unsigned long flags; + + buf.type = GIP_BUF_DATA; + + spin_lock_irqsave(&adap->send_lock, flags); + + err = adap->ops->get_buffer(adap, &buf); + if (err) { + gip_err(client, "%s: get buffer failed: %d\n", __func__, err); + goto err_unlock; + } + + hdr_len = gip_get_header_length(hdr); + if (buf.length < hdr_len + hdr->packet_length) { + err = -ENOSPC; + goto err_unlock; + } + + /* sequence number is always greater than zero */ + while (!hdr->sequence) + hdr->sequence = adap->data_sequence++; + + gip_encode_header(hdr, buf.data); + if (data) + memcpy(buf.data + hdr_len, data, hdr->packet_length); + + /* set actual length */ + buf.length = hdr_len + hdr->packet_length; + + /* debug message sent */ + // gip_dbg(client, "%s: cmd=0x%02x len=0x%04x seq=0x%02x offset=0x%04x\n", + // __func__, hdr->command, buf.length, hdr->sequence, + // hdr->chunk_offset); + + /* always fails on adapter removal */ + err = adap->ops->submit_buffer(adap, &buf); + if (err) + gip_dbg(client, "%s: submit buffer failed: %d\n", __func__, err); + +err_unlock: + spin_unlock_irqrestore(&adap->send_lock, flags); + + return err; +} + +static int gip_send_pkt(struct gip_client *client, + struct gip_header *hdr, void *data) +{ + int err; + + /* packet fits into single buffer */ + if (hdr->packet_length <= GIP_PKT_MAX_LENGTH) + return gip_send_pkt_simple(client, hdr, data); + + /* chunk offset of first chunk is total length */ + hdr->options |= GIP_OPT_ACKNOWLEDGE | GIP_OPT_CHUNK_START | + GIP_OPT_CHUNK; + hdr->chunk_offset = hdr->packet_length; + hdr->packet_length = GIP_PKT_MAX_LENGTH; + + err = gip_send_pkt_simple(client, hdr, data); + if (err) + return err; + + /* allocate output buffer for all chunks */ + err = gip_init_chunk_buffer(client, hdr, &client->chunk_buf_in, false); + if (err) + return err; + + if (data) + memcpy(client->chunk_buf_in->data, data, hdr->chunk_offset); + + return 0; +} + +static int gip_acknowledge_pkt(struct gip_client *client, + struct gip_header *ack) +{ + struct gip_chunk_buffer *buf = client->chunk_buf_out; + struct gip_header hdr = {}; + struct gip_pkt_acknowledge pkt = {}; + u32 len = ack->chunk_offset + ack->packet_length; + + hdr.command = GIP_CMD_ACKNOWLEDGE; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.sequence = ack->sequence; + hdr.packet_length = sizeof(pkt); + + pkt.command = ack->command; + pkt.options = client->id | GIP_OPT_INTERNAL; + pkt.length = cpu_to_le16(len); + + if ((ack->options & GIP_OPT_CHUNK) && buf) + pkt.remaining = cpu_to_le16(buf->length - len); + + // gip_dbg(client, "%s: ACME(host) command=0x%02x, length=0x%04x\n", + // __func__, pkt.command, len); + + return gip_send_pkt(client, &hdr, &pkt); +} + +static int gip_request_identification(struct gip_client *client) +{ + struct gip_header hdr = {}; + + hdr.command = GIP_CMD_IDENTIFY; + hdr.options = client->id | GIP_OPT_INTERNAL; + + return gip_send_pkt(client, &hdr, NULL); +} + +int gip_set_power_mode(struct gip_client *client, enum gip_power_mode mode) +{ + struct gip_header hdr = {}; + struct gip_pkt_power pkt = {}; + + hdr.command = GIP_CMD_POWER; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.packet_length = sizeof(pkt); + + pkt.mode = mode; + + return gip_send_pkt(client, &hdr, &pkt); +} +EXPORT_SYMBOL_GPL(gip_set_power_mode); + +int gip_send_authenticate(struct gip_client *client, void *pkt, u32 len, + bool acknowledge) +{ + struct gip_header hdr = {}; + + hdr.command = GIP_CMD_AUTHENTICATE; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.packet_length = len; + + if (acknowledge) + hdr.options |= GIP_OPT_ACKNOWLEDGE; + + return gip_send_pkt(client, &hdr, pkt); +} + +static int gip_set_audio_format_chat(struct gip_client *client, + enum gip_audio_format in_out) +{ + struct gip_header hdr = {}; + struct gip_pkt_audio_format_chat pkt = {}; + + hdr.command = GIP_CMD_AUDIO_CONTROL; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.packet_length = sizeof(pkt); + + pkt.control.subcommand = GIP_AUD_CTRL_FORMAT_CHAT; + pkt.in_out = in_out; + + return gip_send_pkt(client, &hdr, &pkt); +} + +static int gip_set_audio_format(struct gip_client *client, + enum gip_audio_format in, + enum gip_audio_format out) +{ + struct gip_header hdr = {}; + struct gip_pkt_audio_format pkt = {}; + + hdr.command = GIP_CMD_AUDIO_CONTROL; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.packet_length = sizeof(pkt); + + pkt.control.subcommand = GIP_AUD_CTRL_FORMAT; + pkt.in = in; + pkt.out = out; + + return gip_send_pkt(client, &hdr, &pkt); +} + +int gip_suggest_audio_format(struct gip_client *client, + enum gip_audio_format in, + enum gip_audio_format out, + bool chat) +{ + int err; + + /* special handling for the chat headset */ + if (chat) + err = gip_set_audio_format_chat(client, + GIP_AUD_FORMAT_12KHZ_STEREO); + else + err = gip_set_audio_format(client, in, out); + + if (err) { + gip_err(client, "%s: set format failed: %d\n", __func__, err); + return err; + } + + client->audio_config_in.format = in; + client->audio_config_out.format = out; + + return 0; +} +EXPORT_SYMBOL_GPL(gip_suggest_audio_format); + +int gip_set_audio_volume(struct gip_client *client, u8 in, u8 chat, u8 out) +{ + struct gip_header hdr = {}; + struct gip_pkt_audio_volume pkt = {}; + + hdr.command = GIP_CMD_AUDIO_CONTROL; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.packet_length = sizeof(pkt); + + pkt.control.subcommand = GIP_AUD_CTRL_VOLUME; + pkt.mute = GIP_AUD_VOLUME_UNMUTED; + pkt.out = out; + pkt.chat = chat; + pkt.in = in; + + return gip_send_pkt(client, &hdr, &pkt); +} +EXPORT_SYMBOL_GPL(gip_set_audio_volume); + +int gip_send_rumble(struct gip_client *client, void *pkt, u32 len) +{ + struct gip_header hdr = {}; + + hdr.command = GIP_CMD_RUMBLE; + hdr.options = client->id; + hdr.packet_length = len; + + return gip_send_pkt(client, &hdr, pkt); +} +EXPORT_SYMBOL_GPL(gip_send_rumble); + +int gip_set_led_mode(struct gip_client *client, + enum gip_led_mode mode, u8 brightness) +{ + struct gip_header hdr = {}; + struct gip_pkt_led pkt = {}; + + hdr.command = GIP_CMD_LED; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.packet_length = sizeof(pkt); + + pkt.mode = mode; + pkt.brightness = brightness; + + return gip_send_pkt(client, &hdr, &pkt); +} +EXPORT_SYMBOL_GPL(gip_set_led_mode); + +int gip_send_get_serial_number(struct gip_client *client) +{ + struct gip_header hdr = { + .command = GIP_CMD_EXTENDED, + .options = client->id | GIP_OPT_INTERNAL, + .packet_length = sizeof(struct gip_pkt_get_serial_number) + }; + struct gip_pkt_get_serial_number pkt = { + .command = GIP_EXT_GET_SERIAL_NUMBER + }; + + gip_dbg(client, "%s: sending get serial number packet", __func__); + return gip_send_pkt(client, &hdr, &pkt); +} +EXPORT_SYMBOL_GPL(gip_send_get_serial_number); + +static void gip_copy_audio_samples(struct gip_client *client, + void *samples, void *buf) +{ + struct gip_audio_config *cfg = &client->audio_config_out; + struct gip_header hdr = {}; + void *src, *dest; + int hdr_len, i; + + hdr.command = GIP_CMD_AUDIO_SAMPLES; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.packet_length = cfg->fragment_size; + + hdr_len = gip_get_header_length(&hdr); + + for (i = 0; i < client->adapter->audio_packet_count; i++) { + src = samples + i * cfg->fragment_size; + dest = buf + i * cfg->packet_size; + + /* sequence number is always greater than zero */ + if (!++client->adapter->audio_sequence) + ++client->adapter->audio_sequence; + + hdr.sequence = client->adapter->audio_sequence; + gip_encode_header(&hdr, dest); + memcpy(dest + hdr_len, src, cfg->fragment_size); + } +} + +int gip_send_audio_samples(struct gip_client *client, void *samples) +{ + struct gip_adapter *adap = client->adapter; + struct gip_adapter_buffer buf = {}; + int err; + + buf.type = GIP_BUF_AUDIO; + + /* returns ENOSPC if no buffer is available */ + err = adap->ops->get_buffer(adap, &buf); + if (err) { + gip_err(client, "%s: get buffer failed: %d\n", __func__, err); + return err; + } + + gip_copy_audio_samples(client, samples, buf.data); + + /* set actual length */ + buf.length = client->audio_config_out.packet_size * + adap->audio_packet_count; + + /* always fails on adapter removal */ + err = adap->ops->submit_buffer(adap, &buf); + if (err) + gip_dbg(client, "%s: submit buffer failed: %d\n", + __func__, err); + + return err; +} +EXPORT_SYMBOL_GPL(gip_send_audio_samples); + +bool gip_has_interface(struct gip_client *client, const guid_t *guid) +{ + int i; + + for (i = 0; i < client->interfaces->count; i++) { + if (guid_equal((guid_t *)client->interfaces->data + i, guid)) + return true; + } + + return false; +} +EXPORT_SYMBOL_GPL(gip_has_interface); + +int gip_set_encryption_key(struct gip_client *client, u8 *key, int len) +{ + struct gip_adapter *adap = client->adapter; + int err; + + if (!adap->ops->set_encryption_key) { + gip_dbg(client, "%s: no callback, notifying driver.\n", + __func__); + if (client->drv->ops.authenticated) + return client->drv->ops.authenticated(client); + return 0; + } + + err = adap->ops->set_encryption_key(adap, key, len); + if (err) { + gip_err(client, "%s: set key failed: %d\n", __func__, err); + return err; + } + + if (client->drv->ops.authenticated) + client->drv->ops.authenticated(client); + + return 0; +} + +int gip_enable_audio(struct gip_client *client) +{ + struct gip_adapter *adap = client->adapter; + int err; + + if (!adap->ops->enable_audio) + return 0; + + err = adap->ops->enable_audio(adap); + if (err) + gip_err(client, "%s: enable failed: %d\n", __func__, err); + + return err; +} +EXPORT_SYMBOL_GPL(gip_enable_audio); + +int gip_init_audio_in(struct gip_client *client) +{ + struct gip_adapter *adap = client->adapter; + int err; + + if (!adap->ops->init_audio_in) + return 0; + + err = adap->ops->init_audio_in(adap); + if (err) + gip_err(client, "%s: init failed: %d\n", __func__, err); + + return err; +} +EXPORT_SYMBOL_GPL(gip_init_audio_in); + +int gip_init_audio_out(struct gip_client *client) +{ + struct gip_adapter *adap = client->adapter; + int err; + + if (!adap->ops->init_audio_out) + return 0; + + err = adap->ops->init_audio_out(adap, + client->audio_config_out.packet_size); + if (err) + gip_err(client, "%s: init failed: %d\n", __func__, err); + + return err; +} +EXPORT_SYMBOL_GPL(gip_init_audio_out); + +int gip_init_extra_data(struct gip_client *client) +{ + struct gip_header hdr = { + .command = 0x4d, // ??? + .options = GIP_OPT_ACKNOWLEDGE, // Because 4 + .sequence = 1, + .packet_length = 2, + }; + u8 packet_data[] = { 0x07, 0x00 }; + + return gip_send_pkt(client, &hdr, &packet_data); +} +EXPORT_SYMBOL_GPL(gip_init_extra_data); + +void gip_disable_audio(struct gip_client *client) +{ + struct gip_adapter *adap = client->adapter; + int err; + + if (!adap->ops->disable_audio) + return; + + /* always fails on adapter removal */ + err = adap->ops->disable_audio(adap); + if (err) + gip_dbg(client, "%s: disable failed: %d\n", __func__, err); +} +EXPORT_SYMBOL_GPL(gip_disable_audio); + +static int gip_make_audio_config(struct gip_client *client, + struct gip_audio_config *cfg) +{ + struct gip_header hdr = {}; + + if (cfg->format <= 0 || cfg->format > GIP_AUD_FORMAT_48KHZ_STEREO) { + gip_err(client, "%s: unknown format: 0x%02x\n", __func__, + cfg->format); + return -ENOTSUPP; + } + + /* even-indexed formats are stereo, uneven are mono */ + cfg->channels = 2 - (cfg->format & 1); + cfg->sample_rate = gip_audio_sample_rate[cfg->format]; + + cfg->buffer_size = cfg->sample_rate * cfg->channels * + sizeof(s16) * GIP_AUDIO_INTERVAL / MSEC_PER_SEC; + cfg->fragment_size = cfg->buffer_size / + client->adapter->audio_packet_count; + + /* pseudo header for length calculation */ + hdr.packet_length = cfg->fragment_size; + cfg->packet_size = gip_get_header_length(&hdr) + cfg->fragment_size; + + /* set initial flow rate value */ + cfg->flow_rate = cfg->buffer_size; + + gip_dbg(client, "%s: rate=%d/%d, buffer=%d\n", __func__, + cfg->sample_rate, cfg->channels, cfg->buffer_size); + + return 0; +} + +static struct gip_info_element *gip_parse_info_element(u8 *data, u32 len, + __le16 offset, + int item_length) +{ + struct gip_info_element *elem; + u16 off = le16_to_cpu(offset); + u8 count; + int total; + + if (!off) + return ERR_PTR(-ENOTSUPP); + + if (len < off + sizeof(count)) + return ERR_PTR(-EINVAL); + + count = data[off++]; + if (!count) + return ERR_PTR(-ENOTSUPP); + + total = count * item_length; + if (len < off + total) + return ERR_PTR(-EINVAL); + + elem = kzalloc(struct_size(elem, data, total), GFP_ATOMIC); + if (!elem) + return ERR_PTR(-ENOMEM); + + elem->count = count; + memcpy(elem->data, data + off, total); + + return elem; +} + +static int gip_parse_client_commands(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, u32 len) +{ + struct gip_info_element *cmds; + struct gip_command_descriptor *desc; + int i; + + cmds = gip_parse_info_element(data, len, pkt->client_commands_offset, + sizeof(*desc)); + if (IS_ERR(cmds)) { + if (PTR_ERR(cmds) == -ENOTSUPP) + return 0; + + gip_err(client, "%s: parse failed: %ld\n", + __func__, PTR_ERR(cmds)); + return PTR_ERR(cmds); + } + + for (i = 0; i < cmds->count; i++) { + desc = (struct gip_command_descriptor *)cmds->data + i; + gip_dbg(client, + "%s: command=0x%02x, length=0x%02x, options=0x%02x\n", + __func__, desc->command, desc->length, desc->options); + } + + client->client_commands = cmds; + + return 0; +} + +static int gip_parse_firmware_versions(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, u32 len) +{ + struct gip_info_element *vers; + struct gip_firmware_version *ver; + int i; + + vers = gip_parse_info_element(data, len, pkt->firmware_versions_offset, + sizeof(*ver)); + if (IS_ERR(vers)) { + gip_err(client, "%s: parse failed: %ld\n", + __func__, PTR_ERR(vers)); + return PTR_ERR(vers); + } + + for (i = 0; i < vers->count; i++) { + ver = (struct gip_firmware_version *)vers->data + i; + gip_dbg(client, "%s: version=%u.%u\n", __func__, + le16_to_cpu(ver->major), le16_to_cpu(ver->minor)); + } + + client->firmware_versions = vers; + + return 0; +} + +static int gip_parse_audio_formats(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, u32 len) +{ + struct gip_info_element *fmts; + + fmts = gip_parse_info_element(data, len, + pkt->audio_formats_offset, 2); + if (IS_ERR(fmts)) { + if (PTR_ERR(fmts) == -ENOTSUPP) + return 0; + + gip_err(client, "%s: parse failed: %ld\n", + __func__, PTR_ERR(fmts)); + return PTR_ERR(fmts); + } + + gip_dbg(client, "%s: formats=%*phD\n", __func__, + fmts->count * 2, fmts->data); + client->audio_formats = fmts; + + return 0; +} + +static int gip_parse_capabilities(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, u32 len) +{ + struct gip_info_element *caps; + + caps = gip_parse_info_element(data, len, + pkt->capabilities_out_offset, 1); + if (IS_ERR(caps)) { + gip_err(client, "%s: parse out failed: %ld\n", + __func__, PTR_ERR(caps)); + return PTR_ERR(caps); + } + + gip_dbg(client, "%s: out=%*phD\n", __func__, caps->count, caps->data); + client->capabilities_out = caps; + + caps = gip_parse_info_element(data, len, + pkt->capabilities_in_offset, 1); + if (IS_ERR(caps)) { + gip_err(client, "%s: parse in failed: %ld\n", + __func__, PTR_ERR(caps)); + return PTR_ERR(caps); + } + + gip_dbg(client, "%s: in=%*phD\n", __func__, caps->count, caps->data); + client->capabilities_in = caps; + + return 0; +} + +static int gip_parse_classes(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, u32 len) +{ + struct gip_classes *classes; + u16 off = le16_to_cpu(pkt->classes_offset); + u8 count; + u16 str_len; + char *str; + + if (len < off + sizeof(count)) + return -EINVAL; + + /* number of individual strings */ + count = data[off++]; + if (!count) + return -EINVAL; + + classes = kzalloc(struct_size(classes, strings, count), GFP_ATOMIC); + if (!classes) + return -ENOMEM; + + client->classes = classes; + + while (classes->count < count) { + if (len < off + sizeof(str_len)) + return -EINVAL; + + str_len = le16_to_cpup((__le16 *)(data + off)); + off += sizeof(str_len); + if (!str_len || len < off + str_len) + return -EINVAL; + + /* null-terminated string */ + str = kzalloc(str_len + 1, GFP_ATOMIC); + if (!str) + return -ENOMEM; + + memcpy(str, data + off, str_len); + classes->strings[classes->count] = str; + classes->count++; + off += str_len; + + gip_dbg(client, "%s: class=%s\n", __func__, str); + } + + return 0; +} + +static int gip_parse_interfaces(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, u32 len) +{ + struct gip_info_element *intfs; + guid_t *guid; + int i; + + intfs = gip_parse_info_element(data, len, pkt->interfaces_offset, + sizeof(guid_t)); + if (IS_ERR(intfs)) { + gip_err(client, "%s: parse failed: %ld\n", + __func__, PTR_ERR(intfs)); + return PTR_ERR(intfs); + } + + for (i = 0; i < intfs->count; i++) { + guid = (guid_t *)intfs->data + i; + gip_dbg(client, "%s: guid=%pUb\n", __func__, guid); + } + + client->interfaces = intfs; + + return 0; +} + +static int gip_parse_hid_descriptor(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, u32 len) +{ + struct gip_info_element *desc; + + desc = gip_parse_info_element(data, len, + pkt->hid_descriptor_offset, 1); + if (IS_ERR(desc)) { + if (PTR_ERR(desc) == -ENOTSUPP) + return 0; + + gip_err(client, "%s: parse failed: %ld\n", + __func__, PTR_ERR(desc)); + return PTR_ERR(desc); + } + + gip_dbg(client, "%s: length=0x%02x\n", __func__, desc->count); + client->hid_descriptor = desc; + + return 0; +} + +static int gip_send_remaining_chunks(struct gip_client *client, u32 offset) +{ + struct gip_chunk_buffer *buf = client->chunk_buf_in; + struct gip_header hdr = buf->header; + u32 len = buf->length - offset; + int err; + + gip_dbg(client, "%s: sending chunk 0x%04x/0x%04x/0x%04x\n", __func__, + len, hdr.chunk_offset, buf->length); + + int coalesce_count = GIP_PKT_COALESCE_COUNT; + while (len && coalesce_count) { + /* require acknowledgment for last chunk */ + if (len <= GIP_PKT_MAX_LENGTH) + hdr.options |= GIP_OPT_ACKNOWLEDGE; + + hdr.packet_length = min_t(u32, len, GIP_PKT_MAX_LENGTH); + hdr.chunk_offset = buf->length - len; + + err = gip_send_pkt_simple(client, &hdr, + buf->data + hdr.chunk_offset); + if (err) + return err; + + len -= hdr.packet_length; + coalesce_count--; + } + + return 0; +} + +static int gip_handle_pkt_acknowledge(struct gip_client *client, + void *data, u32 len) +{ + struct gip_pkt_acknowledge *pkt = data; + struct gip_chunk_buffer *buf = client->chunk_buf_in; + struct gip_header hdr; + + if (len != sizeof(*pkt)) + return -EINVAL; + + if (!buf) + return 0; + + gip_dbg(client, "%s: ACME(dev) cmd=0x%02x/0x%02x, len=0x%04x/0x%04x\n", + __func__, pkt->command, buf->header.command, + le16_to_cpu(pkt->length), buf->length); + + /* acknowledgment for different command */ + if (pkt->command != buf->header.command) + return 0; + + /* + * offset comes from the device and may be malicious or invalid + * so sanitize value to prevent buffer overflow. + */ + if (le16_to_cpu(pkt->length) < buf->length) + return gip_send_remaining_chunks(client, le16_to_cpu(pkt->length)); + + gip_dbg(client, "%s: all chunks sent\n", __func__); + + /* empty chunk signals the completion of the transfer */ + hdr = buf->header; + hdr.packet_length = 0; + hdr.chunk_offset = buf->length; + + kfree(buf); + client->chunk_buf_in = NULL; + + return gip_send_pkt_simple(client, &hdr, NULL); +} + +static int gip_handle_pkt_announce(struct gip_client *client, + void *data, u32 len) +{ + struct gip_pkt_announce *pkt = data; + struct gip_hardware *hw = &client->hardware; + + if (len != sizeof(*pkt)) + return -EINVAL; + + if (!hw->vendor && !hw->product && !hw->version) { + hw->vendor = le16_to_cpu(pkt->vendor_id); + hw->product = le16_to_cpu(pkt->product_id); + hw->version = (le16_to_cpu(pkt->fw_version.major) << 8) | + le16_to_cpu(pkt->fw_version.minor); + } + + gip_dbg(client, "%s: address=%pM, vendor=0x%04x, product=0x%04x\n", + __func__, pkt->address, hw->vendor, hw->product); + gip_dbg(client, "%s: firmware=%u.%u.%u.%u, hardware=%u.%u.%u.%u\n", + __func__, + le16_to_cpu(pkt->fw_version.major), + le16_to_cpu(pkt->fw_version.minor), + le16_to_cpu(pkt->fw_version.build), + le16_to_cpu(pkt->fw_version.revision), + le16_to_cpu(pkt->hw_version.major), + le16_to_cpu(pkt->hw_version.minor), + le16_to_cpu(pkt->hw_version.build), + le16_to_cpu(pkt->hw_version.revision)); + + return gip_request_identification(client); +} + +static int gip_handle_pkt_status(struct gip_client *client, + void *data, u32 len) +{ + struct gip_pkt_status *pkt = data; + int err = 0; + u8 batt_type, batt_lvl; + + /* some devices occasionally send larger status packets */ + if (len < sizeof(*pkt)) + return -EINVAL; + + if (!(pkt->status & GIP_STATUS_CONNECTED)) { + gip_dbg(client, "%s: disconnected\n", __func__); + gip_remove_client(client); + return 0; + } + + batt_type = FIELD_GET(GIP_BATT_TYPE, pkt->status); + batt_lvl = FIELD_GET(GIP_BATT_LEVEL, pkt->status); + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.battery) + err = client->drv->ops.battery(client, batt_type, batt_lvl); + + up(&client->drv_lock); + + return err; +} + +static int gip_handle_pkt_extended(struct gip_client *client, void *data, + u32 len) +{ + struct gip_pkt_serial_number *pkt = data; + + if (len > sizeof(*pkt) || len < GIP_PKT_MIN_SERIAL_LENGTH + 2) + return -EINVAL; + + if (pkt->command != GIP_EXT_GET_SERIAL_NUMBER) { + gip_dbg(client, "%s: extended command not Get Serial Number\n", + __func__); + return 0; + } + + if (pkt->status != GIP_EXT_STATUS_OK) { + gip_dbg(client, "%s: extended command status not OK\n", + __func__); + return 1; + } + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + client->serial.len = len - 2; + memcpy(client->serial.data, pkt->serial, client->serial.len); + client->serial.data[client->serial.len] = '\0'; + + up(&client->drv_lock); + + gip_dbg(client, "%s: serial number length: %u\n", __func__, + client->serial.len); + gip_dbg(client, "%s: serial number: %s\n", __func__, + client->serial.data); + return 0; +} + +static int gip_handle_pkt_identify(struct gip_client *client, + void *data, u32 len) +{ + struct gip_pkt_identify *pkt = data; + int err; + + if (len < sizeof(*pkt)) + return -EINVAL; + + if (client->classes) { + gip_warn(client, "%s: already identified\n", __func__); + return 0; + } + + /* skip unknown header */ + data += sizeof(pkt->unknown); + len -= sizeof(pkt->unknown); + + err = gip_parse_client_commands(client, pkt, data, len); + if (err) + goto err_free_info; + + err = gip_parse_firmware_versions(client, pkt, data, len); + if (err) + goto err_free_info; + + err = gip_parse_audio_formats(client, pkt, data, len); + if (err) + goto err_free_info; + + err = gip_parse_capabilities(client, pkt, data, len); + if (err) + goto err_free_info; + + err = gip_parse_classes(client, pkt, data, len); + if (err) + goto err_free_info; + + err = gip_parse_interfaces(client, pkt, data, len); + if (err) + goto err_free_info; + + err = gip_parse_hid_descriptor(client, pkt, data, len); + if (err) + goto err_free_info; + + gip_add_client(client); + + return 0; + +err_free_info: + gip_free_client_info(client); + + return err; +} + +static int gip_handle_pkt_authenticate(struct gip_client *client, + void *data, u32 len) +{ + int err = 0; + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.authenticate) + err = client->drv->ops.authenticate(client, data, len); + + up(&client->drv_lock); + + return err; +} + +static int gip_handle_pkt_virtual_key(struct gip_client *client, + void *data, u32 len) +{ + struct gip_pkt_virtual_key *pkt = data; + int err = 0; + + if (len != sizeof(*pkt)) + return -EINVAL; + + if (pkt->key != GIP_VKEY_LEFT_WIN) + return -EINVAL; + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.guide_button) + err = client->drv->ops.guide_button(client, pkt->down); + + up(&client->drv_lock); + + return err; +} + +static int gip_handle_pkt_audio_format_chat(struct gip_client *client, + void *data, u32 len) +{ + struct gip_pkt_audio_format_chat *pkt = data; + struct gip_audio_config *in = &client->audio_config_in; + struct gip_audio_config *out = &client->audio_config_out; + int err; + + if (len != sizeof(*pkt)) + return -EINVAL; + + /* chat headsets apparently default to 24 kHz */ + if (pkt->in_out != GIP_AUD_FORMAT_12KHZ_STEREO || + in->buffer_size || out->buffer_size) + return -EPROTO; + + err = gip_make_audio_config(client, in); + if (err) + return err; + + err = gip_make_audio_config(client, out); + if (err) + return err; + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.audio_ready) + err = client->drv->ops.audio_ready(client); + + up(&client->drv_lock); + + return err; +} + +static int gip_handle_pkt_audio_volume_chat(struct gip_client *client, + void *data, u32 len) +{ + struct gip_pkt_audio_volume_chat *pkt = data; + int err = 0; + + if (len != sizeof(*pkt)) + return -EINVAL; + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.audio_volume) + err = client->drv->ops.audio_volume(client, pkt->in, pkt->out); + + up(&client->drv_lock); + + return err; +} + +static int gip_handle_pkt_audio_format(struct gip_client *client, + void *data, u32 len) +{ + struct gip_pkt_audio_format *pkt = data; + struct gip_audio_config *in = &client->audio_config_in; + struct gip_audio_config *out = &client->audio_config_out; + int err; + + if (len != sizeof(*pkt)) + return -EINVAL; + + /* format has already been accepted */ + if (in->buffer_size || out->buffer_size) + return -EPROTO; + + /* client rejected format, accept new format */ + if (pkt->in != in->format || pkt->out != out->format) { + gip_warn(client, "%s: rejected: 0x%02x/0x%02x\n", + __func__, in->format, out->format); + return gip_suggest_audio_format(client, pkt->in, pkt->out, + false); + } + + err = gip_make_audio_config(client, in); + if (err) + return err; + + err = gip_make_audio_config(client, out); + if (err) + return err; + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.audio_ready) + err = client->drv->ops.audio_ready(client); + + up(&client->drv_lock); + + return err; +} + +static int gip_handle_pkt_audio_volume(struct gip_client *client, + void *data, u32 len) +{ + struct gip_pkt_audio_volume *pkt = data; + int err = 0; + + if (len != sizeof(*pkt)) + return -EINVAL; + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.audio_volume) + err = client->drv->ops.audio_volume(client, pkt->in, pkt->out); + + up(&client->drv_lock); + + return err; +} + +static int gip_handle_pkt_audio_control(struct gip_client *client, + void *data, u32 len) +{ + struct gip_pkt_audio_control *pkt = data; + + if (len < sizeof(*pkt)) + return -EINVAL; + + switch (pkt->subcommand) { + case GIP_AUD_CTRL_FORMAT_CHAT: + return gip_handle_pkt_audio_format_chat(client, data, len); + case GIP_AUD_CTRL_VOLUME_CHAT: + return gip_handle_pkt_audio_volume_chat(client, data, len); + case GIP_AUD_CTRL_FORMAT: + return gip_handle_pkt_audio_format(client, data, len); + case GIP_AUD_CTRL_VOLUME: + return gip_handle_pkt_audio_volume(client, data, len); + } + + gip_err(client, "%s: unknown subcommand: 0x%02x\n", + __func__, pkt->subcommand); + + return -EPROTO; +} + +static int gip_handle_pkt_hid_report(struct gip_client *client, + void *data, u32 len) +{ + int err = 0; + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.hid_report) + err = client->drv->ops.hid_report(client, data, len); + + up(&client->drv_lock); + + return err; +} + +static int gip_handle_pkt_input(struct gip_client *client, + void *data, u32 len) +{ + int err = 0; + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.input) + err = client->drv->ops.input(client, data, len); + + up(&client->drv_lock); + + return err; +} + +static int gip_handle_pkt_audio_samples(struct gip_client *client, + void *data, u32 len) +{ + struct gip_pkt_audio_samples *pkt = data; + struct gip_audio_config *out = &client->audio_config_out; + int err = 0; + + if (len < sizeof(*pkt)) + return -EINVAL; + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + out->flow_rate = le16_to_cpu(pkt->flow_rate); + if (out->flow_rate != out->buffer_size) + gip_dbg(client, "%s: Unusual flow rate control -> %u", __func__, out->flow_rate); + + if (client->drv && client->drv->ops.audio_samples) + err = client->drv->ops.audio_samples(client, pkt->samples, + len - sizeof(*pkt)); + + up(&client->drv_lock); + + return err; +} + +static int gip_handle_pkt_firmware(struct gip_client *client, void *data, + u32 len) +{ + int err = 0; + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.firmware) + err = client->drv->ops.firmware(client, data, len); + + up(&client->drv_lock); + + return err; +} + +static int gip_dispatch_pkt(struct gip_client *client, + struct gip_header *hdr, void *data, u32 len) +{ + if (hdr->options & GIP_OPT_INTERNAL) { + switch (hdr->command) { + case GIP_CMD_ACKNOWLEDGE: + return gip_handle_pkt_acknowledge(client, data, len); + case GIP_CMD_ANNOUNCE: + return gip_handle_pkt_announce(client, data, len); + case GIP_CMD_STATUS: + return gip_handle_pkt_status(client, data, len); + case GIP_CMD_IDENTIFY: + return gip_handle_pkt_identify(client, data, len); + case GIP_CMD_AUTHENTICATE: + return gip_handle_pkt_authenticate(client, data, len); + case GIP_CMD_VIRTUAL_KEY: + return gip_handle_pkt_virtual_key(client, data, len); + case GIP_CMD_AUDIO_CONTROL: + return gip_handle_pkt_audio_control(client, data, len); + case GIP_CMD_HID_REPORT: + return gip_handle_pkt_hid_report(client, data, len); + case GIP_CMD_AUDIO_SAMPLES: + return gip_handle_pkt_audio_samples(client, data, len); + case GIP_CMD_EXTENDED: + return gip_handle_pkt_extended(client, data, len); + default: + return 0; + } + } + + switch (hdr->command) { + case GIP_CMD_INPUT: + return gip_handle_pkt_input(client, data, len); + case GIP_CMD_FIRMWARE: + return gip_handle_pkt_firmware(client, data, len); + default: + pr_debug("%s: Unknown hdr command: 0x%02x", __func__, + hdr->command); + } + + return 0; +} + +static int gip_process_pkt_chunked(struct gip_client *client, + struct gip_header *hdr, void *data) +{ + struct gip_chunk_buffer *buf = client->chunk_buf_out; + int err; + u32 len; + + gip_dbg(client, "%s: flags=[%s %s %s], offset=0x%04x, length=0x%04x\n", + __func__, + hdr->options & GIP_OPT_CHUNK_START? "Ini":"...", + hdr->options & GIP_OPT_ACKNOWLEDGE? "Ack":"...", + hdr->options & GIP_OPT_INTERNAL? "Sys":"...", + hdr->chunk_offset, hdr->packet_length); + + if (!buf) { + /* older gamepads occasionally send spurious completions */ + if (!hdr->packet_length) + return 0; + + gip_err(client, "%s: buffer not allocated\n", __func__); + return -EPROTO; + } + + if (hdr->command != buf->header.command) { + gip_err(client, "%s: conflicting packet\n", __func__); + return -EALREADY; + } + + len = hdr->chunk_offset + hdr->packet_length; + if (buf->length < len) { + gip_err(client, "%s: buffer too small\n", __func__); + return -EINVAL; + } + + if (hdr->packet_length) { + /* + * acknowledge last non-empty chunked packet even when + * not asked in current chunk header + */ + bool last_chunk = (buf->length == len); + if ((hdr->options & GIP_OPT_ACKNOWLEDGE) || last_chunk) { + err = gip_acknowledge_pkt(client, hdr); + if (err) + return err; + } + memcpy(buf->data + hdr->chunk_offset, data, hdr->packet_length); + return 0; + } + + /* empty chunk signals the completion of the transfer */ + err = gip_dispatch_pkt(client, hdr, buf->data, buf->length); + + kfree(buf); + client->chunk_buf_out = NULL; + + return err; +} + +static int gip_process_pkt(struct gip_client *client, + struct gip_header *hdr, void *data) +{ + int err; + + if (hdr->options & GIP_OPT_CHUNK_START) { + err = gip_init_chunk_buffer(client, hdr, + &client->chunk_buf_out, true); + if (err) + return err; + + hdr->chunk_offset = 0; + } + + if (hdr->options & GIP_OPT_CHUNK) + return gip_process_pkt_chunked(client, hdr, data); + + if (hdr->options & GIP_OPT_ACKNOWLEDGE) { + err = gip_acknowledge_pkt(client, hdr); + if (err) + return err; + } + + return gip_dispatch_pkt(client, hdr, data, hdr->packet_length); +} + +int gip_process_buffer(struct gip_adapter *adap, void *data, int len) +{ + struct gip_header hdr; + struct gip_client *client; + int hdr_len, err; + + while (len > GIP_HDR_MIN_LENGTH) { + hdr_len = gip_decode_header(&hdr, data, len); + if (len < hdr_len + hdr.packet_length) + return -EINVAL; + + client = gip_get_client(adap, hdr.options & GIP_HDR_CLIENT_ID); + if (IS_ERR(client)) + return PTR_ERR(client); + + err = gip_process_pkt(client, &hdr, data + hdr_len); + if (err) + return err; + + data += hdr_len + hdr.packet_length; + len -= hdr_len + hdr.packet_length; + } + + return 0; +} +EXPORT_SYMBOL_GPL(gip_process_buffer); diff --git a/drivers/custom/xonedo/bus/protocol.h b/drivers/custom/xonedo/bus/protocol.h new file mode 100644 index 000000000000..805a7e47a034 --- /dev/null +++ b/drivers/custom/xonedo/bus/protocol.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 Severin von Wnuck-Lipinski + */ + +#pragma once + +#include +#include + +#define GIP_VID_MICROSOFT 0x045e + +/* time between audio packets in ms */ +#define GIP_AUDIO_INTERVAL 8 + +#define GIP_PKT_MIN_SERIAL_LENGTH 12 +#define GIP_PKT_MAX_SERIAL_LENGTH 32 + +enum gip_battery_type { + GIP_BATT_TYPE_NONE = 0x00, + GIP_BATT_TYPE_STANDARD = 0x01, + GIP_BATT_TYPE_KIT = 0x02, +}; + +enum gip_battery_level { + GIP_BATT_LEVEL_LOW = 0x00, + GIP_BATT_LEVEL_NORMAL = 0x01, + GIP_BATT_LEVEL_HIGH = 0x02, + GIP_BATT_LEVEL_FULL = 0x03, +}; + +enum gip_power_mode { + GIP_PWR_ON = 0x00, + GIP_PWR_SLEEP = 0x01, + GIP_PWR_OFF = 0x04, + GIP_PWR_RESET = 0x07, +}; + +enum gip_audio_format { + GIP_AUD_FORMAT_NONE, + GIP_AUD_FORMAT_8KHZ_MONO, + GIP_AUD_FORMAT_8KHZ_STEREO, + GIP_AUD_FORMAT_12KHZ_MONO, + GIP_AUD_FORMAT_12KHZ_STEREO, + GIP_AUD_FORMAT_16KHZ_MONO, + GIP_AUD_FORMAT_16KHZ_STEREO, + GIP_AUD_FORMAT_20KHZ_MONO, + GIP_AUD_FORMAT_20KHZ_STEREO, + GIP_AUD_FORMAT_24KHZ_MONO, + GIP_AUD_FORMAT_24KHZ_STEREO, + GIP_AUD_FORMAT_32KHZ_MONO, + GIP_AUD_FORMAT_32KHZ_STEREO, + GIP_AUD_FORMAT_40KHZ_MONO, + GIP_AUD_FORMAT_40KHZ_STEREO, + GIP_AUD_FORMAT_48KHZ_MONO, + GIP_AUD_FORMAT_48KHZ_STEREO, +}; + +enum gip_led_mode { + GIP_LED_OFF = 0x00, + GIP_LED_ON = 0x01, + GIP_LED_BLINK_FAST = 0x02, + GIP_LED_BLINK_NORMAL = 0x03, + GIP_LED_BLINK_SLOW = 0x04, + GIP_LED_FADE_SLOW = 0x08, + GIP_LED_FADE_FAST = 0x09, +}; + +struct gip_header { + u8 command; + u8 options; + u8 sequence; + u32 packet_length; + u32 chunk_offset; +}; + +struct gip_chunk_buffer { + struct gip_header header; + u32 length; + u8 data[]; +}; + +struct gip_hardware { + u16 vendor; + u16 product; + u16 version; +}; + +struct gip_info_element { + u8 count; + u8 data[]; +}; + +struct gip_serial_number { + u8 len; + char data[GIP_PKT_MAX_SERIAL_LENGTH + 1]; +}; + +struct gip_audio_config { + enum gip_audio_format format; + + int channels; + int sample_rate; + + int buffer_size; + int fragment_size; + int packet_size; + + /* only used for rendered audio (out) */ + u16 flow_rate; +}; + +struct gip_classes { + u8 count; + const char *strings[]; +}; + +struct gip_client; +struct gip_adapter; + +int gip_set_power_mode(struct gip_client *client, enum gip_power_mode mode); +int gip_send_authenticate(struct gip_client *client, void *pkt, u32 len, + bool acknowledge); +int gip_suggest_audio_format(struct gip_client *client, + enum gip_audio_format in, + enum gip_audio_format out, + bool chat); +int gip_set_audio_volume(struct gip_client *client, u8 in, u8 chat, u8 out); +int gip_send_rumble(struct gip_client *client, void *pkt, u32 len); +int gip_set_led_mode(struct gip_client *client, + enum gip_led_mode mode, u8 brightness); +int gip_send_audio_samples(struct gip_client *client, void *samples); +int gip_init_extra_data(struct gip_client *client); +int gip_send_get_serial_number(struct gip_client *client); + +bool gip_has_interface(struct gip_client *client, const guid_t *guid); +int gip_set_encryption_key(struct gip_client *client, u8 *key, int len); +int gip_enable_audio(struct gip_client *client); +int gip_init_audio_in(struct gip_client *client); +int gip_init_audio_out(struct gip_client *client); +void gip_disable_audio(struct gip_client *client); + +int gip_process_buffer(struct gip_adapter *adap, void *data, int len); diff --git a/drivers/custom/xonedo/driver/chatpad.c b/drivers/custom/xonedo/driver/chatpad.c new file mode 100644 index 000000000000..9a7e72f4148b --- /dev/null +++ b/drivers/custom/xonedo/driver/chatpad.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck-Lipinski + */ + +#include +#include + +#include "common.h" + +#define GIP_CP_NAME "Microsoft Xbox Chatpad" + +struct gip_chatpad { + struct gip_client *client; + struct gip_input input; + + struct hid_device *hid_dev; +}; + +static int gip_chatpad_hid_start(struct hid_device *dev) +{ + return 0; +} + +static void gip_chatpad_hid_stop(struct hid_device *dev) +{ +} + +static int gip_chatpad_hid_open(struct hid_device *dev) +{ + return 0; +} + +static void gip_chatpad_hid_close(struct hid_device *dev) +{ +} + +static int gip_chatpad_hid_parse(struct hid_device *dev) +{ + struct gip_chatpad *chatpad = dev->driver_data; + struct gip_client *client = chatpad->client; + struct gip_info_element *desc_info = client->hid_descriptor; + struct hid_descriptor *desc = (struct hid_descriptor *)desc_info->data; + + if (desc->bLength < sizeof(*desc) || desc->bNumDescriptors != 1) { + dev_err(&client->dev, "%s: invalid descriptor\n", __func__); + return -EINVAL; + } + + dev->version = le16_to_cpu(desc->bcdHID); + dev->country = desc->bCountryCode; + + return hid_parse_report(dev, desc_info->data + sizeof(*desc), + desc_info->count - sizeof(*desc)); +} + +static int gip_chatpad_hid_raw_request(struct hid_device *dev, + unsigned char report_num, __u8 *buf, + size_t len, unsigned char report_type, + int request_type) +{ + return 0; +} + +static struct hid_ll_driver gip_chatpad_hid_driver = { + .start = gip_chatpad_hid_start, + .stop = gip_chatpad_hid_stop, + .open = gip_chatpad_hid_open, + .close = gip_chatpad_hid_close, + .parse = gip_chatpad_hid_parse, + .raw_request = gip_chatpad_hid_raw_request, +}; + +static int gip_chatpad_init_input(struct gip_chatpad *chatpad) +{ + int err; + + input_set_capability(chatpad->input.dev, EV_KEY, BTN_MODE); + + err = input_register_device(chatpad->input.dev); + if (err) + dev_err(&chatpad->client->dev, "%s: register failed: %d\n", + __func__, err); + + return err; +} + +static int gip_chatpad_init_hid(struct gip_chatpad *chatpad) +{ + struct gip_client *client = chatpad->client; + struct hid_device *dev; + int err; + + dev = hid_allocate_device(); + if (IS_ERR(dev)) { + dev_err(&client->dev, "%s: allocate failed: %ld\n", + __func__, PTR_ERR(dev)); + return PTR_ERR(dev); + } + + dev->bus = BUS_USB; + dev->vendor = client->hardware.vendor; + dev->product = client->hardware.product; + dev->version = client->hardware.version; + dev->dev.parent = &client->dev; + dev->ll_driver = &gip_chatpad_hid_driver; + + strscpy(dev->name, GIP_CP_NAME, sizeof(dev->name)); + snprintf(dev->phys, sizeof(dev->phys), "%s/input1", + dev_name(&client->dev)); + + dev->driver_data = chatpad; + + err = hid_add_device(dev); + if (err) { + dev_err(&client->dev, "%s: add failed: %d\n", __func__, err); + hid_destroy_device(dev); + return err; + } + + chatpad->hid_dev = dev; + + return 0; +} + +static int gip_chatpad_op_guide_button(struct gip_client *client, bool down) +{ + struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev); + + input_report_key(chatpad->input.dev, BTN_MODE, down); + input_sync(chatpad->input.dev); + + return 0; +} + +static int gip_chatpad_op_hid_report(struct gip_client *client, + void *data, u32 len) +{ + struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev); + + return hid_input_report(chatpad->hid_dev, HID_INPUT_REPORT, + data, len, true); +} + +static int gip_chatpad_probe(struct gip_client *client) +{ + struct gip_chatpad *chatpad; + struct gip_info_element *hid_desc = client->hid_descriptor; + int err; + + if (!hid_desc || hid_desc->count < sizeof(struct hid_descriptor)) + return -ENODEV; + + chatpad = devm_kzalloc(&client->dev, sizeof(*chatpad), GFP_KERNEL); + if (!chatpad) + return -ENOMEM; + + chatpad->client = client; + + err = gip_set_power_mode(client, GIP_PWR_ON); + if (err) + return err; + + err = gip_init_input(&chatpad->input, client, GIP_CP_NAME); + if (err) + return err; + + err = gip_chatpad_init_input(chatpad); + if (err) + return err; + + err = gip_chatpad_init_hid(chatpad); + if (err) + return err; + + dev_set_drvdata(&client->dev, chatpad); + + return 0; +} + +static void gip_chatpad_remove(struct gip_client *client) +{ + struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev); + + hid_destroy_device(chatpad->hid_dev); +} + +static struct gip_driver gip_chatpad_driver = { + .name = "xone-gip-chatpad", + .class = "Windows.Xbox.Input.Chatpad", + .ops = { + .guide_button = gip_chatpad_op_guide_button, + .hid_report = gip_chatpad_op_hid_report, + }, + .probe = gip_chatpad_probe, + .remove = gip_chatpad_remove, +}; +module_gip_driver(gip_chatpad_driver); + +MODULE_ALIAS("gip:Windows.Xbox.Input.Chatpad"); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); +MODULE_DESCRIPTION("xone GIP chatpad driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/custom/xonedo/driver/common.c b/drivers/custom/xonedo/driver/common.c new file mode 100644 index 000000000000..ab3e6c01c361 --- /dev/null +++ b/drivers/custom/xonedo/driver/common.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck-Lipinski + */ + +#include + +#include "common.h" + +#define GIP_LED_BRIGHTNESS_DEFAULT 20 +#define GIP_LED_BRIGHTNESS_MAX 50 + +static enum power_supply_property gip_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +static int gip_get_battery_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct gip_battery *batt = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = batt->status; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = batt->capacity; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = batt->name; + break; + default: + return -EINVAL; + } + + return 0; +} + +int gip_init_battery(struct gip_battery *batt, struct gip_client *client, + const char *name) +{ + struct power_supply_config cfg = {}; + + batt->name = name; + batt->status = POWER_SUPPLY_STATUS_UNKNOWN; + batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + + batt->desc.name = dev_name(&client->dev); + batt->desc.type = POWER_SUPPLY_TYPE_BATTERY; + batt->desc.properties = gip_battery_props; + batt->desc.num_properties = ARRAY_SIZE(gip_battery_props); + batt->desc.get_property = gip_get_battery_prop; + + cfg.drv_data = batt; + + batt->supply = devm_power_supply_register(&client->dev, &batt->desc, + &cfg); + if (IS_ERR(batt->supply)) { + dev_err(&client->dev, "%s: register failed: %ld\n", + __func__, PTR_ERR(batt->supply)); + return PTR_ERR(batt->supply); + } + + power_supply_powers(batt->supply, &client->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(gip_init_battery); + +void gip_report_battery(struct gip_battery *batt, + enum gip_battery_type type, + enum gip_battery_level level) +{ + if (type == GIP_BATT_TYPE_NONE) + batt->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + batt->status = POWER_SUPPLY_STATUS_DISCHARGING; + + if (type == GIP_BATT_TYPE_NONE) + batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + else if (level == GIP_BATT_LEVEL_LOW) + batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (level == GIP_BATT_LEVEL_NORMAL) + batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (level == GIP_BATT_LEVEL_HIGH) + batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + else if (level == GIP_BATT_LEVEL_FULL) + batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + + if (batt->supply) + power_supply_changed(batt->supply); +} +EXPORT_SYMBOL_GPL(gip_report_battery); + +static void gip_led_brightness_set(struct led_classdev *dev, + enum led_brightness brightness) +{ + struct gip_led *led = container_of(dev, typeof(*led), dev); + int err; + + if (dev->flags & LED_UNREGISTERING) + return; + + dev_dbg(&led->client->dev, "%s: brightness=%d\n", __func__, brightness); + + err = gip_set_led_mode(led->client, led->mode, brightness); + if (err) + dev_err(&led->client->dev, "%s: set LED mode failed: %d\n", + __func__, err); +} + +static ssize_t gip_led_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct gip_led *led = container_of(cdev, typeof(*led), dev); + + return sysfs_emit(buf, "%u\n", led->mode); +} + +static ssize_t gip_led_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct gip_led *led = container_of(cdev, typeof(*led), dev); + u8 mode; + int err; + + err = kstrtou8(buf, 10, &mode); + if (err) + return err; + + dev_dbg(&led->client->dev, "%s: mode=%u\n", __func__, mode); + led->mode = mode; + + err = gip_set_led_mode(led->client, mode, cdev->brightness); + if (err) { + dev_err(&led->client->dev, "%s: set LED mode failed: %d\n", + __func__, err); + return err; + } + + return count; +} + +static struct device_attribute gip_led_attr_mode = + __ATTR(mode, 0644, gip_led_mode_show, gip_led_mode_store); + +static struct attribute *gip_led_attrs[] = { + &gip_led_attr_mode.attr, + NULL, +}; +ATTRIBUTE_GROUPS(gip_led); + +int gip_init_led(struct gip_led *led, struct gip_client *client) +{ + int err; + + /* set default brightness */ + err = gip_set_led_mode(client, GIP_LED_ON, GIP_LED_BRIGHTNESS_DEFAULT); + if (err) { + dev_err(&client->dev, "%s: set brightness failed: %d\n", + __func__, err); + return err; + } + + led->dev.name = devm_kasprintf(&client->dev, GFP_KERNEL, + "%s:white:status", + dev_name(&client->dev)); + if (!led->dev.name) + return -ENOMEM; + + led->dev.brightness = GIP_LED_BRIGHTNESS_DEFAULT; + led->dev.max_brightness = GIP_LED_BRIGHTNESS_MAX; + led->dev.brightness_set = gip_led_brightness_set; + led->dev.groups = gip_led_groups; + + led->client = client; + led->mode = GIP_LED_ON; + + err = devm_led_classdev_register(&client->dev, &led->dev); + if (err) + dev_err(&client->dev, "%s: register failed: %d\n", + __func__, err); + + return err; +} +EXPORT_SYMBOL_GPL(gip_init_led); + +int gip_init_input(struct gip_input *input, struct gip_client *client, + const char *name) +{ + input->dev = devm_input_allocate_device(&client->dev); + if (!input->dev) + return -ENOMEM; + + input->dev->phys = devm_kasprintf(&client->dev, GFP_KERNEL, + "%s/input0", dev_name(&client->dev)); + if (!input->dev->phys) + return -ENOMEM; + + gip_send_get_serial_number(client); + + input->dev->name = name; + input->dev->id.bustype = BUS_VIRTUAL; + input->dev->id.vendor = client->hardware.vendor; + input->dev->id.product = client->hardware.product; + input->dev->id.version = client->hardware.version; + input->dev->dev.parent = &client->dev; + input->dev->uniq = client->serial.data; + + return 0; +} +EXPORT_SYMBOL_GPL(gip_init_input); diff --git a/drivers/custom/xonedo/driver/common.h b/drivers/custom/xonedo/driver/common.h new file mode 100644 index 000000000000..b97b4571a58c --- /dev/null +++ b/drivers/custom/xonedo/driver/common.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 Severin von Wnuck-Lipinski + */ + +#pragma once + +#include +#include +#include + +#include "../bus/bus.h" + +struct gip_battery { + struct power_supply *supply; + struct power_supply_desc desc; + + const char *name; + int status; + int capacity; +}; + +struct gip_led { + struct led_classdev dev; + + struct gip_client *client; + enum gip_led_mode mode; +}; + +struct gip_input { + struct input_dev *dev; +}; + +struct gip_vidpid { + u16 vendor; + u16 product; +}; + +int gip_init_battery(struct gip_battery *batt, struct gip_client *client, + const char *name); +void gip_report_battery(struct gip_battery *batt, + enum gip_battery_type type, + enum gip_battery_level level); + +int gip_init_led(struct gip_led *led, struct gip_client *client); + +int gip_init_input(struct gip_input *input, struct gip_client *client, + const char *name); diff --git a/drivers/custom/xonedo/driver/gamepad.c b/drivers/custom/xonedo/driver/gamepad.c new file mode 100644 index 000000000000..dec6856f654d --- /dev/null +++ b/drivers/custom/xonedo/driver/gamepad.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck-Lipinski + */ + +#include +#include +#include +#include + +#include "common.h" +#include "../auth/auth.h" + +#define GIP_GP_NAME "Microsoft Xbox Controller" + +#define GIP_VENDOR_MICROSOFT 0x045e +#define GIP_PRODUCT_ELITE_SERIES_2 0x0b00 +#define GIP_PRODUCT_ELITE 0x02e3 + +/* + * Various versions of the Elite Series 2 firmware have changed the way paddle + * states are sent. Paddle support is only reported up to this firmware + * version. + */ +#define GIP_ELITE_SERIES_2_4X_FIRMWARE 0x04FF +#define GIP_ELITE_SERIES_2_510_FIRMWARE 0x050A + +#define GIP_GP_RUMBLE_DELAY msecs_to_jiffies(10) +#define GIP_GP_RUMBLE_MAX 100 + +/* button offset from end of packet */ +#define GIP_GP_BTN_SHARE_OFFSET 18 + +/* fallback for kernels < 6.17 */ +#ifndef BTN_GRIPR +#define BTN_GRIPR BTN_TRIGGER_HAPPY5 +#define BTN_GRIPR2 BTN_TRIGGER_HAPPY6 +#define BTN_GRIPL BTN_TRIGGER_HAPPY7 +#define BTN_GRIPL2 BTN_TRIGGER_HAPPY8 +#endif + +static const guid_t gip_gamepad_guid_share = + GUID_INIT(0xecddd2fe, 0xd387, 0x4294, + 0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d); + +static const guid_t gip_gamepad_guid_dli = + GUID_INIT(0x87f2e56b, 0xc3bb, 0x49b1, + 0x82, 0x65, 0xff, 0xff, 0xf3, 0x77, 0x99, 0xee); + +enum gip_gamepad_button { + GIP_GP_BTN_MENU = BIT(2), + GIP_GP_BTN_VIEW = BIT(3), + GIP_GP_BTN_A = BIT(4), + GIP_GP_BTN_B = BIT(5), + GIP_GP_BTN_X = BIT(6), + GIP_GP_BTN_Y = BIT(7), + GIP_GP_BTN_DPAD_U = BIT(8), + GIP_GP_BTN_DPAD_D = BIT(9), + GIP_GP_BTN_DPAD_L = BIT(10), + GIP_GP_BTN_DPAD_R = BIT(11), + GIP_GP_BTN_BUMPER_L = BIT(12), + GIP_GP_BTN_BUMPER_R = BIT(13), + GIP_GP_BTN_STICK_L = BIT(14), + GIP_GP_BTN_STICK_R = BIT(15), +}; + +enum gip_gamepad_paddle { + GIP_GP_BTN_P1 = BIT(0), + GIP_GP_BTN_P2 = BIT(1), + GIP_GP_BTN_P3 = BIT(2), + GIP_GP_BTN_P4 = BIT(3), +}; + +enum gip_gamepad_motor { + GIP_GP_MOTOR_R = BIT(0), + GIP_GP_MOTOR_L = BIT(1), + GIP_GP_MOTOR_RT = BIT(2), + GIP_GP_MOTOR_LT = BIT(3), +}; + +/* + * Remember, xpad keeps the 4 bytes. + * Paddles are at [18] in xpad, so, [14] here. + * Pad 14 bytes. + */ +struct gip_gamepad_pkt_firmware { + u8 unknown[14]; + u8 paddles; + u8 profile; +} __packed; + +struct gip_gamepad_pkt_input { + __le16 buttons; + __le16 trigger_left; + __le16 trigger_right; + __le16 stick_left_x; + __le16 stick_left_y; + __le16 stick_right_x; + __le16 stick_right_y; +} __packed; + +struct gip_gamepad_pkt_dli { + u32 counter_us1; + u32 counter_us2; +} __packed; + +struct gip_gamepad_pkt_rumble { + u8 unknown; + u8 motors; + u8 left_trigger; + u8 right_trigger; + u8 left; + u8 right; + u8 duration; + u8 delay; + u8 repeat; +} __packed; + +typedef enum PaddleCapability { + PADDLE_NONE, + PADDLE_ELITE, + PADDLE_ELITE2_4X, // Still in the same packet + PADDLE_ELITE2_510, // Same packet, different location + PADDLE_ELITE2_511, // Different packet entirely. +} PaddleCapability; + +struct gip_gamepad_rumble { + /* serializes access to rumble packet */ + spinlock_t lock; + unsigned long last; + struct timer_list timer; + struct gip_gamepad_pkt_rumble pkt; + + struct gip_gamepad *parent; +}; + +struct gip_gamepad { + struct gip_client *client; + struct gip_battery battery; + struct gip_auth auth; + struct gip_led led; + struct gip_input input; + + bool supports_share; + bool supports_dli; + PaddleCapability paddle_support; + + struct gip_gamepad_rumble rumble; +}; + +static void gip_gamepad_send_rumble(struct timer_list *timer) +{ + // from_timer() has been renamed to timer_container_of() in linux 6.16 + struct gip_gamepad_rumble *rumble = +#if LINUX_VERSION_CODE < KERNEL_VERSION(6,16,0) + from_timer(rumble, timer, timer); +#else + timer_container_of(rumble, timer, timer); +#endif + + struct gip_gamepad *gamepad = rumble->parent; + unsigned long flags; + + spin_lock_irqsave(&rumble->lock, flags); + + gip_send_rumble(gamepad->client, &rumble->pkt, sizeof(rumble->pkt)); + rumble->last = jiffies; + + spin_unlock_irqrestore(&rumble->lock, flags); +} + +static int gip_gamepad_queue_rumble(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct gip_gamepad_rumble *rumble = input_get_drvdata(dev); + u16 mag_left = effect->u.rumble.strong_magnitude; + u16 mag_right = effect->u.rumble.weak_magnitude; + unsigned long flags; + + if (effect->type != FF_RUMBLE) + return 0; + + spin_lock_irqsave(&rumble->lock, flags); + + rumble->pkt.left = mag_left * GIP_GP_RUMBLE_MAX / U16_MAX; + rumble->pkt.right = mag_right * GIP_GP_RUMBLE_MAX / U16_MAX; + + /* delay rumble to work around firmware bug */ + if (!timer_pending(&rumble->timer)) + mod_timer(&rumble->timer, rumble->last + GIP_GP_RUMBLE_DELAY); + + spin_unlock_irqrestore(&rumble->lock, flags); + + return 0; +} + +static int gip_gamepad_init_rumble(struct gip_gamepad *gamepad) +{ + struct gip_gamepad_rumble *rumble = &gamepad->rumble; + struct input_dev *dev = gamepad->input.dev; + + spin_lock_init(&rumble->lock); + timer_setup(&rumble->timer, gip_gamepad_send_rumble, 0); + + /* stop rumble (required for some exotic gamepads to start input) */ + rumble->pkt.motors = GIP_GP_MOTOR_R | GIP_GP_MOTOR_L | + GIP_GP_MOTOR_RT | GIP_GP_MOTOR_LT; + rumble->pkt.duration = 0xff; + rumble->pkt.repeat = 0xeb; + rumble->parent = gamepad; + gip_gamepad_send_rumble(&rumble->timer); + + input_set_capability(dev, EV_FF, FF_RUMBLE); + input_set_drvdata(dev, rumble); + + return input_ff_create_memless(dev, NULL, gip_gamepad_queue_rumble); +} + +static int gip_gamepad_init_extra_data(struct gip_gamepad *gamepad) +{ + return gip_init_extra_data(gamepad->client); +} + +static void gip_gamepad_query_paddles(struct gip_gamepad *gamepad) +{ + struct gip_hardware hardware = gamepad->client->hardware; + + gamepad->paddle_support = PADDLE_NONE; + + if (hardware.vendor != GIP_VENDOR_MICROSOFT) + return; + + if (hardware.product == GIP_PRODUCT_ELITE) { + pr_debug("%s: Elite Series 1\n", __func__); + gamepad->paddle_support = PADDLE_ELITE; + return; + } + + if (hardware.product != GIP_PRODUCT_ELITE_SERIES_2) { + pr_debug("%s: MS controller, no paddle support", __func__); + return; + } + + pr_debug("%s: Elite Series 2\n", __func__); + if (hardware.version <= GIP_ELITE_SERIES_2_4X_FIRMWARE) + gamepad->paddle_support = PADDLE_ELITE2_4X; + + else if (hardware.version <= GIP_ELITE_SERIES_2_510_FIRMWARE) + gamepad->paddle_support = PADDLE_ELITE2_510; + + // If new revisions come, this should become LTE new max + else if (hardware.version > GIP_ELITE_SERIES_2_510_FIRMWARE) { + pr_debug("%s: FW > 5.10\n", __func__); + gamepad->paddle_support = PADDLE_ELITE2_511; + } +} + +static int gip_gamepad_init_input(struct gip_gamepad *gamepad) +{ + struct input_dev *dev = gamepad->input.dev; + int err; + + gamepad->supports_share = gip_has_interface(gamepad->client, + &gip_gamepad_guid_share); + gamepad->supports_dli = gip_has_interface(gamepad->client, + &gip_gamepad_guid_dli); + + if (gamepad->supports_share) + input_set_capability(dev, EV_KEY, KEY_RECORD); + + if (gamepad->paddle_support) { + pr_debug("%s: Paddle support detected", __func__); + input_set_capability(dev, EV_KEY, BTN_GRIPR); + input_set_capability(dev, EV_KEY, BTN_GRIPR2); + input_set_capability(dev, EV_KEY, BTN_GRIPL); + input_set_capability(dev, EV_KEY, BTN_GRIPL2); + } + + input_set_capability(dev, EV_KEY, BTN_MODE); + input_set_capability(dev, EV_KEY, BTN_START); + input_set_capability(dev, EV_KEY, BTN_SELECT); + input_set_capability(dev, EV_KEY, BTN_A); + input_set_capability(dev, EV_KEY, BTN_B); + input_set_capability(dev, EV_KEY, BTN_X); + input_set_capability(dev, EV_KEY, BTN_Y); + input_set_capability(dev, EV_KEY, BTN_TL); + input_set_capability(dev, EV_KEY, BTN_TR); + input_set_capability(dev, EV_KEY, BTN_THUMBL); + input_set_capability(dev, EV_KEY, BTN_THUMBR); + input_set_abs_params(dev, ABS_X, -32768, 32767, 16, 128); + input_set_abs_params(dev, ABS_RX, -32768, 32767, 16, 128); + input_set_abs_params(dev, ABS_Y, -32768, 32767, 16, 128); + input_set_abs_params(dev, ABS_RY, -32768, 32767, 16, 128); + input_set_abs_params(dev, ABS_Z, 0, 1023, 0, 0); + input_set_abs_params(dev, ABS_RZ, 0, 1023, 0, 0); + input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0); + + err = gip_gamepad_init_rumble(gamepad); + if (err) { + dev_err(&gamepad->client->dev, "%s: init rumble failed: %d\n", + __func__, err); + goto err_delete_timer; + } + + err = input_register_device(dev); + if (err) { + dev_err(&gamepad->client->dev, "%s: register failed: %d\n", + __func__, err); + goto err_delete_timer; + } + + return 0; + +err_delete_timer: +#if LINUX_VERSION_CODE < KERNEL_VERSION(6,15,0) + del_timer_sync(&gamepad->rumble.timer); +#else + timer_delete_sync(&gamepad->rumble.timer); +#endif + return err; +} + +static int gip_gamepad_op_battery(struct gip_client *client, + enum gip_battery_type type, + enum gip_battery_level level) +{ + struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); + + gip_report_battery(&gamepad->battery, type, level); + + return 0; +} + +static int gip_gamepad_op_authenticate(struct gip_client *client, + void *data, u32 len) +{ + struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); + + return gip_auth_process_pkt(&gamepad->auth, data, len); +} + +static int gip_gamepad_op_guide_button(struct gip_client *client, bool down) +{ + struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); + + input_report_key(gamepad->input.dev, BTN_MODE, down); + input_sync(gamepad->input.dev); + + return 0; +} + +static int gip_gamepad_op_authenticated(struct gip_client *client) +{ + return 0; +} + +static int gip_gamepad_op_firmware(struct gip_client *client, void *data, + u32 len) +{ + // First, ensure the data is of the correct size. + struct gip_gamepad_pkt_firmware *pkt = data; + if (len < sizeof(*pkt)) + return -EINVAL; + + // Grab our controller + struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); + struct input_dev *dev = gamepad->input.dev; + + input_report_key(dev, BTN_GRIPR, pkt->paddles & GIP_GP_BTN_P1); + input_report_key(dev, BTN_GRIPR2, pkt->paddles & GIP_GP_BTN_P2); + input_report_key(dev, BTN_GRIPL, pkt->paddles & GIP_GP_BTN_P3); + input_report_key(dev, BTN_GRIPL2, pkt->paddles & GIP_GP_BTN_P4); + + input_sync(dev); + return 0; +} + +static int gip_gamepad_op_input(struct gip_client *client, void *data, u32 len) +{ + struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); + struct gip_gamepad_pkt_input *pkt = data; + struct input_dev *dev = gamepad->input.dev; + u16 buttons; + u8 share_offset = GIP_GP_BTN_SHARE_OFFSET; + + if (len < sizeof(*pkt)) + return -EINVAL; + + buttons = le16_to_cpu(pkt->buttons); + + /* share button byte is always at fixed offset from end of packet */ + if (gamepad->supports_share) { + if (gamepad->supports_dli) + share_offset += sizeof(struct gip_gamepad_pkt_dli); + + if (len < share_offset) + return -EINVAL; + + input_report_key(dev, KEY_RECORD, + ((u8 *)data)[len - share_offset]); + } + + input_report_key(dev, BTN_START, buttons & GIP_GP_BTN_MENU); + input_report_key(dev, BTN_SELECT, buttons & GIP_GP_BTN_VIEW); + input_report_key(dev, BTN_A, buttons & GIP_GP_BTN_A); + input_report_key(dev, BTN_B, buttons & GIP_GP_BTN_B); + input_report_key(dev, BTN_X, buttons & GIP_GP_BTN_X); + input_report_key(dev, BTN_Y, buttons & GIP_GP_BTN_Y); + input_report_key(dev, BTN_TL, buttons & GIP_GP_BTN_BUMPER_L); + input_report_key(dev, BTN_TR, buttons & GIP_GP_BTN_BUMPER_R); + input_report_key(dev, BTN_THUMBL, buttons & GIP_GP_BTN_STICK_L); + input_report_key(dev, BTN_THUMBR, buttons & GIP_GP_BTN_STICK_R); + + /* + * For anyone comparing to xpad's paddle handling source, xone strips + * four bytes of header off of the beginning that xpad doesn't, so all + * offsets are 4 less later revisions put paddle support in the firmware + * packet, check gip_gamepad_op_WTFEVER + * + * For 5.10 and below, the paddle data is in various locations within + * the main input packet, for 5.11 and above the data is stored in a + * separate packet and handeled by gip_gamepad_op_firmware(). + */ + + int report_paddles = 0, series_1 = 0; + u8 paddles; + + // Assume the controller might not send profile data, check length + if (gamepad->paddle_support == PADDLE_ELITE2_510 && len > 18) { + /* + * On the Elite Series 2 with newer-ISH firmware (<=5.10) + * paddles are stored at byte 18 (22) + */ + paddles = ((u8 *)data)[18]; + report_paddles = 1; + + } else if (gamepad->paddle_support == PADDLE_ELITE2_4X && len > 14) { + /* + * On the Elite Series 2 with older firmware (<5.0) + * paddles are stored at byte 14 (18) + */ + paddles = ((u8 *)data)[14]; + report_paddles = 1; + + } else if (gamepad->paddle_support == PADDLE_ELITE && len > 28) { + // On the original Elite, paddles are stored at byte 28 + paddles = ((u8 *)data)[28]; + report_paddles = 1; + series_1 = 1; + } + + // Series 1 reports paddles as different buttons than newer ones + if (report_paddles) { + input_report_key(dev, BTN_GRIPR, paddles & + (series_1 ? GIP_GP_BTN_P2 : GIP_GP_BTN_P1)); + input_report_key(dev, BTN_GRIPR2, paddles & + (series_1 ? GIP_GP_BTN_P4 : GIP_GP_BTN_P2)); + input_report_key(dev, BTN_GRIPL, paddles & + (series_1 ? GIP_GP_BTN_P1 : GIP_GP_BTN_P3)); + input_report_key(dev, BTN_GRIPL2, paddles & + (series_1 ? GIP_GP_BTN_P3 : GIP_GP_BTN_P4)); + } + + input_report_abs(dev, ABS_X, (s16)le16_to_cpu(pkt->stick_left_x)); + input_report_abs(dev, ABS_RX, (s16)le16_to_cpu(pkt->stick_right_x)); + input_report_abs(dev, ABS_Y, ~(s16)le16_to_cpu(pkt->stick_left_y)); + input_report_abs(dev, ABS_RY, ~(s16)le16_to_cpu(pkt->stick_right_y)); + input_report_abs(dev, ABS_Z, le16_to_cpu(pkt->trigger_left)); + input_report_abs(dev, ABS_RZ, le16_to_cpu(pkt->trigger_right)); + input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_GP_BTN_DPAD_R) - + !!(buttons & GIP_GP_BTN_DPAD_L)); + input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_GP_BTN_DPAD_D) - + !!(buttons & GIP_GP_BTN_DPAD_U)); + input_sync(dev); + return 0; +} + +static int gip_gamepad_probe(struct gip_client *client) +{ + struct gip_gamepad *gamepad; + int err; + + gamepad = devm_kzalloc(&client->dev, sizeof(*gamepad), GFP_KERNEL); + if (!gamepad) + return -ENOMEM; + + gamepad->client = client; + + err = gip_set_power_mode(client, GIP_PWR_ON); + if (err) + return err; + + gip_gamepad_query_paddles(gamepad); + + /* + * xpad sends this for all Elite 2 firmware versions, + * but it seems to be only necessary for 5.11 paddles. + */ + if(gamepad->paddle_support == PADDLE_ELITE2_511) + { + err = gip_gamepad_init_extra_data(gamepad); + if (err) + return err; + } + + err = gip_init_battery(&gamepad->battery, client, GIP_GP_NAME); + if (err) + return err; + + err = gip_init_led(&gamepad->led, client); + if (err) + return err; + + err = gip_auth_start_handshake(&gamepad->auth, client); + if (err) + return err; + + err = gip_init_input(&gamepad->input, client, GIP_GP_NAME); + if (err) + return err; + + err = gip_gamepad_init_input(gamepad); + if (err) + return err; + + dev_set_drvdata(&client->dev, gamepad); + + return 0; +} + +static void gip_gamepad_remove(struct gip_client *client) +{ + struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6,15,0) + del_timer_sync(&gamepad->rumble.timer); +#else + timer_delete_sync(&gamepad->rumble.timer); +#endif +} + +static struct gip_driver gip_gamepad_driver = { + .name = "xone-gip-gamepad", + .class = "Windows.Xbox.Input.Gamepad", + .ops = { + .battery = gip_gamepad_op_battery, + .authenticate = gip_gamepad_op_authenticate, + .authenticated = gip_gamepad_op_authenticated, + .guide_button = gip_gamepad_op_guide_button, + .input = gip_gamepad_op_input, + .firmware = gip_gamepad_op_firmware, + }, + .probe = gip_gamepad_probe, + .remove = gip_gamepad_remove, +}; +module_gip_driver(gip_gamepad_driver); + +MODULE_ALIAS("gip:Windows.Xbox.Input.Gamepad"); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); +MODULE_DESCRIPTION("xone GIP gamepad driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/custom/xonedo/driver/headset.c b/drivers/custom/xonedo/driver/headset.c new file mode 100644 index 000000000000..44e7572553e9 --- /dev/null +++ b/drivers/custom/xonedo/driver/headset.c @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck-Lipinski + */ + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "../auth/auth.h" + +#define GIP_HS_NAME "Microsoft Xbox Headset" + +#define GIP_HS_NUM_BUFFERS 128 + +/* product ID for the chat headset */ +#define GIP_HS_PID_CHAT 0x0111 + +#define GIP_HS_MAX_RETRIES 6 +#define GIP_HS_POWER_ON_DELAY msecs_to_jiffies(250) +#define GIP_HS_START_DELAY msecs_to_jiffies(500) + +static struct gip_vidpid GIP_HS_CHECK_AUTH_IDS[] = { + { 0x1532, 0x0a16 }, // Razer Thresher + { 0x1532, 0x0a25 }, // Razer Kaira Pro + { 0x1532, 0x0a27 }, // Razer Kaira Pro + { 0x2f12, 0x0023 }, // LucidSound LS35X +}; + +static const struct snd_pcm_hardware gip_headset_pcm_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .periods_min = 2, + .periods_max = GIP_HS_NUM_BUFFERS, +}; + +struct gip_headset { + struct gip_client *client; + struct gip_battery battery; + struct gip_auth auth; + + bool chat_headset; + + struct work_struct work_config; + struct delayed_work work_power_on; + struct work_struct work_register; + bool got_authenticated; + int start_counter; + bool got_initial_volume; + bool got_audio_packet; + + struct hrtimer timer; + struct hrtimer start_audio_timer; + void *buffer; + + struct gip_headset_stream { + struct snd_pcm_substream *substream; + snd_pcm_uframes_t pointer; + snd_pcm_uframes_t period; + } playback, capture; +}; + +static int gip_headset_pcm_open(struct snd_pcm_substream *sub) +{ + struct gip_headset *headset = snd_pcm_substream_chip(sub); + struct gip_audio_config *cfg; + struct snd_pcm_hardware hw = gip_headset_pcm_hw; + + if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + cfg = &headset->client->audio_config_out; + else + cfg = &headset->client->audio_config_in; + + hw.rate_min = cfg->sample_rate; + hw.rate_max = cfg->sample_rate; + hw.channels_min = cfg->channels; + hw.channels_max = cfg->channels; + hw.buffer_bytes_max = cfg->buffer_size * GIP_HS_NUM_BUFFERS; + hw.period_bytes_min = cfg->buffer_size; + hw.period_bytes_max = cfg->buffer_size * 2; + hw.periods_min = 1; + hw.periods_max = 8; + + sub->runtime->hw = hw; + + return 0; +} + +static int gip_headset_pcm_close(struct snd_pcm_substream *sub) +{ + return 0; +} + +static int gip_headset_pcm_prepare(struct snd_pcm_substream *sub) +{ + return 0; +} + +static int gip_headset_pcm_trigger(struct snd_pcm_substream *sub, int cmd) +{ + struct gip_headset *headset = snd_pcm_substream_chip(sub); + struct gip_headset_stream *stream; + + if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + stream = &headset->playback; + else + stream = &headset->capture; + + stream->pointer = 0; + stream->period = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + stream->substream = sub; + break; + case SNDRV_PCM_TRIGGER_STOP: + stream->substream = NULL; + break; + default: + return -EINVAL; + } + + if (!stream->substream && sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + memset(headset->buffer, 0, + headset->client->audio_config_out.buffer_size); + + return 0; +} + +static snd_pcm_uframes_t gip_headset_pcm_pointer(struct snd_pcm_substream *sub) +{ + struct gip_headset *headset = snd_pcm_substream_chip(sub); + struct gip_headset_stream *stream; + + if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + stream = &headset->playback; + else + stream = &headset->capture; + + return bytes_to_frames(sub->runtime, stream->pointer); +} + +static const struct snd_pcm_ops gip_headset_pcm_ops = { + .open = gip_headset_pcm_open, + .close = gip_headset_pcm_close, + .prepare = gip_headset_pcm_prepare, + .trigger = gip_headset_pcm_trigger, + .pointer = gip_headset_pcm_pointer, +}; + +static bool gip_headset_advance_pointer(struct gip_headset_stream *stream, + int len, size_t buf_size) +{ + snd_pcm_uframes_t period = stream->substream->runtime->period_size; + + stream->pointer += len; + if (stream->pointer >= buf_size) + stream->pointer -= buf_size; + + stream->period += len; + if (stream->period >= period) { + stream->period -= period; + return true; + } + + return false; +} + +static bool gip_headset_copy_playback(struct gip_headset_stream *stream, + unsigned char *data, int len) +{ + unsigned char *src = stream->substream->runtime->dma_area; + size_t buf_size = snd_pcm_lib_buffer_bytes(stream->substream); + size_t remaining = buf_size - stream->pointer; + + if (len <= remaining) { + memcpy(data, src + stream->pointer, len); + } else { + memcpy(data, src + stream->pointer, remaining); + memcpy(data + remaining, src, len - remaining); + } + + return gip_headset_advance_pointer(stream, len, buf_size); +} + +static bool gip_headset_copy_capture(struct gip_headset_stream *stream, + unsigned char *data, int len) +{ + unsigned char *dest = stream->substream->runtime->dma_area; + size_t buf_size = snd_pcm_lib_buffer_bytes(stream->substream); + size_t remaining = buf_size - stream->pointer; + + if (len <= remaining) { + memcpy(dest + stream->pointer, data, len); + } else { + memcpy(dest + stream->pointer, data, remaining); + memcpy(dest, data + remaining, len - remaining); + } + + return gip_headset_advance_pointer(stream, len, buf_size); +} + +static enum hrtimer_restart gip_headset_send_samples(struct hrtimer *timer) +{ + struct gip_headset *headset = container_of(timer, typeof(*headset), + timer); + struct gip_audio_config *cfg = &headset->client->audio_config_out; + struct snd_pcm_substream *sub = headset->playback.substream; + bool elapsed = false; + int err; + unsigned long flags; + + if (sub) { + snd_pcm_stream_lock_irqsave(sub, flags); + + if (sub->runtime && snd_pcm_running(sub)) + elapsed = gip_headset_copy_playback(&headset->playback, + headset->buffer, + cfg->buffer_size); + + snd_pcm_stream_unlock_irqrestore(sub, flags); + + if (elapsed) + snd_pcm_period_elapsed(sub); + } + + if (headset->got_authenticated) { + /* retry if driver runs out of buffers */ + err = gip_send_audio_samples(headset->client, headset->buffer); + if (err && err != -ENOSPC) + return HRTIMER_NORESTART; + } + + hrtimer_forward_now(timer, ms_to_ktime(GIP_AUDIO_INTERVAL)); + + return HRTIMER_RESTART; +} + +/* + * start pcm devices then launch the work that + * sends START every 500ms until an audio packet is received + * or audio volume control command is received + * or time out of 3 seconds (5 start message + 500ms timeout) + */ +static enum hrtimer_restart gip_headset_start_audio(struct hrtimer *timer) +{ + struct gip_headset *headset = + container_of(timer, typeof(*headset), start_audio_timer); + int err; + + /* + * check if the number of retries are elapsed (5) : + * start audio anyway + */ + bool max_retries_reached = + (headset->start_counter > GIP_HS_MAX_RETRIES ? true : false); + + /* check here if audio was started : HRTIMER_NORESTART */ + if (headset->got_initial_volume || headset->got_audio_packet || + max_retries_reached) { + dev_dbg(&headset->client->dev, + "%s: start audio try %d/%d, audio = %d, vol = %d.\n", + __func__, headset->start_counter, GIP_HS_MAX_RETRIES, + headset->got_audio_packet, headset->got_initial_volume); + + /* start work handling pcm config and audio timer */ + schedule_work(&headset->work_register); + return HRTIMER_NORESTART; + } + + // otherwise resend START and wait for another GIP_HS_START_DELAY ms + headset->start_counter++; + dev_dbg(&headset->client->dev, "%s: send device start, try %d/%d.\n", + __func__, headset->start_counter, GIP_HS_MAX_RETRIES); + err = gip_set_power_mode(headset->client, GIP_PWR_ON); + if (err) + dev_err(&headset->client->dev, + "%s: set power mode failed: %d\n", __func__, err); + hrtimer_forward_now(timer, ms_to_ktime(GIP_HS_START_DELAY)); + + return HRTIMER_RESTART; +} + +static int gip_headset_init_pcm(struct gip_headset *headset) +{ + struct snd_card *card; + struct snd_pcm *pcm; + int err; + + err = snd_devm_card_new(&headset->client->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, THIS_MODULE, 0, &card); + if (err) + return err; + + strscpy(card->driver, "xone-gip-headset", sizeof(card->driver)); + strscpy(card->shortname, GIP_HS_NAME, sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), "%s at %s", + GIP_HS_NAME, dev_name(&headset->client->dev)); + + err = snd_pcm_new(card, GIP_HS_NAME, 0, 1, 1, &pcm); + if (err) + return err; + + strscpy(pcm->name, GIP_HS_NAME, sizeof(pcm->name)); + pcm->private_data = headset; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &gip_headset_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &gip_headset_pcm_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); + + return snd_card_register(card); +} + +static void gip_headset_config(struct work_struct *work) +{ + struct gip_headset *headset = container_of(work, + typeof(*headset), + work_config); + struct gip_client *client = headset->client; + struct gip_info_element *fmts = client->audio_formats; + int err; + + dev_dbg(&client->dev, "%s: format=0x%02x/0x%02x\n", __func__, + fmts->data[0], fmts->data[1]); + + /* force headset in idle mode */ + err = gip_set_power_mode(client, GIP_PWR_SLEEP); + if (err) + dev_err(&client->dev, + "%s: set headset power mode to IDLE failed: %d\n", + __func__, err); + /* suggest initial audio format */ + dev_dbg(&client->dev, "%s: suggest format.\n", __func__); + err = gip_suggest_audio_format(client, fmts->data[0], fmts->data[1], + headset->chat_headset); + if (err) + dev_err(&client->dev, "%s: suggest format failed: %d\n", + __func__, err); +} + +static void gip_headset_power_on(struct work_struct *work) +{ + struct gip_headset *headset = container_of( + to_delayed_work(work), typeof(*headset), work_power_on); + struct gip_client *client = headset->client; + const struct device *dev = &client->adapter->dev; + int err; + + dev_dbg(dev, "Headset vendor: 0x%04x\n", client->hardware.vendor); + dev_dbg(dev, "Headset product: 0x%04x\n", client->hardware.product); + + /* Check if headset needs authentication before receiving audio samples */ + headset->got_authenticated = true; + for (int i = 0; i < ARRAY_SIZE(GIP_HS_CHECK_AUTH_IDS); i++) + if (client->hardware.vendor == GIP_HS_CHECK_AUTH_IDS[i].vendor && + client->hardware.product == GIP_HS_CHECK_AUTH_IDS[i].product) { + headset->got_authenticated = false; + dev_dbg(dev, "Headset needs auth before receiving audio"); + break; + } + + /* not a standalone headset */ + if (client->id) { + dev_dbg(dev, "Headset is not a standalone headset\n"); + return; + } + + err = gip_init_battery(&headset->battery, client, GIP_HS_NAME); + if (err) { + dev_err(&client->dev, "%s: init battery failed: %d\n", + __func__, err); + return; + } + + err = gip_auth_start_handshake(&headset->auth, client); + if (err) + dev_err(&client->dev, "%s: start handshake failed: %d\n", + __func__, err); +} + +static void gip_headset_register(struct work_struct *work) +{ + struct gip_headset *headset = container_of(work, typeof(*headset), + work_register); + struct gip_client *client = headset->client; + int err; + + headset->buffer = devm_kzalloc(&client->dev, + client->audio_config_out.buffer_size, + GFP_KERNEL); + if (!headset->buffer) + return; + + dev_dbg(&client->dev, "%s: init pcm device.\n", __func__); + err = snd_card_free_on_error(&client->dev, gip_headset_init_pcm(headset)); + if (err) { + dev_err(&client->dev, "%s: init PCM failed: %d\n", + __func__, err); + return; + } + + /* set hardware volume to maximum for headset jack */ + /* standalone & chat headsets have physical volume controls */ + if (client->id && !headset->chat_headset) { + err = gip_set_audio_volume(client, 100, 50, 100); + if (err) { + dev_err(&client->dev, "%s: set volume failed: %d\n", + __func__, err); + return; + } + } + + dev_dbg(&client->dev, "%s: init audio out.\n", __func__); + err = gip_init_audio_out(client); + if (err) { + dev_err(&client->dev, "%s: init audio out failed: %d\n", + __func__, err); + return; + } + + dev_dbg(&client->dev, "%s: init audio in.\n", __func__); + err = gip_init_audio_in(client); + if (err) { + dev_err(&client->dev, "%s: init audio in failed: %d\n", + __func__, err); + return; + } + + /* start audio timer */ + hrtimer_start(&headset->timer, 0, HRTIMER_MODE_REL); +} + +static int gip_headset_op_battery(struct gip_client *client, + enum gip_battery_type type, + enum gip_battery_level level) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + + gip_report_battery(&headset->battery, type, level); + + return 0; +} + +static int gip_headset_op_authenticate(struct gip_client *client, + void *data, u32 len) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + + return gip_auth_process_pkt(&headset->auth, data, len); +} + +static int gip_headset_op_authenticated(struct gip_client *client) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + headset->got_authenticated = true; + return 0; +} + +/* + * headset reported supported audio formats so + * we can allocate buffer with proper size + */ +static int gip_headset_op_audio_ready(struct gip_client *client) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + + dev_dbg(&client->dev, + "%s: audio ready : initialize start sequence.\n", __func__); + headset->start_counter = 0; + hrtimer_start(&headset->start_audio_timer, 0, HRTIMER_MODE_REL); + /* start auth handshake after GIP_HS_POWER_ON_DELAY */ + schedule_delayed_work(&headset->work_power_on, GIP_HS_POWER_ON_DELAY); + + return 0; +} + +static int gip_headset_op_audio_volume(struct gip_client *client, + u8 in, u8 out) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + + /* headset reported initial volume, ready to start audio I/O */ + headset->got_initial_volume = true; + + /* ignore hardware volume, let software handle volume changes */ + return 0; +} + +static int gip_headset_op_audio_samples(struct gip_client *client, + void *data, u32 len) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + struct snd_pcm_substream *sub = headset->capture.substream; + bool elapsed = false; + unsigned long flags; + + headset->got_audio_packet = true; + + if (!sub) + return 0; + + snd_pcm_stream_lock_irqsave(sub, flags); + + if (sub->runtime && snd_pcm_running(sub)) + elapsed = gip_headset_copy_capture(&headset->capture, + data, len); + + snd_pcm_stream_unlock_irqrestore(sub, flags); + + if (elapsed) + snd_pcm_period_elapsed(sub); + + return 0; +} + +static int gip_headset_probe(struct gip_client *client) +{ + struct gip_headset *headset; + struct gip_info_element *fmts = client->audio_formats; + int err; + + if (!fmts || !fmts->count) + return -ENODEV; + + headset = devm_kzalloc(&client->dev, sizeof(*headset), GFP_KERNEL); + if (!headset) + return -ENOMEM; + + headset->client = client; + headset->chat_headset = client->hardware.vendor == GIP_VID_MICROSOFT && + client->hardware.product == GIP_HS_PID_CHAT; + + INIT_WORK(&headset->work_config, gip_headset_config); + INIT_DELAYED_WORK(&headset->work_power_on, gip_headset_power_on); + INIT_WORK(&headset->work_register, gip_headset_register); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6,15,0) + hrtimer_init(&headset->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + headset->timer.function = gip_headset_send_samples; + hrtimer_init(&headset->start_audio_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + headset->start_audio_timer.function = gip_headset_start_audio; +#else + hrtimer_setup(&headset->timer, gip_headset_send_samples, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_setup(&headset->start_audio_timer, gip_headset_start_audio, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); +#endif + + err = gip_enable_audio(client); + if (err) + return err; + + dev_set_drvdata(&client->dev, headset); + + /* start audio configuration */ + schedule_work(&headset->work_config); + + return 0; +} + +static void gip_headset_remove(struct gip_client *client) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + + cancel_work_sync(&headset->work_config); + cancel_delayed_work_sync(&headset->work_power_on); + cancel_work_sync(&headset->work_register); + hrtimer_cancel(&headset->timer); + hrtimer_cancel(&headset->start_audio_timer); + gip_disable_audio(client); +} + +static struct gip_driver gip_headset_driver = { + .name = "xone-gip-headset", + .class = "Windows.Xbox.Input.Headset", + .ops = { + .battery = gip_headset_op_battery, + .authenticate = gip_headset_op_authenticate, + .authenticated = gip_headset_op_authenticated, + .audio_ready = gip_headset_op_audio_ready, + .audio_volume = gip_headset_op_audio_volume, + .audio_samples = gip_headset_op_audio_samples, + }, + .probe = gip_headset_probe, + .remove = gip_headset_remove, +}; +module_gip_driver(gip_headset_driver); + +MODULE_ALIAS("gip:Windows.Xbox.Input.Headset"); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); +MODULE_DESCRIPTION("xone GIP headset driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/custom/xonedo/driver/madcatz_glam.c b/drivers/custom/xonedo/driver/madcatz_glam.c new file mode 100644 index 000000000000..50d067ce5201 --- /dev/null +++ b/drivers/custom/xonedo/driver/madcatz_glam.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Severin von Wnuck-Lipinski + */ + +#include + +#include "common.h" +#include "../auth/auth.h" + +#define GIP_GL_NAME "Mad Catz Rock Band 4 Drum Kit" + +enum gip_glam_button { + GIP_GL_BTN_MENU = BIT(2), + GIP_GL_BTN_VIEW = BIT(3), + GIP_GL_BTN_A = BIT(4), + GIP_GL_BTN_B = BIT(5), + /* swapped X and Y buttons */ + GIP_GL_BTN_X = BIT(7), + GIP_GL_BTN_Y = BIT(6), + GIP_GL_BTN_DPAD_U = BIT(8), + GIP_GL_BTN_DPAD_D = BIT(9), + GIP_GL_BTN_DPAD_L = BIT(10), + GIP_GL_BTN_DPAD_R = BIT(11), + GIP_GL_BTN_KICK_1 = BIT(12), + GIP_GL_BTN_KICK_2 = BIT(13), +}; + +enum gip_glam_pad { + GIP_GL_PAD_YELLOW = BIT(0) | BIT(1) | BIT(2), + GIP_GL_PAD_RED = BIT(4) | BIT(5) | BIT(6), + GIP_GL_PAD_GREEN = BIT(8) | BIT(9) | BIT(10), + GIP_GL_PAD_BLUE = BIT(12) | BIT(13) | BIT(14), +}; + +enum gip_glam_cymbal { + GIP_GL_CBL_BLUE = BIT(0) | BIT(1) | BIT(2), + GIP_GL_CBL_YELLOW = BIT(4) | BIT(5) | BIT(6), + GIP_GL_CBL_GREEN = BIT(12) | BIT(13) | BIT(14), +}; + +struct gip_glam_pkt_input { + __le16 buttons; + __le16 pads; + __le16 cymbals; +} __packed; + +struct gip_glam { + struct gip_client *client; + struct gip_battery battery; + struct gip_input input; +}; + +static int gip_glam_init_input(struct gip_glam *glam) +{ + struct input_dev *dev = glam->input.dev; + int err; + + input_set_capability(dev, EV_KEY, BTN_MODE); + input_set_capability(dev, EV_KEY, BTN_START); + input_set_capability(dev, EV_KEY, BTN_SELECT); + input_set_capability(dev, EV_KEY, BTN_A); + input_set_capability(dev, EV_KEY, BTN_B); + input_set_capability(dev, EV_KEY, BTN_X); + input_set_capability(dev, EV_KEY, BTN_Y); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY1); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY2); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY3); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY4); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY5); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY6); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY7); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY8); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY9); + input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0); + + err = input_register_device(dev); + if (err) + dev_err(&glam->client->dev, "%s: register failed: %d\n", + __func__, err); + + return err; +} + +static int gip_glam_op_battery(struct gip_client *client, + enum gip_battery_type type, + enum gip_battery_level level) +{ + struct gip_glam *glam = dev_get_drvdata(&client->dev); + + gip_report_battery(&glam->battery, type, level); + + return 0; +} + +static int gip_glam_op_guide_button(struct gip_client *client, bool down) +{ + struct gip_glam *glam = dev_get_drvdata(&client->dev); + + input_report_key(glam->input.dev, BTN_MODE, down); + input_sync(glam->input.dev); + + return 0; +} + +static int gip_glam_op_input(struct gip_client *client, void *data, u32 len) +{ + struct gip_glam *glam = dev_get_drvdata(&client->dev); + struct gip_glam_pkt_input *pkt = data; + struct input_dev *dev = glam->input.dev; + u16 buttons; + u16 pads; + u16 cymbals; + + if (len < sizeof(*pkt)) + return -EINVAL; + + buttons = le16_to_cpu(pkt->buttons); + pads = le16_to_cpu(pkt->pads); + cymbals = le16_to_cpu(pkt->cymbals); + + input_report_key(dev, BTN_START, buttons & GIP_GL_BTN_MENU); + input_report_key(dev, BTN_SELECT, buttons & GIP_GL_BTN_VIEW); + input_report_key(dev, BTN_A, buttons & GIP_GL_BTN_A); + input_report_key(dev, BTN_B, buttons & GIP_GL_BTN_B); + input_report_key(dev, BTN_X, buttons & GIP_GL_BTN_X); + input_report_key(dev, BTN_Y, buttons & GIP_GL_BTN_Y); + input_report_key(dev, BTN_TRIGGER_HAPPY1, buttons & GIP_GL_BTN_KICK_1); + input_report_key(dev, BTN_TRIGGER_HAPPY2, buttons & GIP_GL_BTN_KICK_2); + input_report_key(dev, BTN_TRIGGER_HAPPY3, pads & GIP_GL_PAD_RED); + input_report_key(dev, BTN_TRIGGER_HAPPY4, pads & GIP_GL_PAD_YELLOW); + input_report_key(dev, BTN_TRIGGER_HAPPY5, pads & GIP_GL_PAD_BLUE); + input_report_key(dev, BTN_TRIGGER_HAPPY6, pads & GIP_GL_PAD_GREEN); + input_report_key(dev, BTN_TRIGGER_HAPPY7, cymbals & GIP_GL_CBL_YELLOW); + input_report_key(dev, BTN_TRIGGER_HAPPY8, cymbals & GIP_GL_CBL_BLUE); + input_report_key(dev, BTN_TRIGGER_HAPPY9, cymbals & GIP_GL_CBL_GREEN); + input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_GL_BTN_DPAD_R) - + !!(buttons & GIP_GL_BTN_DPAD_L)); + input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_GL_BTN_DPAD_D) - + !!(buttons & GIP_GL_BTN_DPAD_U)); + input_sync(dev); + + return 0; +} + +static int gip_glam_probe(struct gip_client *client) +{ + struct gip_glam *glam; + int err; + + glam = devm_kzalloc(&client->dev, sizeof(*glam), GFP_KERNEL); + if (!glam) + return -ENOMEM; + + glam->client = client; + + err = gip_set_power_mode(client, GIP_PWR_ON); + if (err) + return err; + + err = gip_init_battery(&glam->battery, client, GIP_GL_NAME); + if (err) + return err; + + /* + * The Drum Kit sends auth chunks without specifying the + * acknowledgment option while still expecting an acknowledgment. + * The Windows driver handles this by sending an acknowledgment + * after 100 ms when no further chunks are received. + * We skip the handshake instead, as it is not required. + */ + err = gip_auth_send_complete(client); + if (err) + return err; + + err = gip_init_input(&glam->input, client, GIP_GL_NAME); + if (err) + return err; + + err = gip_glam_init_input(glam); + if (err) + return err; + + dev_set_drvdata(&client->dev, glam); + + return 0; +} + +static struct gip_driver gip_glam_driver = { + .name = "xone-gip-madcatz-glam", + .class = "MadCatz.Xbox.Drums.Glam", + .ops = { + .battery = gip_glam_op_battery, + .guide_button = gip_glam_op_guide_button, + .input = gip_glam_op_input, + }, + .probe = gip_glam_probe, +}; +module_gip_driver(gip_glam_driver); + +MODULE_ALIAS("gip:MadCatz.Xbox.Drums.Glam"); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); +MODULE_DESCRIPTION("xone GIP Mad Catz Drum Kit driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/custom/xonedo/driver/madcatz_strat.c b/drivers/custom/xonedo/driver/madcatz_strat.c new file mode 100644 index 000000000000..445dd194429e --- /dev/null +++ b/drivers/custom/xonedo/driver/madcatz_strat.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022 Severin von Wnuck-Lipinski + */ + +#include + +#include "common.h" +#include "../auth/auth.h" + +#define GIP_ST_NAME "Mad Catz Rock Band 4 Stratocaster" + +enum gip_strat_button { + GIP_ST_BTN_MENU = BIT(2), + GIP_ST_BTN_VIEW = BIT(3), + GIP_ST_BTN_DPAD_U = BIT(8), + GIP_ST_BTN_DPAD_D = BIT(9), + GIP_ST_BTN_DPAD_L = BIT(10), + GIP_ST_BTN_DPAD_R = BIT(11), +}; + +enum gip_strat_fret { + GIP_ST_FRET_GREEN = BIT(0), + GIP_ST_FRET_RED = BIT(1), + GIP_ST_FRET_YELLOW = BIT(2), + GIP_ST_FRET_BLUE = BIT(3), + GIP_ST_FRET_ORANGE = BIT(4), +}; + +struct gip_strat_pkt_input { + __le16 buttons; + u8 tilt; + u8 whammy; + u8 slider; + u8 fret_upper; + u8 fret_lower; +} __packed; + +struct gip_strat { + struct gip_client *client; + struct gip_battery battery; + struct gip_input input; +}; + +static int gip_strat_init_input(struct gip_strat *strat) +{ + struct input_dev *dev = strat->input.dev; + int err; + + input_set_capability(dev, EV_KEY, BTN_MODE); + input_set_capability(dev, EV_KEY, BTN_START); + input_set_capability(dev, EV_KEY, BTN_SELECT); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY1); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY2); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY3); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY4); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY5); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY6); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY7); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY8); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY9); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY10); + input_set_abs_params(dev, ABS_X, 0, 64, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, 255, 0, 0); + input_set_abs_params(dev, ABS_Z, 0, 255, 0, 0); + input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0); + + err = input_register_device(dev); + if (err) + dev_err(&strat->client->dev, "%s: register failed: %d\n", + __func__, err); + + return err; +} + +static int gip_strat_op_battery(struct gip_client *client, + enum gip_battery_type type, + enum gip_battery_level level) +{ + struct gip_strat *strat = dev_get_drvdata(&client->dev); + + gip_report_battery(&strat->battery, type, level); + + return 0; +} + +static int gip_strat_op_guide_button(struct gip_client *client, bool down) +{ + struct gip_strat *strat = dev_get_drvdata(&client->dev); + + input_report_key(strat->input.dev, BTN_MODE, down); + input_sync(strat->input.dev); + + return 0; +} + +static int gip_strat_op_input(struct gip_client *client, void *data, u32 len) +{ + struct gip_strat *strat = dev_get_drvdata(&client->dev); + struct gip_strat_pkt_input *pkt = data; + struct input_dev *dev = strat->input.dev; + u16 buttons; + + if (len < sizeof(*pkt)) + return -EINVAL; + + buttons = le16_to_cpu(pkt->buttons); + + input_report_key(dev, BTN_START, buttons & GIP_ST_BTN_MENU); + input_report_key(dev, BTN_SELECT, buttons & GIP_ST_BTN_VIEW); + input_report_key(dev, BTN_TRIGGER_HAPPY1, + pkt->fret_upper & GIP_ST_FRET_GREEN); + input_report_key(dev, BTN_TRIGGER_HAPPY2, + pkt->fret_upper & GIP_ST_FRET_RED); + input_report_key(dev, BTN_TRIGGER_HAPPY3, + pkt->fret_upper & GIP_ST_FRET_YELLOW); + input_report_key(dev, BTN_TRIGGER_HAPPY4, + pkt->fret_upper & GIP_ST_FRET_BLUE); + input_report_key(dev, BTN_TRIGGER_HAPPY5, + pkt->fret_upper & GIP_ST_FRET_ORANGE); + input_report_key(dev, BTN_TRIGGER_HAPPY6, + pkt->fret_lower & GIP_ST_FRET_GREEN); + input_report_key(dev, BTN_TRIGGER_HAPPY7, + pkt->fret_lower & GIP_ST_FRET_RED); + input_report_key(dev, BTN_TRIGGER_HAPPY8, + pkt->fret_lower & GIP_ST_FRET_YELLOW); + input_report_key(dev, BTN_TRIGGER_HAPPY9, + pkt->fret_lower & GIP_ST_FRET_BLUE); + input_report_key(dev, BTN_TRIGGER_HAPPY10, + pkt->fret_lower & GIP_ST_FRET_ORANGE); + input_report_abs(dev, ABS_X, pkt->slider); + input_report_abs(dev, ABS_Y, pkt->whammy); + input_report_abs(dev, ABS_Z, pkt->tilt); + input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_ST_BTN_DPAD_R) - + !!(buttons & GIP_ST_BTN_DPAD_L)); + input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_ST_BTN_DPAD_D) - + !!(buttons & GIP_ST_BTN_DPAD_U)); + input_sync(dev); + + return 0; +} + +static int gip_strat_probe(struct gip_client *client) +{ + struct gip_strat *strat; + int err; + + strat = devm_kzalloc(&client->dev, sizeof(*strat), GFP_KERNEL); + if (!strat) + return -ENOMEM; + + strat->client = client; + + err = gip_set_power_mode(client, GIP_PWR_ON); + if (err) + return err; + + err = gip_init_battery(&strat->battery, client, GIP_ST_NAME); + if (err) + return err; + + /* + * The Stratocaster sends auth chunks without specifying the + * acknowledgment option while still expecting an acknowledgment. + * The Windows driver handles this by sending an acknowledgment + * after 100 ms when no further chunks are received. + * We skip the handshake instead, as it is not required. + */ + err = gip_auth_send_complete(client); + if (err) + return err; + + err = gip_init_input(&strat->input, client, GIP_ST_NAME); + if (err) + return err; + + err = gip_strat_init_input(strat); + if (err) + return err; + + dev_set_drvdata(&client->dev, strat); + + return 0; +} + +static struct gip_driver gip_strat_driver = { + .name = "xone-gip-madcatz-strat", + .class = "MadCatz.Xbox.Guitar.Stratocaster", + .ops = { + .battery = gip_strat_op_battery, + .guide_button = gip_strat_op_guide_button, + .input = gip_strat_op_input, + }, + .probe = gip_strat_probe, +}; +module_gip_driver(gip_strat_driver); + +MODULE_ALIAS("gip:MadCatz.Xbox.Guitar.Stratocaster"); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); +MODULE_DESCRIPTION("xone GIP Mad Catz Stratocaster driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/custom/xonedo/driver/pdp_jaguar.c b/drivers/custom/xonedo/driver/pdp_jaguar.c new file mode 100644 index 000000000000..b98caf5fa614 --- /dev/null +++ b/drivers/custom/xonedo/driver/pdp_jaguar.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022 Severin von Wnuck-Lipinski + * Copyright (C) 2023 Scott K Logan + */ + +#include + +#include "common.h" +#include "../auth/auth.h" + +#define GIP_JA_NAME "PDP Rock Band 4 Jaguar" + +enum gip_jaguar_button { + GIP_JA_BTN_MENU = BIT(2), + GIP_JA_BTN_VIEW = BIT(3), + GIP_JA_BTN_DPAD_U = BIT(8), + GIP_JA_BTN_DPAD_D = BIT(9), + GIP_JA_BTN_DPAD_L = BIT(10), + GIP_JA_BTN_DPAD_R = BIT(11), +}; + +enum gip_jaguar_fret { + GIP_JA_FRET_GREEN = BIT(4), + GIP_JA_FRET_RED = BIT(5), + GIP_JA_FRET_BLUE = BIT(6), + GIP_JA_FRET_YELLOW = BIT(7), + GIP_JA_FRET_ORANGE = BIT(12), + GIP_JA_FRET_LOWER = BIT(14), +}; + +struct gip_jaguar_pkt_input { + __le16 buttons; + u8 tilt; + u8 whammy; +} __packed; + +struct gip_jaguar { + struct gip_client *client; + struct gip_battery battery; + struct gip_auth auth; + struct gip_input input; +}; + +static int gip_jaguar_init_input(struct gip_jaguar *guitar) +{ + struct input_dev *dev = guitar->input.dev; + int err; + + input_set_capability(dev, EV_KEY, BTN_MODE); + input_set_capability(dev, EV_KEY, BTN_START); + input_set_capability(dev, EV_KEY, BTN_SELECT); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY1); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY2); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY3); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY4); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY5); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY6); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY7); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY8); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY9); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY10); + input_set_abs_params(dev, ABS_Y, 0, 255, 0, 0); + input_set_abs_params(dev, ABS_Z, 0, 255, 0, 0); + input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0); + + err = input_register_device(dev); + if (err) + dev_err(&guitar->client->dev, "%s: register failed: %d\n", + __func__, err); + + return err; +} + +static int gip_jaguar_op_battery(struct gip_client *client, + enum gip_battery_type type, + enum gip_battery_level level) +{ + struct gip_jaguar *guitar = dev_get_drvdata(&client->dev); + + gip_report_battery(&guitar->battery, type, level); + + return 0; +} + +static int gip_jaguar_op_authenticate(struct gip_client *client, + void *data, u32 len) +{ + struct gip_jaguar *guitar = dev_get_drvdata(&client->dev); + + return gip_auth_process_pkt(&guitar->auth, data, len); +} + +static int gip_jaguar_op_guide_button(struct gip_client *client, bool down) +{ + struct gip_jaguar *guitar = dev_get_drvdata(&client->dev); + + input_report_key(guitar->input.dev, BTN_MODE, down); + input_sync(guitar->input.dev); + + return 0; +} + +static int gip_jaguar_op_input(struct gip_client *client, void *data, u32 len) +{ + struct gip_jaguar *guitar = dev_get_drvdata(&client->dev); + struct gip_jaguar_pkt_input *pkt = data; + struct input_dev *dev = guitar->input.dev; + u16 buttons; + bool lower; + + if (len < sizeof(*pkt)) + return -EINVAL; + + buttons = le16_to_cpu(pkt->buttons); + lower = buttons & GIP_JA_FRET_LOWER; + + input_report_key(dev, BTN_START, buttons & GIP_JA_BTN_MENU); + input_report_key(dev, BTN_SELECT, buttons & GIP_JA_BTN_VIEW); + input_report_key(dev, BTN_TRIGGER_HAPPY1, + (buttons & GIP_JA_FRET_GREEN) && !lower); + input_report_key(dev, BTN_TRIGGER_HAPPY2, + (buttons & GIP_JA_FRET_RED) && !lower); + input_report_key(dev, BTN_TRIGGER_HAPPY3, + (buttons & GIP_JA_FRET_YELLOW) && !lower); + input_report_key(dev, BTN_TRIGGER_HAPPY4, + (buttons & GIP_JA_FRET_BLUE) && !lower); + input_report_key(dev, BTN_TRIGGER_HAPPY5, + (buttons & GIP_JA_FRET_ORANGE) && !lower); + input_report_key(dev, BTN_TRIGGER_HAPPY6, + (buttons & GIP_JA_FRET_GREEN) && lower); + input_report_key(dev, BTN_TRIGGER_HAPPY7, + (buttons & GIP_JA_FRET_RED) && lower); + input_report_key(dev, BTN_TRIGGER_HAPPY8, + (buttons & GIP_JA_FRET_YELLOW) && lower); + input_report_key(dev, BTN_TRIGGER_HAPPY9, + (buttons & GIP_JA_FRET_BLUE) && lower); + input_report_key(dev, BTN_TRIGGER_HAPPY10, + (buttons & GIP_JA_FRET_ORANGE) && lower); + input_report_abs(dev, ABS_Y, pkt->whammy); + input_report_abs(dev, ABS_Z, pkt->tilt); + input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_JA_BTN_DPAD_R) - + !!(buttons & GIP_JA_BTN_DPAD_L)); + input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_JA_BTN_DPAD_D) - + !!(buttons & GIP_JA_BTN_DPAD_U)); + input_sync(dev); + + return 0; +} + +static int gip_jaguar_probe(struct gip_client *client) +{ + struct gip_jaguar *guitar; + int err; + + guitar = devm_kzalloc(&client->dev, sizeof(*guitar), GFP_KERNEL); + if (!guitar) + return -ENOMEM; + + guitar->client = client; + + err = gip_set_power_mode(client, GIP_PWR_ON); + if (err) + return err; + + err = gip_init_battery(&guitar->battery, client, GIP_JA_NAME); + if (err) + return err; + + err = gip_auth_start_handshake(&guitar->auth, client); + if (err) + return err; + + err = gip_init_input(&guitar->input, client, GIP_JA_NAME); + if (err) + return err; + + err = gip_jaguar_init_input(guitar); + if (err) + return err; + + dev_set_drvdata(&client->dev, guitar); + + return 0; +} + +static struct gip_driver gip_jaguar_driver = { + .name = "xone-gip-pdp-jaguar", + .class = "PDP.Xbox.Guitar.Jaguar", + .ops = { + .battery = gip_jaguar_op_battery, + .authenticate = gip_jaguar_op_authenticate, + .guide_button = gip_jaguar_op_guide_button, + .input = gip_jaguar_op_input, + }, + .probe = gip_jaguar_probe, +}; +module_gip_driver(gip_jaguar_driver); + +MODULE_ALIAS("gip:PDP.Xbox.Guitar.Jaguar"); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); +MODULE_AUTHOR("Scott K Logan "); +MODULE_DESCRIPTION("xone GIP PDP Jaguar driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/custom/xonedo/install/modprobe.conf b/drivers/custom/xonedo/install/modprobe.conf new file mode 100644 index 000000000000..11da85aced6c --- /dev/null +++ b/drivers/custom/xonedo/install/modprobe.conf @@ -0,0 +1 @@ +blacklist mt76x2u diff --git a/drivers/custom/xonedo/transport/dongle.c b/drivers/custom/xonedo/transport/dongle.c new file mode 100644 index 000000000000..fd4506b4f823 --- /dev/null +++ b/drivers/custom/xonedo/transport/dongle.c @@ -0,0 +1,1296 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck-Lipinski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mt76.h" +#include "../bus/bus.h" + +#define XONE_DONGLE_NUM_IN_URBS 12 +#define XONE_DONGLE_NUM_OUT_URBS 12 + +#define XONE_DONGLE_LEN_CMD_PKT 0x0654 +#define XONE_DONGLE_LEN_WLAN_PKT 0x8400 + +#define XONE_DONGLE_MAX_CLIENTS 16 + +#define XONE_DONGLE_PAIRING_TIMEOUT msecs_to_jiffies(60000) +#define XONE_DONGLE_PWR_OFF_TIMEOUT msecs_to_jiffies(5000) +#define XONE_DONGLE_FW_REQ_TIMEOUT_MS 3000 +#define XONE_DONGLE_FW_REQ_RETRIES 11 // 30 seconds +#define XONE_DONGLE_FW_LOAD_RETRIES 3 + +enum xone_dongle_queue { + XONE_DONGLE_QUEUE_DATA = 0x00, + XONE_DONGLE_QUEUE_AUDIO = 0x02, +}; + +enum xone_dongle_fw_state { + XONE_DONGLE_FW_STATE_PENDING, + XONE_DONGLE_FW_STATE_STOP_LOADING, + XONE_DONGLE_FW_STATE_ERROR, + XONE_DONGLE_FW_STATE_READY, +}; + +struct xone_dongle_skb_cb { + struct xone_dongle *dongle; + struct urb *urb; +}; + +struct xone_dongle_client { + struct xone_dongle *dongle; + u8 wcid; + u8 address[ETH_ALEN]; + bool encryption_enabled; + + struct gip_adapter *adapter; +}; + +enum xone_dongle_event_type { + XONE_DONGLE_EVT_ADD_CLIENT, + XONE_DONGLE_EVT_REMOVE_CLIENT, + XONE_DONGLE_EVT_PAIR_CLIENT, + XONE_DONGLE_EVT_ENABLE_PAIRING, + XONE_DONGLE_EVT_ENABLE_ENCRYPTION, +}; + +struct xone_dongle_event { + enum xone_dongle_event_type type; + + struct xone_dongle *dongle; + u8 address[ETH_ALEN]; + u8 wcid; + + struct work_struct work; +}; + +struct xone_dongle { + struct xone_mt76 mt; + + struct usb_anchor urbs_in_idle; + struct usb_anchor urbs_in_busy; + struct usb_anchor urbs_out_idle; + struct usb_anchor urbs_out_busy; + + /* serializes pairing changes */ + struct mutex pairing_lock; + struct delayed_work pairing_work; + bool pairing; + + /* serializes access to clients array */ + spinlock_t clients_lock; + struct xone_dongle_client *clients[XONE_DONGLE_MAX_CLIENTS]; + atomic_t client_count; + wait_queue_head_t disconnect_wait; + + struct workqueue_struct *event_wq; + struct work_struct load_fw_work; + + enum xone_dongle_fw_state fw_state; + u16 vendor; + u16 product; +}; + +static void xone_dongle_prep_packet(struct xone_dongle_client *client, + struct sk_buff *skb, + enum xone_dongle_queue queue) +{ + struct ieee80211_qos_hdr hdr = {}; + struct mt76_txwi txwi = {}; + u8 data[] = { + 0x00, 0x00, queue, client->wcid - 1, 0x00, 0x00, 0x00, 0x00, + }; + + /* frame is sent from AP (DS) */ + /* duration is the time required to transmit (in μs) */ + hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_QOS_DATA | + IEEE80211_FCTL_FROMDS); + + /* encrypt frame on transmission */ + if (client->encryption_enabled) + hdr.frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED); + + hdr.duration_id = cpu_to_le16(144); + memcpy(hdr.addr1, client->address, ETH_ALEN); + memcpy(hdr.addr2, client->dongle->mt.address, ETH_ALEN); + memcpy(hdr.addr3, client->dongle->mt.address, ETH_ALEN); + + /* wait for acknowledgment */ + txwi.flags = cpu_to_le16(FIELD_PREP(MT_TXWI_FLAGS_MPDU_DENSITY, + IEEE80211_HT_MPDU_DENSITY_4)); + txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); + txwi.ack_ctl = MT_TXWI_ACK_CTL_REQ; + txwi.wcid = client->wcid - 1; + txwi.len_ctl = cpu_to_le16(sizeof(hdr) + skb->len); + + memset(skb_push(skb, 2), 0, 2); + memcpy(skb_push(skb, sizeof(hdr)), &hdr, sizeof(hdr)); + memcpy(skb_push(skb, sizeof(txwi)), &txwi, sizeof(txwi)); + memcpy(skb_push(skb, sizeof(data)), data, sizeof(data)); + + xone_mt76_prep_command(skb, 0); +} + +static int xone_dongle_get_buffer(struct gip_adapter *adap, + struct gip_adapter_buffer *buf) +{ + struct xone_dongle_client *client = dev_get_drvdata(&adap->dev); + struct xone_dongle_skb_cb *cb; + struct urb *urb; + struct sk_buff *skb; + + urb = usb_get_from_anchor(&client->dongle->urbs_out_idle); + if (!urb) + return -ENOSPC; + + skb = xone_mt76_alloc_message(XONE_DONGLE_LEN_CMD_PKT, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + /* command header + WCID data + TXWI + QoS header + padding */ + /* see xone_dongle_prep_packet and xone_mt76_prep_message */ + skb_reserve(skb, MT_CMD_HDR_LEN + 8 + sizeof(struct mt76_txwi) + + sizeof(struct ieee80211_qos_hdr) + 2 + MT_CMD_HDR_LEN); + + cb = (struct xone_dongle_skb_cb *)skb->cb; + cb->dongle = client->dongle; + cb->urb = urb; + + buf->context = skb; + buf->data = skb->data; + buf->length = skb_tailroom(skb); + + return 0; +} + +static int xone_dongle_submit_buffer(struct gip_adapter *adap, + struct gip_adapter_buffer *buf) +{ + struct xone_dongle_client *client = dev_get_drvdata(&adap->dev); + struct xone_dongle_skb_cb *cb; + struct sk_buff *skb = buf->context; + int err; + + skb_put(skb, buf->length); + + if (buf->type == GIP_BUF_DATA) + xone_dongle_prep_packet(client, skb, XONE_DONGLE_QUEUE_DATA); + else if (buf->type == GIP_BUF_AUDIO) + xone_dongle_prep_packet(client, skb, XONE_DONGLE_QUEUE_AUDIO); + else + return -EINVAL; + + cb = (struct xone_dongle_skb_cb *)skb->cb; + cb->urb->context = skb; + cb->urb->transfer_buffer = skb->data; + cb->urb->transfer_buffer_length = skb->len; + usb_anchor_urb(cb->urb, &client->dongle->urbs_out_busy); + + err = usb_submit_urb(cb->urb, GFP_ATOMIC); + if (err) { + usb_unanchor_urb(cb->urb); + usb_anchor_urb(cb->urb, &client->dongle->urbs_out_idle); + dev_kfree_skb_any(skb); + } + + usb_free_urb(cb->urb); + + return err; +} + +static int xone_dongle_set_encryption_key(struct gip_adapter *adap, + u8 *key, int len) +{ + struct xone_dongle_client *client = dev_get_drvdata(&adap->dev); + + return xone_mt76_set_client_key(&client->dongle->mt, client->wcid, + key, len); +} + +static struct gip_adapter_ops xone_dongle_adapter_ops = { + .get_buffer = xone_dongle_get_buffer, + .submit_buffer = xone_dongle_submit_buffer, + .set_encryption_key = xone_dongle_set_encryption_key, +}; + +static int xone_dongle_toggle_pairing(struct xone_dongle *dongle, bool enable) +{ + enum xone_mt76_led_mode led; + int err = 0; + + mutex_lock(&dongle->pairing_lock); + + /* pairing is already enabled/disabled */ + if (dongle->pairing == enable) + goto err_unlock; + + err = xone_mt76_set_pairing(&dongle->mt, enable); + if (err) + goto err_unlock; + + if (enable) + led = XONE_MT_LED_BLINK; + else if (atomic_read(&dongle->client_count)) + led = XONE_MT_LED_ON; + else + led = XONE_MT_LED_OFF; + + err = xone_mt76_set_led_mode(&dongle->mt, led); + if (err) + goto err_unlock; + + dev_dbg(dongle->mt.dev, "%s: enabled=%d\n", __func__, enable); + dongle->pairing = enable; + + if (enable) + mod_delayed_work(system_wq, &dongle->pairing_work, + XONE_DONGLE_PAIRING_TIMEOUT); + +err_unlock: + mutex_unlock(&dongle->pairing_lock); + + return err; +} + +static void xone_dongle_pairing_timeout(struct work_struct *work) +{ + struct xone_dongle *dongle = container_of(to_delayed_work(work), + typeof(*dongle), + pairing_work); + int err; + + if (!dongle) + return; + + err = xone_dongle_toggle_pairing(dongle, false); + if (err) + dev_err(dongle->mt.dev, "%s: disable pairing failed: %d\n", + __func__, err); +} + +static ssize_t xone_dongle_pairing_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct xone_dongle *dongle = usb_get_intfdata(intf); + + return sysfs_emit(buf, "%d\n", dongle->pairing); +} + +static ssize_t xone_dongle_pairing_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct xone_dongle *dongle = usb_get_intfdata(intf); + bool enable; + int err; + + err = kstrtobool(buf, &enable); + if (err) + return err; + + err = pm_runtime_resume_and_get(dev); + if (err) + return err; + + err = xone_dongle_toggle_pairing(dongle, enable); + if (err) + return err; + + pm_runtime_put(dev); + + return count; +} + +static struct device_attribute xone_dongle_attr_pairing = + __ATTR(pairing, 0644, + xone_dongle_pairing_show, + xone_dongle_pairing_store); + +static struct attribute *xone_dongle_attrs[] = { + &xone_dongle_attr_pairing.attr, + NULL, +}; +ATTRIBUTE_GROUPS(xone_dongle); + +static struct xone_dongle_client * +xone_dongle_create_client(struct xone_dongle *dongle, u8 *addr) +{ + struct xone_dongle_client *client; + int i, err; + + /* find free WCID */ + for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) + if (!dongle->clients[i]) + break; + + if (i == XONE_DONGLE_MAX_CLIENTS) + return ERR_PTR(-ENOSPC); + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + client->dongle = dongle; + client->wcid = i + 1; + memcpy(client->address, addr, ETH_ALEN); + + client->adapter = gip_create_adapter(dongle->mt.dev, + &xone_dongle_adapter_ops, 1); + if (IS_ERR(client->adapter)) { + err = PTR_ERR(client->adapter); + kfree(client); + return ERR_PTR(err); + } + + dev_set_drvdata(&client->adapter->dev, client); + + return client; +} + +static int xone_dongle_add_client(struct xone_dongle *dongle, u8 *addr) +{ + struct xone_dongle_client *client; + int err; + unsigned long flags; + + client = xone_dongle_create_client(dongle, addr); + if (IS_ERR(client)) + return PTR_ERR(client); + + err = xone_mt76_associate_client(&dongle->mt, client->wcid, addr); + if (err) + goto err_free_client; + + if (!dongle->pairing) { + err = xone_mt76_set_led_mode(&dongle->mt, XONE_MT_LED_ON); + if (err) + goto err_free_client; + } + + dev_dbg(dongle->mt.dev, "%s: wcid=%d, address=%pM\n", + __func__, client->wcid, addr); + + spin_lock_irqsave(&dongle->clients_lock, flags); + dongle->clients[client->wcid - 1] = client; + spin_unlock_irqrestore(&dongle->clients_lock, flags); + + atomic_inc(&dongle->client_count); + + return 0; + +err_free_client: + gip_destroy_adapter(client->adapter); + kfree(client); + + return err; +} + +static int xone_dongle_remove_client(struct xone_dongle *dongle, u8 wcid) +{ + struct xone_dongle_client *client; + int err; + unsigned long flags; + + client = dongle->clients[wcid - 1]; + if (!client) + return 0; + + dev_dbg(dongle->mt.dev, "%s: wcid=%d, address=%pM\n", + __func__, wcid, client->address); + + spin_lock_irqsave(&dongle->clients_lock, flags); + dongle->clients[wcid - 1] = NULL; + spin_unlock_irqrestore(&dongle->clients_lock, flags); + + gip_destroy_adapter(client->adapter); + kfree(client); + + err = xone_mt76_remove_client(&dongle->mt, wcid); + if (err) + dev_err(dongle->mt.dev, "%s: remove failed: %d\n", + __func__, err); + + /* turn off LED if all clients have disconnected */ + if (atomic_dec_and_test(&dongle->client_count) && !dongle->pairing) + err = xone_mt76_set_led_mode(&dongle->mt, XONE_MT_LED_OFF); + + wake_up(&dongle->disconnect_wait); + return err; +} + +static int xone_dongle_pair_client(struct xone_dongle *dongle, u8 *addr) +{ + int err; + + dev_dbg(dongle->mt.dev, "%s: address=%pM\n", __func__, addr); + + err = xone_mt76_pair_client(&dongle->mt, addr); + if (err) + return err; + + return xone_dongle_toggle_pairing(dongle, false); +} + +static int xone_dongle_enable_client_encryption(struct xone_dongle *dongle, + u8 wcid) +{ + struct xone_dongle_client *client; + u8 data[] = { 0x00, 0x00 }; + int err; + + client = dongle->clients[wcid - 1]; + if (!client) + return -EINVAL; + + dev_dbg(dongle->mt.dev, "%s: wcid=%d, address=%pM\n", + __func__, wcid, client->address); + + err = xone_mt76_send_client_command(&dongle->mt, wcid, client->address, + XONE_MT_CLIENT_ENABLE_ENCRYPTION, + data, sizeof(data)); + if (err) + return err; + + client->encryption_enabled = true; + + return 0; +} + +static void xone_dongle_handle_event(struct work_struct *work) +{ + struct xone_dongle_event *evt = container_of(work, typeof(*evt), work); + int err = 0; + + /* Do not process events when firmware is not ready */ + if (evt->dongle->fw_state < XONE_DONGLE_FW_STATE_READY) { + pr_debug("%s: firmware not loaded yet", __func__); + goto handle_event_free; + } + + switch (evt->type) { + case XONE_DONGLE_EVT_ADD_CLIENT: + pr_debug("%s: XONE_DONGLE_EVT_ADD_CLIENT", __func__); + err = xone_dongle_add_client(evt->dongle, evt->address); + break; + case XONE_DONGLE_EVT_REMOVE_CLIENT: + pr_debug("%s: XONE_DONGLE_EVT_REMOVE_CLIENT", __func__); + err = xone_dongle_remove_client(evt->dongle, evt->wcid); + break; + case XONE_DONGLE_EVT_PAIR_CLIENT: + pr_debug("%s: XONE_DONGLE_EVT_PAIR_CLIENT", __func__); + err = xone_dongle_pair_client(evt->dongle, evt->address); + break; + case XONE_DONGLE_EVT_ENABLE_PAIRING: + pr_debug("%s: XONE_DONGLE_EVT_ENABLE_PAIRING", __func__); + err = xone_dongle_toggle_pairing(evt->dongle, true); + break; + case XONE_DONGLE_EVT_ENABLE_ENCRYPTION: + pr_debug("%s: XONE_DONGLE_EVT_ENABLE_ENCRYPTION", __func__); + err = xone_dongle_enable_client_encryption(evt->dongle, + evt->wcid); + break; + } + + if (err) + dev_err(evt->dongle->mt.dev, "%s: handle event failed: %d\n", + __func__, err); + +handle_event_free: + kfree(evt); +} + +static struct xone_dongle_event * +xone_dongle_alloc_event(struct xone_dongle *dongle, + enum xone_dongle_event_type type) +{ + struct xone_dongle_event *evt; + + evt = kzalloc(sizeof(*evt), GFP_ATOMIC); + if (!evt) + return NULL; + + evt->type = type; + evt->dongle = dongle; + INIT_WORK(&evt->work, xone_dongle_handle_event); + + return evt; +} + +static int xone_dongle_handle_qos_data(struct xone_dongle *dongle, + struct sk_buff *skb, u8 wcid) +{ + struct xone_dongle_client *client; + int err = 0; + unsigned long flags; + + if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS) + return 0; + + spin_lock_irqsave(&dongle->clients_lock, flags); + + client = dongle->clients[wcid - 1]; + if (client) + err = gip_process_buffer(client->adapter, skb->data, skb->len); + + spin_unlock_irqrestore(&dongle->clients_lock, flags); + + return err; +} + +static int xone_dongle_handle_association(struct xone_dongle *dongle, u8 *addr) +{ + struct xone_dongle_event *evt; + + evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_ADD_CLIENT); + if (!evt) + return -ENOMEM; + + memcpy(evt->address, addr, ETH_ALEN); + + queue_work(dongle->event_wq, &evt->work); + + return 0; +} + +static int xone_dongle_handle_disassociation(struct xone_dongle *dongle, + u8 wcid) +{ + struct xone_dongle_event *evt; + + if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS) + return 0; + + evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_REMOVE_CLIENT); + if (!evt) + return -ENOMEM; + + evt->wcid = wcid; + + queue_work(dongle->event_wq, &evt->work); + + return 0; +} + +static int xone_dongle_handle_client_command(struct xone_dongle *dongle, + struct sk_buff *skb, + u8 wcid, u8 *addr) +{ + struct xone_dongle_event *evt; + enum xone_dongle_event_type evt_type; + + if (skb->len < 2 || skb->data[0] != XONE_MT_WLAN_RESERVED) + return -EINVAL; + + switch (skb->data[1]) { + case XONE_MT_CLIENT_PAIR_REQ: + evt_type = XONE_DONGLE_EVT_PAIR_CLIENT; + break; + case XONE_MT_CLIENT_ENABLE_ENCRYPTION: + if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS) + return -EINVAL; + + evt_type = XONE_DONGLE_EVT_ENABLE_ENCRYPTION; + break; + default: + return 0; + } + + evt = xone_dongle_alloc_event(dongle, evt_type); + if (!evt) + return -ENOMEM; + + evt->wcid = wcid; + memcpy(evt->address, addr, ETH_ALEN); + + queue_work(dongle->event_wq, &evt->work); + + return 0; +} + +static int xone_dongle_handle_button(struct xone_dongle *dongle) +{ + struct xone_dongle_event *evt; + + evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_ENABLE_PAIRING); + if (!evt) + return -ENOMEM; + + queue_work(dongle->event_wq, &evt->work); + + return 0; +} + +static int xone_dongle_handle_loss(struct xone_dongle *dongle, + struct sk_buff *skb) +{ + u8 wcid; + + if (skb->len < sizeof(wcid)) + return -EINVAL; + + wcid = skb->data[0]; + if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS) + return 0; + + dev_dbg(dongle->mt.dev, "%s: wcid=%d\n", __func__, wcid); + + return xone_dongle_handle_disassociation(dongle, wcid); +} + +static int xone_dongle_process_frame(struct xone_dongle *dongle, + struct sk_buff *skb, + unsigned int hdr_len, u8 wcid) +{ + struct ieee80211_hdr_3addr *hdr = + (struct ieee80211_hdr_3addr *)skb->data; + u16 type; + + /* ignore invalid frames */ + if (skb->len < hdr_len || hdr_len < sizeof(*hdr)) + return 0; + + skb_pull(skb, hdr_len); + type = le16_to_cpu(hdr->frame_control); + + switch (type & (IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) { + case IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_DATA: + return xone_dongle_handle_qos_data(dongle, skb, wcid); + case IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ASSOC_REQ: + return xone_dongle_handle_association(dongle, hdr->addr2); + case IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_DISASSOC: + return xone_dongle_handle_disassociation(dongle, wcid); + case IEEE80211_FTYPE_MGMT | XONE_MT_WLAN_RESERVED: + return xone_dongle_handle_client_command(dongle, skb, wcid, + hdr->addr2); + } + + return 0; +} + +static int xone_dongle_process_wlan(struct xone_dongle *dongle, + struct sk_buff *skb) +{ + struct mt76_rxwi *rxwi = (struct mt76_rxwi *)skb->data; + unsigned int hdr_len; + u32 ctl; + + if (skb->len < sizeof(*rxwi)) + return -EINVAL; + + skb_pull(skb, sizeof(*rxwi)); + hdr_len = ieee80211_get_hdrlen_from_skb(skb); + + /* 2 bytes of padding after 802.11 header */ + if (rxwi->rxinfo & cpu_to_le32(MT_RXINFO_L2PAD)) { + if (skb->len < hdr_len + 2) + return -EINVAL; + + memmove(skb->data + 2, skb->data, hdr_len); + skb_pull(skb, 2); + } + + ctl = le32_to_cpu(rxwi->ctl); + skb_trim(skb, FIELD_GET(MT_RXWI_CTL_MPDU_LEN, ctl)); + + return xone_dongle_process_frame(dongle, skb, hdr_len, + FIELD_GET(MT_RXWI_CTL_WCID, ctl)); +} + +static int xone_dongle_process_message(struct xone_dongle *dongle, + struct sk_buff *skb) +{ + enum mt76_dma_msg_port port; + u32 info; + + /* command header + trailer */ + if (skb->len < MT_CMD_HDR_LEN * 2) + return -EINVAL; + + info = get_unaligned_le32(skb->data); + port = FIELD_GET(MT_RX_FCE_INFO_D_PORT, info); + + /* ignore command reponses */ + if (FIELD_GET(MT_RX_FCE_INFO_CMD_SEQ, info) == 0x01) + return 0; + + /* remove header + trailer */ + skb_pull(skb, MT_CMD_HDR_LEN); + skb_trim(skb, skb->len - MT_CMD_HDR_LEN); + + if (port == MT_WLAN_PORT) + return xone_dongle_process_wlan(dongle, skb); + + if (port != MT_CPU_RX_PORT) + return 0; + + switch (FIELD_GET(MT_RX_FCE_INFO_EVT_TYPE, info)) { + case XONE_MT_EVT_BUTTON: + return xone_dongle_handle_button(dongle); + case XONE_MT_EVT_PACKET_RX: + return xone_dongle_process_wlan(dongle, skb); + case XONE_MT_EVT_CLIENT_LOST: + return xone_dongle_handle_loss(dongle, skb); + } + + return 0; +} + +static int xone_dongle_process_buffer(struct xone_dongle *dongle, + void *data, int len) +{ + struct sk_buff *skb; + int err; + + if (!len) + return 0; + + skb = dev_alloc_skb(len); + if (!skb) + return -ENOMEM; + + skb_put_data(skb, data, len); + + err = xone_dongle_process_message(dongle, skb); + if (err) { + dev_err(dongle->mt.dev, "%s: process failed: %d\n", + __func__, err); + print_hex_dump_bytes("xone-dongle packet: ", DUMP_PREFIX_NONE, + data, len); + } + + dev_kfree_skb(skb); + + return err; +} + +static void xone_dongle_complete_in(struct urb *urb) +{ + struct xone_dongle *dongle = urb->context; + int err; + + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + usb_anchor_urb(urb, &dongle->urbs_in_idle); + return; + default: + goto resubmit; + } + + err = xone_dongle_process_buffer(dongle, urb->transfer_buffer, + urb->actual_length); + if (err) + dev_err(dongle->mt.dev, "%s: process failed: %d\n", + __func__, err); + +resubmit: + /* can fail during USB device removal */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + dev_dbg(dongle->mt.dev, "%s: submit failed: %d\n", + __func__, err); + usb_anchor_urb(urb, &dongle->urbs_in_idle); + } else { + usb_anchor_urb(urb, &dongle->urbs_in_busy); + } +} + +static void xone_dongle_complete_out(struct urb *urb) +{ + struct sk_buff *skb = urb->context; + struct xone_dongle_skb_cb *cb = (struct xone_dongle_skb_cb *)skb->cb; + + usb_anchor_urb(urb, &cb->dongle->urbs_out_idle); + dev_consume_skb_any(skb); +} + +static int xone_dongle_init_urbs_in(struct xone_dongle *dongle, + int ep, int buf_len) +{ + struct xone_mt76 *mt = &dongle->mt; + struct urb *urb; + void *buf; + int i, err; + + for (i = 0; i < XONE_DONGLE_NUM_IN_URBS; i++) { + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + usb_anchor_urb(urb, &dongle->urbs_in_busy); + usb_free_urb(urb); + + buf = usb_alloc_coherent(mt->udev, buf_len, + GFP_KERNEL, &urb->transfer_dma); + if (!buf) + return -ENOMEM; + + usb_fill_bulk_urb(urb, mt->udev, + usb_rcvbulkpipe(mt->udev, ep), buf, buf_len, + xone_dongle_complete_in, dongle); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + err = usb_submit_urb(urb, GFP_KERNEL); + if (err) + return err; + } + + return 0; +} + +static int xone_dongle_init_urbs_out(struct xone_dongle *dongle) +{ + struct xone_mt76 *mt = &dongle->mt; + struct urb *urb; + int i; + + for (i = 0; i < XONE_DONGLE_NUM_OUT_URBS; i++) { + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + usb_fill_bulk_urb(urb, mt->udev, + usb_sndbulkpipe(mt->udev, XONE_MT_EP_OUT), + NULL, 0, xone_dongle_complete_out, NULL); + usb_anchor_urb(urb, &dongle->urbs_out_idle); + usb_free_urb(urb); + } + + return 0; +} + +static int xone_dongle_fw_requester(const struct firmware **fw, + struct xone_dongle *dongle, + const char *fwname) +{ + struct device *dev = dongle->mt.dev; + int err; + + dev_dbg(dev, "%s: trying to load firmware %s\n", __func__, fwname); + for (int i = 0; i < XONE_DONGLE_FW_REQ_RETRIES; ++i) { + if (dongle->fw_state == XONE_DONGLE_FW_STATE_STOP_LOADING) { + pr_debug("%s: Stopping firmware load on demand", __func__); + return 1; + } + + dev_dbg(dev, "%s: attempt: %d\n", __func__, i + 1); + err = request_firmware(fw, fwname, dev); + if (!err) + return 0; + + msleep(XONE_DONGLE_FW_REQ_TIMEOUT_MS); + } + + return err; +} + +static void xone_dongle_fw_load(struct work_struct *work) +{ + struct xone_dongle *dongle = + container_of(work, struct xone_dongle, load_fw_work); + + struct xone_mt76 *mt = &dongle->mt; + const struct firmware *fw; + char fwname[21]; + int err; + + sprintf(fwname, "xone_dongle_%04x.bin", dongle->product); + err = xone_dongle_fw_requester(&fw, dongle, fwname); + if (dongle->fw_state == XONE_DONGLE_FW_STATE_STOP_LOADING) { + dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; + return; + } + + if (err) { + dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; + dev_err(mt->dev, "%s: request firmware failed: %d\n", __func__, + err); + return; + } + dev_dbg(mt->dev, "%s: firmware requested successfully\n", __func__); + + + err = xone_mt76_load_firmware(mt, fw); + release_firmware(fw); + if (err) { + dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; + dev_err(mt->dev, "%s: load firmware failed: %d\n", + __func__, err); + return; + } + + err = xone_dongle_init_urbs_out(dongle); + if (err) { + dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; + return; + } + + err = xone_dongle_init_urbs_in(dongle, XONE_MT_EP_IN_CMD, + XONE_DONGLE_LEN_CMD_PKT); + if (err) { + dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; + return; + } + + err = xone_dongle_init_urbs_in(dongle, XONE_MT_EP_IN_WLAN, + XONE_DONGLE_LEN_WLAN_PKT); + if (err) { + dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; + return; + } + + err = xone_mt76_init_radio(mt); + if (err){ + dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; + dev_err(mt->dev, "%s: init radio failed: %d\n", __func__, err); + return; + } + + dongle->fw_state = XONE_DONGLE_FW_STATE_READY; + + device_wakeup_enable(&dongle->mt.udev->dev); +} + +static int xone_dongle_init(struct xone_dongle *dongle) +{ + init_usb_anchor(&dongle->urbs_out_idle); + init_usb_anchor(&dongle->urbs_out_busy); + init_usb_anchor(&dongle->urbs_in_idle); + init_usb_anchor(&dongle->urbs_in_busy); + + dongle->fw_state = XONE_DONGLE_FW_STATE_PENDING; + schedule_work(&dongle->load_fw_work); + return 0; +} + +static int xone_dongle_power_off_clients(struct xone_dongle *dongle) +{ + struct xone_dongle_client *client; + int i; + int err = 0; + unsigned long flags; + + if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY) + return 0; + + spin_lock_irqsave(&dongle->clients_lock, flags); + + for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) { + client = dongle->clients[i]; + if (!client) + continue; + + err = gip_power_off_adapter(client->adapter); + if (err) + break; + } + + spin_unlock_irqrestore(&dongle->clients_lock, flags); + + if (err) + return err; + + /* can time out if new client connects */ + if (!wait_event_timeout(dongle->disconnect_wait, + !atomic_read(&dongle->client_count), + XONE_DONGLE_PWR_OFF_TIMEOUT)) + return -ETIMEDOUT; + + return xone_dongle_toggle_pairing(dongle, false); +} + +static void xone_dongle_destroy(struct xone_dongle *dongle) +{ + struct xone_dongle_client *client; + struct urb *urb; + int i; + + usb_kill_anchored_urbs(&dongle->urbs_in_busy); + destroy_workqueue(dongle->event_wq); + cancel_delayed_work(&dongle->pairing_work); + + if (dongle->fw_state < XONE_DONGLE_FW_STATE_ERROR) { + pr_debug("%s: Firmware not loaded, stopping work", __func__); + dongle->fw_state = XONE_DONGLE_FW_STATE_STOP_LOADING; + pr_debug("%s: Waiting for fw load work to finish", __func__); + + while (dongle->fw_state == XONE_DONGLE_FW_STATE_STOP_LOADING) + msleep(500); + + pr_debug("%s: FW loading cancelled", __func__); + } + + for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) { + client = dongle->clients[i]; + if (!client) + continue; + + gip_destroy_adapter(client->adapter); + kfree(client); + dongle->clients[i] = NULL; + } + + usb_kill_anchored_urbs(&dongle->urbs_out_busy); + + while ((urb = usb_get_from_anchor(&dongle->urbs_out_idle))) + usb_free_urb(urb); + + while ((urb = usb_get_from_anchor(&dongle->urbs_in_idle))) { + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); + } + + mutex_destroy(&dongle->pairing_lock); +} + +static int xone_dongle_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct xone_dongle *dongle; + int err; + + dongle = devm_kzalloc(&intf->dev, sizeof(*dongle), GFP_KERNEL); + if (!dongle) + return -ENOMEM; + + dongle->mt.dev = &intf->dev; + dongle->mt.udev = interface_to_usbdev(intf); + + dongle->vendor = id->idVendor; + dongle->product = id->idProduct; + + dongle->event_wq = alloc_ordered_workqueue("xone_dongle", 0); + if (!dongle->event_wq) + return -ENOMEM; + + mutex_init(&dongle->pairing_lock); + INIT_DELAYED_WORK(&dongle->pairing_work, xone_dongle_pairing_timeout); + INIT_WORK(&dongle->load_fw_work, xone_dongle_fw_load); + spin_lock_init(&dongle->clients_lock); + init_waitqueue_head(&dongle->disconnect_wait); + + usb_reset_device(dongle->mt.udev); + err = xone_dongle_init(dongle); + if (err) { + xone_dongle_destroy(dongle); + return err; + } + + usb_set_intfdata(intf, dongle); + + err = device_add_groups(&intf->dev, xone_dongle_groups); + if (err) { + xone_dongle_destroy(dongle); + return err; + } + + /* enable USB remote wakeup and autosuspend */ + intf->needs_remote_wakeup = true; + return 0; +} + +static void xone_dongle_disconnect(struct usb_interface *intf) +{ + struct xone_dongle *dongle = usb_get_intfdata(intf); + int err; + + device_remove_groups(&intf->dev, xone_dongle_groups); + + /* can fail during USB device removal */ + err = xone_dongle_power_off_clients(dongle); + if (err) + dev_dbg(dongle->mt.dev, "%s: power off failed: %d\n", + __func__, err); + + xone_dongle_destroy(dongle); + usb_set_intfdata(intf, NULL); +} + +static int xone_dongle_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct xone_dongle *dongle = usb_get_intfdata(intf); + int err; + + if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY){ + pr_debug("%s: Skipping radio suspend", __func__); + return 0; + } + + err = xone_dongle_power_off_clients(dongle); + if (err) + dev_err(dongle->mt.dev, "%s: power off failed: %d\n", + __func__, err); + + usb_kill_anchored_urbs(&dongle->urbs_in_busy); + usb_kill_anchored_urbs(&dongle->urbs_out_busy); + cancel_delayed_work(&dongle->pairing_work); + + return xone_mt76_suspend_radio(&dongle->mt); +} + +static int xone_dongle_resume(struct usb_interface *intf) +{ + struct xone_dongle *dongle = usb_get_intfdata(intf); + struct urb *urb; + int err; + + if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY) { + pr_debug("%s: Skipping radio resume", __func__); + return 0; + } + + while ((urb = usb_get_from_anchor(&dongle->urbs_in_idle))) { + usb_anchor_urb(urb, &dongle->urbs_in_busy); + usb_free_urb(urb); + + err = usb_submit_urb(urb, GFP_KERNEL); + if (err) + return err; + } + return xone_mt76_resume_radio(&dongle->mt); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) +static void xone_dongle_shutdown(struct device *dev) +{ + struct usb_interface *intf = to_usb_interface(dev); +#else +static void xone_dongle_shutdown(struct usb_interface *intf) +{ +#endif + struct xone_dongle *dongle = usb_get_intfdata(intf); + int err; + + if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY) + dongle->fw_state = XONE_DONGLE_FW_STATE_STOP_LOADING; + + if (system_state == SYSTEM_RESTART) + return; + + err = xone_dongle_power_off_clients(dongle); + if (err) + dev_err(dongle->mt.dev, "%s: power off failed: %d\n", + __func__, err); +} + +static int xone_dongle_pre_reset(struct usb_interface *intf) +{ + struct xone_dongle *dongle = usb_get_intfdata(intf); + struct urb *urb; + + pr_debug("%s", __func__); + + /* For reset during probe */ + if (!dongle) + return 0; + + if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY) + dongle->fw_state = XONE_DONGLE_FW_STATE_STOP_LOADING; + + cancel_delayed_work(&dongle->pairing_work); + usb_kill_anchored_urbs(&dongle->urbs_in_busy); + usb_kill_anchored_urbs(&dongle->urbs_out_busy); + + while ((urb = usb_get_from_anchor(&dongle->urbs_out_idle))) + usb_free_urb(urb); + + while ((urb = usb_get_from_anchor(&dongle->urbs_in_idle))) { + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); + } + + return 0; +} + +static int xone_dongle_post_reset(struct usb_interface *intf) +{ + struct xone_dongle *dongle = usb_get_intfdata(intf); + + pr_debug("%s", __func__); + + /* For reset during probe */ + if (!dongle) + return 0; + + pr_debug("%s: Re-initializing dongle after reset", __func__); + return xone_dongle_init(dongle); +} + +static int xone_dongle_reset_resume(struct usb_interface *intf) +{ + struct xone_dongle *dongle = usb_get_intfdata(intf); + int err; + + pr_debug("%s", __func__); + + err = usb_reset_device(dongle->mt.udev); + if (err == -EINPROGRESS) { + pr_debug("%s: Reset already in progress", __func__); + return 0; + } + + return err; +} + +static const struct usb_device_id xone_dongle_id_table[] = { + { USB_DEVICE(0x045e, 0x02e6) }, /* old dongle */ + { USB_DEVICE(0x045e, 0x02fe) }, /* new dongle */ + { USB_DEVICE(0x045e, 0x02f9) }, /* built-in dongle (ASUS, Lenovo) */ + { USB_DEVICE(0x045e, 0x091e) }, /* built-in dongle (Surface Book 2) */ + { }, +}; + +static struct usb_driver xone_dongle_driver = { + .name = "xone-dongle", + .probe = xone_dongle_probe, + .disconnect = xone_dongle_disconnect, + .id_table = xone_dongle_id_table, + +#ifdef CONFIG_PM + .suspend = xone_dongle_suspend, + .resume = xone_dongle_resume, + .reset_resume = xone_dongle_reset_resume, +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) + .drvwrap.driver.shutdown = xone_dongle_shutdown, +#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) + .driver.shutdown = xone_dongle_shutdown, +#else + .shutdown = xone_dongle_shutdown, +#endif + .pre_reset = xone_dongle_pre_reset, + .post_reset = xone_dongle_post_reset, + .supports_autosuspend = false, + .disable_hub_initiated_lpm = true, + .soft_unbind = true, +}; + +module_usb_driver(xone_dongle_driver); + +MODULE_DEVICE_TABLE(usb, xone_dongle_id_table); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); +MODULE_DESCRIPTION("xone dongle driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/custom/xonedo/transport/mt76.c b/drivers/custom/xonedo/transport/mt76.c new file mode 100644 index 000000000000..11403efd0a23 --- /dev/null +++ b/drivers/custom/xonedo/transport/mt76.c @@ -0,0 +1,1266 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck-Lipinski + */ + +#include +#include +#include +#include +#include +#include + +#include "mt76.h" + +/* bulk transfer timeout in ms */ +#define XONE_MT_USB_TIMEOUT 1000 + +#define XONE_MT_POLL_RETRIES 50 + +#define XONE_MT_RF_PATCH 0x0130 +#define XONE_MT_FW_LOAD_IVB 0x12 +#define XONE_MT_FW_ILM_OFFSET 0x080000 +#define XONE_MT_FW_DLM_OFFSET 0x110800 +#define XONE_MT_FW_CHUNK_SIZE 0x3800 + +/* wireless channel bands */ +#define XONE_MT_CH_2G_LOW 0x01 +#define XONE_MT_CH_2G_MID 0x02 +#define XONE_MT_CH_2G_HIGH 0x03 +#define XONE_MT_CH_5G_LOW 0x01 +#define XONE_MT_CH_5G_HIGH 0x02 + +#define XONE_MT_WCID_KEY_LEN 16 + +/* commands specific to the dongle's firmware */ +enum xone_mt76_ms_command { + XONE_MT_SET_MAC_ADDRESS = 0x00, + XONE_MT_ADD_CLIENT = 0x01, + XONE_MT_REMOVE_CLIENT = 0x02, + XONE_MT_SET_IDLE_TIME = 0x05, + XONE_MT_SET_CHAN_CANDIDATES = 0x07, +}; + +enum xone_mt76_wow_feature { + XONE_MT_WOW_ENABLE = 0x01, + XONE_MT_WOW_TRAFFIC = 0x03, +}; + +enum xone_mt76_wow_traffic { + XONE_MT_WOW_TO_FIRMWARE = 0x00, + XONE_MT_WOW_TO_HOST = 0x01, +}; + +struct xone_mt76_msg_load_cr { + u8 mode; + u8 temperature; + u8 channel; + u8 padding; +} __packed; + +struct xone_mt76_msg_switch_channel { + u8 channel; + u8 padding1[3]; + __le16 tx_rx_setting; + u8 padding2[10]; + u8 bandwidth; + u8 tx_power; + u8 scan; + u8 unknown; +} __packed; + +static char override_mac[ETH_ALEN] = { 0 }; +module_param_array(override_mac, byte, NULL, 0444); +MODULE_PARM_DESC(override_mac, "Override MAC address (6 bytes), helps deconflict counterfeit adapters"); + +static u32 xone_mt76_read_register(struct xone_mt76 *mt, u32 addr) +{ + u8 req = MT_VEND_MULTI_READ; + int ret; + + if (addr & MT_VEND_TYPE_CFG) { + req = MT_VEND_READ_CFG; + addr &= ~MT_VEND_TYPE_CFG; + } + + ret = usb_control_msg(mt->udev, usb_rcvctrlpipe(mt->udev, 0), req, + USB_DIR_IN | USB_TYPE_VENDOR, addr >> 16, addr, + &mt->control_data, sizeof(mt->control_data), + XONE_MT_USB_TIMEOUT); + if (ret != sizeof(mt->control_data)) + ret = -EREMOTEIO; + + if (ret < 0) { + dev_err(mt->dev, "%s: control message failed: %d\n", + __func__, ret); + return 0; + } + + return le32_to_cpu(mt->control_data); +} + +static void xone_mt76_write_register(struct xone_mt76 *mt, u32 addr, u32 val) +{ + u8 req = MT_VEND_MULTI_WRITE; + int ret; + + if (addr & MT_VEND_TYPE_CFG) { + req = MT_VEND_WRITE_CFG; + addr &= ~MT_VEND_TYPE_CFG; + } + + mt->control_data = cpu_to_le32(val); + + ret = usb_control_msg(mt->udev, usb_sndctrlpipe(mt->udev, 0), req, + USB_DIR_OUT | USB_TYPE_VENDOR, addr >> 16, addr, + &mt->control_data, sizeof(mt->control_data), + XONE_MT_USB_TIMEOUT); + if (ret != sizeof(mt->control_data)) + ret = -EREMOTEIO; + + if (ret < 0) + dev_err(mt->dev, "%s: control message failed: %d\n", + __func__, ret); +} + +static int xone_mt76_load_ivb(struct xone_mt76 *mt) +{ + /* load interrupt vector block */ + return usb_control_msg(mt->udev, usb_sndctrlpipe(mt->udev, 0), + MT_VEND_DEV_MODE, USB_DIR_OUT | USB_TYPE_VENDOR, + XONE_MT_FW_LOAD_IVB, 0, NULL, 0, + XONE_MT_USB_TIMEOUT); +} + +static bool xone_mt76_poll(struct xone_mt76 *mt, u32 offset, u32 mask, u32 val) +{ + int i; + u32 reg; + + for (i = 0; i < XONE_MT_POLL_RETRIES; i++) { + reg = xone_mt76_read_register(mt, offset); + if ((reg & mask) == val) + return true; + + usleep_range(10000, 20000); + } + + return false; +} + +static int xone_mt76_read_efuse(struct xone_mt76 *mt, u16 addr, + void *data, int len) +{ + u32 ctrl, offset, val; + int i, remaining; + + ctrl = xone_mt76_read_register(mt, MT_EFUSE_CTRL); + ctrl &= ~(MT_EFUSE_CTRL_AIN | MT_EFUSE_CTRL_MODE); + ctrl |= MT_EFUSE_CTRL_KICK; + ctrl |= FIELD_PREP(MT_EFUSE_CTRL_AIN, addr & ~0x0f); + ctrl |= FIELD_PREP(MT_EFUSE_CTRL_MODE, MT_EE_READ); + xone_mt76_write_register(mt, MT_EFUSE_CTRL, ctrl); + + if (!xone_mt76_poll(mt, MT_EFUSE_CTRL, MT_EFUSE_CTRL_KICK, 0)) + return -ETIMEDOUT; + + for (i = 0; i < len; i += sizeof(u32)) { + /* block data offset (multiple of 32 bits) */ + offset = (addr & GENMASK(3, 2)) + i; + val = xone_mt76_read_register(mt, MT_EFUSE_DATA_BASE + offset); + remaining = min_t(int, len - i, sizeof(u32)); + + memcpy(data + i, &val, remaining); + } + + return 0; +} + +struct sk_buff *xone_mt76_alloc_message(int len, gfp_t gfp) +{ + struct sk_buff *skb; + + /* up to 4 bytes of padding */ + skb = alloc_skb(MT_CMD_HDR_LEN + len + sizeof(u32) + MT_CMD_HDR_LEN, + gfp); + if (!skb) + return NULL; + + skb_reserve(skb, MT_CMD_HDR_LEN); + + return skb; +} + +static void xone_mt76_prep_message(struct sk_buff *skb, u32 info) +{ + int len, pad; + + /* padding and trailer */ + len = round_up(skb->len, sizeof(u32)); + pad = len - skb->len + MT_CMD_HDR_LEN; + + put_unaligned_le32(info | FIELD_PREP(MT_MCU_MSG_LEN, len), + skb_push(skb, MT_CMD_HDR_LEN)); + memset(skb_put(skb, pad), 0, pad); +} + +void xone_mt76_prep_command(struct sk_buff *skb, enum mt76_mcu_cmd cmd) +{ + xone_mt76_prep_message(skb, MT_MCU_MSG_TYPE_CMD | + FIELD_PREP(MT_MCU_MSG_PORT, MT_CPU_TX_PORT) | + FIELD_PREP(MT_MCU_MSG_CMD_TYPE, cmd)); +} + +static int xone_mt76_send_command(struct xone_mt76 *mt, struct sk_buff *skb, + enum mt76_mcu_cmd cmd) +{ + int err; + + xone_mt76_prep_command(skb, cmd); + + err = usb_bulk_msg(mt->udev, usb_sndbulkpipe(mt->udev, XONE_MT_EP_OUT), + skb->data, skb->len, NULL, XONE_MT_USB_TIMEOUT); + consume_skb(skb); + + return err; +} + +static int xone_mt76_send_wlan(struct xone_mt76 *mt, struct sk_buff *skb) +{ + struct mt76_txwi txwi = {}; + int err; + + /* wait for acknowledgment */ + /* ignore wireless client identifier (WCID) */ + txwi.flags = cpu_to_le16(FIELD_PREP(MT_TXWI_FLAGS_MPDU_DENSITY, + IEEE80211_HT_MPDU_DENSITY_4)); + txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); + txwi.ack_ctl = MT_TXWI_ACK_CTL_REQ; + txwi.wcid = 0xff; + txwi.len_ctl = cpu_to_le16(skb->len); + + memcpy(skb_push(skb, sizeof(txwi)), &txwi, sizeof(txwi)); + + /* enhanced distributed channel access (EDCA) */ + /* wireless information valid (WIV) */ + xone_mt76_prep_message(skb, + FIELD_PREP(MT_TXD_INFO_DPORT, MT_WLAN_PORT) | + FIELD_PREP(MT_TXD_INFO_QSEL, MT_QSEL_EDCA) | + MT_TXD_INFO_WIV | + MT_TXD_INFO_80211); + + err = usb_bulk_msg(mt->udev, usb_sndbulkpipe(mt->udev, XONE_MT_EP_OUT), + skb->data, skb->len, NULL, XONE_MT_USB_TIMEOUT); + consume_skb(skb); + + return err; +} + +static int xone_mt76_select_function(struct xone_mt76 *mt, + enum mt76_mcu_function func, u32 val) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32) * 2, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(func, skb_put(skb, sizeof(u32))); + put_unaligned_le32(val, skb_put(skb, sizeof(u32))); + + return xone_mt76_send_command(mt, skb, MT_CMD_FUN_SET_OP); +} + +static int xone_mt76_load_cr(struct xone_mt76 *mt, enum mt76_mcu_cr_mode mode) +{ + struct sk_buff *skb; + struct xone_mt76_msg_load_cr msg = {}; + + skb = xone_mt76_alloc_message(sizeof(msg), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + msg.mode = mode; + skb_put_data(skb, &msg, sizeof(msg)); + + return xone_mt76_send_command(mt, skb, MT_CMD_LOAD_CR); +} + +static int xone_mt76_send_ms_command(struct xone_mt76 *mt, + enum xone_mt76_ms_command cmd, + void *data, int len) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32) + len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(cmd, skb_put(skb, sizeof(u32))); + skb_put_data(skb, data, len); + + /* send command to Microsoft's proprietary firmware */ + return xone_mt76_send_command(mt, skb, MT_CMD_INIT_GAIN_OP); +} + +static int xone_mt76_write_burst(struct xone_mt76 *mt, u32 idx, + void *data, int len) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(idx) + len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + /* register offset in memory */ + put_unaligned_le32(idx + MT_MCU_MEMMAP_WLAN, skb_put(skb, sizeof(idx))); + skb_put_data(skb, data, len); + + return xone_mt76_send_command(mt, skb, MT_CMD_BURST_WRITE); +} + +int xone_mt76_set_led_mode(struct xone_mt76 *mt, enum xone_mt76_led_mode mode) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(mode, skb_put(skb, sizeof(u32))); + + return xone_mt76_send_command(mt, skb, MT_CMD_LED_MODE_OP); +} + +static int xone_mt76_set_power_mode(struct xone_mt76 *mt, + enum mt76_mcu_power_mode mode) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(mode, skb_put(skb, sizeof(u32))); + + return xone_mt76_send_command(mt, skb, MT_CMD_POWER_SAVING_OP); +} + +static int xone_mt76_set_wow_enable(struct xone_mt76 *mt, bool enable) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32) + sizeof(u8) * 2, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(XONE_MT_WOW_ENABLE, skb_put(skb, sizeof(u32))); + skb_put_u8(skb, enable); + skb_put_u8(skb, mt->channel->index); + + return xone_mt76_send_command(mt, skb, MT_CMD_WOW_FEATURE); +} + +static int xone_mt76_set_wow_traffic(struct xone_mt76 *mt, + enum xone_mt76_wow_traffic traffic) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32) + sizeof(u8), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(XONE_MT_WOW_TRAFFIC, skb_put(skb, sizeof(u32))); + skb_put_u8(skb, traffic); + + return xone_mt76_send_command(mt, skb, MT_CMD_WOW_FEATURE); +} + +static int xone_mt76_switch_channel(struct xone_mt76 *mt, + struct xone_mt76_channel *chan) +{ + struct sk_buff *skb; + struct xone_mt76_msg_switch_channel msg = {}; + + skb = xone_mt76_alloc_message(sizeof(msg), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + /* select TX and RX stream 1 */ + /* enable or disable scanning (unknown purpose) */ + msg.channel = chan->index; + msg.tx_rx_setting = cpu_to_le16(0x0101); + msg.bandwidth = chan->bandwidth; + msg.tx_power = chan->power; + msg.scan = chan->scan; + skb_put_data(skb, &msg, sizeof(msg)); + + return xone_mt76_send_command(mt, skb, MT_CMD_SWITCH_CHANNEL_OP); +} + +static int xone_mt76_calibrate(struct xone_mt76 *mt, + enum mt76_mcu_calibration calib, u32 val) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32) * 2, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(calib, skb_put(skb, sizeof(u32))); + put_unaligned_le32(val, skb_put(skb, sizeof(u32))); + + return xone_mt76_send_command(mt, skb, MT_CMD_CALIBRATION_OP); +} + +static int xone_mt76_send_firmware_part(struct xone_mt76 *mt, u32 offset, + const u8 *data, u32 len) +{ + struct sk_buff *skb; + u32 pos, chunk_len, complete; + int err; + + for (pos = 0; pos < len; pos += XONE_MT_FW_CHUNK_SIZE) { + chunk_len = min_t(u32, len - pos, XONE_MT_FW_CHUNK_SIZE); + + skb = xone_mt76_alloc_message(chunk_len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + skb_put_data(skb, data + pos, chunk_len); + chunk_len = roundup(chunk_len, sizeof(u32)); + + xone_mt76_write_register(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG, + offset + pos); + xone_mt76_write_register(mt, MT_FCE_DMA_LEN | MT_VEND_TYPE_CFG, + chunk_len << 16); + + err = xone_mt76_send_command(mt, skb, 0); + if (err) + return err; + + complete = 0xc0000000 | (chunk_len << 16); + if (!xone_mt76_poll(mt, MT_FCE_DMA_LEN | MT_VEND_TYPE_CFG, + 0xffffffff, complete)) + return -ETIMEDOUT; + } + + return 0; +} + +static int xone_mt76_send_firmware(struct xone_mt76 *mt, + const struct firmware *fw) +{ + const struct mt76_fw_header *hdr; + u32 ilm_len, dlm_len; + int err; + + if (fw->size < sizeof(*hdr)) + return -EINVAL; + + hdr = (const struct mt76_fw_header *)fw->data; + ilm_len = le32_to_cpu(hdr->ilm_len); + dlm_len = le32_to_cpu(hdr->dlm_len); + + if (fw->size != sizeof(*hdr) + ilm_len + dlm_len) + return -EINVAL; + + dev_dbg(mt->dev, "%s: build=%.16s\n", __func__, hdr->build_time); + + /* configure DMA, enable FCE and packet DMA */ + xone_mt76_write_register(mt, MT_USB_U3DMA_CFG | MT_VEND_TYPE_CFG, + MT_USB_DMA_CFG_TX_BULK_EN | + MT_USB_DMA_CFG_RX_BULK_EN); + xone_mt76_write_register(mt, MT_FCE_PSE_CTRL, 0x01); + xone_mt76_write_register(mt, MT_TX_CPU_FROM_FCE_BASE_PTR, 0x00400230); + xone_mt76_write_register(mt, MT_TX_CPU_FROM_FCE_MAX_COUNT, 0x01); + xone_mt76_write_register(mt, MT_TX_CPU_FROM_FCE_CPU_DESC_IDX, 0x01); + xone_mt76_write_register(mt, MT_FCE_PDMA_GLOBAL_CONF, 0x44); + xone_mt76_write_register(mt, MT_FCE_SKIP_FS, 0x03); + + /* send instruction local memory */ + err = xone_mt76_send_firmware_part(mt, XONE_MT_FW_ILM_OFFSET, + fw->data + sizeof(*hdr), ilm_len); + if (err) + return err; + + /* send data local memory */ + return xone_mt76_send_firmware_part(mt, XONE_MT_FW_DLM_OFFSET, + fw->data + sizeof(*hdr) + ilm_len, + dlm_len); +} + +static int xone_mt76_reset_firmware(struct xone_mt76 *mt) +{ + u32 val; + int err; + + /* apply power-on RF patch */ + val = xone_mt76_read_register(mt, XONE_MT_RF_PATCH | MT_VEND_TYPE_CFG); + xone_mt76_write_register(mt, XONE_MT_RF_PATCH | MT_VEND_TYPE_CFG, + val & ~BIT(19)); + + err = xone_mt76_load_ivb(mt); + if (err) + return err; + + /* wait for reset */ + if (!xone_mt76_poll(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG, + 0x80000000, 0x80000000)) + return -ETIMEDOUT; + + return 0; +} + +int xone_mt76_load_firmware(struct xone_mt76 *mt, const struct firmware *fw) +{ + int err; + + if (xone_mt76_read_register(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG)) { + dev_dbg(mt->dev, "%s: resetting firmware...\n", __func__); + return xone_mt76_reset_firmware(mt); + } + + err = xone_mt76_send_firmware(mt, fw); + if (err) + return err; + + xone_mt76_write_register(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG, 0); + + err = xone_mt76_load_ivb(mt); + if (err) + return err; + + if (!xone_mt76_poll(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG, 0x01, 0x01)) + err = -ETIMEDOUT; + + return err; +} + +static const struct xone_mt76_channel +xone_mt76_channels[XONE_MT_NUM_CHANNELS] = { + { 0x01, XONE_MT_CH_2G_LOW, MT_PHY_BW_20, 0, true, 0 }, + { 0x06, XONE_MT_CH_2G_MID, MT_PHY_BW_20, 0, true, 0 }, + { 0x0b, XONE_MT_CH_2G_HIGH, MT_PHY_BW_20, 0, true, 0 }, + { 0x24, XONE_MT_CH_5G_LOW, MT_PHY_BW_40, MT_CH_5G_UNII_1, true, 0 }, + { 0x28, XONE_MT_CH_5G_LOW, MT_PHY_BW_40, MT_CH_5G_UNII_1, false, 0 }, + { 0x2c, XONE_MT_CH_5G_HIGH, MT_PHY_BW_40, MT_CH_5G_UNII_1, true, 0 }, + { 0x30, XONE_MT_CH_5G_HIGH, MT_PHY_BW_40, MT_CH_5G_UNII_1, false, 0 }, + { 0x95, XONE_MT_CH_5G_LOW, MT_PHY_BW_80, MT_CH_5G_UNII_3, true, 0 }, + { 0x99, XONE_MT_CH_5G_LOW, MT_PHY_BW_80, MT_CH_5G_UNII_3, false, 0 }, + { 0x9d, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, true, 0 }, + { 0xa1, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, false, 0 }, + { 0xa5, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, false, 0 }, +}; + +static int xone_mt76_set_channel_candidates(struct xone_mt76 *mt) +{ + struct sk_buff *skb; + u8 best_chan = mt->channel->index; + u8 chan; + int i, err; + + skb = alloc_skb(sizeof(u32) * 2 + sizeof(u32) * XONE_MT_NUM_CHANNELS, + GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(1, skb_put(skb, sizeof(u32))); + put_unaligned_le32(best_chan, skb_put(skb, sizeof(u32))); + put_unaligned_le32(XONE_MT_NUM_CHANNELS - 1, skb_put(skb, sizeof(u32))); + + for (i = 0; i < XONE_MT_NUM_CHANNELS; i++) { + chan = mt->channels[i].index; + if (chan != best_chan) + put_unaligned_le32(chan, skb_put(skb, sizeof(u32))); + } + + err = xone_mt76_send_ms_command(mt, XONE_MT_SET_CHAN_CANDIDATES, + skb->data, skb->len); + consume_skb(skb); + + return err; +} + +static int xone_mt76_get_channel_power(struct xone_mt76 *mt, + struct xone_mt76_channel *chan) +{ + u16 addr; + u8 idx, target, offset; + u8 entry[8]; + int err; + + if (chan->bandwidth == MT_PHY_BW_20) { + addr = MT_EE_TX_POWER_0_START_2G; + idx = 4; + } else { + /* each group has its own power table */ + addr = MT_EE_TX_POWER_0_START_5G + + chan->group * MT_TX_POWER_GROUP_SIZE_5G; + idx = 5; + } + + err = xone_mt76_read_efuse(mt, addr, entry, sizeof(entry)); + if (err) { + dev_err(mt->dev, "%s: read EFUSE failed: %d\n", __func__, err); + return err; + } + + target = entry[idx]; + offset = entry[idx + chan->band]; + + /* increase or decrease power by offset (in 0.5 dB steps) */ + if (offset & BIT(7)) + chan->power = (offset & BIT(6)) ? + target + (offset & GENMASK(5, 0)) : + target - (offset & GENMASK(5, 0)); + else + chan->power = target; + + return 0; +} + +static int xone_mt76_evaluate_channels(struct xone_mt76 *mt) +{ + struct xone_mt76_channel *chan; + int i, err, pow = 0; + + mt->channel = NULL; + + memcpy(mt->channels, xone_mt76_channels, sizeof(xone_mt76_channels)); + + for (i = 0; i < XONE_MT_NUM_CHANNELS; i++) { + chan = &mt->channels[i]; + + /* original driver increases power for channels 0x24 to 0x30 */ + err = xone_mt76_get_channel_power(mt, chan); + if (err) + return err; + + err = xone_mt76_switch_channel(mt, chan); + if (err) + return err; + + /* pick the highest power channel seen first */ + /* the last channel might not be the best one */ + if (chan->power > pow) { + mt->channel = chan; + pow = chan->power; + } + + dev_dbg(mt->dev, "%s: channel=%u, power=%u\n", __func__, + chan->index, chan->power); + } + + if (mt->channel == NULL) + mt->channel = chan; + + return 0; +} + +static int xone_mt76_init_channels(struct xone_mt76 *mt) +{ + int err; + + /* enable promiscuous mode */ + xone_mt76_write_register(mt, MT_RX_FILTR_CFG, 0x014f13); + + err = xone_mt76_evaluate_channels(mt); + if (err) + return err; + + /* disable promiscuous mode */ + xone_mt76_write_register(mt, MT_RX_FILTR_CFG, 0x017f17); + + dev_dbg(mt->dev, "%s: channel=%u\n", __func__, mt->channel->index); + + mt->channel->scan = true; + + err = xone_mt76_switch_channel(mt, mt->channel); + if (err) + return err; + + err = xone_mt76_set_power_mode(mt, MT_RADIO_OFF); + if (err) + return err; + + msleep(50); + + err = xone_mt76_set_power_mode(mt, MT_RADIO_ON); + if (err) + return err; + + mt->channel->scan = false; + + err = xone_mt76_switch_channel(mt, mt->channel); + if (err) + return err; + + return xone_mt76_set_channel_candidates(mt); +} + +static int xone_mt76_set_idle_time(struct xone_mt76 *mt) +{ + __le32 time = cpu_to_le32(64); + + /* prevent wireless clients from disconnecting when idle */ + return xone_mt76_send_ms_command(mt, XONE_MT_SET_IDLE_TIME, + &time, sizeof(time)); +} + +/* + * There are a number of knockoff adapters out there that share the same MAC address(es). + * This will create problems if two of them are used within range of each other. + * So far, the following MACs are known to be associated with knockoff adapters: + */ +static const u8 xone_counterfeit_macs[][ETH_ALEN] = { + {0x62, 0x45, 0xb4, 0xe7, 0xa4, 0xef}, +}; + +static int xone_mt76_mac_looks_counterfeit(const u8* addr) +{ + for (int i = 0; i < ARRAY_SIZE(xone_counterfeit_macs); i++) { + if (ether_addr_equal(addr, xone_counterfeit_macs[i])) + return true; + } + + return false; +} + +static int xone_mt76_init_address(struct xone_mt76 *mt) +{ + int err; + + err = xone_mt76_read_efuse(mt, MT_EE_MAC_ADDR, + mt->address, sizeof(mt->address)); + if (err) + return err; + + dev_dbg(mt->dev, "%s: fuse_address=%pM\n", __func__, mt->address); + + if (xone_mt76_mac_looks_counterfeit(mt->address) && is_zero_ether_addr(override_mac)) + dev_warn(mt->dev, "%s: MAC address %pM looks suspicious. Counterfeit " + "adapter? That may be fine, but consider passing override_mac= to " + "deconflict with others nearby\n", __func__, mt->address); + + /* Override MAC address if present */ + if (!is_zero_ether_addr(override_mac)) { + memcpy(mt->address, override_mac, ETH_ALEN); + dev_dbg(mt->dev, "%s: overriding MAC address to %pM\n", __func__, mt->address); + } + + /* some addresses start with 6c:5d:3a */ + /* clients only connect to 62:45:bx:xx:xx:xx */ + if (mt->address[0] != 0x62) { + mt->address[0] = 0x62; + mt->address[1] = 0x45; + mt->address[2] = 0xbd; + dev_dbg(mt->dev, "%s: address=%pM\n", __func__, mt->address); + } + + err = xone_mt76_write_burst(mt, MT_MAC_ADDR_DW0, + mt->address, sizeof(mt->address)); + if (err) + return err; + + err = xone_mt76_write_burst(mt, MT_MAC_BSSID_DW0, + mt->address, sizeof(mt->address)); + if (err) + return err; + + return xone_mt76_send_ms_command(mt, XONE_MT_SET_MAC_ADDRESS, + mt->address, sizeof(mt->address)); +} + +static int xone_mt76_calibrate_crystal(struct xone_mt76 *mt) +{ + u8 trim[4]; + u16 val; + s8 offset; + u32 ctrl; + int err; + + err = xone_mt76_read_efuse(mt, MT_EE_XTAL_TRIM_2, trim, sizeof(trim)); + if (err) + return err; + + val = (trim[3] << 8) | trim[2]; + offset = val & GENMASK(6, 0); + if ((val & 0xff) == 0xff) + offset = 0; + else if (val & BIT(7)) + offset = -offset; + + val >>= 8; + if (!val || val == 0xff) { + err = xone_mt76_read_efuse(mt, MT_EE_XTAL_TRIM_1, trim, + sizeof(trim)); + if (err) + return err; + + val = (trim[3] << 8) | trim[2]; + val &= 0xff; + if (!val || val == 0xff) + val = 0x14; + } + + val = (val & GENMASK(6, 0)) + offset; + ctrl = xone_mt76_read_register(mt, MT_XO_CTRL5 | MT_VEND_TYPE_CFG); + xone_mt76_write_register(mt, MT_XO_CTRL5 | MT_VEND_TYPE_CFG, + (ctrl & ~MT_XO_CTRL5_C2_VAL) | (val << 8)); + xone_mt76_write_register(mt, MT_XO_CTRL6 | MT_VEND_TYPE_CFG, + MT_XO_CTRL6_C2_CTRL); + xone_mt76_write_register(mt, MT_CMB_CTRL, 0x0091a7ff); + + return 0; +} + +static int xone_mt76_calibrate_radio(struct xone_mt76 *mt) +{ + int err; + + /* configure automatic gain control (AGC) */ + xone_mt76_write_register(mt, MT_BBP(AGC, 8), 0x18365efa); + xone_mt76_write_register(mt, MT_BBP(AGC, 9), 0x18365efa); + + /* reset required for reliable WLAN associations */ + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, 0); + xone_mt76_write_register(mt, MT_RF_BYPASS_0, 0); + xone_mt76_write_register(mt, MT_RF_SETTING_0, 0); + + err = xone_mt76_calibrate(mt, MT_MCU_CAL_TEMP_SENSOR, 0); + if (err) + return err; + + err = xone_mt76_calibrate(mt, MT_MCU_CAL_RXDCOC, 1); + if (err) + return err; + + err = xone_mt76_calibrate(mt, MT_MCU_CAL_RC, 0); + if (err) + return err; + + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_ENABLE_RX | + MT_MAC_SYS_CTRL_ENABLE_TX); + + return 0; +} + +static void xone_mt76_init_registers(struct xone_mt76 *mt) +{ + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_RESET_BBP | + MT_MAC_SYS_CTRL_RESET_CSR); + xone_mt76_write_register(mt, MT_USB_DMA_CFG, 0); + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, 0); + xone_mt76_write_register(mt, MT_PWR_PIN_CFG, 0); + xone_mt76_write_register(mt, MT_LDO_CTRL_1, 0x6b006464); + xone_mt76_write_register(mt, MT_WPDMA_GLO_CFG, 0x70); + xone_mt76_write_register(mt, MT_WMM_AIFSN, 0x2273); + xone_mt76_write_register(mt, MT_WMM_CWMIN, 0x2344); + xone_mt76_write_register(mt, MT_WMM_CWMAX, 0x34aa); + xone_mt76_write_register(mt, MT_FCE_DMA_ADDR, 0x041200); + xone_mt76_write_register(mt, MT_TSO_CTRL, 0); + xone_mt76_write_register(mt, MT_PBF_SYS_CTRL, 0x080c00); + xone_mt76_write_register(mt, MT_PBF_TX_MAX_PCNT, 0x1fbf1f1f); + xone_mt76_write_register(mt, MT_FCE_PSE_CTRL, 0x01); + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_ENABLE_RX | + MT_MAC_SYS_CTRL_ENABLE_TX); + xone_mt76_write_register(mt, MT_AUTO_RSP_CFG, 0x13); + xone_mt76_write_register(mt, MT_MAX_LEN_CFG, 0x3e3fff); + xone_mt76_write_register(mt, MT_AMPDU_MAX_LEN_20M1S, 0xfffc9855); + xone_mt76_write_register(mt, MT_AMPDU_MAX_LEN_20M2S, 0xff); + xone_mt76_write_register(mt, MT_BKOFF_SLOT_CFG, 0x0109); + xone_mt76_write_register(mt, MT_PWR_PIN_CFG, 0); + xone_mt76_write_register(mt, MT_EDCA_CFG_AC(0), 0x064320); + xone_mt76_write_register(mt, MT_EDCA_CFG_AC(1), 0x0a4700); + xone_mt76_write_register(mt, MT_EDCA_CFG_AC(2), 0x043238); + xone_mt76_write_register(mt, MT_EDCA_CFG_AC(3), 0x03212f); + xone_mt76_write_register(mt, MT_TX_PIN_CFG, 0x150f0f); + xone_mt76_write_register(mt, MT_TX_SW_CFG0, 0x101001); + xone_mt76_write_register(mt, MT_TX_SW_CFG1, 0x010000); + xone_mt76_write_register(mt, MT_TXOP_CTRL_CFG, 0x10583f); + xone_mt76_write_register(mt, MT_TX_TIMEOUT_CFG, 0x0a0f90); + xone_mt76_write_register(mt, MT_TX_RETRY_CFG, 0x47d01f0f); + xone_mt76_write_register(mt, MT_CCK_PROT_CFG, 0x03f40003); + xone_mt76_write_register(mt, MT_OFDM_PROT_CFG, 0x03f40003); + xone_mt76_write_register(mt, MT_MM20_PROT_CFG, 0x01742004); + xone_mt76_write_register(mt, MT_GF20_PROT_CFG, 0x01742004); + xone_mt76_write_register(mt, MT_GF40_PROT_CFG, 0x03f42084); + xone_mt76_write_register(mt, MT_EXP_ACK_TIME, 0x2c00dc); + xone_mt76_write_register(mt, MT_TX_ALC_CFG_2, 0x22160a00); + xone_mt76_write_register(mt, MT_TX_ALC_CFG_3, 0x22160a76); + xone_mt76_write_register(mt, MT_TX_ALC_CFG_0, 0x3f3f1818); + xone_mt76_write_register(mt, MT_TX_ALC_CFG_4, 0x0606); + xone_mt76_write_register(mt, MT_PIFS_TX_CFG, 0x060fff); + xone_mt76_write_register(mt, MT_RX_FILTR_CFG, 0x017f17); + xone_mt76_write_register(mt, MT_LEGACY_BASIC_RATE, 0x017f); + xone_mt76_write_register(mt, MT_HT_BASIC_RATE, 0x8003); + xone_mt76_write_register(mt, MT_PN_PAD_MODE, 0x02); + xone_mt76_write_register(mt, MT_TXOP_HLDR_ET, 0x02); + xone_mt76_write_register(mt, MT_TX_PROT_CFG6, 0xe3f42004); + xone_mt76_write_register(mt, MT_TX_PROT_CFG7, 0xe3f42084); + xone_mt76_write_register(mt, MT_TX_PROT_CFG8, 0xe3f42104); + xone_mt76_write_register(mt, MT_DACCLK_EN_DLY_CFG, 0); + xone_mt76_write_register(mt, MT_RF_PA_MODE_ADJ0, 0xee000000); + xone_mt76_write_register(mt, MT_RF_PA_MODE_ADJ1, 0xee000000); + xone_mt76_write_register(mt, MT_TX0_RF_GAIN_CORR, 0x0f3c3c3c); + xone_mt76_write_register(mt, MT_TX1_RF_GAIN_CORR, 0x0f3c3c3c); + xone_mt76_write_register(mt, MT_PBF_CFG, 0x1efebcf5); + xone_mt76_write_register(mt, MT_PAUSE_ENABLE_CONTROL1, 0x0a); + xone_mt76_write_register(mt, MT_RF_BYPASS_0, 0x7f000000); + xone_mt76_write_register(mt, MT_RF_SETTING_0, 0x1a800000); + xone_mt76_write_register(mt, MT_XIFS_TIME_CFG, 0x33a40e0a); + xone_mt76_write_register(mt, MT_FCE_L2_STUFF, 0x03ff0223); + xone_mt76_write_register(mt, MT_TX_RTS_CFG, 0); + xone_mt76_write_register(mt, MT_BEACON_TIME_CFG, 0x0640); + xone_mt76_write_register(mt, MT_EXT_CCA_CFG, 0xf0e4); + xone_mt76_write_register(mt, MT_CH_TIME_CFG, 0x015f); +} + +static u16 xone_mt76_get_chip_id(struct xone_mt76 *mt) +{ + u8 id[4]; + + if (xone_mt76_read_efuse(mt, MT_EE_CHIP_ID, &id, sizeof(id))) + return 0; + + return (id[1] << 8) | id[2]; +} + +int xone_mt76_init_radio(struct xone_mt76 *mt) +{ + int err; + + dev_dbg(mt->dev, "%s: id=0x%04x\n", __func__, + xone_mt76_get_chip_id(mt)); + + err = xone_mt76_select_function(mt, MT_Q_SELECT, 1); + if (err) + return err; + + err = xone_mt76_set_power_mode(mt, MT_RADIO_ON); + if (err) + return err; + + err = xone_mt76_load_cr(mt, MT_RF_BBP_CR); + if (err) + return err; + + xone_mt76_init_registers(mt); + + err = xone_mt76_calibrate_crystal(mt); + if (err) + return err; + + err = xone_mt76_init_address(mt); + if (err) + return err; + + err = xone_mt76_set_idle_time(mt); + if (err) + return err; + + err = xone_mt76_calibrate_radio(mt); + if (err) + return err; + + err = xone_mt76_init_channels(mt); + if (err) + return err; + + /* mandatory delay after channel change */ + msleep(1000); + + return xone_mt76_set_pairing(mt, false); +} + +int xone_mt76_suspend_radio(struct xone_mt76 *mt) +{ + int err; + + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, 0); + + /* enable wake-on-wireless */ + err = xone_mt76_set_wow_enable(mt, true); + if (err) + return err; + + err = xone_mt76_set_wow_traffic(mt, XONE_MT_WOW_TO_HOST); + if (err) + return err; + + dev_dbg(mt->dev, "%s: suspended\n", __func__); + + return 0; +} + +int xone_mt76_resume_radio(struct xone_mt76 *mt) +{ + int err; + + err = xone_mt76_set_wow_traffic(mt, XONE_MT_WOW_TO_FIRMWARE); + if (err) + return err; + + /* disable wake-on-wireless */ + err = xone_mt76_set_wow_enable(mt, false); + if (err) + return err; + + err = xone_mt76_switch_channel(mt, mt->channel); + if (err) + return err; + + err = xone_mt76_set_pairing(mt, false); + if (err) + return err; + + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_ENABLE_RX | + MT_MAC_SYS_CTRL_ENABLE_TX); + + dev_dbg(mt->dev, "%s: resumed\n", __func__); + + return 0; +} + +static int xone_mt76_write_beacon(struct xone_mt76 *mt, bool pair) +{ + struct sk_buff *skb; + struct mt76_txwi txwi = {}; + struct ieee80211_mgmt mgmt = {}; + u8 data[] = { + /* information element with Microsoft's OUI (00:50:f2) */ + /* probably includes the selected channel pair */ + 0x00, 0x00, 0xdd, 0x10, 0x00, 0x50, 0xf2, 0x11, + 0x01, 0x10, pair, 0xa5, 0x30, 0x99, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + int mgmt_len = sizeof(struct ieee80211_hdr_3addr) + + sizeof(mgmt.u.beacon); + int err; + + skb = alloc_skb(sizeof(txwi) + mgmt_len + sizeof(data), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + /* generate beacon timestamp */ + /* use hardware sequence control */ + txwi.flags = cpu_to_le16(MT_TXWI_FLAGS_TS); + txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); + txwi.ack_ctl = MT_TXWI_ACK_CTL_NSEQ; + txwi.len_ctl = cpu_to_le16(mgmt_len + sizeof(data)); + + mgmt.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_BEACON); + eth_broadcast_addr(mgmt.da); + memcpy(mgmt.sa, mt->address, ETH_ALEN); + memcpy(mgmt.bssid, mt->address, ETH_ALEN); + + /* default beacon interval (100 ms) */ + /* original capability info */ + mgmt.u.beacon.beacon_int = cpu_to_le16(100); + mgmt.u.beacon.capab_info = cpu_to_le16(0xc631); + + skb_put_data(skb, &txwi, sizeof(txwi)); + skb_put_data(skb, &mgmt, mgmt_len); + skb_put_data(skb, data, sizeof(data)); + + err = xone_mt76_write_burst(mt, MT_BEACON_BASE, skb->data, skb->len); + consume_skb(skb); + + return err; +} + +int xone_mt76_set_pairing(struct xone_mt76 *mt, bool enable) +{ + int err; + + err = xone_mt76_write_beacon(mt, enable); + if (err) + return err; + + /* enable timing synchronization function (TSF) timer */ + /* enable target beacon transmission time (TBTT) timer */ + /* set TSF timer to AP mode */ + /* activate beacon transmission */ + xone_mt76_write_register(mt, MT_BEACON_TIME_CFG, + MT_BEACON_TIME_CFG_BEACON_TX | + MT_BEACON_TIME_CFG_TBTT_EN | + MT_BEACON_TIME_CFG_SYNC_MODE | + MT_BEACON_TIME_CFG_TIMER_EN | + FIELD_PREP(MT_BEACON_TIME_CFG_INTVAL, 0x0640)); + + return 0; +} + +int xone_mt76_pair_client(struct xone_mt76 *mt, u8 *addr) +{ + struct sk_buff *skb; + struct ieee80211_hdr_3addr hdr = {}; + u8 data[] = { 0x00, 0x45, 0x55, 0x01, 0x0f, 0x8f, 0xff, 0x87, 0x1f }; + + skb = xone_mt76_alloc_message(sizeof(struct mt76_txwi) + sizeof(hdr) + + sizeof(u8) * 2 + sizeof(data), + GFP_KERNEL); + if (!skb) + return -ENOMEM; + + hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + XONE_MT_WLAN_RESERVED); + memcpy(hdr.addr1, addr, ETH_ALEN); + memcpy(hdr.addr2, mt->address, ETH_ALEN); + memcpy(hdr.addr3, mt->address, ETH_ALEN); + + skb_reserve(skb, sizeof(struct mt76_txwi)); + skb_put_data(skb, &hdr, sizeof(hdr)); + skb_put_u8(skb, XONE_MT_WLAN_RESERVED); + skb_put_u8(skb, XONE_MT_CLIENT_PAIR_RESP); + skb_put_data(skb, data, sizeof(data)); + + return xone_mt76_send_wlan(mt, skb); +} + +int xone_mt76_associate_client(struct xone_mt76 *mt, u8 wcid, u8 *addr) +{ + struct sk_buff *skb; + struct ieee80211_mgmt mgmt = {}; + u8 data[] = { wcid - 1, 0x00, 0x00, 0x00, 0x40, 0x1f, 0x00, 0x00 }; + int mgmt_len = sizeof(struct ieee80211_hdr_3addr) + + sizeof(mgmt.u.assoc_resp); + int err; + + skb = xone_mt76_alloc_message(sizeof(struct mt76_txwi) + mgmt_len + 8, + GFP_KERNEL); + if (!skb) + return -ENOMEM; + + mgmt.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_ASSOC_RESP); + memcpy(mgmt.da, addr, ETH_ALEN); + memcpy(mgmt.sa, mt->address, ETH_ALEN); + memcpy(mgmt.bssid, mt->address, ETH_ALEN); + + /* original status code and association ID */ + mgmt.u.assoc_resp.status_code = cpu_to_le16(0x0110); + mgmt.u.assoc_resp.aid = cpu_to_le16(0x0f00); + + skb_reserve(skb, sizeof(struct mt76_txwi)); + skb_put_data(skb, &mgmt, mgmt_len); + memset(skb_put(skb, 8), 0, 8); + + err = xone_mt76_write_burst(mt, MT_WCID_ADDR(wcid), addr, ETH_ALEN); + if (err) + goto err_free_skb; + + err = xone_mt76_send_ms_command(mt, XONE_MT_ADD_CLIENT, + data, sizeof(data)); + if (err) + goto err_free_skb; + + return xone_mt76_send_wlan(mt, skb); + +err_free_skb: + kfree_skb(skb); + + return err; +} + +int xone_mt76_send_client_command(struct xone_mt76 *mt, u8 wcid, u8 *addr, + enum xone_mt76_client_command cmd, + u8 *data, int len) +{ + struct sk_buff *skb; + struct mt76_txwi txwi = {}; + struct ieee80211_hdr_3addr hdr = {}; + u8 info[] = { + 0x00, 0x00, 0x00, wcid - 1, 0x00, 0x00, 0x00, 0x00, + }; + + skb = xone_mt76_alloc_message(sizeof(info) + sizeof(txwi) + + sizeof(hdr) + sizeof(u8) * 2 + len, + GFP_KERNEL); + if (!skb) + return -ENOMEM; + + /* wait for acknowledgment */ + txwi.flags = cpu_to_le16(FIELD_PREP(MT_TXWI_FLAGS_MPDU_DENSITY, + IEEE80211_HT_MPDU_DENSITY_4)); + txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); + txwi.ack_ctl = MT_TXWI_ACK_CTL_REQ; + txwi.wcid = wcid - 1; + txwi.len_ctl = cpu_to_le16(sizeof(hdr) + sizeof(u8) * 2 + len); + + hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + XONE_MT_WLAN_RESERVED); + memcpy(hdr.addr1, addr, ETH_ALEN); + memcpy(hdr.addr2, mt->address, ETH_ALEN); + memcpy(hdr.addr3, mt->address, ETH_ALEN); + + skb_put_data(skb, info, sizeof(info)); + skb_put_data(skb, &txwi, sizeof(txwi)); + skb_put_data(skb, &hdr, sizeof(hdr)); + skb_put_u8(skb, XONE_MT_WLAN_RESERVED); + skb_put_u8(skb, cmd); + + if (data) + skb_put_data(skb, data, len); + + return xone_mt76_send_command(mt, skb, 0); +} + +int xone_mt76_set_client_key(struct xone_mt76 *mt, u8 wcid, u8 *key, int len) +{ + u8 iv[] = { 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00 }; + __le32 attr = cpu_to_le32(FIELD_PREP(MT_WCID_ATTR_PKEY_MODE, + MT_CIPHER_AES_CCMP) | + MT_WCID_ATTR_PAIRWISE); + int err; + + if (len != XONE_MT_WCID_KEY_LEN) + return -EINVAL; + + err = xone_mt76_write_burst(mt, MT_WCID_KEY(wcid), key, len); + if (err) + return err; + + err = xone_mt76_write_burst(mt, MT_WCID_IV(wcid), iv, sizeof(iv)); + if (err) + return err; + + return xone_mt76_write_burst(mt, MT_WCID_ATTR(wcid), + &attr, sizeof(attr)); +} + +int xone_mt76_remove_client(struct xone_mt76 *mt, u8 wcid) +{ + u8 data[] = { wcid - 1, 0x00, 0x00, 0x00 }; + u8 addr[ETH_ALEN] = {}; + u8 iv[8] = {}; + u32 attr = 0; + u8 key[XONE_MT_WCID_KEY_LEN] = {}; + int err; + + err = xone_mt76_send_ms_command(mt, XONE_MT_REMOVE_CLIENT, + data, sizeof(data)); + if (err) + return err; + + err = xone_mt76_write_burst(mt, MT_WCID_ADDR(wcid), addr, sizeof(addr)); + if (err) + return err; + + err = xone_mt76_write_burst(mt, MT_WCID_IV(wcid), iv, sizeof(iv)); + if (err) + return err; + + err = xone_mt76_write_burst(mt, MT_WCID_ATTR(wcid), + &attr, sizeof(attr)); + if (err) + return err; + + return xone_mt76_write_burst(mt, MT_WCID_KEY(wcid), key, sizeof(key)); +} diff --git a/drivers/custom/xonedo/transport/mt76.h b/drivers/custom/xonedo/transport/mt76.h new file mode 100644 index 000000000000..f1ba59e42302 --- /dev/null +++ b/drivers/custom/xonedo/transport/mt76.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 Severin von Wnuck-Lipinski + */ + +#pragma once + +#include "mt76_defs.h" + +#define XONE_MT_EP_IN_CMD 0x05 +#define XONE_MT_EP_IN_WLAN 0x04 +#define XONE_MT_EP_OUT 0x04 + +#define XONE_MT_NUM_CHANNELS 12 + +/* 802.11 frame subtype: reserved */ +#define XONE_MT_WLAN_RESERVED 0x70 + +enum xone_mt76_led_mode { + XONE_MT_LED_BLINK = 0x00, + XONE_MT_LED_ON = 0x01, + XONE_MT_LED_OFF = 0x02, +}; + +enum xone_mt76_event { + XONE_MT_EVT_BUTTON = 0x04, + XONE_MT_EVT_CHANNELS = 0x0a, + XONE_MT_EVT_PACKET_RX = 0x0c, + XONE_MT_EVT_COREDUMP = 0x0d, + XONE_MT_EVT_CLIENT_LOST = 0x0e, +}; + +enum xone_mt76_client_command { + XONE_MT_CLIENT_PAIR_REQ = 0x01, + XONE_MT_CLIENT_PAIR_RESP = 0x02, + XONE_MT_CLIENT_CHANGE_CHAN_REQ = 0x03, + XONE_MT_CLIENT_CHANGE_CHAN_RESP = 0x04, + XONE_MT_CLIENT_STATISTICS_REQ = 0x05, + XONE_MT_CLIENT_STATISTICS_RESP = 0x06, + XONE_MT_CLIENT_SCAN_CHAN_REQ = 0x07, + XONE_MT_CLIENT_SCAN_CHAN_RESP = 0x08, + XONE_MT_CLIENT_ENABLE_ENCRYPTION = 0x10, +}; + +struct xone_mt76_channel { + u8 index; + u8 band; + enum mt76_phy_bandwidth bandwidth; + enum mt76_cal_channel_group group; + bool scan; + u8 power; +}; + +struct xone_mt76 { + struct device *dev; + struct usb_device *udev; + + __le32 control_data; + u8 address[ETH_ALEN]; + + struct xone_mt76_channel channels[XONE_MT_NUM_CHANNELS]; + struct xone_mt76_channel *channel; +}; + +struct sk_buff *xone_mt76_alloc_message(int len, gfp_t gfp); +void xone_mt76_prep_command(struct sk_buff *skb, enum mt76_mcu_cmd cmd); + +int xone_mt76_set_led_mode(struct xone_mt76 *mt, enum xone_mt76_led_mode mode); +int xone_mt76_load_firmware(struct xone_mt76 *mt, const struct firmware *fw); +int xone_mt76_init_radio(struct xone_mt76 *mt); +int xone_mt76_suspend_radio(struct xone_mt76 *mt); +int xone_mt76_resume_radio(struct xone_mt76 *mt); +int xone_mt76_set_pairing(struct xone_mt76 *mt, bool enable); + +int xone_mt76_pair_client(struct xone_mt76 *mt, u8 *addr); +int xone_mt76_associate_client(struct xone_mt76 *mt, u8 wcid, u8 *addr); +int xone_mt76_send_client_command(struct xone_mt76 *mt, u8 wcid, u8 *addr, + enum xone_mt76_client_command cmd, + u8 *data, int len); +int xone_mt76_set_client_key(struct xone_mt76 *mt, u8 wcid, u8 *key, int len); +int xone_mt76_remove_client(struct xone_mt76 *mt, u8 wcid); diff --git a/drivers/custom/xonedo/transport/mt76_defs.h b/drivers/custom/xonedo/transport/mt76_defs.h new file mode 100644 index 000000000000..f5fe65b149aa --- /dev/null +++ b/drivers/custom/xonedo/transport/mt76_defs.h @@ -0,0 +1,1064 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Based on code from the open source mt76 driver with minor modifications. + * + * Copyright (C) 2021 Severin von Wnuck-Lipinski + * + * Special thanks to the authors of the mt76 driver: + * + * Copyright (C) Felix Fietkau + * Copyright (C) Lorenzo Bianconi + * Copyright (C) Stanislaw Gruszka + */ + +#pragma once + +#include + +#define MT_ASIC_VERSION 0x0000 + +#define MT_CMB_CTRL 0x0020 +#define MT_CMB_CTRL_XTAL_RDY BIT(22) +#define MT_CMB_CTRL_PLL_LD BIT(23) + +#define MT_EFUSE_CTRL 0x0024 +#define MT_EFUSE_CTRL_AOUT GENMASK(5, 0) +#define MT_EFUSE_CTRL_MODE GENMASK(7, 6) +#define MT_EFUSE_CTRL_LDO_OFF_TIME GENMASK(13, 8) +#define MT_EFUSE_CTRL_LDO_ON_TIME GENMASK(15, 14) +#define MT_EFUSE_CTRL_AIN GENMASK(25, 16) +#define MT_EFUSE_CTRL_KICK BIT(30) +#define MT_EFUSE_CTRL_SEL BIT(31) + +#define MT_EFUSE_DATA_BASE 0x0028 +#define MT_EFUSE_DATA(n) (MT_EFUSE_DATA_BASE + ((n) << 2)) + +#define MT_COEXCFG0 0x0040 +#define MT_COEXCFG0_COEX_EN BIT(0) + +#define MT_WLAN_FUN_CTRL 0x0080 +#define MT_WLAN_FUN_CTRL_WLAN_EN BIT(0) +#define MT_WLAN_FUN_CTRL_WLAN_CLK_EN BIT(1) +#define MT_WLAN_FUN_CTRL_WLAN_RESET_RF BIT(2) + +#define MT_COEXCFG3 0x004c + +#define MT_LDO_CTRL_0 0x006c +#define MT_LDO_CTRL_1 0x0070 + +#define MT_WLAN_FUN_CTRL_CSR_F20M_CKEN BIT(3) + +#define MT_WLAN_FUN_CTRL_PCIE_CLK_REQ BIT(4) +#define MT_WLAN_FUN_CTRL_FRC_WL_ANT_SEL BIT(5) +#define MT_WLAN_FUN_CTRL_INV_ANT_SEL BIT(6) +#define MT_WLAN_FUN_CTRL_WAKE_HOST BIT(7) + +#define MT_WLAN_FUN_CTRL_THERM_RST BIT(8) +#define MT_WLAN_FUN_CTRL_THERM_CKEN BIT(9) + +#define MT_XO_CTRL0 0x0100 +#define MT_XO_CTRL1 0x0104 +#define MT_XO_CTRL2 0x0108 +#define MT_XO_CTRL3 0x010c +#define MT_XO_CTRL4 0x0110 + +#define MT_XO_CTRL5 0x0114 +#define MT_XO_CTRL5_C2_VAL GENMASK(14, 8) + +#define MT_XO_CTRL6 0x0118 +#define MT_XO_CTRL6_C2_CTRL GENMASK(14, 8) + +#define MT_XO_CTRL7 0x011c + +#define MT_IOCFG_6 0x0124 + +#define MT_USB_U3DMA_CFG 0x9018 +#define MT_USB_DMA_CFG_RX_BULK_AGG_TOUT GENMASK(7, 0) +#define MT_USB_DMA_CFG_RX_BULK_AGG_LMT GENMASK(15, 8) +#define MT_USB_DMA_CFG_UDMA_TX_WL_DROP BIT(16) +#define MT_USB_DMA_CFG_WAKE_UP_EN BIT(17) +#define MT_USB_DMA_CFG_RX_DROP_OR_PAD BIT(18) +#define MT_USB_DMA_CFG_TX_CLR BIT(19) +#define MT_USB_DMA_CFG_TXOP_HALT BIT(20) +#define MT_USB_DMA_CFG_RX_BULK_AGG_EN BIT(21) +#define MT_USB_DMA_CFG_RX_BULK_EN BIT(22) +#define MT_USB_DMA_CFG_TX_BULK_EN BIT(23) +#define MT_USB_DMA_CFG_EP_OUT_VALID GENMASK(29, 24) +#define MT_USB_DMA_CFG_RX_BUSY BIT(30) +#define MT_USB_DMA_CFG_TX_BUSY BIT(31) + +#define MT_WLAN_MTC_CTRL 0x010148 +#define MT_WLAN_MTC_CTRL_MTCMOS_PWR_UP BIT(0) +#define MT_WLAN_MTC_CTRL_PWR_ACK BIT(12) +#define MT_WLAN_MTC_CTRL_PWR_ACK_S BIT(13) +#define MT_WLAN_MTC_CTRL_BBP_MEM_PD GENMASK(19, 16) +#define MT_WLAN_MTC_CTRL_PBF_MEM_PD BIT(20) +#define MT_WLAN_MTC_CTRL_FCE_MEM_PD BIT(21) +#define MT_WLAN_MTC_CTRL_TSO_MEM_PD BIT(22) +#define MT_WLAN_MTC_CTRL_BBP_MEM_RB BIT(24) +#define MT_WLAN_MTC_CTRL_PBF_MEM_RB BIT(25) +#define MT_WLAN_MTC_CTRL_FCE_MEM_RB BIT(26) +#define MT_WLAN_MTC_CTRL_TSO_MEM_RB BIT(27) +#define MT_WLAN_MTC_CTRL_STATE_UP BIT(28) + +#define MT_INT_SOURCE_CSR 0x0200 +#define MT_INT_MASK_CSR 0x0204 + +#define MT_INT_RX_DONE(n) BIT(n) +#define MT_INT_RX_DONE_ALL GENMASK(1, 0) +#define MT_INT_TX_DONE_ALL GENMASK(13, 4) +#define MT_INT_TX_DONE(n) BIT((n) + 4) +#define MT_INT_RX_COHERENT BIT(16) +#define MT_INT_TX_COHERENT BIT(17) +#define MT_INT_ANY_COHERENT BIT(18) +#define MT_INT_MCU_CMD BIT(19) +#define MT_INT_TBTT BIT(20) +#define MT_INT_PRE_TBTT BIT(21) +#define MT_INT_TX_STAT BIT(22) +#define MT_INT_AUTO_WAKEUP BIT(23) +#define MT_INT_GPTIMER BIT(24) +#define MT_INT_RXDELAYINT BIT(26) +#define MT_INT_TXDELAYINT BIT(27) + +#define MT_WPDMA_GLO_CFG 0x0208 +#define MT_WPDMA_GLO_CFG_TX_DMA_EN BIT(0) +#define MT_WPDMA_GLO_CFG_TX_DMA_BUSY BIT(1) +#define MT_WPDMA_GLO_CFG_RX_DMA_EN BIT(2) +#define MT_WPDMA_GLO_CFG_RX_DMA_BUSY BIT(3) +#define MT_WPDMA_GLO_CFG_DMA_BURST_SIZE GENMASK(5, 4) +#define MT_WPDMA_GLO_CFG_TX_WRITEBACK_DONE BIT(6) +#define MT_WPDMA_GLO_CFG_BIG_ENDIAN BIT(7) +#define MT_WPDMA_GLO_CFG_HDR_SEG_LEN GENMASK(15, 8) +#define MT_WPDMA_GLO_CFG_CLK_GATE_DIS BIT(30) +#define MT_WPDMA_GLO_CFG_RX_2B_OFFSET BIT(31) + +#define MT_WPDMA_RST_IDX 0x020c + +#define MT_WPDMA_DELAY_INT_CFG 0x0210 + +#define MT_WMM_AIFSN 0x0214 +#define MT_WMM_AIFSN_MASK GENMASK(3, 0) +#define MT_WMM_AIFSN_SHIFT(n) ((n) * 4) + +#define MT_WMM_CWMIN 0x0218 +#define MT_WMM_CWMIN_MASK GENMASK(3, 0) +#define MT_WMM_CWMIN_SHIFT(n) ((n) * 4) + +#define MT_WMM_CWMAX 0x021c +#define MT_WMM_CWMAX_MASK GENMASK(3, 0) +#define MT_WMM_CWMAX_SHIFT(n) ((n) * 4) + +#define MT_WMM_TXOP_BASE 0x0220 +#define MT_WMM_TXOP(n) (MT_WMM_TXOP_BASE + (((n) / 2) << 2)) +#define MT_WMM_TXOP_SHIFT(n) (((n) & 1) * 16) +#define MT_WMM_TXOP_MASK GENMASK(15, 0) + +#define MT_FCE_DMA_ADDR 0x0230 +#define MT_FCE_DMA_LEN 0x0234 +#define MT_USB_DMA_CFG 0x0238 + +#define MT_TSO_CTRL 0x0250 +#define MT_HEADER_TRANS_CTRL_REG 0x0260 + +#define MT_US_CYC_CFG 0x02a4 +#define MT_US_CYC_CNT GENMASK(7, 0) + +#define MT_TX_RING_BASE 0x0300 +#define MT_RX_RING_BASE 0x03c0 + +#define MT_PBF_SYS_CTRL 0x0400 +#define MT_PBF_SYS_CTRL_MCU_RESET BIT(0) +#define MT_PBF_SYS_CTRL_DMA_RESET BIT(1) +#define MT_PBF_SYS_CTRL_MAC_RESET BIT(2) +#define MT_PBF_SYS_CTRL_PBF_RESET BIT(3) +#define MT_PBF_SYS_CTRL_ASY_RESET BIT(4) + +#define MT_PBF_CFG 0x0404 +#define MT_PBF_CFG_TX0Q_EN BIT(0) +#define MT_PBF_CFG_TX1Q_EN BIT(1) +#define MT_PBF_CFG_TX2Q_EN BIT(2) +#define MT_PBF_CFG_TX3Q_EN BIT(3) +#define MT_PBF_CFG_RX0Q_EN BIT(4) +#define MT_PBF_CFG_RX_DROP_EN BIT(8) + +#define MT_PBF_TX_MAX_PCNT 0x0408 +#define MT_PBF_RX_MAX_PCNT 0x040c + +#define MT_BCN_OFFSET_BASE 0x041c +#define MT_BCN_OFFSET(n) (MT_BCN_OFFSET_BASE + ((n) << 2)) + +#define MT_RXQ_STA 0x0430 +#define MT_TXQ_STA 0x0434 +#define MT_RF_CSR_CFG 0x0500 +#define MT_RF_CSR_CFG_DATA GENMASK(7, 0) +#define MT_RF_CSR_CFG_REG_ID GENMASK(14, 8) +#define MT_RF_CSR_CFG_REG_BANK GENMASK(17, 15) +#define MT_RF_CSR_CFG_WR BIT(30) +#define MT_RF_CSR_CFG_KICK BIT(31) + +#define MT_RF_BYPASS_0 0x0504 +#define MT_RF_BYPASS_1 0x0508 +#define MT_RF_SETTING_0 0x050c + +#define MT_RF_MISC 0x0518 +#define MT_RF_DATA_WRITE 0x0524 + +#define MT_RF_CTRL 0x0528 +#define MT_RF_CTRL_ADDR GENMASK(11, 0) +#define MT_RF_CTRL_WRITE BIT(12) +#define MT_RF_CTRL_BUSY BIT(13) +#define MT_RF_CTRL_IDX BIT(16) + +#define MT_RF_DATA_READ 0x052c + +#define MT_COM_REG0 0x0730 +#define MT_COM_REG1 0x0734 +#define MT_COM_REG2 0x0738 +#define MT_COM_REG3 0x073c + +#define MT_LED_CTRL 0x0770 +#define MT_LED_CTRL_REPLAY(n) BIT(0 + (8 * (n))) +#define MT_LED_CTRL_POLARITY(n) BIT(1 + (8 * (n))) +#define MT_LED_CTRL_TX_BLINK_MODE(n) BIT(2 + (8 * (n))) +#define MT_LED_CTRL_KICK(n) BIT(7 + (8 * (n))) + +#define MT_LED_TX_BLINK_0 0x0774 +#define MT_LED_TX_BLINK_1 0x0778 + +#define MT_LED_S0_BASE 0x077c +#define MT_LED_S0(n) (MT_LED_S0_BASE + 8 * (n)) +#define MT_LED_S1_BASE 0x0780 +#define MT_LED_S1(n) (MT_LED_S1_BASE + 8 * (n)) +#define MT_LED_STATUS_OFF GENMASK(31, 24) +#define MT_LED_STATUS_ON GENMASK(23, 16) +#define MT_LED_STATUS_DURATION GENMASK(15, 8) + +#define MT_FCE_PSE_CTRL 0x0800 +#define MT_FCE_PARAMETERS 0x0804 +#define MT_FCE_CSO 0x0808 + +#define MT_FCE_L2_STUFF 0x080c +#define MT_FCE_L2_STUFF_HT_L2_EN BIT(0) +#define MT_FCE_L2_STUFF_QOS_L2_EN BIT(1) +#define MT_FCE_L2_STUFF_RX_STUFF_EN BIT(2) +#define MT_FCE_L2_STUFF_TX_STUFF_EN BIT(3) +#define MT_FCE_L2_STUFF_WR_MPDU_LEN_EN BIT(4) +#define MT_FCE_L2_STUFF_MVINV_BSWAP BIT(5) +#define MT_FCE_L2_STUFF_TS_CMD_QSEL_EN GENMASK(15, 8) +#define MT_FCE_L2_STUFF_TS_LEN_EN GENMASK(23, 16) +#define MT_FCE_L2_STUFF_OTHER_PORT GENMASK(25, 24) + +#define MT_FCE_WLAN_FLOW_CONTROL1 0x0824 + +#define MT_TX_CPU_FROM_FCE_BASE_PTR 0x09a0 +#define MT_TX_CPU_FROM_FCE_MAX_COUNT 0x09a4 +#define MT_TX_CPU_FROM_FCE_CPU_DESC_IDX 0x09a8 +#define MT_FCE_PDMA_GLOBAL_CONF 0x09c4 +#define MT_FCE_SKIP_FS 0x0a6c + +#define MT_PAUSE_ENABLE_CONTROL1 0x0a38 + +#define MT_MAC_CSR0 0x1000 + +#define MT_MAC_SYS_CTRL 0x1004 +#define MT_MAC_SYS_CTRL_RESET_CSR BIT(0) +#define MT_MAC_SYS_CTRL_RESET_BBP BIT(1) +#define MT_MAC_SYS_CTRL_ENABLE_TX BIT(2) +#define MT_MAC_SYS_CTRL_ENABLE_RX BIT(3) + +#define MT_MAC_ADDR_DW0 0x1008 +#define MT_MAC_ADDR_DW1 0x100c +#define MT_MAC_ADDR_DW1_U2ME_MASK GENMASK(23, 16) + +#define MT_MAC_BSSID_DW0 0x1010 +#define MT_MAC_BSSID_DW1 0x1014 +#define MT_MAC_BSSID_DW1_ADDR GENMASK(15, 0) +#define MT_MAC_BSSID_DW1_MBSS_MODE GENMASK(17, 16) +#define MT_MAC_BSSID_DW1_MBEACON_N GENMASK(20, 18) +#define MT_MAC_BSSID_DW1_MBSS_LOCAL_BIT BIT(21) +#define MT_MAC_BSSID_DW1_MBSS_MODE_B2 BIT(22) +#define MT_MAC_BSSID_DW1_MBEACON_N_B3 BIT(23) +#define MT_MAC_BSSID_DW1_MBSS_IDX_BYTE GENMASK(26, 24) + +#define MT_MAX_LEN_CFG 0x1018 +#define MT_MAX_LEN_CFG_AMPDU GENMASK(13, 12) + +#define MT_LED_CFG 0x102c + +#define MT_AMPDU_MAX_LEN_20M1S 0x1030 +#define MT_AMPDU_MAX_LEN_20M2S 0x1034 +#define MT_AMPDU_MAX_LEN_40M1S 0x1038 +#define MT_AMPDU_MAX_LEN_40M2S 0x103c +#define MT_AMPDU_MAX_LEN 0x1040 + +#define MT_WCID_DROP_BASE 0x106c +#define MT_WCID_DROP(n) (MT_WCID_DROP_BASE + ((n) >> 5) * 4) +#define MT_WCID_DROP_MASK(n) BIT((n) % 32) + +#define MT_BCN_BYPASS_MASK 0x108c + +#define MT_MAC_APC_BSSID_BASE 0x1090 +#define MT_MAC_APC_BSSID_L(n) (MT_MAC_APC_BSSID_BASE + ((n) * 8)) +#define MT_MAC_APC_BSSID_H(n) (MT_MAC_APC_BSSID_BASE + ((n) * 8 + 4)) +#define MT_MAC_APC_BSSID_H_ADDR GENMASK(15, 0) +#define MT_MAC_APC_BSSID0_H_EN BIT(16) + +#define MT_XIFS_TIME_CFG 0x1100 +#define MT_XIFS_TIME_CFG_CCK_SIFS GENMASK(7, 0) +#define MT_XIFS_TIME_CFG_OFDM_SIFS GENMASK(15, 8) +#define MT_XIFS_TIME_CFG_OFDM_XIFS GENMASK(19, 16) +#define MT_XIFS_TIME_CFG_EIFS GENMASK(28, 20) +#define MT_XIFS_TIME_CFG_BB_RXEND_EN BIT(29) + +#define MT_BKOFF_SLOT_CFG 0x1104 +#define MT_BKOFF_SLOT_CFG_SLOTTIME GENMASK(7, 0) +#define MT_BKOFF_SLOT_CFG_CC_DELAY GENMASK(11, 8) + +#define MT_CH_TIME_CFG 0x110c +#define MT_CH_TIME_CFG_TIMER_EN BIT(0) +#define MT_CH_TIME_CFG_TX_AS_BUSY BIT(1) +#define MT_CH_TIME_CFG_RX_AS_BUSY BIT(2) +#define MT_CH_TIME_CFG_NAV_AS_BUSY BIT(3) +#define MT_CH_TIME_CFG_EIFS_AS_BUSY BIT(4) +#define MT_CH_TIME_CFG_MDRDY_CNT_EN BIT(5) +#define MT_CH_CCA_RC_EN BIT(6) +#define MT_CH_TIME_CFG_CH_TIMER_CLR GENMASK(9, 8) +#define MT_CH_TIME_CFG_MDRDY_CLR GENMASK(11, 10) + +#define MT_PBF_LIFE_TIMER 0x1110 + +#define MT_BEACON_TIME_CFG 0x1114 +#define MT_BEACON_TIME_CFG_INTVAL GENMASK(15, 0) +#define MT_BEACON_TIME_CFG_TIMER_EN BIT(16) +#define MT_BEACON_TIME_CFG_SYNC_MODE GENMASK(18, 17) +#define MT_BEACON_TIME_CFG_TBTT_EN BIT(19) +#define MT_BEACON_TIME_CFG_BEACON_TX BIT(20) +#define MT_BEACON_TIME_CFG_TSF_COMP GENMASK(31, 24) + +#define MT_TBTT_SYNC_CFG 0x1118 +#define MT_TSF_TIMER_DW0 0x111c +#define MT_TSF_TIMER_DW1 0x1120 +#define MT_TBTT_TIMER 0x1124 +#define MT_TBTT_TIMER_VAL GENMASK(16, 0) + +#define MT_INT_TIMER_CFG 0x1128 +#define MT_INT_TIMER_CFG_PRE_TBTT GENMASK(15, 0) +#define MT_INT_TIMER_CFG_GP_TIMER GENMASK(31, 16) + +#define MT_INT_TIMER_EN 0x112c +#define MT_INT_TIMER_EN_PRE_TBTT_EN BIT(0) +#define MT_INT_TIMER_EN_GP_TIMER_EN BIT(1) + +#define MT_CH_IDLE 0x1130 +#define MT_CH_BUSY 0x1134 +#define MT_EXT_CH_BUSY 0x1138 +#define MT_ED_CCA_TIMER 0x1140 + +#define MT_MAC_STATUS 0x1200 +#define MT_MAC_STATUS_TX BIT(0) +#define MT_MAC_STATUS_RX BIT(1) + +#define MT_PWR_PIN_CFG 0x1204 +#define MT_AUX_CLK_CFG 0x120c + +#define MT_BB_PA_MODE_CFG0 0x1214 +#define MT_BB_PA_MODE_CFG1 0x1218 +#define MT_RF_PA_MODE_CFG0 0x121c +#define MT_RF_PA_MODE_CFG1 0x1220 + +#define MT_RF_PA_MODE_ADJ0 0x1228 +#define MT_RF_PA_MODE_ADJ1 0x122c + +#define MT_DACCLK_EN_DLY_CFG 0x1264 + +#define MT_EDCA_CFG_BASE 0x1300 +#define MT_EDCA_CFG_AC(n) (MT_EDCA_CFG_BASE + ((n) << 2)) +#define MT_EDCA_CFG_TXOP GENMASK(7, 0) +#define MT_EDCA_CFG_AIFSN GENMASK(11, 8) +#define MT_EDCA_CFG_CWMIN GENMASK(15, 12) +#define MT_EDCA_CFG_CWMAX GENMASK(19, 16) + +#define MT_TX_PWR_CFG_0 0x1314 +#define MT_TX_PWR_CFG_1 0x1318 +#define MT_TX_PWR_CFG_2 0x131c +#define MT_TX_PWR_CFG_3 0x1320 +#define MT_TX_PWR_CFG_4 0x1324 +#define MT_TX_PIN_CFG 0x1328 +#define MT_TX_PIN_CFG_TXANT GENMASK(3, 0) +#define MT_TX_PIN_CFG_RXANT GENMASK(11, 8) +#define MT_TX_PIN_RFTR_EN BIT(16) +#define MT_TX_PIN_TRSW_EN BIT(18) + +#define MT_TX_BAND_CFG 0x132c +#define MT_TX_BAND_CFG_UPPER_40M BIT(0) +#define MT_TX_BAND_CFG_5G BIT(1) +#define MT_TX_BAND_CFG_2G BIT(2) + +#define MT_HT_FBK_TO_LEGACY 0x1384 +#define MT_TX_MPDU_ADJ_INT 0x1388 + +#define MT_TX_PWR_CFG_7 0x13d4 +#define MT_TX_PWR_CFG_8 0x13d8 +#define MT_TX_PWR_CFG_9 0x13dc + +#define MT_TX_SW_CFG0 0x1330 +#define MT_TX_SW_CFG1 0x1334 +#define MT_TX_SW_CFG2 0x1338 + +#define MT_TXOP_CTRL_CFG 0x1340 +#define MT_TXOP_TRUN_EN GENMASK(5, 0) +#define MT_TXOP_EXT_CCA_DLY GENMASK(15, 8) +#define MT_TXOP_ED_CCA_EN BIT(20) + +#define MT_TX_RTS_CFG 0x1344 +#define MT_TX_RTS_CFG_RETRY_LIMIT GENMASK(7, 0) +#define MT_TX_RTS_CFG_THRESH GENMASK(23, 8) +#define MT_TX_RTS_FALLBACK BIT(24) + +#define MT_TX_TIMEOUT_CFG 0x1348 +#define MT_TX_TIMEOUT_CFG_ACKTO GENMASK(15, 8) + +#define MT_TX_RETRY_CFG 0x134c +#define MT_TX_LINK_CFG 0x1350 +#define MT_TX_CFACK_EN BIT(12) +#define MT_VHT_HT_FBK_CFG0 0x1354 +#define MT_VHT_HT_FBK_CFG1 0x1358 +#define MT_LG_FBK_CFG0 0x135c +#define MT_LG_FBK_CFG1 0x1360 + +#define MT_PROT_CFG_RATE GENMASK(15, 0) +#define MT_PROT_CFG_CTRL GENMASK(17, 16) +#define MT_PROT_CFG_NAV GENMASK(19, 18) +#define MT_PROT_CFG_TXOP_ALLOW GENMASK(25, 20) +#define MT_PROT_CFG_RTS_THRESH BIT(26) + +#define MT_CCK_PROT_CFG 0x1364 +#define MT_OFDM_PROT_CFG 0x1368 +#define MT_MM20_PROT_CFG 0x136c +#define MT_MM40_PROT_CFG 0x1370 +#define MT_GF20_PROT_CFG 0x1374 +#define MT_GF40_PROT_CFG 0x1378 + +#define MT_PROT_RATE GENMASK(15, 0) +#define MT_PROT_CTRL_RTS_CTS BIT(16) +#define MT_PROT_CTRL_CTS2SELF BIT(17) +#define MT_PROT_NAV_SHORT BIT(18) +#define MT_PROT_NAV_LONG BIT(19) +#define MT_PROT_TXOP_ALLOW_CCK BIT(20) +#define MT_PROT_TXOP_ALLOW_OFDM BIT(21) +#define MT_PROT_TXOP_ALLOW_MM20 BIT(22) +#define MT_PROT_TXOP_ALLOW_MM40 BIT(23) +#define MT_PROT_TXOP_ALLOW_GF20 BIT(24) +#define MT_PROT_TXOP_ALLOW_GF40 BIT(25) +#define MT_PROT_RTS_THR_EN BIT(26) +#define MT_PROT_RATE_CCK_11 0x0003 +#define MT_PROT_RATE_OFDM_6 0x2000 +#define MT_PROT_RATE_OFDM_24 0x2004 +#define MT_PROT_RATE_DUP_OFDM_24 0x2084 +#define MT_PROT_RATE_SGI_OFDM_24 0x2104 +#define MT_PROT_TXOP_ALLOW_ALL GENMASK(25, 20) +#define MT_PROT_TXOP_ALLOW_BW20 (MT_PROT_TXOP_ALLOW_ALL & \ + ~MT_PROT_TXOP_ALLOW_MM40 & \ + ~MT_PROT_TXOP_ALLOW_GF40) + +#define MT_EXP_ACK_TIME 0x1380 + +#define MT_TX_PWR_CFG_0_EXT 0x1390 +#define MT_TX_PWR_CFG_1_EXT 0x1394 + +#define MT_TX_FBK_LIMIT 0x1398 +#define MT_TX_FBK_LIMIT_MPDU_FBK GENMASK(7, 0) +#define MT_TX_FBK_LIMIT_AMPDU_FBK GENMASK(15, 8) +#define MT_TX_FBK_LIMIT_MPDU_UP_CLEAR BIT(16) +#define MT_TX_FBK_LIMIT_AMPDU_UP_CLEAR BIT(17) +#define MT_TX_FBK_LIMIT_RATE_LUT BIT(18) + +#define MT_TX0_RF_GAIN_CORR 0x13a0 +#define MT_TX1_RF_GAIN_CORR 0x13a4 +#define MT_TX0_RF_GAIN_ATTEN 0x13a8 + +#define MT_TX_ALC_CFG_0 0x13b0 +#define MT_TX_ALC_CFG_0_CH_INIT_0 GENMASK(5, 0) +#define MT_TX_ALC_CFG_0_CH_INIT_1 GENMASK(13, 8) +#define MT_TX_ALC_CFG_0_LIMIT_0 GENMASK(21, 16) +#define MT_TX_ALC_CFG_0_LIMIT_1 GENMASK(29, 24) + +#define MT_TX_ALC_CFG_1 0x13b4 +#define MT_TX_ALC_CFG_1_TEMP_COMP GENMASK(5, 0) + +#define MT_TX_ALC_CFG_2 0x13a8 +#define MT_TX_ALC_CFG_2_TEMP_COMP GENMASK(5, 0) + +#define MT_TX_ALC_CFG_3 0x13ac +#define MT_TX_ALC_CFG_4 0x13c0 +#define MT_TX_ALC_CFG_4_LOWGAIN_CH_EN BIT(31) + +#define MT_TX_ALC_VGA3 0x13c8 + +#define MT_TX_PROT_CFG6 0x13e0 +#define MT_TX_PROT_CFG7 0x13e4 +#define MT_TX_PROT_CFG8 0x13e8 + +#define MT_PIFS_TX_CFG 0x13ec + +#define MT_RX_FILTR_CFG 0x1400 + +#define MT_RX_FILTR_CFG_CRC_ERR BIT(0) +#define MT_RX_FILTR_CFG_PHY_ERR BIT(1) +#define MT_RX_FILTR_CFG_PROMISC BIT(2) +#define MT_RX_FILTR_CFG_OTHER_BSS BIT(3) +#define MT_RX_FILTR_CFG_VER_ERR BIT(4) +#define MT_RX_FILTR_CFG_MCAST BIT(5) +#define MT_RX_FILTR_CFG_BCAST BIT(6) +#define MT_RX_FILTR_CFG_DUP BIT(7) +#define MT_RX_FILTR_CFG_CFACK BIT(8) +#define MT_RX_FILTR_CFG_CFEND BIT(9) +#define MT_RX_FILTR_CFG_ACK BIT(10) +#define MT_RX_FILTR_CFG_CTS BIT(11) +#define MT_RX_FILTR_CFG_RTS BIT(12) +#define MT_RX_FILTR_CFG_PSPOLL BIT(13) +#define MT_RX_FILTR_CFG_BA BIT(14) +#define MT_RX_FILTR_CFG_BAR BIT(15) +#define MT_RX_FILTR_CFG_CTRL_RSV BIT(16) + +#define MT_AUTO_RSP_CFG 0x1404 +#define MT_AUTO_RSP_EN BIT(0) +#define MT_AUTO_RSP_PREAMB_SHORT BIT(4) +#define MT_LEGACY_BASIC_RATE 0x1408 +#define MT_HT_BASIC_RATE 0x140c + +#define MT_HT_CTRL_CFG 0x1410 +#define MT_RX_PARSER_CFG 0x1418 +#define MT_RX_PARSER_RX_SET_NAV_ALL BIT(0) + +#define MT_EXT_CCA_CFG 0x141c +#define MT_EXT_CCA_CFG_CCA0 GENMASK(1, 0) +#define MT_EXT_CCA_CFG_CCA1 GENMASK(3, 2) +#define MT_EXT_CCA_CFG_CCA2 GENMASK(5, 4) +#define MT_EXT_CCA_CFG_CCA3 GENMASK(7, 6) +#define MT_EXT_CCA_CFG_CCA_MASK GENMASK(11, 8) +#define MT_EXT_CCA_CFG_ED_CCA_MASK GENMASK(15, 12) + +#define MT_TX_SW_CFG3 0x1478 + +#define MT_PN_PAD_MODE 0x150c + +#define MT_TXOP_HLDR_ET 0x1608 +#define MT_TXOP_HLDR_TX40M_BLK_EN BIT(1) + +#define MT_PROT_AUTO_TX_CFG 0x1648 +#define MT_PROT_AUTO_TX_CFG_PROT_PADJ GENMASK(11, 8) +#define MT_PROT_AUTO_TX_CFG_AUTO_PADJ GENMASK(27, 24) + +#define MT_RX_STAT_0 0x1700 +#define MT_RX_STAT_0_CRC_ERRORS GENMASK(15, 0) +#define MT_RX_STAT_0_PHY_ERRORS GENMASK(31, 16) + +#define MT_RX_STAT_1 0x1704 +#define MT_RX_STAT_1_CCA_ERRORS GENMASK(15, 0) +#define MT_RX_STAT_1_PLCP_ERRORS GENMASK(31, 16) + +#define MT_RX_STAT_2 0x1708 +#define MT_RX_STAT_2_DUP_ERRORS GENMASK(15, 0) +#define MT_RX_STAT_2_OVERFLOW_ERRORS GENMASK(31, 16) + +#define MT_TX_STA_0 0x170c +#define MT_TX_STA_1 0x1710 +#define MT_TX_STA_2 0x1714 + +#define MT_TX_STAT_FIFO 0x1718 +#define MT_TX_STAT_FIFO_VALID BIT(0) +#define MT_TX_STAT_FIFO_SUCCESS BIT(5) +#define MT_TX_STAT_FIFO_AGGR BIT(6) +#define MT_TX_STAT_FIFO_ACKREQ BIT(7) +#define MT_TX_STAT_FIFO_WCID GENMASK(15, 8) +#define MT_TX_STAT_FIFO_RATE GENMASK(31, 16) + +#define MT_TX_AGG_STAT 0x171c + +#define MT_TX_AGG_CNT_BASE0 0x1720 +#define MT_MPDU_DENSITY_CNT 0x1740 +#define MT_TX_AGG_CNT_BASE1 0x174c + +#define MT_TX_STAT_FIFO_EXT 0x1798 +#define MT_TX_STAT_FIFO_EXT_RETRY GENMASK(7, 0) +#define MT_TX_STAT_FIFO_EXT_PKTID GENMASK(15, 8) + +#define MT_WCID_TX_RATE_BASE 0x1c00 +#define MT_WCID_TX_RATE(i) (MT_WCID_TX_RATE_BASE + ((i) << 3)) + +#define MT_BBP_CORE_BASE 0x2000 +#define MT_BBP_IBI_BASE 0x2100 +#define MT_BBP_AGC_BASE 0x2300 +#define MT_BBP_TXC_BASE 0x2400 +#define MT_BBP_RXC_BASE 0x2500 +#define MT_BBP_TXO_BASE 0x2600 +#define MT_BBP_TXBE_BASE 0x2700 +#define MT_BBP_RXFE_BASE 0x2800 +#define MT_BBP_RXO_BASE 0x2900 +#define MT_BBP_DFS_BASE 0x2a00 +#define MT_BBP_TR_BASE 0x2b00 +#define MT_BBP_CAL_BASE 0x2c00 +#define MT_BBP_DSC_BASE 0x2e00 +#define MT_BBP_PFMU_BASE 0x2f00 + +#define MT_BBP(type, n) (MT_BBP_##type##_BASE + ((n) << 2)) + +#define MT_BBP_CORE_R1_BW GENMASK(4, 3) + +#define MT_BBP_AGC_R0_CTRL_CHAN GENMASK(9, 8) +#define MT_BBP_AGC_R0_BW GENMASK(14, 12) + +/* AGC, R4/R5 */ +#define MT_BBP_AGC_LNA_HIGH_GAIN GENMASK(21, 16) +#define MT_BBP_AGC_LNA_MID_GAIN GENMASK(13, 8) +#define MT_BBP_AGC_LNA_LOW_GAIN GENMASK(5, 0) + +/* AGC, R6/R7 */ +#define MT_BBP_AGC_LNA_ULOW_GAIN GENMASK(5, 0) + +/* AGC, R8/R9 */ +#define MT_BBP_AGC_LNA_GAIN_MODE GENMASK(7, 6) +#define MT_BBP_AGC_GAIN GENMASK(14, 8) + +#define MT_BBP_AGC20_RSSI0 GENMASK(7, 0) +#define MT_BBP_AGC20_RSSI1 GENMASK(15, 8) + +#define MT_BBP_TXBE_R0_CTRL_CHAN GENMASK(1, 0) + +#define MT_WCID_ADDR_BASE 0x1800 +#define MT_WCID_ADDR(n) (MT_WCID_ADDR_BASE + (n) * 8) + +#define MT_SRAM_BASE 0x4000 + +#define MT_WCID_KEY_BASE 0x8000 +#define MT_WCID_KEY(n) (MT_WCID_KEY_BASE + (n) * 32) + +#define MT_WCID_IV_BASE 0xa000 +#define MT_WCID_IV(n) (MT_WCID_IV_BASE + (n) * 8) + +#define MT_WCID_ATTR_BASE 0xa800 +#define MT_WCID_ATTR(n) (MT_WCID_ATTR_BASE + (n) * 4) + +#define MT_WCID_ATTR_PAIRWISE BIT(0) +#define MT_WCID_ATTR_PKEY_MODE GENMASK(3, 1) +#define MT_WCID_ATTR_BSS_IDX GENMASK(6, 4) +#define MT_WCID_ATTR_RXWI_UDF GENMASK(9, 7) +#define MT_WCID_ATTR_PKEY_MODE_EXT BIT(10) +#define MT_WCID_ATTR_BSS_IDX_EXT BIT(11) +#define MT_WCID_ATTR_WAPI_MCBC BIT(15) +#define MT_WCID_ATTR_WAPI_KEYID GENMASK(31, 24) + +#define MT_SKEY_BASE_0 0xac00 +#define MT_SKEY_BASE_1 0xb400 +#define MT_SKEY_0(bss, idx) (MT_SKEY_BASE_0 + (4 * (bss) + (idx)) * 32) +#define MT_SKEY_1(bss, idx) (MT_SKEY_BASE_1 + (4 * ((bss) & 7) + (idx)) * 32) + +#define MT_SKEY_MODE_BASE_0 0xb000 +#define MT_SKEY_MODE_BASE_1 0xb3f0 +#define MT_SKEY_MODE_0(bss) (MT_SKEY_MODE_BASE_0 + (((bss) / 2) << 2)) +#define MT_SKEY_MODE_1(bss) (MT_SKEY_MODE_BASE_1 + ((((bss) & 7) / 2) << 2)) +#define MT_SKEY_MODE_MASK GENMASK(3, 0) +#define MT_SKEY_MODE_SHIFT(bss, idx) (4 * ((idx) + 4 * ((bss) & 1))) + +#define MT_BEACON_BASE 0xc000 + +#define MT_TEMP_SENSOR 0x01d000 +#define MT_TEMP_SENSOR_VAL GENMASK(6, 0) + +#define MT_MCU_RESET_CTL 0x070c +#define MT_MCU_INT_LEVEL 0x0718 +#define MT_MCU_COM_REG0 0x0730 +#define MT_MCU_COM_REG1 0x0734 +#define MT_MCU_COM_REG2 0x0738 +#define MT_MCU_COM_REG3 0x073c + +#define MT_MCU_MEMMAP_WLAN 0x410000 + +#define MT_TXD_INFO_LEN GENMASK(15, 0) +#define MT_TXD_INFO_NEXT_VLD BIT(16) +#define MT_TXD_INFO_TX_BURST BIT(17) +#define MT_TXD_INFO_80211 BIT(19) +#define MT_TXD_INFO_TSO BIT(20) +#define MT_TXD_INFO_CSO BIT(21) +#define MT_TXD_INFO_WIV BIT(24) +#define MT_TXD_INFO_QSEL GENMASK(26, 25) +#define MT_TXD_INFO_DPORT GENMASK(29, 27) +#define MT_TXD_INFO_TYPE GENMASK(31, 30) + +#define MT_RX_FCE_INFO_LEN GENMASK(13, 0) +#define MT_RX_FCE_INFO_SELF_GEN BIT(15) +#define MT_RX_FCE_INFO_CMD_SEQ GENMASK(19, 16) +#define MT_RX_FCE_INFO_EVT_TYPE GENMASK(23, 20) +#define MT_RX_FCE_INFO_PCIE_INTR BIT(24) +#define MT_RX_FCE_INFO_QSEL GENMASK(26, 25) +#define MT_RX_FCE_INFO_D_PORT GENMASK(29, 27) +#define MT_RX_FCE_INFO_TYPE GENMASK(31, 30) + +#define MT_MCU_MSG_LEN GENMASK(15, 0) +#define MT_MCU_MSG_CMD_SEQ GENMASK(19, 16) +#define MT_MCU_MSG_CMD_TYPE GENMASK(26, 20) +#define MT_MCU_MSG_PORT GENMASK(29, 27) +#define MT_MCU_MSG_TYPE GENMASK(31, 30) +#define MT_MCU_MSG_TYPE_CMD BIT(30) + +#define MT_FCE_DMA_ADDR 0x0230 +#define MT_FCE_DMA_LEN 0x0234 + +#define MT_TX_CPU_FROM_FCE_CPU_DESC_IDX 0x09a8 + +#define MT_PKTID_RATE GENMASK(4, 0) +#define MT_PKTID_AC GENMASK(6, 5) + +#define MT_RXINFO_BA BIT(0) +#define MT_RXINFO_DATA BIT(1) +#define MT_RXINFO_NULL BIT(2) +#define MT_RXINFO_FRAG BIT(3) +#define MT_RXINFO_UNICAST BIT(4) +#define MT_RXINFO_MULTICAST BIT(5) +#define MT_RXINFO_BROADCAST BIT(6) +#define MT_RXINFO_MYBSS BIT(7) +#define MT_RXINFO_CRCERR BIT(8) +#define MT_RXINFO_ICVERR BIT(9) +#define MT_RXINFO_MICERR BIT(10) +#define MT_RXINFO_AMSDU BIT(11) +#define MT_RXINFO_HTC BIT(12) +#define MT_RXINFO_RSSI BIT(13) +#define MT_RXINFO_L2PAD BIT(14) +#define MT_RXINFO_AMPDU BIT(15) +#define MT_RXINFO_DECRYPT BIT(16) +#define MT_RXINFO_BSSIDX3 BIT(17) +#define MT_RXINFO_WAPI_KEY BIT(18) +#define MT_RXINFO_PN_LEN GENMASK(21, 19) +#define MT_RXINFO_SW_FTYPE0 BIT(22) +#define MT_RXINFO_SW_FTYPE1 BIT(23) +#define MT_RXINFO_PROBE_RESP BIT(24) +#define MT_RXINFO_BEACON BIT(25) +#define MT_RXINFO_DISASSOC BIT(26) +#define MT_RXINFO_DEAUTH BIT(27) +#define MT_RXINFO_ACTION BIT(28) +#define MT_RXINFO_TCP_SUM_ERR BIT(30) +#define MT_RXINFO_IP_SUM_ERR BIT(31) + +#define MT_RXWI_CTL_WCID GENMASK(7, 0) +#define MT_RXWI_CTL_KEY_IDX GENMASK(9, 8) +#define MT_RXWI_CTL_BSS_IDX GENMASK(12, 10) +#define MT_RXWI_CTL_UDF GENMASK(15, 13) +#define MT_RXWI_CTL_MPDU_LEN GENMASK(29, 16) +#define MT_RXWI_CTL_EOF BIT(31) + +#define MT_RXWI_TID GENMASK(3, 0) +#define MT_RXWI_SN GENMASK(15, 4) + +#define MT_RXWI_RATE_INDEX GENMASK(5, 0) +#define MT_RXWI_RATE_LDPC BIT(6) +#define MT_RXWI_RATE_BW GENMASK(8, 7) +#define MT_RXWI_RATE_SGI BIT(9) +#define MT_RXWI_RATE_STBC BIT(10) +#define MT_RXWI_RATE_LDPC_EXSYM BIT(11) +#define MT_RXWI_RATE_PHY GENMASK(15, 13) + +#define MT_RATE_INDEX_VHT_IDX GENMASK(3, 0) +#define MT_RATE_INDEX_VHT_NSS GENMASK(5, 4) + +#define MT_TX_PWR_ADJ GENMASK(3, 0) + +#define MT_TXWI_FLAGS_FRAG BIT(0) +#define MT_TXWI_FLAGS_MMPS BIT(1) +#define MT_TXWI_FLAGS_CFACK BIT(2) +#define MT_TXWI_FLAGS_TS BIT(3) +#define MT_TXWI_FLAGS_AMPDU BIT(4) +#define MT_TXWI_FLAGS_MPDU_DENSITY GENMASK(7, 5) +#define MT_TXWI_FLAGS_TXOP GENMASK(9, 8) +#define MT_TXWI_FLAGS_NDPS BIT(10) +#define MT_TXWI_FLAGS_RTSBWSIG BIT(11) +#define MT_TXWI_FLAGS_NDP_BW GENMASK(13, 12) +#define MT_TXWI_FLAGS_SOUND BIT(14) +#define MT_TXWI_FLAGS_TX_RATE_LUT BIT(15) + +#define MT_TXWI_ACK_CTL_REQ BIT(0) +#define MT_TXWI_ACK_CTL_NSEQ BIT(1) +#define MT_TXWI_ACK_CTL_BA_WINDOW GENMASK(7, 2) + +#define MT_EE_ANTENNA_DUAL BIT(15) + +#define MT_EE_NIC_CONF_0_RX_PATH GENMASK(3, 0) +#define MT_EE_NIC_CONF_0_TX_PATH GENMASK(7, 4) +#define MT_EE_NIC_CONF_0_PA_TYPE GENMASK(9, 8) +#define MT_EE_NIC_CONF_0_PA_INT_2G BIT(8) +#define MT_EE_NIC_CONF_0_PA_INT_5G BIT(9) +#define MT_EE_NIC_CONF_0_PA_IO_CURRENT BIT(10) +#define MT_EE_NIC_CONF_0_BOARD_TYPE GENMASK(13, 12) + +#define MT_EE_NIC_CONF_1_HW_RF_CTRL BIT(0) +#define MT_EE_NIC_CONF_1_TEMP_TX_ALC BIT(1) +#define MT_EE_NIC_CONF_1_LNA_EXT_2G BIT(2) +#define MT_EE_NIC_CONF_1_LNA_EXT_5G BIT(3) +#define MT_EE_NIC_CONF_1_TX_ALC_EN BIT(13) + +#define MT_EE_NIC_CONF_2_ANT_OPT BIT(3) +#define MT_EE_NIC_CONF_2_ANT_DIV BIT(4) +#define MT_EE_NIC_CONF_2_XTAL_OPTION GENMASK(10, 9) + +#define MT_VEND_TYPE_CFG BIT(30) + +#define MT_CMD_HDR_LEN 4 + +enum mt76_vendor_req { + MT_VEND_DEV_MODE = 0x01, + MT_VEND_WRITE = 0x02, + MT_VEND_POWER_ON = 0x04, + MT_VEND_MULTI_WRITE = 0x06, + MT_VEND_MULTI_READ = 0x07, + MT_VEND_READ_EEPROM = 0x09, + MT_VEND_WRITE_FCE = 0x42, + MT_VEND_WRITE_CFG = 0x46, + MT_VEND_READ_CFG = 0x47, + MT_VEND_READ_EXT = 0x63, + MT_VEND_WRITE_EXT = 0x66, + MT_VEND_FEATURE_SET = 0x91, +}; + +enum mt76_dma_msg_port { + MT_WLAN_PORT, + MT_CPU_RX_PORT, + MT_CPU_TX_PORT, + MT_HOST_PORT, + MT_VIRTUAL_CPU_RX_PORT, + MT_VIRTUAL_CPU_TX_PORT, + MT_DISCARD, +}; + +enum mt76_mcu_cmd { + MT_CMD_FUN_SET_OP = 1, + MT_CMD_LOAD_CR = 2, + MT_CMD_INIT_GAIN_OP = 3, + MT_CMD_DYNC_VGA_OP = 6, + MT_CMD_TDLS_CH_SW = 7, + MT_CMD_BURST_WRITE = 8, + MT_CMD_READ_MODIFY_WRITE = 9, + MT_CMD_RANDOM_READ = 10, + MT_CMD_BURST_READ = 11, + MT_CMD_RANDOM_WRITE = 12, + MT_CMD_LED_MODE_OP = 16, + MT_CMD_POWER_SAVING_OP = 20, + MT_CMD_WOW_CONFIG = 21, + MT_CMD_WOW_QUERY = 22, + MT_CMD_WOW_FEATURE = 24, + MT_CMD_CARRIER_DETECT_OP = 28, + MT_CMD_RADOR_DETECT_OP = 29, + MT_CMD_SWITCH_CHANNEL_OP = 30, + MT_CMD_CALIBRATION_OP = 31, + MT_CMD_BEACON_OP = 32, + MT_CMD_ANTENNA_OP = 33, +}; + +enum mt76_mcu_function { + MT_Q_SELECT = 1, + MT_BW_SETTING = 2, + MT_USB2_SW_DISCONNECT = 2, + MT_USB3_SW_DISCONNECT = 3, + MT_LOG_FW_DEBUG_MSG = 4, + MT_GET_FW_VERSION = 5, +}; + +enum mt76_mcu_cr_mode { + MT_RF_CR, + MT_BBP_CR, + MT_RF_BBP_CR, + MT_HL_TEMP_CR_UPDATE, +}; + +enum mt76_mcu_power_mode { + MT_RADIO_OFF = 0x30, + MT_RADIO_ON = 0x31, + MT_RADIO_OFF_AUTO_WAKEUP = 0x32, + MT_RADIO_OFF_ADVANCE = 0x33, + MT_RADIO_ON_ADVANCE = 0x34, +}; + +enum mt76_mcu_calibration { + MT_MCU_CAL_R = 1, + MT_MCU_CAL_TEMP_SENSOR, + MT_MCU_CAL_RXDCOC, + MT_MCU_CAL_RC, + MT_MCU_CAL_SX_LOGEN, + MT_MCU_CAL_LC, + MT_MCU_CAL_TX_LOFT, + MT_MCU_CAL_TXIQ, + MT_MCU_CAL_TSSI, + MT_MCU_CAL_TSSI_COMP, + MT_MCU_CAL_DPD, + MT_MCU_CAL_RXIQC_FI, + MT_MCU_CAL_RXIQC_FD, + MT_MCU_CAL_PWRON, + MT_MCU_CAL_TX_SHAPING, +}; + +enum mt76_eeprom_mode { + MT_EE_READ, + MT_EE_PHYSICAL_READ, +}; + +enum mt76_eeprom_field { + MT_EE_CHIP_ID = 0x0000, + MT_EE_VERSION = 0x0002, + MT_EE_MAC_ADDR = 0x0004, + MT_EE_PCI_ID = 0x000a, + MT_EE_ANTENNA = 0x0022, + MT_EE_CFG1_INIT = 0x0024, + MT_EE_NIC_CONF_0 = 0x0034, + MT_EE_NIC_CONF_1 = 0x0036, + MT_EE_COUNTRY_REGION_5GHZ = 0x0038, + MT_EE_COUNTRY_REGION_2GHZ = 0x0039, + MT_EE_FREQ_OFFSET = 0x003a, + MT_EE_NIC_CONF_2 = 0x0042, + + MT_EE_XTAL_TRIM_1 = 0x003a, + MT_EE_XTAL_TRIM_2 = 0x009e, + + MT_EE_LNA_GAIN = 0x0044, + MT_EE_RSSI_OFFSET_2G_0 = 0x0046, + MT_EE_RSSI_OFFSET_2G_1 = 0x0048, + MT_EE_LNA_GAIN_5GHZ_1 = 0x0049, + MT_EE_RSSI_OFFSET_5G_0 = 0x004a, + MT_EE_RSSI_OFFSET_5G_1 = 0x004c, + MT_EE_LNA_GAIN_5GHZ_2 = 0x004d, + + MT_EE_TX_POWER_DELTA_BW40 = 0x0050, + MT_EE_TX_POWER_DELTA_BW80 = 0x0052, + + MT_EE_TX_POWER_EXT_PA_5G = 0x0054, + + MT_EE_TX_POWER_0_START_2G = 0x0056, + MT_EE_TX_POWER_1_START_2G = 0x005c, + +#define MT_TX_POWER_GROUP_SIZE_5G 5 +#define MT_TX_POWER_GROUPS_5G 6 + MT_EE_TX_POWER_0_START_5G = 0x0062, + MT_EE_TSSI_SLOPE_2G = 0x006e, + + MT_EE_TX_POWER_0_GRP3_TX_POWER_DELTA = 0x0074, + MT_EE_TX_POWER_0_GRP4_TSSI_SLOPE = 0x0076, + + MT_EE_TX_POWER_1_START_5G = 0x0080, + + MT_EE_TX_POWER_CCK = 0x00a0, + MT_EE_TX_POWER_OFDM_2G_6M = 0x00a2, + MT_EE_TX_POWER_OFDM_2G_24M = 0x00a4, + MT_EE_TX_POWER_OFDM_5G_6M = 0x00b2, + MT_EE_TX_POWER_OFDM_5G_24M = 0x00b4, + MT_EE_TX_POWER_HT_MCS0 = 0x00a6, + MT_EE_TX_POWER_HT_MCS4 = 0x00a8, + MT_EE_TX_POWER_HT_MCS8 = 0x00aa, + MT_EE_TX_POWER_HT_MCS12 = 0x00ac, + MT_EE_TX_POWER_VHT_MCS0 = 0x00ba, + MT_EE_TX_POWER_VHT_MCS4 = 0x00bc, + MT_EE_TX_POWER_VHT_MCS8 = 0x00be, + + MT_EE_2G_TARGET_POWER = 0x00d0, + MT_EE_TEMP_OFFSET = 0x00d1, + MT_EE_5G_TARGET_POWER = 0x00d2, + MT_EE_TSSI_BOUND1 = 0x00d4, + MT_EE_TSSI_BOUND2 = 0x00d6, + MT_EE_TSSI_BOUND3 = 0x00d8, + MT_EE_TSSI_BOUND4 = 0x00da, + MT_EE_FREQ_OFFSET_COMPENSATION = 0x00db, + MT_EE_TSSI_BOUND5 = 0x00dc, + MT_EE_TX_POWER_BYRATE_BASE = 0x00de, + + MT_EE_TSSI_SLOPE_5G = 0x00f0, + MT_EE_RF_TEMP_COMP_SLOPE_5G = 0x00f2, + MT_EE_RF_TEMP_COMP_SLOPE_2G = 0x00f4, + + MT_EE_RF_2G_TSSI_OFF_TXPOWER = 0x00f6, + MT_EE_RF_2G_RX_HIGH_GAIN = 0x00f8, + MT_EE_RF_5G_GRP0_1_RX_HIGH_GAIN = 0x00fa, + MT_EE_RF_5G_GRP2_3_RX_HIGH_GAIN = 0x00fc, + MT_EE_RF_5G_GRP4_5_RX_HIGH_GAIN = 0x00fe, + + MT_EE_BT_RCAL_RESULT = 0x0138, + MT_EE_BT_VCDL_CALIBRATION = 0x013c, + MT_EE_BT_PMUCFG = 0x013e, + + MT_EE_USAGE_MAP_START = 0x01e0, + MT_EE_USAGE_MAP_END = 0x01fc, +}; + +enum mt76_phy_type { + MT_PHY_TYPE_CCK, + MT_PHY_TYPE_OFDM, + MT_PHY_TYPE_HT, + MT_PHY_TYPE_HT_GF, + MT_PHY_TYPE_VHT, + MT_PHY_TYPE_HE_SU = 8, + MT_PHY_TYPE_HE_EXT_SU, + MT_PHY_TYPE_HE_TB, + MT_PHY_TYPE_HE_MU, +}; + +enum mt76_phy_bandwidth { + MT_PHY_BW_20, + MT_PHY_BW_40, + MT_PHY_BW_80, +}; + +enum mt76_cal_channel_group { + MT_CH_5G_JAPAN, + MT_CH_5G_UNII_1, + MT_CH_5G_UNII_2, + MT_CH_5G_UNII_2E_1, + MT_CH_5G_UNII_2E_2, + MT_CH_5G_UNII_3, +}; + +enum mt76_qsel { + MT_QSEL_MGMT, + MT_QSEL_HCCA, + MT_QSEL_EDCA, + MT_QSEL_EDCA_2, +}; + +enum mt76_cipher_type { + MT_CIPHER_NONE, + MT_CIPHER_WEP40, + MT_CIPHER_WEP104, + MT_CIPHER_TKIP, + MT_CIPHER_AES_CCMP, + MT_CIPHER_CKIP40, + MT_CIPHER_CKIP104, + MT_CIPHER_CKIP128, + MT_CIPHER_WAPI, +}; + +struct mt76_fw_header { + __le32 ilm_len; + __le32 dlm_len; + __le16 build_ver; + __le16 fw_ver; + u8 pad[4]; + char build_time[16]; +} __packed; + +struct mt76_rxwi { + __le32 rxinfo; + __le32 ctl; + __le16 tid_sn; + __le16 rate; + u8 rssi[4]; + __le32 bbp_rxinfo[4]; +} __packed; + +struct mt76_txwi { + __le16 flags; + __le16 rate; + u8 ack_ctl; + u8 wcid; + __le16 len_ctl; + __le32 iv; + __le32 eiv; + u8 aid; + u8 txstream; + u8 ctl2; + u8 pktid; +} __packed; # ---------------------------------------- # Module: zenergy # Version: 310ed88b13c6 # ---------------------------------------- diff --git a/drivers/custom/zenergy/Makefile b/drivers/custom/zenergy/Makefile new file mode 100644 index 000000000000..4bbb27788692 --- /dev/null +++ b/drivers/custom/zenergy/Makefile @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for AMD Energy driver +# +# Copyright (C) 2021 Advanced Micro Devices, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 + +# If KDIR is not specified, assume the development source link +# is in the modules directory for the running kernel +KDIR ?= /lib/modules/`uname -r`/build +obj-m := zenergy.o + + +default: + export CONFIG_SENSOR_zenergy=m; \ + $(MAKE) -C $(KDIR) M=$$PWD modules + +modules: default + +modules_install: + $(MAKE) -C $(KDIR) M=$$PWD modules_install + +clean: + $(MAKE) -C $(KDIR) M=$$PWD clean + +help: + @echo "\nThe following make targets are supported:\n" + @echo "default\t\tBuild the driver module (or if no make target is supplied)" + @echo "modules\t\tSame as default" + @echo "modules_install\tBuild and install the driver module" + @echo "clean" + @echo + +.PHONY: default modules modules_install clean help + diff --git a/drivers/custom/zenergy/zenergy.c b/drivers/custom/zenergy/zenergy.c new file mode 100644 index 000000000000..0ff97cdd7858 --- /dev/null +++ b/drivers/custom/zenergy/zenergy.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Copyright (C) 2020 Advanced Micro Devices, Inc. + * Edited by BouHaa (http://github.com/boukehaarsma23) + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_MODULE_DESCRIPTION "AMD energy driver" +#define DRV_MODULE_VERSION "1.0" + +MODULE_VERSION(DRV_MODULE_VERSION); + +#define DRVNAME "zenergy" + +#define ENERGY_PWR_UNIT_MSR 0xC0010299 +#define ENERGY_CORE_MSR 0xC001029A +#define ENERGY_PKG_MSR 0xC001029B + +#define zenergy_UNIT_MASK 0x01F00 +#define zenergy_MASK 0xFFFFFFFF + +struct sensor_accumulator { + u64 energy_ctr; + u64 prev_value; + unsigned long cache_timeout; +}; + +struct zenergy_data { + struct hwmon_channel_info energy_info; + const struct hwmon_channel_info *info[2]; + struct hwmon_chip_info chip; + struct task_struct *wrap_accumulate; + /* Lock around the accumulator */ + struct mutex lock; + /* An accumulator for each core and socket */ + struct sensor_accumulator *accums; + unsigned int timeout_ms; + /* Energy Status Units */ + int energy_units; + int nr_cpus; + int nr_socks; + int core_id; + char (*label)[10]; + bool do_not_accum; +}; + +static int zenergy_read_labels(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, + const char **str) +{ + struct zenergy_data *data = dev_get_drvdata(dev); + + *str = data->label[channel]; + return 0; +} + +static void get_energy_units(struct zenergy_data *data) +{ + u64 rapl_units; + +#if KERNEL_VERSION(6, 16, 0) <= LINUX_VERSION_CODE + rdmsrq_safe(ENERGY_PWR_UNIT_MSR, &rapl_units); +#else + rdmsrl_safe(ENERGY_PWR_UNIT_MSR, &rapl_units); +#endif + data->energy_units = (rapl_units & zenergy_UNIT_MASK) >> 8; +} + +static void __accumulate_delta(struct sensor_accumulator *accum, + int cpu, u32 reg) +{ + u64 input; + +#if KERNEL_VERSION(6, 16, 0) <= LINUX_VERSION_CODE + rdmsrq_safe_on_cpu(cpu, reg, &input); +#else + rdmsrl_safe_on_cpu(cpu, reg, &input); +#endif + input &= zenergy_MASK; + + if (input >= accum->prev_value) + accum->energy_ctr += + input - accum->prev_value; + else + accum->energy_ctr += UINT_MAX - + accum->prev_value + input; + + accum->prev_value = input; + accum->cache_timeout = (jiffies + HZ + get_random_long()) % HZ; +} + +static void accumulate_delta(struct zenergy_data *data, + int channel, int cpu, u32 reg) +{ + mutex_lock(&data->lock); + __accumulate_delta(&data->accums[channel], cpu, reg); + mutex_unlock(&data->lock); +} + +static void read_accumulate(struct zenergy_data *data) +{ + int sock, scpu, cpu; + + for (sock = 0; sock < data->nr_socks; sock++) { + scpu = cpumask_first_and(cpu_online_mask, + cpumask_of_node(sock)); + + accumulate_delta(data, data->nr_cpus + sock, + scpu, ENERGY_PKG_MSR); + } + + if (data->core_id >= data->nr_cpus) + data->core_id = 0; + + cpu = data->core_id; + if (cpu_online(cpu)) + accumulate_delta(data, cpu, cpu, ENERGY_CORE_MSR); + + data->core_id++; +} + +static int zenergy_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct zenergy_data *data = dev_get_drvdata(dev); + struct sensor_accumulator *accum; + u64 energy; + u32 reg; + int cpu; + + if (channel >= data->nr_cpus) { + cpu = cpumask_first_and(cpu_online_mask, + cpumask_of_node + (channel - data->nr_cpus)); + reg = ENERGY_PKG_MSR; + } else { + cpu = channel; + if (!cpu_online(cpu)) + return -ENODEV; + + reg = ENERGY_CORE_MSR; + } + + accum = &data->accums[channel]; + + mutex_lock(&data->lock); + if (!accum->energy_ctr || time_after(jiffies, accum->cache_timeout)) + __accumulate_delta(accum, cpu, reg); + energy = accum->energy_ctr; + mutex_unlock(&data->lock); + + *val = div64_ul(energy * 1000000UL, BIT(data->energy_units)); + + return 0; +} + +static umode_t zenergy_is_visible(const void *_data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return 0444; +} + +static int energy_accumulator(void *p) +{ + struct zenergy_data *data = (struct zenergy_data *)p; + unsigned int timeout = data->timeout_ms; + + while (!kthread_should_stop()) { + /* + * Ignoring the conditions such as + * cpu being offline or rdmsr failure + */ + read_accumulate(data); + + set_current_state(TASK_INTERRUPTIBLE); + if (kthread_should_stop()) + break; + + schedule_timeout(msecs_to_jiffies(timeout)); + } + return 0; +} + +static const struct hwmon_ops zenergy_ops = { + .is_visible = zenergy_is_visible, + .read = zenergy_read, + .read_string = zenergy_read_labels, +}; + +static int amd_create_sensor(struct device *dev, + struct zenergy_data *data, + enum hwmon_sensor_types type, u32 config) +{ + struct hwmon_channel_info *info = &data->energy_info; +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 9, 0) + struct cpuinfo_x86 *c = &boot_cpu_data; + int num_siblings; +#endif + struct sensor_accumulator *accums; + int i, cpus, sockets; + u32 *s_config; + char (*label_l)[10]; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 9, 0) + /* Identify the number of siblings per core */ + num_siblings = ((cpuid_ebx(0x8000001e) >> 8) & 0xff) + 1; + + /* + * Energy counter register is accessed at core level. + * Hence, filterout the siblings. + */ + cpus = num_present_cpus() / num_siblings; + + /* + * topology_num_cores_per_package (or c->x86_max_cores prior to 6.9) is + * the linux count of physical cores. + * total physical cores/ core per socket gives total number of sockets. + */ + + sockets = cpus / c->x86_max_cores; +#else + cpus = num_present_cpus() / __max_threads_per_core; + sockets = cpus / topology_num_cores_per_package(); +#endif + + s_config = devm_kcalloc(dev, cpus + sockets + 1, + sizeof(u32), GFP_KERNEL); + if (!s_config) + return -ENOMEM; + + accums = devm_kcalloc(dev, cpus + sockets, + sizeof(struct sensor_accumulator), + GFP_KERNEL); + if (!accums) + return -ENOMEM; + + label_l = devm_kcalloc(dev, cpus + sockets, + sizeof(*label_l), GFP_KERNEL); + if (!label_l) + return -ENOMEM; + + info->type = type; + info->config = s_config; + + data->nr_cpus = cpus; + data->nr_socks = sockets; + data->accums = accums; + data->label = label_l; + + for (i = 0; i < cpus + sockets; i++) { + s_config[i] = config; + if (i < cpus) + scnprintf(label_l[i], 10, "Ecore%03u", i); + else + scnprintf(label_l[i], 10, "Esocket%u", (i - cpus)); + } + + s_config[i] = 0; + return 0; +} + +static const struct x86_cpu_id bit32_rapl_cpus[] = { + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x01, NULL), /* Zen */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x08, NULL), /* Zen+ */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x11, NULL), /* Zen APU */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x18, NULL), /* Picasso */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x20, NULL), /* Picasso APU */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x31, NULL), /* Zen2 Threadripper */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x60, NULL), /* Renoir */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x68, NULL), /* Lucienne */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x71, NULL), /* Zen2 */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x01, NULL), /* Zen3 Threadripper */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x21, NULL), /* Zen3 */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x50, NULL), /* Cezanne */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x44, NULL), /* Rembrandt */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x60, NULL), /* Rembrandt */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x1A, 0x44, NULL), /* Granite Ridge */ + // Zen4 (0x19, 0x61) features 64-bit registers for both Core::X86::Msr::CORE_ENERGY_STAT & L3::L3CT::L3PackageEnergyStatus), + // c.f., https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/programmer-references/56713-B1_3_05.zip + {} +}; + +static int zenergy_probe(struct platform_device *pdev) +{ + struct device *hwmon_dev; + struct zenergy_data *data; + struct device *dev = &pdev->dev; + int ret; + + data = devm_kzalloc(dev, + sizeof(struct zenergy_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->chip.ops = &zenergy_ops; + data->chip.info = data->info; + + dev_set_drvdata(dev, data); + /* Populate per-core energy reporting */ + data->info[0] = &data->energy_info; + ret = amd_create_sensor(dev, data, hwmon_energy, + HWMON_E_INPUT | HWMON_E_LABEL); + if (ret) + return ret; + + mutex_init(&data->lock); + get_energy_units(data); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, DRVNAME, + data, + &data->chip, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + /* + * On a system with peak wattage of 250W + * timeout = 2 ^ 32 / 2 ^ energy_units / 250 secs + */ + data->timeout_ms = 1000 * + BIT(min(28, 31 - data->energy_units)) / 250; + + /* + * For AMD platforms with 64-bit RAPL MSR registers, accumulation + * of the energy counters are not necessary. + */ + if (!x86_match_cpu(bit32_rapl_cpus)) { + data->do_not_accum = true; + pr_info("CPU supports 64-bit RAPL MSR registers\n"); + return 0; + } + + data->wrap_accumulate = kthread_run(energy_accumulator, data, + "%s", dev_name(hwmon_dev)); + return PTR_ERR_OR_ZERO(data->wrap_accumulate); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) +static int zenergy_remove(struct platform_device *pdev) +#else +static void zenergy_remove(struct platform_device *pdev) +#endif +{ + struct zenergy_data *data = dev_get_drvdata(&pdev->dev); + + if (data && data->wrap_accumulate) + kthread_stop(data->wrap_accumulate); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) + return 0; +#endif +} + +static const struct platform_device_id zenergy_ids[] = { + { .name = DRVNAME, }, + {} +}; +MODULE_DEVICE_TABLE(platform, zenergy_ids); + +static struct platform_driver zenergy_driver = { + .probe = zenergy_probe, + .remove = zenergy_remove, + .id_table = zenergy_ids, + .driver = { + .name = DRVNAME, + }, +}; + +static struct platform_device *zenergy_platdev; + +static const struct x86_cpu_id cpu_ids[] __initconst = { + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x01, NULL), /* Zen */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x08, NULL), /* Zen+ */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x11, NULL), /* Zen APU */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x18, NULL), /* Picasso */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x20, NULL), /* Picasso APU */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x31, NULL), /* Zen2 Threadripper */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x60, NULL), /* Renoir */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x68, NULL), /* Lucienne */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x71, NULL), /* Zen2 */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x01, NULL), /* Zen3 Threadripper */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x21, NULL), /* Zen3 */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x50, NULL), /* Cezanne */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x44, NULL), /* Rembrandt */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x60, NULL), /* Rembrandt */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x61, NULL), /* Zen 4 */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x1A, 0x44, NULL), /* Zen 5 */ + {} +}; +MODULE_DEVICE_TABLE(x86cpu, cpu_ids); + +static int __init zenergy_init(void) +{ + int ret; + + if (!x86_match_cpu(cpu_ids)) + return -ENODEV; + + ret = platform_driver_register(&zenergy_driver); + if (ret) + return ret; + + zenergy_platdev = platform_device_alloc(DRVNAME, 0); + if (!zenergy_platdev) { + platform_driver_unregister(&zenergy_driver); + return -ENOMEM; + } + + ret = platform_device_add(zenergy_platdev); + if (ret) { + platform_device_put(zenergy_platdev); + platform_driver_unregister(&zenergy_driver); + return ret; + } + + return ret; +} + +static void __exit zenergy_exit(void) +{ + platform_device_unregister(zenergy_platdev); + platform_driver_unregister(&zenergy_driver); +} + +module_init(zenergy_init); +module_exit(zenergy_exit); + +MODULE_DESCRIPTION("Driver for AMD Energy reporting from RAPL MSR via HWMON interface"); +MODULE_AUTHOR("Naveen Krishna Chatradhi "); +MODULE_LICENSE("GPL");