12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * nct6775 - Platform driver for the hardware monitoring
- * functionality of Nuvoton NCT677x Super-I/O chips
- *
- * Copyright (C) 2012 Guenter Roeck <[email protected]>
- */
- #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
- #include <linux/acpi.h>
- #include <linux/dmi.h>
- #include <linux/hwmon-sysfs.h>
- #include <linux/hwmon-vid.h>
- #include <linux/init.h>
- #include <linux/io.h>
- #include <linux/module.h>
- #include <linux/platform_device.h>
- #include <linux/regmap.h>
- #include "nct6775.h"
- enum sensor_access { access_direct, access_asuswmi };
- static const char * const nct6775_sio_names[] __initconst = {
- "NCT6106D",
- "NCT6116D",
- "NCT6775F",
- "NCT6776D/F",
- "NCT6779D",
- "NCT6791D",
- "NCT6792D",
- "NCT6793D",
- "NCT6795D",
- "NCT6796D",
- "NCT6797D",
- "NCT6798D",
- };
- static unsigned short force_id;
- module_param(force_id, ushort, 0);
- MODULE_PARM_DESC(force_id, "Override the detected device ID");
- static unsigned short fan_debounce;
- module_param(fan_debounce, ushort, 0);
- MODULE_PARM_DESC(fan_debounce, "Enable debouncing for fan RPM signal");
- #define DRVNAME "nct6775"
- #define NCT6775_PORT_CHIPID 0x58
- /*
- * ISA constants
- */
- #define IOREGION_ALIGNMENT (~7)
- #define IOREGION_OFFSET 5
- #define IOREGION_LENGTH 2
- #define ADDR_REG_OFFSET 0
- #define DATA_REG_OFFSET 1
- /*
- * Super-I/O constants and functions
- */
- #define NCT6775_LD_ACPI 0x0a
- #define NCT6775_LD_HWM 0x0b
- #define NCT6775_LD_VID 0x0d
- #define NCT6775_LD_12 0x12
- #define SIO_REG_LDSEL 0x07 /* Logical device select */
- #define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
- #define SIO_REG_ENABLE 0x30 /* Logical device enable */
- #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */
- #define SIO_NCT6106_ID 0xc450
- #define SIO_NCT6116_ID 0xd280
- #define SIO_NCT6775_ID 0xb470
- #define SIO_NCT6776_ID 0xc330
- #define SIO_NCT6779_ID 0xc560
- #define SIO_NCT6791_ID 0xc800
- #define SIO_NCT6792_ID 0xc910
- #define SIO_NCT6793_ID 0xd120
- #define SIO_NCT6795_ID 0xd350
- #define SIO_NCT6796_ID 0xd420
- #define SIO_NCT6797_ID 0xd450
- #define SIO_NCT6798_ID 0xd428
- #define SIO_ID_MASK 0xFFF8
- /*
- * Control registers
- */
- #define NCT6775_REG_CR_FAN_DEBOUNCE 0xf0
- struct nct6775_sio_data {
- int sioreg;
- int ld;
- enum kinds kind;
- enum sensor_access access;
- /* superio_() callbacks */
- void (*sio_outb)(struct nct6775_sio_data *sio_data, int reg, int val);
- int (*sio_inb)(struct nct6775_sio_data *sio_data, int reg);
- void (*sio_select)(struct nct6775_sio_data *sio_data, int ld);
- int (*sio_enter)(struct nct6775_sio_data *sio_data);
- void (*sio_exit)(struct nct6775_sio_data *sio_data);
- };
- #define ASUSWMI_METHOD "WMBD"
- #define ASUSWMI_METHODID_RSIO 0x5253494F
- #define ASUSWMI_METHODID_WSIO 0x5753494F
- #define ASUSWMI_METHODID_RHWM 0x5248574D
- #define ASUSWMI_METHODID_WHWM 0x5748574D
- #define ASUSWMI_UNSUPPORTED_METHOD 0xFFFFFFFE
- #define ASUSWMI_DEVICE_HID "PNP0C14"
- #define ASUSWMI_DEVICE_UID "ASUSWMI"
- #define ASUSMSI_DEVICE_UID "AsusMbSwInterface"
- #if IS_ENABLED(CONFIG_ACPI)
- /*
- * ASUS boards have only one device with WMI "WMBD" method and have provided
- * access to only one SuperIO chip at 0x0290.
- */
- static struct acpi_device *asus_acpi_dev;
- #endif
- static int nct6775_asuswmi_evaluate_method(u32 method_id, u8 bank, u8 reg, u8 val, u32 *retval)
- {
- #if IS_ENABLED(CONFIG_ACPI)
- acpi_handle handle = acpi_device_handle(asus_acpi_dev);
- u32 args = bank | (reg << 8) | (val << 16);
- struct acpi_object_list input;
- union acpi_object params[3];
- unsigned long long result;
- acpi_status status;
- params[0].type = ACPI_TYPE_INTEGER;
- params[0].integer.value = 0;
- params[1].type = ACPI_TYPE_INTEGER;
- params[1].integer.value = method_id;
- params[2].type = ACPI_TYPE_BUFFER;
- params[2].buffer.length = sizeof(args);
- params[2].buffer.pointer = (void *)&args;
- input.count = 3;
- input.pointer = params;
- status = acpi_evaluate_integer(handle, ASUSWMI_METHOD, &input, &result);
- if (ACPI_FAILURE(status))
- return -EIO;
- if (retval)
- *retval = (u32)result & 0xFFFFFFFF;
- return 0;
- #else
- return -EOPNOTSUPP;
- #endif
- }
- static inline int nct6775_asuswmi_write(u8 bank, u8 reg, u8 val)
- {
- return nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_WHWM, bank,
- reg, val, NULL);
- }
- static inline int nct6775_asuswmi_read(u8 bank, u8 reg, u8 *val)
- {
- u32 ret, tmp = 0;
- ret = nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RHWM, bank,
- reg, 0, &tmp);
- *val = tmp;
- return ret;
- }
- static int superio_wmi_inb(struct nct6775_sio_data *sio_data, int reg)
- {
- int tmp = 0;
- nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RSIO, sio_data->ld,
- reg, 0, &tmp);
- return tmp;
- }
- static void superio_wmi_outb(struct nct6775_sio_data *sio_data, int reg, int val)
- {
- nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_WSIO, sio_data->ld,
- reg, val, NULL);
- }
- static void superio_wmi_select(struct nct6775_sio_data *sio_data, int ld)
- {
- sio_data->ld = ld;
- }
- static int superio_wmi_enter(struct nct6775_sio_data *sio_data)
- {
- return 0;
- }
- static void superio_wmi_exit(struct nct6775_sio_data *sio_data)
- {
- }
- static void superio_outb(struct nct6775_sio_data *sio_data, int reg, int val)
- {
- int ioreg = sio_data->sioreg;
- outb(reg, ioreg);
- outb(val, ioreg + 1);
- }
- static int superio_inb(struct nct6775_sio_data *sio_data, int reg)
- {
- int ioreg = sio_data->sioreg;
- outb(reg, ioreg);
- return inb(ioreg + 1);
- }
- static void superio_select(struct nct6775_sio_data *sio_data, int ld)
- {
- int ioreg = sio_data->sioreg;
- outb(SIO_REG_LDSEL, ioreg);
- outb(ld, ioreg + 1);
- }
- static int superio_enter(struct nct6775_sio_data *sio_data)
- {
- int ioreg = sio_data->sioreg;
- /*
- * Try to reserve <ioreg> and <ioreg + 1> for exclusive access.
- */
- if (!request_muxed_region(ioreg, 2, DRVNAME))
- return -EBUSY;
- outb(0x87, ioreg);
- outb(0x87, ioreg);
- return 0;
- }
- static void superio_exit(struct nct6775_sio_data *sio_data)
- {
- int ioreg = sio_data->sioreg;
- outb(0xaa, ioreg);
- outb(0x02, ioreg);
- outb(0x02, ioreg + 1);
- release_region(ioreg, 2);
- }
- static inline void nct6775_wmi_set_bank(struct nct6775_data *data, u16 reg)
- {
- u8 bank = reg >> 8;
- data->bank = bank;
- }
- static int nct6775_wmi_reg_read(void *ctx, unsigned int reg, unsigned int *val)
- {
- struct nct6775_data *data = ctx;
- int err, word_sized = nct6775_reg_is_word_sized(data, reg);
- u8 tmp = 0;
- u16 res;
- nct6775_wmi_set_bank(data, reg);
- err = nct6775_asuswmi_read(data->bank, reg & 0xff, &tmp);
- if (err)
- return err;
- res = tmp;
- if (word_sized) {
- err = nct6775_asuswmi_read(data->bank, (reg & 0xff) + 1, &tmp);
- if (err)
- return err;
- res = (res << 8) + tmp;
- }
- *val = res;
- return 0;
- }
- static int nct6775_wmi_reg_write(void *ctx, unsigned int reg, unsigned int value)
- {
- struct nct6775_data *data = ctx;
- int res, word_sized = nct6775_reg_is_word_sized(data, reg);
- nct6775_wmi_set_bank(data, reg);
- if (word_sized) {
- res = nct6775_asuswmi_write(data->bank, reg & 0xff, value >> 8);
- if (res)
- return res;
- res = nct6775_asuswmi_write(data->bank, (reg & 0xff) + 1, value);
- } else {
- res = nct6775_asuswmi_write(data->bank, reg & 0xff, value);
- }
- return res;
- }
- /*
- * On older chips, only registers 0x50-0x5f are banked.
- * On more recent chips, all registers are banked.
- * Assume that is the case and set the bank number for each access.
- * Cache the bank number so it only needs to be set if it changes.
- */
- static inline void nct6775_set_bank(struct nct6775_data *data, u16 reg)
- {
- u8 bank = reg >> 8;
- if (data->bank != bank) {
- outb_p(NCT6775_REG_BANK, data->addr + ADDR_REG_OFFSET);
- outb_p(bank, data->addr + DATA_REG_OFFSET);
- data->bank = bank;
- }
- }
- static int nct6775_reg_read(void *ctx, unsigned int reg, unsigned int *val)
- {
- struct nct6775_data *data = ctx;
- int word_sized = nct6775_reg_is_word_sized(data, reg);
- nct6775_set_bank(data, reg);
- outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET);
- *val = inb_p(data->addr + DATA_REG_OFFSET);
- if (word_sized) {
- outb_p((reg & 0xff) + 1,
- data->addr + ADDR_REG_OFFSET);
- *val = (*val << 8) + inb_p(data->addr + DATA_REG_OFFSET);
- }
- return 0;
- }
- static int nct6775_reg_write(void *ctx, unsigned int reg, unsigned int value)
- {
- struct nct6775_data *data = ctx;
- int word_sized = nct6775_reg_is_word_sized(data, reg);
- nct6775_set_bank(data, reg);
- outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET);
- if (word_sized) {
- outb_p(value >> 8, data->addr + DATA_REG_OFFSET);
- outb_p((reg & 0xff) + 1,
- data->addr + ADDR_REG_OFFSET);
- }
- outb_p(value & 0xff, data->addr + DATA_REG_OFFSET);
- return 0;
- }
- static void nct6791_enable_io_mapping(struct nct6775_sio_data *sio_data)
- {
- int val;
- val = sio_data->sio_inb(sio_data, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE);
- if (val & 0x10) {
- pr_info("Enabling hardware monitor logical device mappings.\n");
- sio_data->sio_outb(sio_data, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE,
- val & ~0x10);
- }
- }
- static int nct6775_suspend(struct device *dev)
- {
- int err;
- u16 tmp;
- struct nct6775_data *data = nct6775_update_device(dev);
- if (IS_ERR(data))
- return PTR_ERR(data);
- mutex_lock(&data->update_lock);
- err = nct6775_read_value(data, data->REG_VBAT, &tmp);
- if (err)
- goto out;
- data->vbat = tmp;
- if (data->kind == nct6775) {
- err = nct6775_read_value(data, NCT6775_REG_FANDIV1, &tmp);
- if (err)
- goto out;
- data->fandiv1 = tmp;
- err = nct6775_read_value(data, NCT6775_REG_FANDIV2, &tmp);
- if (err)
- goto out;
- data->fandiv2 = tmp;
- }
- out:
- mutex_unlock(&data->update_lock);
- return err;
- }
- static int nct6775_resume(struct device *dev)
- {
- struct nct6775_data *data = dev_get_drvdata(dev);
- struct nct6775_sio_data *sio_data = dev_get_platdata(dev);
- int i, j, err = 0;
- u8 reg;
- mutex_lock(&data->update_lock);
- data->bank = 0xff; /* Force initial bank selection */
- err = sio_data->sio_enter(sio_data);
- if (err)
- goto abort;
- sio_data->sio_select(sio_data, NCT6775_LD_HWM);
- reg = sio_data->sio_inb(sio_data, SIO_REG_ENABLE);
- if (reg != data->sio_reg_enable)
- sio_data->sio_outb(sio_data, SIO_REG_ENABLE, data->sio_reg_enable);
- if (data->kind == nct6791 || data->kind == nct6792 ||
- data->kind == nct6793 || data->kind == nct6795 ||
- data->kind == nct6796 || data->kind == nct6797 ||
- data->kind == nct6798)
- nct6791_enable_io_mapping(sio_data);
- sio_data->sio_exit(sio_data);
- /* Restore limits */
- for (i = 0; i < data->in_num; i++) {
- if (!(data->have_in & BIT(i)))
- continue;
- err = nct6775_write_value(data, data->REG_IN_MINMAX[0][i], data->in[i][1]);
- if (err)
- goto abort;
- err = nct6775_write_value(data, data->REG_IN_MINMAX[1][i], data->in[i][2]);
- if (err)
- goto abort;
- }
- for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) {
- if (!(data->has_fan_min & BIT(i)))
- continue;
- err = nct6775_write_value(data, data->REG_FAN_MIN[i], data->fan_min[i]);
- if (err)
- goto abort;
- }
- for (i = 0; i < NUM_TEMP; i++) {
- if (!(data->have_temp & BIT(i)))
- continue;
- for (j = 1; j < ARRAY_SIZE(data->reg_temp); j++)
- if (data->reg_temp[j][i]) {
- err = nct6775_write_temp(data, data->reg_temp[j][i],
- data->temp[j][i]);
- if (err)
- goto abort;
- }
- }
- /* Restore other settings */
- err = nct6775_write_value(data, data->REG_VBAT, data->vbat);
- if (err)
- goto abort;
- if (data->kind == nct6775) {
- err = nct6775_write_value(data, NCT6775_REG_FANDIV1, data->fandiv1);
- if (err)
- goto abort;
- err = nct6775_write_value(data, NCT6775_REG_FANDIV2, data->fandiv2);
- }
- abort:
- /* Force re-reading all values */
- data->valid = false;
- mutex_unlock(&data->update_lock);
- return err;
- }
- static DEFINE_SIMPLE_DEV_PM_OPS(nct6775_dev_pm_ops, nct6775_suspend, nct6775_resume);
- static void
- nct6775_check_fan_inputs(struct nct6775_data *data, struct nct6775_sio_data *sio_data)
- {
- bool fan3pin = false, fan4pin = false, fan4min = false;
- bool fan5pin = false, fan6pin = false, fan7pin = false;
- bool pwm3pin = false, pwm4pin = false, pwm5pin = false;
- bool pwm6pin = false, pwm7pin = false;
- /* Store SIO_REG_ENABLE for use during resume */
- sio_data->sio_select(sio_data, NCT6775_LD_HWM);
- data->sio_reg_enable = sio_data->sio_inb(sio_data, SIO_REG_ENABLE);
- /* fan4 and fan5 share some pins with the GPIO and serial flash */
- if (data->kind == nct6775) {
- int cr2c = sio_data->sio_inb(sio_data, 0x2c);
- fan3pin = cr2c & BIT(6);
- pwm3pin = cr2c & BIT(7);
- /* On NCT6775, fan4 shares pins with the fdc interface */
- fan4pin = !(sio_data->sio_inb(sio_data, 0x2A) & 0x80);
- } else if (data->kind == nct6776) {
- bool gpok = sio_data->sio_inb(sio_data, 0x27) & 0x80;
- const char *board_vendor, *board_name;
- board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR);
- board_name = dmi_get_system_info(DMI_BOARD_NAME);
- if (board_name && board_vendor &&
- !strcmp(board_vendor, "ASRock")) {
- /*
- * Auxiliary fan monitoring is not enabled on ASRock
- * Z77 Pro4-M if booted in UEFI Ultra-FastBoot mode.
- * Observed with BIOS version 2.00.
- */
- if (!strcmp(board_name, "Z77 Pro4-M")) {
- if ((data->sio_reg_enable & 0xe0) != 0xe0) {
- data->sio_reg_enable |= 0xe0;
- sio_data->sio_outb(sio_data, SIO_REG_ENABLE,
- data->sio_reg_enable);
- }
- }
- }
- if (data->sio_reg_enable & 0x80)
- fan3pin = gpok;
- else
- fan3pin = !(sio_data->sio_inb(sio_data, 0x24) & 0x40);
- if (data->sio_reg_enable & 0x40)
- fan4pin = gpok;
- else
- fan4pin = sio_data->sio_inb(sio_data, 0x1C) & 0x01;
- if (data->sio_reg_enable & 0x20)
- fan5pin = gpok;
- else
- fan5pin = sio_data->sio_inb(sio_data, 0x1C) & 0x02;
- fan4min = fan4pin;
- pwm3pin = fan3pin;
- } else if (data->kind == nct6106) {
- int cr24 = sio_data->sio_inb(sio_data, 0x24);
- fan3pin = !(cr24 & 0x80);
- pwm3pin = cr24 & 0x08;
- } else if (data->kind == nct6116) {
- int cr1a = sio_data->sio_inb(sio_data, 0x1a);
- int cr1b = sio_data->sio_inb(sio_data, 0x1b);
- int cr24 = sio_data->sio_inb(sio_data, 0x24);
- int cr2a = sio_data->sio_inb(sio_data, 0x2a);
- int cr2b = sio_data->sio_inb(sio_data, 0x2b);
- int cr2f = sio_data->sio_inb(sio_data, 0x2f);
- fan3pin = !(cr2b & 0x10);
- fan4pin = (cr2b & 0x80) || // pin 1(2)
- (!(cr2f & 0x10) && (cr1a & 0x04)); // pin 65(66)
- fan5pin = (cr2b & 0x80) || // pin 126(127)
- (!(cr1b & 0x03) && (cr2a & 0x02)); // pin 94(96)
- pwm3pin = fan3pin && (cr24 & 0x08);
- pwm4pin = fan4pin;
- pwm5pin = fan5pin;
- } else {
- /*
- * NCT6779D, NCT6791D, NCT6792D, NCT6793D, NCT6795D, NCT6796D,
- * NCT6797D, NCT6798D
- */
- int cr1a = sio_data->sio_inb(sio_data, 0x1a);
- int cr1b = sio_data->sio_inb(sio_data, 0x1b);
- int cr1c = sio_data->sio_inb(sio_data, 0x1c);
- int cr1d = sio_data->sio_inb(sio_data, 0x1d);
- int cr2a = sio_data->sio_inb(sio_data, 0x2a);
- int cr2b = sio_data->sio_inb(sio_data, 0x2b);
- int cr2d = sio_data->sio_inb(sio_data, 0x2d);
- int cr2f = sio_data->sio_inb(sio_data, 0x2f);
- bool dsw_en = cr2f & BIT(3);
- bool ddr4_en = cr2f & BIT(4);
- int cre0;
- int creb;
- int cred;
- sio_data->sio_select(sio_data, NCT6775_LD_12);
- cre0 = sio_data->sio_inb(sio_data, 0xe0);
- creb = sio_data->sio_inb(sio_data, 0xeb);
- cred = sio_data->sio_inb(sio_data, 0xed);
- fan3pin = !(cr1c & BIT(5));
- fan4pin = !(cr1c & BIT(6));
- fan5pin = !(cr1c & BIT(7));
- pwm3pin = !(cr1c & BIT(0));
- pwm4pin = !(cr1c & BIT(1));
- pwm5pin = !(cr1c & BIT(2));
- switch (data->kind) {
- case nct6791:
- fan6pin = cr2d & BIT(1);
- pwm6pin = cr2d & BIT(0);
- break;
- case nct6792:
- fan6pin = !dsw_en && (cr2d & BIT(1));
- pwm6pin = !dsw_en && (cr2d & BIT(0));
- break;
- case nct6793:
- fan5pin |= cr1b & BIT(5);
- fan5pin |= creb & BIT(5);
- fan6pin = !dsw_en && (cr2d & BIT(1));
- fan6pin |= creb & BIT(3);
- pwm5pin |= cr2d & BIT(7);
- pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0));
- pwm6pin = !dsw_en && (cr2d & BIT(0));
- pwm6pin |= creb & BIT(2);
- break;
- case nct6795:
- fan5pin |= cr1b & BIT(5);
- fan5pin |= creb & BIT(5);
- fan6pin = (cr2a & BIT(4)) &&
- (!dsw_en || (cred & BIT(4)));
- fan6pin |= creb & BIT(3);
- pwm5pin |= cr2d & BIT(7);
- pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0));
- pwm6pin = (cr2a & BIT(3)) && (cred & BIT(2));
- pwm6pin |= creb & BIT(2);
- break;
- case nct6796:
- fan5pin |= cr1b & BIT(5);
- fan5pin |= (cre0 & BIT(3)) && !(cr1b & BIT(0));
- fan5pin |= creb & BIT(5);
- fan6pin = (cr2a & BIT(4)) &&
- (!dsw_en || (cred & BIT(4)));
- fan6pin |= creb & BIT(3);
- fan7pin = !(cr2b & BIT(2));
- pwm5pin |= cr2d & BIT(7);
- pwm5pin |= (cre0 & BIT(4)) && !(cr1b & BIT(0));
- pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0));
- pwm6pin = (cr2a & BIT(3)) && (cred & BIT(2));
- pwm6pin |= creb & BIT(2);
- pwm7pin = !(cr1d & (BIT(2) | BIT(3)));
- break;
- case nct6797:
- fan5pin |= !ddr4_en && (cr1b & BIT(5));
- fan5pin |= creb & BIT(5);
- fan6pin = cr2a & BIT(4);
- fan6pin |= creb & BIT(3);
- fan7pin = cr1a & BIT(1);
- pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0));
- pwm5pin |= !ddr4_en && (cr2d & BIT(7));
- pwm6pin = creb & BIT(2);
- pwm6pin |= cred & BIT(2);
- pwm7pin = cr1d & BIT(4);
- break;
- case nct6798:
- fan6pin = !(cr1b & BIT(0)) && (cre0 & BIT(3));
- fan6pin |= cr2a & BIT(4);
- fan6pin |= creb & BIT(5);
- fan7pin = cr1b & BIT(5);
- fan7pin |= !(cr2b & BIT(2));
- fan7pin |= creb & BIT(3);
- pwm6pin = !(cr1b & BIT(0)) && (cre0 & BIT(4));
- pwm6pin |= !(cred & BIT(2)) && (cr2a & BIT(3));
- pwm6pin |= (creb & BIT(4)) && !(cr2a & BIT(0));
- pwm7pin = !(cr1d & (BIT(2) | BIT(3)));
- pwm7pin |= cr2d & BIT(7);
- pwm7pin |= creb & BIT(2);
- break;
- default: /* NCT6779D */
- break;
- }
- fan4min = fan4pin;
- }
- /* fan 1 and 2 (0x03) are always present */
- data->has_fan = 0x03 | (fan3pin << 2) | (fan4pin << 3) |
- (fan5pin << 4) | (fan6pin << 5) | (fan7pin << 6);
- data->has_fan_min = 0x03 | (fan3pin << 2) | (fan4min << 3) |
- (fan5pin << 4) | (fan6pin << 5) | (fan7pin << 6);
- data->has_pwm = 0x03 | (pwm3pin << 2) | (pwm4pin << 3) |
- (pwm5pin << 4) | (pwm6pin << 5) | (pwm7pin << 6);
- }
- static ssize_t
- cpu0_vid_show(struct device *dev, struct device_attribute *attr, char *buf)
- {
- struct nct6775_data *data = dev_get_drvdata(dev);
- return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm));
- }
- static DEVICE_ATTR_RO(cpu0_vid);
- /* Case open detection */
- static const u8 NCT6775_REG_CR_CASEOPEN_CLR[] = { 0xe6, 0xee };
- static const u8 NCT6775_CR_CASEOPEN_CLR_MASK[] = { 0x20, 0x01 };
- static ssize_t
- clear_caseopen(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
- {
- struct nct6775_data *data = dev_get_drvdata(dev);
- struct nct6775_sio_data *sio_data = data->driver_data;
- int nr = to_sensor_dev_attr(attr)->index - INTRUSION_ALARM_BASE;
- unsigned long val;
- u8 reg;
- int ret;
- if (kstrtoul(buf, 10, &val) || val != 0)
- return -EINVAL;
- mutex_lock(&data->update_lock);
- /*
- * Use CR registers to clear caseopen status.
- * The CR registers are the same for all chips, and not all chips
- * support clearing the caseopen status through "regular" registers.
- */
- ret = sio_data->sio_enter(sio_data);
- if (ret) {
- count = ret;
- goto error;
- }
- sio_data->sio_select(sio_data, NCT6775_LD_ACPI);
- reg = sio_data->sio_inb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr]);
- reg |= NCT6775_CR_CASEOPEN_CLR_MASK[nr];
- sio_data->sio_outb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr], reg);
- reg &= ~NCT6775_CR_CASEOPEN_CLR_MASK[nr];
- sio_data->sio_outb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr], reg);
- sio_data->sio_exit(sio_data);
- data->valid = false; /* Force cache refresh */
- error:
- mutex_unlock(&data->update_lock);
- return count;
- }
- static SENSOR_DEVICE_ATTR(intrusion0_alarm, 0644, nct6775_show_alarm,
- clear_caseopen, INTRUSION_ALARM_BASE);
- static SENSOR_DEVICE_ATTR(intrusion1_alarm, 0644, nct6775_show_alarm,
- clear_caseopen, INTRUSION_ALARM_BASE + 1);
- static SENSOR_DEVICE_ATTR(intrusion0_beep, 0644, nct6775_show_beep,
- nct6775_store_beep, INTRUSION_ALARM_BASE);
- static SENSOR_DEVICE_ATTR(intrusion1_beep, 0644, nct6775_show_beep,
- nct6775_store_beep, INTRUSION_ALARM_BASE + 1);
- static SENSOR_DEVICE_ATTR(beep_enable, 0644, nct6775_show_beep,
- nct6775_store_beep, BEEP_ENABLE_BASE);
- static umode_t nct6775_other_is_visible(struct kobject *kobj,
- struct attribute *attr, int index)
- {
- struct device *dev = kobj_to_dev(kobj);
- struct nct6775_data *data = dev_get_drvdata(dev);
- if (index == 0 && !data->have_vid)
- return 0;
- if (index == 1 || index == 2) {
- if (data->ALARM_BITS[INTRUSION_ALARM_BASE + index - 1] < 0)
- return 0;
- }
- if (index == 3 || index == 4) {
- if (data->BEEP_BITS[INTRUSION_ALARM_BASE + index - 3] < 0)
- return 0;
- }
- return nct6775_attr_mode(data, attr);
- }
- /*
- * nct6775_other_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 attribute *nct6775_attributes_other[] = {
- &dev_attr_cpu0_vid.attr, /* 0 */
- &sensor_dev_attr_intrusion0_alarm.dev_attr.attr, /* 1 */
- &sensor_dev_attr_intrusion1_alarm.dev_attr.attr, /* 2 */
- &sensor_dev_attr_intrusion0_beep.dev_attr.attr, /* 3 */
- &sensor_dev_attr_intrusion1_beep.dev_attr.attr, /* 4 */
- &sensor_dev_attr_beep_enable.dev_attr.attr, /* 5 */
- NULL
- };
- static const struct attribute_group nct6775_group_other = {
- .attrs = nct6775_attributes_other,
- .is_visible = nct6775_other_is_visible,
- };
- static int nct6775_platform_probe_init(struct nct6775_data *data)
- {
- int err;
- u8 cr2a;
- struct nct6775_sio_data *sio_data = data->driver_data;
- err = sio_data->sio_enter(sio_data);
- if (err)
- return err;
- cr2a = sio_data->sio_inb(sio_data, 0x2a);
- switch (data->kind) {
- case nct6775:
- data->have_vid = (cr2a & 0x40);
- break;
- case nct6776:
- data->have_vid = (cr2a & 0x60) == 0x40;
- break;
- case nct6106:
- case nct6116:
- case nct6779:
- case nct6791:
- case nct6792:
- case nct6793:
- case nct6795:
- case nct6796:
- case nct6797:
- case nct6798:
- break;
- }
- /*
- * Read VID value
- * We can get the VID input values directly at logical device D 0xe3.
- */
- if (data->have_vid) {
- sio_data->sio_select(sio_data, NCT6775_LD_VID);
- data->vid = sio_data->sio_inb(sio_data, 0xe3);
- data->vrm = vid_which_vrm();
- }
- if (fan_debounce) {
- u8 tmp;
- sio_data->sio_select(sio_data, NCT6775_LD_HWM);
- tmp = sio_data->sio_inb(sio_data,
- NCT6775_REG_CR_FAN_DEBOUNCE);
- switch (data->kind) {
- case nct6106:
- case nct6116:
- tmp |= 0xe0;
- break;
- case nct6775:
- tmp |= 0x1e;
- break;
- case nct6776:
- case nct6779:
- tmp |= 0x3e;
- break;
- case nct6791:
- case nct6792:
- case nct6793:
- case nct6795:
- case nct6796:
- case nct6797:
- case nct6798:
- tmp |= 0x7e;
- break;
- }
- sio_data->sio_outb(sio_data, NCT6775_REG_CR_FAN_DEBOUNCE,
- tmp);
- pr_info("Enabled fan debounce for chip %s\n", data->name);
- }
- nct6775_check_fan_inputs(data, sio_data);
- sio_data->sio_exit(sio_data);
- return nct6775_add_attr_group(data, &nct6775_group_other);
- }
- static const struct regmap_config nct6775_regmap_config = {
- .reg_bits = 16,
- .val_bits = 16,
- .reg_read = nct6775_reg_read,
- .reg_write = nct6775_reg_write,
- };
- static const struct regmap_config nct6775_wmi_regmap_config = {
- .reg_bits = 16,
- .val_bits = 16,
- .reg_read = nct6775_wmi_reg_read,
- .reg_write = nct6775_wmi_reg_write,
- };
- static int nct6775_platform_probe(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct nct6775_sio_data *sio_data = dev_get_platdata(dev);
- struct nct6775_data *data;
- struct resource *res;
- const struct regmap_config *regmapcfg;
- if (sio_data->access == access_direct) {
- res = platform_get_resource(pdev, IORESOURCE_IO, 0);
- if (!devm_request_region(&pdev->dev, res->start, IOREGION_LENGTH, DRVNAME))
- return -EBUSY;
- }
- data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
- data->kind = sio_data->kind;
- data->sioreg = sio_data->sioreg;
- if (sio_data->access == access_direct) {
- data->addr = res->start;
- regmapcfg = &nct6775_regmap_config;
- } else {
- regmapcfg = &nct6775_wmi_regmap_config;
- }
- platform_set_drvdata(pdev, data);
- data->driver_data = sio_data;
- data->driver_init = nct6775_platform_probe_init;
- return nct6775_probe(&pdev->dev, data, regmapcfg);
- }
- static struct platform_driver nct6775_driver = {
- .driver = {
- .name = DRVNAME,
- .pm = pm_sleep_ptr(&nct6775_dev_pm_ops),
- },
- .probe = nct6775_platform_probe,
- };
- /* nct6775_find() looks for a '627 in the Super-I/O config space */
- static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data)
- {
- u16 val;
- int err;
- int addr;
- sio_data->access = access_direct;
- sio_data->sioreg = sioaddr;
- err = sio_data->sio_enter(sio_data);
- if (err)
- return err;
- val = (sio_data->sio_inb(sio_data, SIO_REG_DEVID) << 8) |
- sio_data->sio_inb(sio_data, SIO_REG_DEVID + 1);
- if (force_id && val != 0xffff)
- val = force_id;
- switch (val & SIO_ID_MASK) {
- case SIO_NCT6106_ID:
- sio_data->kind = nct6106;
- break;
- case SIO_NCT6116_ID:
- sio_data->kind = nct6116;
- break;
- case SIO_NCT6775_ID:
- sio_data->kind = nct6775;
- break;
- case SIO_NCT6776_ID:
- sio_data->kind = nct6776;
- break;
- case SIO_NCT6779_ID:
- sio_data->kind = nct6779;
- break;
- case SIO_NCT6791_ID:
- sio_data->kind = nct6791;
- break;
- case SIO_NCT6792_ID:
- sio_data->kind = nct6792;
- break;
- case SIO_NCT6793_ID:
- sio_data->kind = nct6793;
- break;
- case SIO_NCT6795_ID:
- sio_data->kind = nct6795;
- break;
- case SIO_NCT6796_ID:
- sio_data->kind = nct6796;
- break;
- case SIO_NCT6797_ID:
- sio_data->kind = nct6797;
- break;
- case SIO_NCT6798_ID:
- sio_data->kind = nct6798;
- break;
- default:
- if (val != 0xffff)
- pr_debug("unsupported chip ID: 0x%04x\n", val);
- sio_data->sio_exit(sio_data);
- return -ENODEV;
- }
- /* We have a known chip, find the HWM I/O address */
- sio_data->sio_select(sio_data, NCT6775_LD_HWM);
- val = (sio_data->sio_inb(sio_data, SIO_REG_ADDR) << 8)
- | sio_data->sio_inb(sio_data, SIO_REG_ADDR + 1);
- addr = val & IOREGION_ALIGNMENT;
- if (addr == 0) {
- pr_err("Refusing to enable a Super-I/O device with a base I/O port 0\n");
- sio_data->sio_exit(sio_data);
- return -ENODEV;
- }
- /* Activate logical device if needed */
- val = sio_data->sio_inb(sio_data, SIO_REG_ENABLE);
- if (!(val & 0x01)) {
- pr_warn("Forcibly enabling Super-I/O. Sensor is probably unusable.\n");
- sio_data->sio_outb(sio_data, SIO_REG_ENABLE, val | 0x01);
- }
- if (sio_data->kind == nct6791 || sio_data->kind == nct6792 ||
- sio_data->kind == nct6793 || sio_data->kind == nct6795 ||
- sio_data->kind == nct6796 || sio_data->kind == nct6797 ||
- sio_data->kind == nct6798)
- nct6791_enable_io_mapping(sio_data);
- sio_data->sio_exit(sio_data);
- pr_info("Found %s or compatible chip at %#x:%#x\n",
- nct6775_sio_names[sio_data->kind], sioaddr, addr);
- return addr;
- }
- /*
- * 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 nct6775 driver. But since we use platform_device_alloc(), we
- * must keep track of the device
- */
- static struct platform_device *pdev[2];
- static const char * const asus_wmi_boards[] = {
- "PRO H410T",
- "ProArt B550-CREATOR",
- "ProArt X570-CREATOR WIFI",
- "ProArt Z490-CREATOR 10G",
- "Pro B550M-C",
- "Pro WS X570-ACE",
- "PRIME B360-PLUS",
- "PRIME B460-PLUS",
- "PRIME B550-PLUS",
- "PRIME B550M-A",
- "PRIME B550M-A (WI-FI)",
- "PRIME H410M-R",
- "PRIME X570-P",
- "PRIME X570-PRO",
- "ROG CROSSHAIR VIII DARK HERO",
- "ROG CROSSHAIR VIII EXTREME",
- "ROG CROSSHAIR VIII FORMULA",
- "ROG CROSSHAIR VIII HERO",
- "ROG CROSSHAIR VIII HERO (WI-FI)",
- "ROG CROSSHAIR VIII IMPACT",
- "ROG STRIX B550-A GAMING",
- "ROG STRIX B550-E GAMING",
- "ROG STRIX B550-F GAMING",
- "ROG STRIX B550-F GAMING (WI-FI)",
- "ROG STRIX B550-F GAMING WIFI II",
- "ROG STRIX B550-I GAMING",
- "ROG STRIX B550-XE GAMING (WI-FI)",
- "ROG STRIX X570-E GAMING",
- "ROG STRIX X570-E GAMING WIFI II",
- "ROG STRIX X570-F GAMING",
- "ROG STRIX X570-I GAMING",
- "ROG STRIX Z390-E GAMING",
- "ROG STRIX Z390-F GAMING",
- "ROG STRIX Z390-H GAMING",
- "ROG STRIX Z390-I GAMING",
- "ROG STRIX Z490-A GAMING",
- "ROG STRIX Z490-E GAMING",
- "ROG STRIX Z490-F GAMING",
- "ROG STRIX Z490-G GAMING",
- "ROG STRIX Z490-G GAMING (WI-FI)",
- "ROG STRIX Z490-H GAMING",
- "ROG STRIX Z490-I GAMING",
- "TUF GAMING B550M-E",
- "TUF GAMING B550M-E (WI-FI)",
- "TUF GAMING B550M-PLUS",
- "TUF GAMING B550M-PLUS (WI-FI)",
- "TUF GAMING B550M-PLUS WIFI II",
- "TUF GAMING B550-PLUS",
- "TUF GAMING B550-PLUS WIFI II",
- "TUF GAMING B550-PRO",
- "TUF GAMING X570-PLUS",
- "TUF GAMING X570-PLUS (WI-FI)",
- "TUF GAMING X570-PRO (WI-FI)",
- "TUF GAMING Z490-PLUS",
- "TUF GAMING Z490-PLUS (WI-FI)",
- };
- static const char * const asus_msi_boards[] = {
- "EX-B660M-V5 PRO D4",
- "PRIME B650-PLUS",
- "PRIME B650M-A",
- "PRIME B650M-A AX",
- "PRIME B650M-A II",
- "PRIME B650M-A WIFI",
- "PRIME B650M-A WIFI II",
- "PRIME B660M-A D4",
- "PRIME B660M-A WIFI D4",
- "PRIME X670-P",
- "PRIME X670-P WIFI",
- "PRIME X670E-PRO WIFI",
- "Pro B660M-C-D4",
- "ProArt B660-CREATOR D4",
- "ProArt X670E-CREATOR WIFI",
- "ROG CROSSHAIR X670E EXTREME",
- "ROG CROSSHAIR X670E GENE",
- "ROG CROSSHAIR X670E HERO",
- "ROG MAXIMUS XIII EXTREME GLACIAL",
- "ROG MAXIMUS Z690 EXTREME",
- "ROG MAXIMUS Z690 EXTREME GLACIAL",
- "ROG STRIX B650-A GAMING WIFI",
- "ROG STRIX B650E-E GAMING WIFI",
- "ROG STRIX B650E-F GAMING WIFI",
- "ROG STRIX B650E-I GAMING WIFI",
- "ROG STRIX B660-A GAMING WIFI D4",
- "ROG STRIX B660-F GAMING WIFI",
- "ROG STRIX B660-G GAMING WIFI",
- "ROG STRIX B660-I GAMING WIFI",
- "ROG STRIX X670E-A GAMING WIFI",
- "ROG STRIX X670E-E GAMING WIFI",
- "ROG STRIX X670E-F GAMING WIFI",
- "ROG STRIX X670E-I GAMING WIFI",
- "ROG STRIX Z590-A GAMING WIFI II",
- "ROG STRIX Z690-A GAMING WIFI D4",
- "TUF GAMING B650-PLUS",
- "TUF GAMING B650-PLUS WIFI",
- "TUF GAMING B650M-PLUS",
- "TUF GAMING B650M-PLUS WIFI",
- "TUF GAMING B660M-PLUS WIFI",
- "TUF GAMING X670E-PLUS",
- "TUF GAMING X670E-PLUS WIFI",
- "TUF GAMING Z590-PLUS WIFI",
- };
- #if IS_ENABLED(CONFIG_ACPI)
- /*
- * Callback for acpi_bus_for_each_dev() to find the right device
- * by _UID and _HID and return 1 to stop iteration.
- */
- static int nct6775_asuswmi_device_match(struct device *dev, void *data)
- {
- struct acpi_device *adev = to_acpi_device(dev);
- const char *uid = acpi_device_uid(adev);
- const char *hid = acpi_device_hid(adev);
- if (hid && !strcmp(hid, ASUSWMI_DEVICE_HID) && uid && !strcmp(uid, data)) {
- asus_acpi_dev = adev;
- return 1;
- }
- return 0;
- }
- #endif
- static enum sensor_access nct6775_determine_access(const char *device_uid)
- {
- #if IS_ENABLED(CONFIG_ACPI)
- u8 tmp;
- acpi_bus_for_each_dev(nct6775_asuswmi_device_match, (void *)device_uid);
- if (!asus_acpi_dev)
- return access_direct;
- /* if reading chip id via ACPI succeeds, use WMI "WMBD" method for access */
- if (!nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp) && tmp) {
- pr_debug("Using Asus WMBD method of %s to access %#x chip.\n", device_uid, tmp);
- return access_asuswmi;
- }
- #endif
- return access_direct;
- }
- static int __init sensors_nct6775_platform_init(void)
- {
- int i, err;
- bool found = false;
- int address;
- struct resource res;
- struct nct6775_sio_data sio_data;
- int sioaddr[2] = { 0x2e, 0x4e };
- enum sensor_access access = access_direct;
- const char *board_vendor, *board_name;
- err = platform_driver_register(&nct6775_driver);
- if (err)
- return err;
- board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR);
- board_name = dmi_get_system_info(DMI_BOARD_NAME);
- if (board_name && board_vendor &&
- !strcmp(board_vendor, "ASUSTeK COMPUTER INC.")) {
- err = match_string(asus_wmi_boards, ARRAY_SIZE(asus_wmi_boards),
- board_name);
- if (err >= 0)
- access = nct6775_determine_access(ASUSWMI_DEVICE_UID);
- err = match_string(asus_msi_boards, ARRAY_SIZE(asus_msi_boards),
- board_name);
- if (err >= 0)
- access = nct6775_determine_access(ASUSMSI_DEVICE_UID);
- }
- /*
- * 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
- * nct6775 hardware monitor, and call probe()
- */
- for (i = 0; i < ARRAY_SIZE(pdev); i++) {
- sio_data.sio_outb = superio_outb;
- sio_data.sio_inb = superio_inb;
- sio_data.sio_select = superio_select;
- sio_data.sio_enter = superio_enter;
- sio_data.sio_exit = superio_exit;
- address = nct6775_find(sioaddr[i], &sio_data);
- if (address <= 0)
- continue;
- found = true;
- sio_data.access = access;
- if (access == access_asuswmi) {
- sio_data.sio_outb = superio_wmi_outb;
- sio_data.sio_inb = superio_wmi_inb;
- sio_data.sio_select = superio_wmi_select;
- sio_data.sio_enter = superio_wmi_enter;
- sio_data.sio_exit = superio_wmi_exit;
- }
- 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 nct6775_sio_data));
- if (err)
- goto exit_device_put;
- if (sio_data.access == access_direct) {
- 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--)
- platform_device_unregister(pdev[i]);
- exit_unregister:
- platform_driver_unregister(&nct6775_driver);
- return err;
- }
- static void __exit sensors_nct6775_platform_exit(void)
- {
- int i;
- for (i = 0; i < ARRAY_SIZE(pdev); i++)
- platform_device_unregister(pdev[i]);
- platform_driver_unregister(&nct6775_driver);
- }
- MODULE_AUTHOR("Guenter Roeck <[email protected]>");
- MODULE_DESCRIPTION("Platform driver for NCT6775F and compatible chips");
- MODULE_LICENSE("GPL");
- MODULE_IMPORT_NS(HWMON_NCT6775);
- module_init(sensors_nct6775_platform_init);
- module_exit(sensors_nct6775_platform_exit);
|