diff --git a/src/superio/fintek/Makefile.inc b/src/superio/fintek/Makefile.inc index 796e5194d1..db683fdfe6 100644 --- a/src/superio/fintek/Makefile.inc +++ b/src/superio/fintek/Makefile.inc @@ -26,3 +26,4 @@ subdirs-y += f71872 subdirs-y += f81216h subdirs-y += f81865f subdirs-y += f81866d +subdirs-y += f81803a diff --git a/src/superio/fintek/common/fan_api_call.c b/src/superio/fintek/common/fan_api_call.c new file mode 100644 index 0000000000..1bd5b2e7d4 --- /dev/null +++ b/src/superio/fintek/common/fan_api_call.c @@ -0,0 +1,63 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2019 Richard Spiegel + * Copyright (C) 2019 Silverback 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; version 2 of the License. + * + * 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. + */ + +#include +#include "fan_control.h" + +static int check_status(int status) +{ + if (status < HWM_STATUS_SUCCESS) + return status; + return HWM_STATUS_SUCCESS; /* positive values are warnings only */ +} + +int set_fan(struct fintek_fan *fan_init) +{ + int s; + + s = set_sensor_type(CONFIG_HWM_PORT, fan_init->sensor, fan_init->stype); + if (check_status(s) != HWM_STATUS_SUCCESS) + return s; + + s = set_fan_temperature_source(CONFIG_HWM_PORT, fan_init->fan, fan_init->temp_source); + if (check_status(s) != HWM_STATUS_SUCCESS) + return s; + + s = set_fan_type_mode(CONFIG_HWM_PORT, fan_init->fan, fan_init->ftype, fan_init->fmode); + if (check_status(s) != HWM_STATUS_SUCCESS) + return s; + + s = set_pwm_frequency(CONFIG_HWM_PORT, fan_init->fan, fan_init->fan_freq); + if (check_status(s) != HWM_STATUS_SUCCESS) + return s; + + s = set_fan_speed_change_rate(CONFIG_HWM_PORT, fan_init->fan, fan_init->rate_up, + fan_init->rate_down); + if (check_status(s) != HWM_STATUS_SUCCESS) + return s; + + s = set_fan_follow(CONFIG_HWM_PORT, fan_init->fan, fan_init->follow); + if (check_status(s) != HWM_STATUS_SUCCESS) + return s; + + s = set_sections(CONFIG_HWM_PORT, fan_init->fan, fan_init->boundaries, + fan_init->sections); + if (check_status(s) != HWM_STATUS_SUCCESS) + return s; + + printk(BIOS_DEBUG, "Fan %d completed\n", fan_init->fan); + return HWM_STATUS_SUCCESS; +} diff --git a/src/superio/fintek/common/fan_control.h b/src/superio/fintek/common/fan_control.h new file mode 100644 index 0000000000..fbe784bfa1 --- /dev/null +++ b/src/superio/fintek/common/fan_control.h @@ -0,0 +1,199 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2019 Richard Spiegel + * Copyright (C) 2019 Silverback 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; version 2 of the License. + * + * 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. + */ + +#ifndef SUPERIO_FINTEK_FAN_CONTROL_H +#define SUPERIO_FINTEK_FAN_CONTROL_H + +#include +#include + +typedef enum { + IGNORE_SENSOR = 0, + EXTERNAL_SENSOR1, + EXTERNAL_SENSOR2, + EXTERNAL_SENSOR3, + EXTERNAL_SENSOR4 +} external_sensor; + +typedef enum { + TEMP_SENSOR_THERMISTOR = 0, + TEMP_SENSOR_BJT, + TEMP_SENSOR_DEFAULT +} temp_sensor_type; + +typedef enum { + FAN_TYPE_PWM_PUSH_PULL = 0, + FAN_TYPE_DAC_POWER, + FAN_TYPE_PWM_OPEN_DRAIN, + FAN_TYPE_RESERVED +} fan_type; +#define FAN_TYPE_PWM_CHECK 1 /* bit 0 must be 0 for PWM */ + +typedef enum { + FAN_MODE_AUTO_RPM = 0, + FAN_MODE_AUTO_PWM_DAC, + FAN_MODE_MANUAL_RPM, + FAN_MODE_MANUAL_PWM_DAC, + FAN_MODE_DEFAULT +} fan_mode; + +typedef enum { + FAN_PWM_FREQ_23500 = 0, + FAN_PWM_FREQ_11750, + FAN_PWM_FREQ_5875, + FAN_PWM_FREQ_220 +} fan_pwm_freq; + +typedef enum { + FAN_TEMP_PECI = 0, + FAN_TEMP_EXTERNAL_1, + FAN_TEMP_EXTERNAL_2, + FAN_TEMP_TSI = 4, + FAN_TEMP_MXM, +} fan_temp_source; + +typedef enum { + FAN_UP_RATE_2HZ = 0, + FAN_UP_RATE_5HZ, + FAN_UP_RATE_10HZ, + FAN_UP_RATE_20HZ, + FAN_UP_RATE_DEFAULT, + FAN_UP_RATE_JUMP = 8 +} fan_rate_up; + +typedef enum { + FAN_DOWN_RATE_2HZ = 0, + FAN_DOWN_RATE_5HZ, + FAN_DOWN_RATE_10HZ, + FAN_DOWN_RATE_20HZ, + FAN_DOWN_RATE_DEFAULT, + FAN_DOWN_RATE_SAME_AS_UP, + FAN_DOWN_RATE_JUMP = 8 +} fan_rate_down; + +typedef enum { + FAN_FOLLOW_STEP = 0, + FAN_FOLLOW_INTERPOLATION +} fan_follow; + +struct fintek_fan { + uint8_t fan; + external_sensor sensor; + temp_sensor_type stype; + fan_temp_source temp_source; + fan_type ftype; + fan_mode fmode; + fan_pwm_freq fan_freq; + fan_rate_up rate_up; + fan_rate_down rate_down; + fan_follow follow; + uint8_t *boundaries; + uint8_t *sections; +}; + +#define HWM_STATUS_SUCCESS 0 +#define HWM_STATUS_INVALID_FAN -1 +#define HWM_STATUS_INVALID_TEMP_SOURCE -2 +#define HWM_STATUS_INVALID_TYPE -3 +#define HWM_STATUS_INVALID_MODE -4 +#define HWM_STATUS_INVALID_RATE -5 +#define HWM_STATUS_INVALID_FREQUENCY -6 +#define HWM_STATUS_INVALID_TEMP_SENSOR -7 +#define HWM_STATUS_INVALID_BOUNDARY_VALUE -8 +#define HWM_STATUS_INVALID_SECTION_VALUE -9 +#define HWM_STATUS_BOUNDARY_WRONG_ORDER -10 +#define HWM_STATUS_SECTIONS_WRONG_ORDER -11 +#define HWM_STATUS_WARNING_SENSOR_DISCONECTED 1 +#define HWM_STATUS_WARNING_FAN_NOT_PWM 2 + +#define CPU_DAMAGE_TEMP 110 + +/* + * Boundaries order is from highest temp. to lowest. Values from 0 to 127. + * Boundaries should be defined as u8 boundaries[FINTEK_BOUNDARIES_SIZE]. + */ +#define FINTEK_BOUNDARIES_SIZE 4 +/* + * Section defines the duty_cycle/voltage to be used based on where the + * temperature lies with respect to the boundaries. There are 5 sections + * (4 boundaries) and the order must be from highest to lowest. Values + * from 0% to 100%, will be converted internally to percent of 255. + * Sections should be defined as u8 sections[FINTEK_SECTIONS_SIZE]. + */ +#define FINTEK_SECTIONS_SIZE 5 + +/* + * When using external sensor, its type must be defined. When using PECI, + * TSI or MXM use IGNORE_SENSOR to indicate so. + */ +int set_sensor_type(u16 base_address, external_sensor sensor, + temp_sensor_type type); + +/* + * Define the temperature source used to control a fan. + */ +int set_fan_temperature_source(u16 base_address, u8 fan, + fan_temp_source source); + +/* + * Define if fan is controlled through PWM or absolute voltage powering it + * (DAC). Then, under mode, define if control is automatic (SIO) or manual + * (CPU, through ACPI). Notice there needs to be a match between type and + * mode (PWM with PWM or DAC with DAC). + */ +int set_fan_type_mode(u16 base_address, u8 fan, fan_type type, fan_mode mode); + +/* + * For fans controlled through pulse width, define the base frequency used. + */ +int set_pwm_frequency(u16 base_address, u8 fan, fan_pwm_freq frequency); + +/* + * For fintek SIO HWM there are 4 (temperature) boundaries points, defining + * 5 sections (1 fan speed per section). Start with the highest temperature/ + * speed. Temperature is in Celsius, speed is in percentile of max speed. The + * highest speed should be 100%, no requirements for minimum speed, could be + * 0 or above 0. + */ +int set_sections(u16 base_address, u8 fan, u8 *boundaries, u8 *sections); + +/* + * Define how often temperature is measured to change fan speed. + */ +int set_fan_speed_change_rate(u16 base_address, u8 fan, fan_rate_up rate_up, + fan_rate_down rate_down); + +/* + * There a 2 ways a fan can be controlled: A single speed per section, or + * interpolation. Under interpolation, the section speed is the speed at the + * lowest temperature of the section (0 Celsius for the lowest section), and + * it's the speed of the next section at the boundary to the next section. + * In between these 2 points, it's a linear function. For example, midway + * between temperature points it'll have a speed that is midway between the + * section speed and next section speed. Obviously, there's no variation for + * the highest section, reason why it must be 100% max speed. + */ +int set_fan_follow(u16 base_address, u8 fan, fan_follow follow); + +/* + * This is an upper level API which calls all the above APIs in the + * appropriate order. Any API failure will be displayed. Alerts will + * also be displayed, but will not interrupt the sequence, while errors + * will interrupt the sequence. + */ +int set_fan(struct fintek_fan *fan_init); + +#endif /* SUPERIO_FINTEK_FAN_CONTROL_H */ diff --git a/src/superio/fintek/f81803a/Kconfig b/src/superio/fintek/f81803a/Kconfig new file mode 100644 index 0000000000..e1aa537e78 --- /dev/null +++ b/src/superio/fintek/f81803a/Kconfig @@ -0,0 +1,28 @@ +## +## This file is part of the coreboot project. +## +## Copyright (C) 2009 Ronald G. Minnich +## Copyright (C) 2014 Edward O'Callaghan +## +## 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; version 2 of the License. +## +## 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. +## + +config SUPERIO_FINTEK_F81803A + bool + select SUPERIO_FINTEK_COMMON_PRE_RAM + +config SUPERIO_FINTEK_FAN_CONTROL + bool + default n + +config SUPERIO_FINTEK_FAN_API_CALL + depends on SUPERIO_FINTEK_FAN_CONTROL + bool + default n diff --git a/src/superio/fintek/f81803a/Makefile.inc b/src/superio/fintek/f81803a/Makefile.inc new file mode 100644 index 0000000000..6fe13aaab7 --- /dev/null +++ b/src/superio/fintek/f81803a/Makefile.inc @@ -0,0 +1,30 @@ +## +## This file is part of the coreboot project. +## +## Copyright (C) 2011 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 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. +## + +ifeq ($(CONFIG_BOOTBLOCK_CONSOLE),y) +bootblock-$(CONFIG_SUPERIO_FINTEK_F81803A) += ../common/early_serial.c +endif + +## Notice: For fan control at romstage, HWM must be initialized before +## the API is called. Ramstage can use devicetree to initialize it. + +romstage-$(CONFIG_SUPERIO_FINTEK_F81803A) += ../common/early_serial.c +romstage-$(CONFIG_SUPERIO_FINTEK_FAN_CONTROL) += fan_control.c +romstage-$(CONFIG_SUPERIO_FINTEK_FAN_API_CALL) += ../common/fan_api_call.c + +ramstage-$(CONFIG_SUPERIO_FINTEK_F81803A) += superio.c +ramstage-$(CONFIG_SUPERIO_FINTEK_FAN_CONTROL) += fan_control.c +ramstage-$(CONFIG_SUPERIO_FINTEK_FAN_API_CALL) += ../common/fan_api_call.c diff --git a/src/superio/fintek/f81803a/acpi/superio.asl b/src/superio/fintek/f81803a/acpi/superio.asl new file mode 100644 index 0000000000..ae8e6dc242 --- /dev/null +++ b/src/superio/fintek/f81803a/acpi/superio.asl @@ -0,0 +1,266 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Christoph Grenz + * Copyright (C) 2013 secunet Security Networks AG + * Copyright (C) 2019, Silverback, 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; version 2 of the License. + * + * 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. + */ + +/* + * Include this file into a mainboard's DSDT _SB device tree and it will + * expose the F81803A SuperIO and some of its functionality. + * + * It allows the change of IO ports, IRQs and DMA settings on logical + * devices, disabling and reenabling logical devices and controlling power + * saving mode on logical devices or the whole chip. + * + * LDN State + * 0x1 UARTA Implemented, partially tested + * 0x2 UARTB Implemented, partially tested + * 0x4 HWM Not implemented + * 0x5 KBC Not implemented + * 0x6 GPIO6 Not implemented + * 0x7 WDT0&PLED Not implemented + * 0xa ACPI/PME/ERP Partially implemented + * + * Controllable through preprocessor defines: + * SUPERIO_DEV Device identifier for this SIO (e.g. SIO0) + * SUPERIO_PNP_BASE I/o address of the first PnP configuration register + * F81803A_SHOW_UARTA If defined, UARTA will be exposed. + * F81803A_SHOW_UARTB If defined, UARTB will be exposed. + * F81803A_SHOW_HWMON If defined, the hardware monitor will be exposed. + * F81803A_SHOW_PME If defined, the PME/EARP/ACPI will be exposed. + * + * Known issue: + * Do not enable UARTA and UARTB simultaneously, Linux boot will crash. + * Select one or the other. + */ +#undef SUPERIO_CHIP_NAME +#define SUPERIO_CHIP_NAME F81803A +#include + +#undef PNP_DEFAULT_PSC +#define PNP_DEFAULT_PSC Return (0) /* no power management */ + +Device(SUPERIO_DEV) { + Name (_HID, EisaId("PNP0A05")) + Name (_STR, Unicode("Fintek F81803A Super I/O")) + Name (_UID, SUPERIO_UID(SUPERIO_DEV,)) + + /* Mutex for accesses to the configuration ports */ + Mutex(CRMX, 1) + + /* SuperIO configuration ports */ + OperationRegion (CREG, SystemIO, SUPERIO_PNP_BASE, 0x02) + Field (CREG, ByteAcc, NoLock, Preserve) + { + PNP_ADDR_REG, 8, + PNP_DATA_REG, 8 + } + IndexField (ADDR, DATA, ByteAcc, NoLock, Preserve) + { + Offset (0x07), + PNP_LOGICAL_DEVICE, 8, /* Logical device selector */ + Offset (0x30), + PNP_DEVICE_ACTIVE, 1, /* Logical device activation */ + Offset (0x60), + PNP_IO0_HIGH_BYTE, 8, /* First I/O port base - high byte */ + PNP_IO0_LOW_BYTE, 8, /* First I/O port base - low byte */ + Offset (0x62), + PNP_IO1_HIGH_BYTE, 8, /* Second I/O port base - high byte */ + PNP_IO1_LOW_BYTE, 8, /* Second I/O port base - low byte */ + Offset (0x70), + PNP_IRQ0, 8, /* First IRQ */ + offset(0xFB), + APC5, 8, /* PME ACPI Control Register 5 */ + } + + Method(_CRS) + { + /* Announce the used i/o ports to the OS */ + Return (ResourceTemplate () { + IO (Decode16, SUPERIO_PNP_BASE, SUPERIO_PNP_BASE, 0x01, 0x02) + }) + } + + #undef PNP_ENTER_MAGIC_1ST + #undef PNP_ENTER_MAGIC_2ND + #undef PNP_ENTER_MAGIC_3RD + #undef PNP_ENTER_MAGIC_4TH + #undef PNP_EXIT_MAGIC_1ST + #undef PNP_EXIT_SPECIAL_REG + #undef PNP_EXIT_SPECIAL_VAL + #define PNP_ENTER_MAGIC_1ST 0x87 + #define PNP_ENTER_MAGIC_2ND 0x87 + #define PNP_EXIT_MAGIC_1ST 0xaa + #include + +#ifdef F81803A_SHOW_UARTA + #undef SUPERIO_UART_LDN + #undef SUPERIO_UART_PM_REG + #undef SUPERIO_UART_PM_VAL + #undef SUPERIO_UART_PM_LDN + #define SUPERIO_UART_LDN 1 + + Device (SUPERIO_ID(SER, SUPERIO_UART_LDN)) { + Name (_HID, EisaId ("PNP0501")) + Name (_UID, SUPERIO_UID(SER, SUPERIO_UART_LDN)) + + Method (_STA) + { + PNP_GENERIC_STA(SUPERIO_UART_LDN) + } + + Method (_CRS, 0, Serialized) + { + Name (CRS, ResourceTemplate () { + IO (Decode16, 0x0000, 0x0000, 0x08, 0x08, IO0) + IRQNoFlags (IR0) {} + }) + ENTER_CONFIG_MODE (SUPERIO_UART_LDN) + PNP_READ_IO(PNP_IO0, CRS, IO0) + PNP_READ_IRQ(PNP_IRQ0, CRS, IR0) + EXIT_CONFIG_MODE () + Return (CRS) + } + + Name (_PRS, ResourceTemplate () + { + StartDependentFn (0,0) { + IO (Decode16, 0x03f8, 0x03f8, 0x08, 0x08) + IRQNoFlags () {3,4,5,7,9,10,11,12} + } + StartDependentFn (0,0) { + IO (Decode16, 0x02f8, 0x02f8, 0x08, 0x08) + IRQNoFlags () {3,4,5,7,9,10,11,12} + } + StartDependentFn (1,0) { + IO (Decode16, 0x03e8, 0x03e8, 0x08, 0x08) + IRQNoFlags () {3,4,5,7,9,10,11,12} + } + StartDependentFn (1,0) { + IO (Decode16, 0x02e8, 0x02e8, 0x08, 0x08) + IRQNoFlags () {3,4,5,7,9,10,11,12} + } + StartDependentFn (2,0) { + IO (Decode16, 0x0100, 0x0ff8, 0x08, 0x08) + IRQNoFlags () {3,4,5,7,9,10,11,12} + } + EndDependentFn() + }) + + Method (_SRS, 1, Serialized) + { + Name (TMPL, ResourceTemplate () { + IO (Decode16, 0x0000, 0x0000, 0x00, 0x00, IO0) + IRQNoFlags (IR0) {} + }) + ENTER_CONFIG_MODE (SUPERIO_UART_LDN) + PNP_WRITE_IO(PNP_IO0, Arg0, IO0) + PNP_WRITE_IRQ(PNP_IRQ0, Arg0, IR0) + Store (One, PNP_DEVICE_ACTIVE) + EXIT_CONFIG_MODE () + } + } +#endif + +#ifdef F81803A_SHOW_UARTB + #undef SUPERIO_UART_LDN + #undef SUPERIO_UART_PM_REG + #undef SUPERIO_UART_PM_VAL + #undef SUPERIO_UART_PM_LDN + #define SUPERIO_UART_LDN 2 + + Device (SUPERIO_ID(SER, SUPERIO_UART_LDN)) { + Name (_HID, EisaId ("PNP0501")) + Name (_UID, SUPERIO_UID(SER, SUPERIO_UART_LDN)) + + Method (_STA) + { + PNP_GENERIC_STA(SUPERIO_UART_LDN) + } + + Method (_CRS, 0, Serialized) + { + Name (CRS, ResourceTemplate () { + IO (Decode16, 0x0000, 0x0000, 0x08, 0x08, IO0) + IRQNoFlags (IR0) {} + }) + ENTER_CONFIG_MODE (SUPERIO_UART_LDN) + PNP_READ_IO(PNP_IO0, CRS, IO0) + PNP_READ_IRQ(PNP_IRQ0, CRS, IR0) + EXIT_CONFIG_MODE () + Return (CRS) + } + + Name (_PRS, ResourceTemplate () + { + StartDependentFn (0,0) { + IO (Decode16, 0x03f8, 0x03f8, 0x08, 0x08) + IRQNoFlags () {3,4,5,7,9,10,11,12} + } + StartDependentFn (0,0) { + IO (Decode16, 0x02f8, 0x02f8, 0x08, 0x08) + IRQNoFlags () {3,4,5,7,9,10,11,12} + } + StartDependentFn (1,0) { + IO (Decode16, 0x03e8, 0x03e8, 0x08, 0x08) + IRQNoFlags () {3,4,5,7,9,10,11,12} + } + StartDependentFn (1,0) { + IO (Decode16, 0x02e8, 0x02e8, 0x08, 0x08) + IRQNoFlags () {3,4,5,7,9,10,11,12} + } + StartDependentFn (2,0) { + IO (Decode16, 0x0100, 0x0ff8, 0x08, 0x08) + IRQNoFlags () {3,4,5,7,9,10,11,12} + } + EndDependentFn() + }) + + Method (_SRS, 1, Serialized) + { + Name (TMPL, ResourceTemplate () { + IO (Decode16, 0x0000, 0x0000, 0x00, 0x00, IO0) + IRQNoFlags (IR0) {} + }) + ENTER_CONFIG_MODE (SUPERIO_UART_LDN) + PNP_WRITE_IO(PNP_IO0, Arg0, IO0) + PNP_WRITE_IRQ(PNP_IRQ0, Arg0, IR0) + Store (One, PNP_DEVICE_ACTIVE) + EXIT_CONFIG_MODE () + } + } +#endif + +#ifdef F81803A_SHOW_PME + #undef SUPERIO_PME_LDN + #define SUPERIO_PME_LDN 0x0A + + OperationRegion(APCx, SystemIO, APC5, 0x01) + Field(APCx, ByteAcc, Nolock, Preserve) /* bits in PME ACPI CONTROL Reg 5*/ + { + Offset(0x00), /*Control Reg 5 */ + , 7, + PSIN, 1 /* PSIN_FLAG */ + } + + /* routine to clear PSIN_FLAG in ACPI_CONTROL_REG_5 of SIO */ + Method(CPSI, 0, Serialized) + { + /* DBG0("SIO CPSI")*/ + ENTER_CONFIG_MODE(SUPERIO_PME_LDN) + Store(1, PSIN) + EXIT_CONFIG_MODE() + } +#endif +} diff --git a/src/superio/fintek/f81803a/f81803a.h b/src/superio/fintek/f81803a/f81803a.h new file mode 100644 index 0000000000..c986cb806e --- /dev/null +++ b/src/superio/fintek/f81803a/f81803a.h @@ -0,0 +1,71 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 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 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. + */ + +/* + * Datasheet: + * - Name: F81803A + */ + +#ifndef SUPERIO_FINTEK_F81803_H +#define SUPERIO_FINTEK_F81803_H + +#define LDN_REG 0x07 /* Not defined under PNP */ +/* Logical Device Numbers (LDN) */ + #define F81803A_SP1 0x01 /* UART1 */ + #define F81803A_SP2 0x02 /* UART2 */ + #define F81803A_HWM 0x04 /* Hardware Monitor */ + #define F81803A_KBC 0x05 /* Keyboard/Mouse */ + #define F81803A_GPIO 0x06 /* General Purpose I/O (GPIO) */ + #define F81803A_WDT 0x07 /* Watch Dog Timer */ + #define F81803A_PME 0x0a /* Power Management Events (PME) */ + +/* Global Control Registers */ +#define CLOCK_SELECT_REG 0x26 +#define FUNC_PROG_SELECT (1<<3) +#define PORT_SELECT_REG 0x27 + +#define TSI_LEVEL_SELECT_REG 0x28 /* FUNC_PROG_SEL = 0 */ +#define TSI_PIN_SELECT_REG 0x28 /* FUNC_PROG_SEL = 1 */ +#define MULTI_FUNC_SEL_REG1 0x29 +#define MULTI_FUNC_SEL_REG2 0x2A +#define MULTI_FUNC_SEL_REG3 0x2B +#define MULTI_FUNC_SEL_REG 0x2C +#define WAKEUP_CONTROL_REG 0x2d + +/* LUN A - PME, ACPI, ERP */ +#define PME_DEVICE_ENABLE_REG 0x30 +#define PME_ENABLE (1<<0) +#define PME_ERP_ENABLE_REG 0xE0 +#define ERP_ENABLE (1<<7) +#define ERP_PME_EN (1<<1) +#define ERP_PSOUT_EN (1<<0) +#define PME_ERP_CONTROL_1_REG 0xE1 +#define PME_ERP_CONTROL_2_REG 0xE2 +#define PME_ERP_PSIN_DEBOUNCE_REG 0xE3 +#define PME_ERP_WAKEUP_ENABLE_REG 0xE8 +#define PME_ERP_MODE_SELECT_REG 0xEC +#define PME_EVENT_ENABLE_1_REG 0xF0 +#define PME_EVENT_STATUS_1_REG 0xF1 +#define PME_EVENT_ENABLE_2_REG 0xF2 +#define PME_EVENT_STATUS_2_REG 0xF3 +#define PME_ACPI_CONTROL_1_REG 0xF4 +#define PME_ACPI_CONTROL_2_REG 0xF5 +#define PME_ACPI_CONTROL_3_REG 0xF6 +#define PME_ACPI_CONTROL_4_REG 0xF7 +#define PME_ACPI_CONTROL_5_REG 0xFB +#define PME_ACPI_CONTROL_6_REG 0xFC + +#endif /* SUPERIO_FINTEK_F81803_H */ diff --git a/src/superio/fintek/f81803a/f81803a_hwm.h b/src/superio/fintek/f81803a/f81803a_hwm.h new file mode 100644 index 0000000000..a7647057d1 --- /dev/null +++ b/src/superio/fintek/f81803a/f81803a_hwm.h @@ -0,0 +1,72 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2019 Richard Spiegel + * Copyright (C) 2019 Silverback 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; version 2 of the License. + * + * 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. + */ + +#ifndef SUPERIO_FINTEK_F81803_HWM_H +#define SUPERIO_FINTEK_F81803_HWM_H + +#define TP_SENSOR_TYPE 0x6b +#define TP_SENSOR1_TYPE_SHIFT 1 +#define TP_SENSOR2_TYPE_SHIFT 2 +#define TP_SENSOR_TYPE_MASK 0x01 +#define TP_DIODE_STATUS 0x6f +#define TP_MMX_OPEN 0x40 +#define TP_PECI_OPEN 0x20 +#define TP_TSI_OPEN 0x10 +#define TP_EXTERNAL_SENSOR2_OPEN 0x04 +#define TP_EXTERNAL_SENSOR1_OPEN 0x02 + +#define FAN_TYPE_REG 0x94 +#define FAN_TYPE_SHIFT(fan) ((fan - 1) * 2) +#define FAN_TYPE_MASK 0x03 +#define FAN_MODE_REG 0x96 + /* FUNC_PROG_SEL = 0 */ +#define FAN_MODE_SHIFT(fan) ((fan - 1) * 4) +#define FAN_MODE_MASK 0x07 + /* FUNC_PROG_SEL = 1 */ +#define FAN1_ADJ_SEL_SHIFT 0 +#define FAN1_ADJ_SEL_MASK 0x07 +#define FAN_FREQ_SEL_ADD_SHIFT(fan) (fan + 2) +#define FAN_UP_RATE_REG 0x9a +#define FAN_RATE_SHIFT(fan) ((fan - 1) * 2) +#define FAN_RATE_MASK 0x03 +#define FAN_DOWN_RATE_REG 0x9b +#define FAN_DOWN_RATE_DIFF_FROM_UP_SHIFT 7 /* FUNC_PROG_SEL = 1 */ +#define FAN_DIRECT_LOAD_EN_SHIFT 6 /* FUNC_PROG_SEL = 1 */ +#define FAN_FAULT_TIME_REG 0x9f +#define FAN_FUNC_PROG_SEL_SHIFT 7 + +#define FAN_BOUND_TEMP 0xa6 /* 4 temperatures */ +#define FAN_SECTION_SPEED 0xaa /* 5 sections */ +#define FAN_TMP_MAPPING 0xaf +#define FAN_TEMP_SEL_HIGH_SHIFT 7 +#define FAN_PWM_FREQ_SEL_SHIFT 6 +#define FAN_INTERPOLATION_SHIFT 4 +#define FAN_JUMP_UP_SHIFT 3 +#define FAN_JUMP_DOWN_SHIFT 2 +#define FAN_TEMP_SEL_LOW_SHIFT 0 +#define FAN_TEMP_SEL_LOW_MASK 0x03 +#define FAN_BIT_MASK 0x01 + +#define FAN_ADJUST(fan, start) (((fan - 1) * 0x10) + start) + +#define STATUS_INVALID_VALUE -1 +#define STATUS_INVALID_ORDER -2 + +#define FIRST_FAN 1 +#define LAST_FAN 2 +#define MAX_DUTY 100 + +#endif /* SUPERIO_FINTEK_F81803_HWM_H */ diff --git a/src/superio/fintek/f81803a/fan_control.c b/src/superio/fintek/f81803a/fan_control.c new file mode 100644 index 0000000000..17ae9c6a20 --- /dev/null +++ b/src/superio/fintek/f81803a/fan_control.c @@ -0,0 +1,362 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2019 Richard Spiegel + * Copyright (C) 2019 Silverback 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; version 2 of the License. + * + * 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. + */ + +#include +#include "../common/fan_control.h" +#include "f81803a_hwm.h" + +static const char msg_err_invalid[] = "Error: invalid"; +static const char msg_err_wrong_order[] = "Error: wrong order,"; +static const char msg_err_fan[] = "fan"; +static const char msg_err_temp_source[] = "temperature source"; +static const char msg_err_type[] = "type"; +static const char msg_err_mode[] = "mode"; +static const char msg_err_rate[] = "change rate"; +static const char msg_err_frequency[] = "frequency"; +static const char msg_err_temp_sensor[] = "temperature sensor"; +static const char msg_err_bondary[] = "boundary"; +static const char msg_err_section[] = "section"; +static const char no_msg[] = ""; + +struct cross_ref { + int selection; + const char *message; +}; +static struct cross_ref msg_table[] = { + {HWM_STATUS_INVALID_FAN, msg_err_fan}, + {HWM_STATUS_INVALID_TEMP_SOURCE, msg_err_temp_source}, + {HWM_STATUS_INVALID_TYPE, msg_err_type}, + {HWM_STATUS_INVALID_MODE, msg_err_mode}, + {HWM_STATUS_INVALID_RATE, msg_err_rate}, + {HWM_STATUS_INVALID_FREQUENCY, msg_err_frequency}, + {HWM_STATUS_INVALID_TEMP_SENSOR, msg_err_temp_sensor}, + {0, NULL}, +}; + +static const char *get_msg(int err) +{ + int i = 0; + while (msg_table[i].selection) { + if (msg_table[i].selection == err) + return msg_table[i].message; + i++; + } + return no_msg; +} + +static int message_invalid_1(int err, u8 fan) +{ + if (err == HWM_STATUS_INVALID_FAN) + printk(BIOS_ERR, "%s %s %d!\n", msg_err_invalid, get_msg(err), fan); + else + printk(BIOS_ERR, "%s Fan %d %s!\n", msg_err_invalid, fan, get_msg(err)); + return err; +} + +static int message_invalid_2(int err, u8 fan) +{ + switch (err) { + case HWM_STATUS_INVALID_BOUNDARY_VALUE: + printk(BIOS_ERR, "%s fan %d %s value!\n", msg_err_invalid, fan, + msg_err_bondary); + break; + case HWM_STATUS_INVALID_SECTION_VALUE: + printk(BIOS_ERR, "%s fan %d %s value!\n", msg_err_invalid, fan, + msg_err_section); + break; + case HWM_STATUS_BOUNDARY_WRONG_ORDER: + printk(BIOS_ERR, "%s fan %d %s!\n", msg_err_wrong_order, fan, msg_err_bondary); + break; + case HWM_STATUS_SECTIONS_WRONG_ORDER: + printk(BIOS_ERR, "%s fan %d %s!\n", msg_err_wrong_order, fan, msg_err_section); + break; + default: + break; + } + return err; +} + +static void write_hwm_reg(u16 address, u8 index, u8 value) +{ + u16 index_add, data_add; + index_add = address | 0x0001; /* force odd address */ + data_add = index_add + 1; + outb(index, index_add); + outb(value, data_add); +} + +static u8 read_hwm_reg(u16 address, u8 index) +{ + u16 index_add, data_add; + index_add = address | 0x0001; /* force odd address */ + data_add = index_add + 1; + outb(index, index_add); + return inb(data_add); +} + +static void hwm_reg_modify(u16 address, u8 index, u8 shift, u8 mask, + u8 value) +{ + u8 use_mask = mask << shift; + u8 use_value = (value & mask) << shift; + u8 temp = read_hwm_reg(address, index); + + temp &= ~use_mask; + temp |= use_value; + write_hwm_reg(address, index, temp); +} + +/* + * Registers 0x94,0x95, 0x96 and 0x9b have 2 versions (banks) selected through + * bit 7 of register 0x9f. + */ +static inline void select_hwm_bank(u16 address, u8 value) +{ + hwm_reg_modify(address, FAN_FAULT_TIME_REG, FAN_FUNC_PROG_SEL_SHIFT, + FAN_BIT_MASK, value); +} + +/* + * Boundaries and sections must be presented in the same order as in the HWM + * registers, that is, from highest value to lowest. This procedure checks for + * the correct order. + */ +static int check_value_seq(u8 *values, u8 count) +{ + u8 last_value = CPU_DAMAGE_TEMP; + u8 current_value, i; + for (i = 0; i < count; i++) { + current_value = values[i]; + if (current_value > CPU_DAMAGE_TEMP) + return STATUS_INVALID_VALUE; + if (current_value >= last_value) + return STATUS_INVALID_ORDER; + last_value = current_value; + } + return HWM_STATUS_SUCCESS; +} + +int set_sensor_type(u16 base_address, external_sensor sensor, + temp_sensor_type type) +{ + u8 sensor_status = read_hwm_reg(base_address, TP_DIODE_STATUS); + + printk(BIOS_DEBUG, "%s\n", __func__); + switch (sensor) { + case EXTERNAL_SENSOR1: + if (sensor_status & TP_EXTERNAL_SENSOR1_OPEN) { + printk(BIOS_WARNING, "Sensor 1 disconected!\n"); + return HWM_STATUS_WARNING_SENSOR_DISCONECTED; + } + hwm_reg_modify(base_address, TP_SENSOR_TYPE, + TP_SENSOR1_TYPE_SHIFT, TP_SENSOR_TYPE_MASK, type); + break; + case EXTERNAL_SENSOR2: + if (sensor_status & TP_EXTERNAL_SENSOR2_OPEN) { + printk(BIOS_WARNING, "Sensor 2 disconected!\n"); + return HWM_STATUS_WARNING_SENSOR_DISCONECTED; + } + hwm_reg_modify(base_address, TP_SENSOR_TYPE, + TP_SENSOR2_TYPE_SHIFT, TP_SENSOR_TYPE_MASK, type); + break; + case IGNORE_SENSOR: + break; + default: + return message_invalid_1(HWM_STATUS_INVALID_TEMP_SENSOR, 0); + } + return HWM_STATUS_SUCCESS; +} + +int set_fan_temperature_source(u16 base_address, u8 fan, + fan_temp_source source) +{ + u8 index, high_value, low_value; + + printk(BIOS_DEBUG, "%s\n", __func__); + if ((fan < FIRST_FAN) || (fan > LAST_FAN)) + return message_invalid_1(HWM_STATUS_INVALID_FAN, fan); + index = FAN_ADJUST(fan, FAN_TMP_MAPPING); + high_value = (source >> 2) & FAN_BIT_MASK; + low_value = source & FAN_TEMP_SEL_LOW_MASK; + hwm_reg_modify(base_address, index, FAN_TEMP_SEL_HIGH_SHIFT, + FAN_BIT_MASK, high_value); + hwm_reg_modify(base_address, index, FAN_TEMP_SEL_LOW_SHIFT, + FAN_TEMP_SEL_LOW_MASK, low_value); + /* + * Fan 1 has a weight mechanism for adjusting for next fan speed. Basically the idea is + * to react more aggressively (normally CPU fan) based on how high another temperature + * (system, thermistor near the CPU, anything) is. This would be highly platform + * dependent, and by setting the weight temperature same as the control temperature. + * This code cancels the weight mechanism and make it work with any board. If a board + * wants to use the weight mechanism, OEM should implement it after calling the main + * HWM programming. + */ + if (fan == FIRST_FAN) { + select_hwm_bank(base_address, 1); + hwm_reg_modify(base_address, FAN_MODE_REG, + FAN1_ADJ_SEL_SHIFT, FAN1_ADJ_SEL_MASK, source); + select_hwm_bank(base_address, 0); + } + return HWM_STATUS_SUCCESS; +} + +int set_fan_type_mode(u16 base_address, u8 fan, fan_type type, fan_mode mode) +{ + u8 shift; + + printk(BIOS_DEBUG, "%s\n", __func__); + if ((fan < FIRST_FAN) || (fan > LAST_FAN)) + return message_invalid_1(HWM_STATUS_INVALID_FAN, fan); + select_hwm_bank(base_address, 0); + if (type < FAN_TYPE_RESERVED) { + shift = FAN_TYPE_SHIFT(fan); + hwm_reg_modify(base_address, FAN_TYPE_REG, shift, + FAN_TYPE_MASK, type); + } + if (mode < FAN_MODE_DEFAULT) { + shift = FAN_MODE_SHIFT(fan); + hwm_reg_modify(base_address, FAN_MODE_REG, shift, + FAN_MODE_MASK, mode); + } + return HWM_STATUS_SUCCESS; +} + +int set_pwm_frequency(u16 base_address, u8 fan, fan_pwm_freq frequency) +{ + u8 shift, index, byte; + + printk(BIOS_DEBUG, "%s\n", __func__); + if ((fan < FIRST_FAN) || (fan > LAST_FAN)) + return message_invalid_1(HWM_STATUS_INVALID_FAN, fan); + byte = read_hwm_reg(base_address, FAN_TYPE_REG); + shift = FAN_TYPE_SHIFT(fan); + if (((byte >> shift) & FAN_TYPE_PWM_CHECK) == FAN_TYPE_PWM_CHECK) { + printk(BIOS_WARNING, "Fan %d not programmed as PWM!\n", fan); + return HWM_STATUS_WARNING_FAN_NOT_PWM; + } + select_hwm_bank(base_address, 1); + shift = FAN_FREQ_SEL_ADD_SHIFT(fan); + byte = (frequency >> 1) & FAN_BIT_MASK; + hwm_reg_modify(base_address, FAN_MODE_REG, shift, FAN_BIT_MASK, + byte); + select_hwm_bank(base_address, 0); + index = FAN_ADJUST(fan, FAN_TMP_MAPPING); + byte = frequency & FAN_BIT_MASK; + hwm_reg_modify(base_address, index, FAN_PWM_FREQ_SEL_SHIFT, + FAN_BIT_MASK, byte); + return HWM_STATUS_SUCCESS; +} + +int set_sections(u16 base_address, u8 fan, u8 *boundaries, u8 *sections) +{ + int status, temp; + u8 i, index, value; + + printk(BIOS_DEBUG, "%s\n", __func__); + if ((fan < FIRST_FAN) || (fan > LAST_FAN)) + return message_invalid_1(HWM_STATUS_INVALID_FAN, fan); + status = check_value_seq(boundaries, + FINTEK_BOUNDARIES_SIZE); + if (status != HWM_STATUS_SUCCESS) { + if (status == STATUS_INVALID_VALUE) + return message_invalid_2(HWM_STATUS_INVALID_BOUNDARY_VALUE, fan); + return message_invalid_2(HWM_STATUS_BOUNDARY_WRONG_ORDER, fan); + } + status = check_value_seq(sections, + FINTEK_SECTIONS_SIZE); + if (status != HWM_STATUS_SUCCESS) { + if (status == STATUS_INVALID_VALUE) + return message_invalid_2(HWM_STATUS_INVALID_SECTION_VALUE, fan); + return message_invalid_2(HWM_STATUS_SECTIONS_WRONG_ORDER, fan); + } + index = FAN_ADJUST(fan, FAN_BOUND_TEMP); + for (i = 0; i < FINTEK_BOUNDARIES_SIZE; i++) { + value = boundaries[i]; + write_hwm_reg(base_address, index, value); + index++; + } + index = FAN_ADJUST(fan, FAN_SECTION_SPEED); + for (i = 0; i < FINTEK_SECTIONS_SIZE; i++) { + value = sections[i]; + if (value > 100) + return message_invalid_2(HWM_STATUS_INVALID_SECTION_VALUE, fan); + temp = (255 * value) / 100; + value = (u8) (temp & 0x00ff); + write_hwm_reg(base_address, index, value); + index++; + } + return HWM_STATUS_SUCCESS; +} + +int set_fan_speed_change_rate(u16 base_address, u8 fan, fan_rate_up rate_up, + fan_rate_down rate_down) +{ + u8 shift, index; + + printk(BIOS_DEBUG, "%s\n", __func__); + if ((fan < FIRST_FAN) || (fan > LAST_FAN)) + return message_invalid_1(HWM_STATUS_INVALID_FAN, fan); + + index = FAN_ADJUST(fan, FAN_TMP_MAPPING); + shift = FAN_RATE_SHIFT(fan); + + if (rate_up == FAN_UP_RATE_JUMP) { + hwm_reg_modify(base_address, index, FAN_JUMP_UP_SHIFT, + FAN_BIT_MASK, 1); + } else { + hwm_reg_modify(base_address, index, FAN_JUMP_UP_SHIFT, + FAN_BIT_MASK, 0); + if (rate_up < FAN_UP_RATE_DEFAULT) { + hwm_reg_modify(base_address, FAN_UP_RATE_REG, + shift, FAN_RATE_MASK, rate_up); + } + } + + if (rate_down == FAN_DOWN_RATE_JUMP) { + hwm_reg_modify(base_address, index, FAN_JUMP_DOWN_SHIFT, + FAN_BIT_MASK, 1); + } else { + hwm_reg_modify(base_address, index, FAN_JUMP_UP_SHIFT, + FAN_BIT_MASK, 0); + select_hwm_bank(base_address, 0); + if (rate_down < FAN_DOWN_RATE_DEFAULT) { + hwm_reg_modify(base_address, FAN_DOWN_RATE_REG, + shift, FAN_RATE_MASK, rate_down); + hwm_reg_modify(base_address, FAN_DOWN_RATE_REG, + FAN_DOWN_RATE_DIFF_FROM_UP_SHIFT, + FAN_BIT_MASK, 0); + } + if (rate_down == FAN_DOWN_RATE_SAME_AS_UP) { + hwm_reg_modify(base_address, FAN_DOWN_RATE_REG, + FAN_DOWN_RATE_DIFF_FROM_UP_SHIFT, + FAN_BIT_MASK, 1); + } + } + return HWM_STATUS_SUCCESS; +} + +int set_fan_follow(u16 base_address, u8 fan, fan_follow follow) +{ + u8 index; + + printk(BIOS_DEBUG, "%s\n", __func__); + if ((fan < FIRST_FAN) || (fan > LAST_FAN)) + return message_invalid_1(HWM_STATUS_INVALID_FAN, fan); + index = FAN_ADJUST(fan, FAN_TMP_MAPPING); + hwm_reg_modify(base_address, index, FAN_INTERPOLATION_SHIFT, + FAN_BIT_MASK, follow); + return HWM_STATUS_SUCCESS; +} diff --git a/src/superio/fintek/f81803a/superio.c b/src/superio/fintek/f81803a/superio.c new file mode 100644 index 0000000000..a07b69e97e --- /dev/null +++ b/src/superio/fintek/f81803a/superio.c @@ -0,0 +1,78 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 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 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. + */ + +#include +#include +#include +#include +#include +#include +#include "f81803a.h" + +static void f81803a_pme_init(struct device *dev) +{ + pnp_enter_conf_mode(dev); + pnp_write_config(dev, LDN_REG, F81803A_PME); + /* enable ERP function*/ + /* also set PSIN to generate PSOUT*/ + pnp_write_config(dev, PME_ERP_ENABLE_REG, ERP_ENABLE | ERP_PSOUT_EN); + pnp_exit_conf_mode(dev); +} + +static void f81803a_init(struct device *dev) +{ + if (!dev->enabled) + return; + switch (dev->path.pnp.device) { + /* TODO: Might potentially need code for GPIO or WDT. */ + case F81803A_KBC: + pc_keyboard_init(NO_AUX_DEVICE); + break; + case F81803A_PME: + f81803a_pme_init(dev); + break; + } + +} + +static struct device_operations ops = { + .read_resources = pnp_read_resources, + .set_resources = pnp_set_resources, + .enable_resources = pnp_enable_resources, + .enable = pnp_alt_enable, + .init = f81803a_init, + .ops_pnp_mode = &pnp_conf_mode_8787_aa, +}; + +static struct pnp_info pnp_dev_info[] = { + { &ops, F81803A_SP1, PNP_IO0 | PNP_IRQ0, 0x7f8, }, + { &ops, F81803A_SP2, PNP_IO0 | PNP_IRQ0, 0x7f8, }, + { &ops, F81803A_HWM, PNP_IO0 | PNP_IRQ0, 0xff8, }, + { &ops, F81803A_KBC, PNP_IO0 | PNP_IRQ0 | PNP_IRQ1, 0x07f8, }, + { &ops, F81803A_GPIO, PNP_IO0 | PNP_IRQ0, 0x7f8, }, + { &ops, F81803A_WDT, PNP_IO0, 0x7f8 }, + { &ops, F81803A_PME, }, +}; + +static void enable_dev(struct device *dev) +{ + pnp_enable_devices(dev, &ops, ARRAY_SIZE(pnp_dev_info), pnp_dev_info); +} + +struct chip_operations superio_fintek_f81803a_ops = { + CHIP_NAME("Fintek F81803A Super I/O") + .enable_dev = enable_dev +};