Forum | Documentation | Website | Blog

Skip to content
Snippets Groups Projects
it87.c 96.6 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
/*
 *  it87.c - Part of lm_sensors, Linux kernel modules for hardware
 *           monitoring.
 *
 *  The IT8705F is an LPC-based Super I/O part that contains UARTs, a
 *  parallel port, an IR port, a MIDI port, a floppy controller, etc., in
 *  addition to an Environment Controller (Enhanced Hardware Monitor and
 *  Fan Controller)
 *
 *  This driver supports only the Environment Controller in the IT8705F and
 *  similar parts.  The other devices are supported by different drivers.
 *
 *  Supports: IT8603E  Super I/O chip w/LPC interface
 *            IT8620E  Super I/O chip w/LPC interface
 *            IT8623E  Super I/O chip w/LPC interface
 *            IT8628E  Super I/O chip w/LPC interface
 *            IT8705F  Super I/O chip w/LPC interface
 *            IT8712F  Super I/O chip w/LPC interface
 *            IT8716F  Super I/O chip w/LPC interface
 *            IT8718F  Super I/O chip w/LPC interface
 *            IT8720F  Super I/O chip w/LPC interface
 *            IT8721F  Super I/O chip w/LPC interface
 *            IT8726F  Super I/O chip w/LPC interface
 *            IT8728F  Super I/O chip w/LPC interface
 *            IT8732F  Super I/O chip w/LPC interface
 *            IT8758E  Super I/O chip w/LPC interface
 *            IT8771E  Super I/O chip w/LPC interface
 *            IT8772E  Super I/O chip w/LPC interface
 *            IT8781F  Super I/O chip w/LPC interface
 *            IT8782F  Super I/O chip w/LPC interface
 *            IT8783E/F Super I/O chip w/LPC interface
 *            IT8786E  Super I/O chip w/LPC interface
 *            IT8790E  Super I/O chip w/LPC interface
 *            Sis950   A clone of the IT8705F
 *
 *  Copyright (C) 2001 Chris Gauthron
 *  Copyright (C) 2005-2010 Jean Delvare <jdelvare@suse.de>
 *
 *  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.
 */
Linus Torvalds's avatar
Linus Torvalds committed

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/bitops.h>
Linus Torvalds's avatar
Linus Torvalds committed
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/platform_device.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/hwmon-vid.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#include <linux/string.h>
#include <linux/dmi.h>
#include <linux/acpi.h>
#include <linux/io.h>
Linus Torvalds's avatar
Linus Torvalds committed

#define DRVNAME "it87"
Linus Torvalds's avatar
Linus Torvalds committed

enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8732,
	     it8771, it8772, it8781, it8782, it8783, it8786, it8790, it8603,
	     it8620, it8628 };
Linus Torvalds's avatar
Linus Torvalds committed

static unsigned short force_id;
module_param(force_id, ushort, 0);
MODULE_PARM_DESC(force_id, "Override the detected device ID");

static struct platform_device *it87_pdev[2];
#define	REG_2E	0x2e	/* The register to read/write */
#define	REG_4E	0x4e	/* Secondary register to read/write */
Linus Torvalds's avatar
Linus Torvalds committed
#define	DEV	0x07	/* Register: Logical device select */
#define PME	0x04	/* The device with the fan registers in it */

/* The device with the IT8718F/IT8720F VID value in it */
#define GPIO	0x07

Linus Torvalds's avatar
Linus Torvalds committed
#define	DEVID	0x20	/* Register: Device ID */
#define	DEVREV	0x22	/* Register: Device Revision */

static inline int superio_inb(int ioreg, int reg)
Linus Torvalds's avatar
Linus Torvalds committed
{
	outb(reg, ioreg);
	return inb(ioreg + 1);
Linus Torvalds's avatar
Linus Torvalds committed
}

static inline void superio_outb(int ioreg, int reg, int val)
	outb(reg, ioreg);
	outb(val, ioreg + 1);
static int superio_inw(int ioreg, int reg)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int val;
	outb(reg++, ioreg);
	val = inb(ioreg + 1) << 8;
	outb(reg, ioreg);
	val |= inb(ioreg + 1);
Linus Torvalds's avatar
Linus Torvalds committed
	return val;
}

static inline void superio_select(int ioreg, int ldn)
Linus Torvalds's avatar
Linus Torvalds committed
{
	outb(DEV, ioreg);
	outb(ldn, ioreg + 1);
static inline int superio_enter(int ioreg)
Linus Torvalds's avatar
Linus Torvalds committed
{
	 * Try to reserve ioreg and ioreg + 1 for exclusive access.
	if (!request_muxed_region(ioreg, 2, DRVNAME))
	outb(0x87, ioreg);
	outb(0x01, ioreg);
	outb(0x55, ioreg);
	outb(ioreg == REG_4E ? 0xaa : 0x55, ioreg);
static inline void superio_exit(int ioreg)
Linus Torvalds's avatar
Linus Torvalds committed
{
	outb(0x02, ioreg);
	outb(0x02, ioreg + 1);
	release_region(ioreg, 2);
/* Logical device 4 registers */
Linus Torvalds's avatar
Linus Torvalds committed
#define IT8712F_DEVID 0x8712
#define IT8705F_DEVID 0x8705
#define IT8716F_DEVID 0x8716
#define IT8718F_DEVID 0x8718
#define IT8720F_DEVID 0x8720
#define IT8721F_DEVID 0x8721
#define IT8726F_DEVID 0x8726
#define IT8728F_DEVID 0x8728
#define IT8732F_DEVID 0x8732
#define IT8771E_DEVID 0x8771
#define IT8772E_DEVID 0x8772
#define IT8781F_DEVID 0x8781
#define IT8782F_DEVID 0x8782
#define IT8783E_DEVID 0x8783
#define IT8786E_DEVID 0x8786
#define IT8790E_DEVID 0x8790
#define IT8603E_DEVID 0x8603
#define IT8620E_DEVID 0x8620
#define IT8623E_DEVID 0x8623
#define IT8628E_DEVID 0x8628
Linus Torvalds's avatar
Linus Torvalds committed
#define IT87_ACT_REG  0x30
#define IT87_BASE_REG 0x60

/* Logical device 7 registers (IT8712F and later) */
#define IT87_SIO_GPIO1_REG	0x25
#define IT87_SIO_GPIO2_REG	0x26
#define IT87_SIO_GPIO3_REG	0x27
#define IT87_SIO_GPIO4_REG	0x28
#define IT87_SIO_GPIO5_REG	0x29
#define IT87_SIO_PINX1_REG	0x2a	/* Pin selection */
#define IT87_SIO_PINX2_REG	0x2c	/* Pin selection */
#define IT87_SIO_SPI_REG	0xef	/* SPI function pin select */
#define IT87_SIO_VID_REG	0xfc	/* VID value */
#define IT87_SIO_BEEP_PIN_REG	0xf6	/* Beep pin mapping */
Linus Torvalds's avatar
Linus Torvalds committed
/* Update battery voltage after every reading if true */
Linus Torvalds's avatar
Linus Torvalds committed

/* Not all BIOSes properly configure the PWM registers */
static bool fix_pwm_polarity;
Linus Torvalds's avatar
Linus Torvalds committed

/* Many IT87 constants specified below */

/* Length of ISA address segment */
#define IT87_EXTENT 8

/* Length of ISA address segment for Environmental Controller */
#define IT87_EC_EXTENT 2

/* Offset of EC registers from ISA base address */
#define IT87_EC_OFFSET 5

/* Where are the ISA address/data registers relative to the EC base address */
#define IT87_ADDR_REG_OFFSET 0
#define IT87_DATA_REG_OFFSET 1
Linus Torvalds's avatar
Linus Torvalds committed

/*----- The IT87 registers -----*/

#define IT87_REG_CONFIG        0x00

#define IT87_REG_ALARM1        0x01
#define IT87_REG_ALARM2        0x02
#define IT87_REG_ALARM3        0x03

/*
 * The IT8718F and IT8720F have the VID value in a different register, in
 * Super-I/O configuration space.
 */
Linus Torvalds's avatar
Linus Torvalds committed
#define IT87_REG_VID           0x0a
/*
 * The IT8705F and IT8712F earlier than revision 0x08 use register 0x0b
 * for fan divisors. Later IT8712F revisions must use 16-bit tachometer
 * mode.
 */
Linus Torvalds's avatar
Linus Torvalds committed
#define IT87_REG_FAN_DIV       0x0b
#define IT87_REG_FAN_16BIT     0x0c
Linus Torvalds's avatar
Linus Torvalds committed

/*
 * Monitors:
 * - up to 13 voltage (0 to 7, battery, avcc, 10 to 12)
 * - up to 6 temp (1 to 6)
 * - up to 6 fan (1 to 6)
 */
Linus Torvalds's avatar
Linus Torvalds committed

static const u8 IT87_REG_FAN[]         = { 0x0d, 0x0e, 0x0f, 0x80, 0x82, 0x4c };
static const u8 IT87_REG_FAN_MIN[]     = { 0x10, 0x11, 0x12, 0x84, 0x86, 0x4e };
static const u8 IT87_REG_FANX[]        = { 0x18, 0x19, 0x1a, 0x81, 0x83, 0x4d };
static const u8 IT87_REG_FANX_MIN[]    = { 0x1b, 0x1c, 0x1d, 0x85, 0x87, 0x4f };
static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59 };
Linus Torvalds's avatar
Linus Torvalds committed
#define IT87_REG_FAN_MAIN_CTRL 0x13
#define IT87_REG_FAN_CTL       0x14
static const u8 IT87_REG_PWM[]         = { 0x15, 0x16, 0x17, 0x7f, 0xa7, 0xaf };
static const u8 IT87_REG_PWM_DUTY[]    = { 0x63, 0x6b, 0x73, 0x7b, 0xa3, 0xab };
Linus Torvalds's avatar
Linus Torvalds committed

static const u8 IT87_REG_VIN[]	= { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
				    0x27, 0x28, 0x2f, 0x2c, 0x2d, 0x2e };
Linus Torvalds's avatar
Linus Torvalds committed

#define IT87_REG_TEMP(nr)      (0x29 + (nr))
Linus Torvalds's avatar
Linus Torvalds committed
#define IT87_REG_VIN_MAX(nr)   (0x30 + (nr) * 2)
#define IT87_REG_VIN_MIN(nr)   (0x31 + (nr) * 2)
#define IT87_REG_TEMP_HIGH(nr) (0x40 + (nr) * 2)
#define IT87_REG_TEMP_LOW(nr)  (0x41 + (nr) * 2)

#define IT87_REG_VIN_ENABLE    0x50
#define IT87_REG_TEMP_ENABLE   0x51
#define IT87_REG_TEMP_EXTRA    0x55
#define IT87_REG_BEEP_ENABLE   0x5c
Linus Torvalds's avatar
Linus Torvalds committed

#define IT87_REG_CHIPID        0x58

static const u8 IT87_REG_AUTO_BASE[] = { 0x60, 0x68, 0x70, 0x78, 0xa0, 0xa8 };

#define IT87_REG_AUTO_TEMP(nr, i) (IT87_REG_AUTO_BASE[nr] + (i))
#define IT87_REG_AUTO_PWM(nr, i)  (IT87_REG_AUTO_BASE[nr] + 5 + (i))
#define IT87_REG_TEMP456_ENABLE	0x77

#define NUM_VIN			ARRAY_SIZE(IT87_REG_VIN)
#define NUM_VIN_LIMIT		8
#define NUM_TEMP		6
#define NUM_TEMP_OFFSET		ARRAY_SIZE(IT87_REG_TEMP_OFFSET)
#define NUM_TEMP_LIMIT		3
#define NUM_FAN			ARRAY_SIZE(IT87_REG_FAN)
#define NUM_FAN_DIV		3
#define NUM_PWM			ARRAY_SIZE(IT87_REG_PWM)
#define NUM_AUTO_PWM		ARRAY_SIZE(IT87_REG_PWM)

struct it87_devices {
	const char *name;
	u8 peci_mask;
	u8 old_peci_mask;
#define FEAT_12MV_ADC		BIT(0)
#define FEAT_NEWER_AUTOPWM	BIT(1)
#define FEAT_OLD_AUTOPWM	BIT(2)
#define FEAT_16BIT_FANS		BIT(3)
#define FEAT_TEMP_OFFSET	BIT(4)
#define FEAT_TEMP_PECI		BIT(5)
#define FEAT_TEMP_OLD_PECI	BIT(6)
#define FEAT_FAN16_CONFIG	BIT(7)	/* Need to enable 16-bit fans */
#define FEAT_FIVE_FANS		BIT(8)	/* Supports five fans */
#define FEAT_VID		BIT(9)	/* Set if chip supports VID */
#define FEAT_IN7_INTERNAL	BIT(10)	/* Set if in7 is internal */
#define FEAT_SIX_FANS		BIT(11)	/* Supports six fans */
#define FEAT_10_9MV_ADC		BIT(12)
#define FEAT_AVCC3		BIT(13)	/* Chip supports in9/AVCC3 */
#define FEAT_SIX_PWM		BIT(14)	/* Chip supports 6 pwm chn */
#define FEAT_PWM_FREQ2		BIT(15)	/* Separate pwm freq 2 */
#define FEAT_SIX_TEMP		BIT(16)	/* Up to 6 temp sensors */

static const struct it87_devices it87_devices[] = {
	[it87] = {
		.name = "it87",
		.features = FEAT_OLD_AUTOPWM,	/* may need to overwrite */
	},
	[it8712] = {
		.name = "it8712",
		.features = FEAT_OLD_AUTOPWM | FEAT_VID,
						/* may need to overwrite */
	},
	[it8716] = {
		.name = "it8716",
		.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
		  | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_PWM_FREQ2,
	},
	[it8718] = {
		.name = "it8718",
		.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
		  | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
		  | FEAT_PWM_FREQ2,
		.old_peci_mask = 0x4,
	},
	[it8720] = {
		.name = "it8720",
		.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
		  | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
		  | FEAT_PWM_FREQ2,
		.old_peci_mask = 0x4,
	},
	[it8721] = {
		.name = "it8721",
		.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
		  | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
		  | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_IN7_INTERNAL
		  | FEAT_PWM_FREQ2,
		.old_peci_mask = 0x02,	/* Actually reports PCH */
	},
	[it8728] = {
		.name = "it8728",
		.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
		  | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
		  | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2,
	[it8732] = {
		.name = "it8732",
		.suffix = "F",
		.features = FEAT_NEWER_AUTOPWM | FEAT_16BIT_FANS
		  | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
		  | FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL,
		.peci_mask = 0x07,
		.old_peci_mask = 0x02,	/* Actually reports PCH */
	},
	[it8771] = {
		.name = "it8771",
		.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
		  | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
		  | FEAT_PWM_FREQ2,
				/* PECI: guesswork */
				/* 12mV ADC (OHM) */
				/* 16 bit fans (OHM) */
				/* three fans, always 16 bit (guesswork) */
		.peci_mask = 0x07,
	},
	[it8772] = {
		.name = "it8772",
		.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
		  | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
		  | FEAT_PWM_FREQ2,
				/* PECI (coreboot) */
				/* 12mV ADC (HWSensors4, OHM) */
				/* 16 bit fans (HWSensors4, OHM) */
				/* three fans, always 16 bit (datasheet) */
	[it8781] = {
		.name = "it8781",
		.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
		  | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2,
		.old_peci_mask = 0x4,
	},
	[it8782] = {
		.name = "it8782",
		.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
		  | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2,
		.old_peci_mask = 0x4,
	},
	[it8783] = {
		.name = "it8783",
		.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
		  | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2,
		.old_peci_mask = 0x4,
	[it8786] = {
		.name = "it8786",
		.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
		  | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
		  | FEAT_PWM_FREQ2,
		.peci_mask = 0x07,
	},
	[it8790] = {
		.name = "it8790",
		.suffix = "E",
		.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
		  | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
		  | FEAT_PWM_FREQ2,
		.peci_mask = 0x07,
	},
	[it8603] = {
		.name = "it8603",
		.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
		  | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
		  | FEAT_AVCC3 | FEAT_PWM_FREQ2,
		.peci_mask = 0x07,
	},
	[it8620] = {
		.name = "it8620",
		.suffix = "E",
		.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
		  | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
		  | FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
		  | FEAT_SIX_TEMP,
		.peci_mask = 0x07,
	},
	[it8628] = {
		.name = "it8628",
		.suffix = "E",
		.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
		  | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
		  | FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
		  | FEAT_SIX_TEMP,
		.peci_mask = 0x07,
	},
};

#define has_16bit_fans(data)	((data)->features & FEAT_16BIT_FANS)
#define has_12mv_adc(data)	((data)->features & FEAT_12MV_ADC)
#define has_10_9mv_adc(data)	((data)->features & FEAT_10_9MV_ADC)
#define has_newer_autopwm(data)	((data)->features & FEAT_NEWER_AUTOPWM)
#define has_old_autopwm(data)	((data)->features & FEAT_OLD_AUTOPWM)
#define has_temp_offset(data)	((data)->features & FEAT_TEMP_OFFSET)
#define has_temp_peci(data, nr)	(((data)->features & FEAT_TEMP_PECI) && \
				 ((data)->peci_mask & BIT(nr)))
#define has_temp_old_peci(data, nr) \
				(((data)->features & FEAT_TEMP_OLD_PECI) && \
				 ((data)->old_peci_mask & BIT(nr)))
#define has_fan16_config(data)	((data)->features & FEAT_FAN16_CONFIG)
#define has_five_fans(data)	((data)->features & (FEAT_FIVE_FANS | \
						     FEAT_SIX_FANS))
#define has_vid(data)		((data)->features & FEAT_VID)
#define has_in7_internal(data)	((data)->features & FEAT_IN7_INTERNAL)
#define has_six_fans(data)	((data)->features & FEAT_SIX_FANS)
#define has_avcc3(data)		((data)->features & FEAT_AVCC3)
#define has_six_pwm(data)	((data)->features & FEAT_SIX_PWM)
#define has_pwm_freq2(data)	((data)->features & FEAT_PWM_FREQ2)
#define has_six_temp(data)	((data)->features & FEAT_SIX_TEMP)
Linus Torvalds's avatar
Linus Torvalds committed

struct it87_sio_data {
	enum chips type;
	/* Values read from Super-I/O config space */
	u8 vid_value;
	u8 internal;	/* Internal sensors can be labeled */
	/* Features skipped based on config or DMI */
	u8 skip_vid;
/*
 * For each registered chip, we need to keep some data in memory.
 * The structure is dynamically allocated.
 */
Linus Torvalds's avatar
Linus Torvalds committed
struct it87_data {
	const struct attribute_group *groups[7];
Linus Torvalds's avatar
Linus Torvalds committed
	enum chips type;
	u8 peci_mask;
	u8 old_peci_mask;
Linus Torvalds's avatar
Linus Torvalds committed

	unsigned short addr;
	const char *name;
	struct mutex update_lock;
Linus Torvalds's avatar
Linus Torvalds committed
	char valid;		/* !=0 if following fields are valid */
	unsigned long last_updated;	/* In jiffies */

	u16 in_scaled;		/* Internal voltage sensors are scaled */
	u16 in_internal;	/* Bitfield, internal sensors (for labels) */
	u16 has_in;		/* Bitfield, voltage sensors enabled */
	u8 in[NUM_VIN][3];		/* [nr][0]=in, [1]=min, [2]=max */
	u8 has_fan;		/* Bitfield, fans enabled */
	u16 fan[NUM_FAN][2];	/* Register values, [nr][0]=fan, [1]=min */
	u8 has_temp;		/* Bitfield, temp sensors enabled */
	s8 temp[NUM_TEMP][4];	/* [nr][0]=temp, [1]=min, [2]=max, [3]=offset */
	u8 sensor;		/* Register value (IT87_REG_TEMP_ENABLE) */
	u8 extra;		/* Register value (IT87_REG_TEMP_EXTRA) */
	u8 fan_div[NUM_FAN_DIV];/* Register encoding, shifted right */
	bool has_vid;		/* True if VID supported */
Linus Torvalds's avatar
Linus Torvalds committed
	u8 vid;			/* Register encoding, combined */
Linus Torvalds's avatar
Linus Torvalds committed
	u32 alarms;		/* Register encoding, combined */
	bool has_beep;		/* true if beep supported */
	u8 beeps;		/* Register encoding */
Linus Torvalds's avatar
Linus Torvalds committed
	u8 fan_main_ctrl;	/* Register value */
	u8 fan_ctl;		/* Register value */
	/*
	 * The following 3 arrays correspond to the same registers up to
	 * the IT8720F. The meaning of bits 6-0 depends on the value of bit
	 * 7, and we want to preserve settings on mode changes, so we have
	 * to track all values separately.
	 * Starting with the IT8721F, the manual PWM duty cycles are stored
	 * in separate registers (8-bit values), so the separate tracking
	 * is no longer needed, but it is still done to keep the driver
	u8 has_pwm;		/* Bitfield, pwm control enabled */
	u8 pwm_ctrl[NUM_PWM];	/* Register value */
	u8 pwm_duty[NUM_PWM];	/* Manual PWM value set by user */
	u8 pwm_temp_map[NUM_PWM];/* PWM to temp. chan. mapping (bits 1-0) */

	/* Automatic fan speed control registers */
	u8 auto_pwm[NUM_AUTO_PWM][4];	/* [nr][3] is hard-coded */
	s8 auto_temp[NUM_AUTO_PWM][5];	/* [nr][0] is point1_temp_hyst */
Linus Torvalds's avatar
Linus Torvalds committed
};
static int adc_lsb(const struct it87_data *data, int nr)
	int lsb;

	if (has_12mv_adc(data))
		lsb = 120;
	else if (has_10_9mv_adc(data))
		lsb = 109;
	else
		lsb = 160;
	if (data->in_scaled & BIT(nr))
static u8 in_to_reg(const struct it87_data *data, int nr, long val)
{
	val = DIV_ROUND_CLOSEST(val * 10, adc_lsb(data, nr));
	return clamp_val(val, 0, 255);
}

static int in_from_reg(const struct it87_data *data, int nr, int val)
{
	return DIV_ROUND_CLOSEST(val * adc_lsb(data, nr), 10);

static inline u8 FAN_TO_REG(long rpm, int div)
{
	if (rpm == 0)
		return 255;
	rpm = clamp_val(rpm, 1, 1000000);
	return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254);
}

static inline u16 FAN16_TO_REG(long rpm)
{
	if (rpm == 0)
		return 0xffff;
	return clamp_val((1350000 + rpm) / (rpm * 2), 1, 0xfffe);
}

#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : (val) == 255 ? 0 : \
				1350000 / ((val) * (div)))
/* The divider is fixed to 2 in 16-bit mode */
#define FAN16_FROM_REG(val) ((val) == 0 ? -1 : (val) == 0xffff ? 0 : \
			     1350000 / ((val) * 2))

#define TEMP_TO_REG(val) (clamp_val(((val) < 0 ? (((val) - 500) / 1000) : \
				    ((val) + 500) / 1000), -128, 127))
#define TEMP_FROM_REG(val) ((val) * 1000)

static u8 pwm_to_reg(const struct it87_data *data, long val)
{
	if (has_newer_autopwm(data))
		return val;
	else
		return val >> 1;
}

static int pwm_from_reg(const struct it87_data *data, u8 reg)
{
	if (has_newer_autopwm(data))
		return reg;
	else
		return (reg & 0x7f) << 1;
}

static int DIV_TO_REG(int val)
{
	int answer = 0;
	while (answer < 7 && (val >>= 1))
		answer++;
	return answer;
}

#define DIV_FROM_REG(val) BIT(val)
/*
 * PWM base frequencies. The frequency has to be divided by either 128 or 256,
 * depending on the chip type, to calculate the actual PWM frequency.
 *
 * Some of the chip datasheets suggest a base frequency of 51 kHz instead
 * of 750 kHz for the slowest base frequency, resulting in a PWM frequency
 * of 200 Hz. Sometimes both PWM frequency select registers are affected,
 * sometimes just one. It is unknown if this is a datasheet error or real,
 * so this is ignored for now.
 */
static const unsigned int pwm_freq[8] = {
	48000000,
	24000000,
	12000000,
	8000000,
	6000000,
	3000000,
	1500000,
	750000,
Linus Torvalds's avatar
Linus Torvalds committed

/*
 * Must be called with data->update_lock held, except during initialization.
 * We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks,
 * would slow down the IT87 access and should not be necessary.
 */
static int it87_read_value(struct it87_data *data, u8 reg)
{
	outb_p(reg, data->addr + IT87_ADDR_REG_OFFSET);
	return inb_p(data->addr + IT87_DATA_REG_OFFSET);
}

/*
 * Must be called with data->update_lock held, except during initialization.
 * We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks,
 * would slow down the IT87 access and should not be necessary.
 */
static void it87_write_value(struct it87_data *data, u8 reg, u8 value)
{
	outb_p(reg, data->addr + IT87_ADDR_REG_OFFSET);
	outb_p(value, data->addr + IT87_DATA_REG_OFFSET);
}

static void it87_update_pwm_ctrl(struct it87_data *data, int nr)
{
	data->pwm_ctrl[nr] = it87_read_value(data, IT87_REG_PWM[nr]);
	if (has_newer_autopwm(data)) {
		data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
		data->pwm_duty[nr] = it87_read_value(data,
						     IT87_REG_PWM_DUTY[nr]);
	} else {
		if (data->pwm_ctrl[nr] & 0x80)	/* Automatic mode */
			data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
		else				/* Manual mode */
			data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f;
	}
Linus Torvalds's avatar
Linus Torvalds committed

	if (has_old_autopwm(data)) {
		int i;
Linus Torvalds's avatar
Linus Torvalds committed

		for (i = 0; i < 5 ; i++)
			data->auto_temp[nr][i] = it87_read_value(data,
						IT87_REG_AUTO_TEMP(nr, i));
		for (i = 0; i < 3 ; i++)
			data->auto_pwm[nr][i] = it87_read_value(data,
						IT87_REG_AUTO_PWM(nr, i));
	} else if (has_newer_autopwm(data)) {
		int i;

		/*
		 * 0: temperature hysteresis (base + 5)
		 * 1: fan off temperature (base + 0)
		 * 2: fan start temperature (base + 1)
		 * 3: fan max temperature (base + 2)
		 */
		data->auto_temp[nr][0] =
			it87_read_value(data, IT87_REG_AUTO_TEMP(nr, 5));

		for (i = 0; i < 3 ; i++)
			data->auto_temp[nr][i + 1] =
				it87_read_value(data,
						IT87_REG_AUTO_TEMP(nr, i));
		/*
		 * 0: start pwm value (base + 3)
		 * 1: pwm slope (base + 4, 1/8th pwm)
		 */
		data->auto_pwm[nr][0] =
			it87_read_value(data, IT87_REG_AUTO_TEMP(nr, 3));
		data->auto_pwm[nr][1] =
			it87_read_value(data, IT87_REG_AUTO_TEMP(nr, 4));
Linus Torvalds's avatar
Linus Torvalds committed

static struct it87_data *it87_update_device(struct device *dev)
{
	struct it87_data *data = dev_get_drvdata(dev);
	int i;

	mutex_lock(&data->update_lock);

	if (time_after(jiffies, data->last_updated + HZ + HZ / 2) ||
	    !data->valid) {
		if (update_vbat) {
			/*
			 * Cleared after each update, so reenable.  Value
			 * returned by this read will be previous value
			 */
			it87_write_value(data, IT87_REG_CONFIG,
				it87_read_value(data, IT87_REG_CONFIG) | 0x40);
		}
		for (i = 0; i < NUM_VIN; i++) {
			if (!(data->has_in & BIT(i)))
				it87_read_value(data, IT87_REG_VIN[i]);

			/* VBAT and AVCC don't have limit registers */
			data->in[i][1] =
				it87_read_value(data, IT87_REG_VIN_MIN(i));
			data->in[i][2] =
				it87_read_value(data, IT87_REG_VIN_MAX(i));
		}

		for (i = 0; i < NUM_FAN; i++) {
			if (!(data->has_fan & BIT(i)))
				continue;

			data->fan[i][1] =
				it87_read_value(data, IT87_REG_FAN_MIN[i]);
			data->fan[i][0] = it87_read_value(data,
				       IT87_REG_FAN[i]);
			/* Add high byte if in 16-bit mode */
			if (has_16bit_fans(data)) {
				data->fan[i][0] |= it87_read_value(data,
						IT87_REG_FANX[i]) << 8;
				data->fan[i][1] |= it87_read_value(data,
						IT87_REG_FANX_MIN[i]) << 8;
			}
		}
		for (i = 0; i < NUM_TEMP; i++) {
			if (!(data->has_temp & BIT(i)))
				continue;
			data->temp[i][0] =
				it87_read_value(data, IT87_REG_TEMP(i));
			if (has_temp_offset(data) && i < NUM_TEMP_OFFSET)
				data->temp[i][3] =
				  it87_read_value(data,
						  IT87_REG_TEMP_OFFSET[i]);

			if (i >= NUM_TEMP_LIMIT)
			data->temp[i][1] =
				it87_read_value(data, IT87_REG_TEMP_LOW(i));
			data->temp[i][2] =
				it87_read_value(data, IT87_REG_TEMP_HIGH(i));
		}

		/* Newer chips don't have clock dividers */
		if ((data->has_fan & 0x07) && !has_16bit_fans(data)) {
			i = it87_read_value(data, IT87_REG_FAN_DIV);
			data->fan_div[0] = i & 0x07;
			data->fan_div[1] = (i >> 3) & 0x07;
			data->fan_div[2] = (i & 0x40) ? 3 : 1;
		}

		data->alarms =
			it87_read_value(data, IT87_REG_ALARM1) |
			(it87_read_value(data, IT87_REG_ALARM2) << 8) |
			(it87_read_value(data, IT87_REG_ALARM3) << 16);
		data->beeps = it87_read_value(data, IT87_REG_BEEP_ENABLE);

		data->fan_main_ctrl = it87_read_value(data,
				IT87_REG_FAN_MAIN_CTRL);
		data->fan_ctl = it87_read_value(data, IT87_REG_FAN_CTL);
		for (i = 0; i < NUM_PWM; i++) {
			if (!(data->has_pwm & BIT(i)))
				continue;
			it87_update_pwm_ctrl(data, i);

		data->sensor = it87_read_value(data, IT87_REG_TEMP_ENABLE);
		data->extra = it87_read_value(data, IT87_REG_TEMP_EXTRA);
		/*
		 * The IT8705F does not have VID capability.
		 * The IT8718F and later don't use IT87_REG_VID for the
		 * same purpose.
		 */
		if (data->type == it8712 || data->type == it8716) {
			data->vid = it87_read_value(data, IT87_REG_VID);
			/*
			 * The older IT8712F revisions had only 5 VID pins,
			 * but we assume it is always safe to read 6 bits.
			 */
			data->vid &= 0x3f;
		}
		data->last_updated = jiffies;
		data->valid = 1;
	}

	mutex_unlock(&data->update_lock);

	return data;
}
static ssize_t show_in(struct device *dev, struct device_attribute *attr,
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
	struct it87_data *data = it87_update_device(dev);
	int nr = sattr->nr;
	return sprintf(buf, "%d\n", in_from_reg(data, nr, data->in[nr][index]));
static ssize_t set_in(struct device *dev, struct device_attribute *attr,
		      const char *buf, size_t count)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
	struct it87_data *data = dev_get_drvdata(dev);
	int index = sattr->index;
	int nr = sattr->nr;
	if (kstrtoul(buf, 10, &val) < 0)
Linus Torvalds's avatar
Linus Torvalds committed

	mutex_lock(&data->update_lock);
	data->in[nr][index] = in_to_reg(data, nr, val);
	it87_write_value(data,
			 index == 1 ? IT87_REG_VIN_MIN(nr)
				    : IT87_REG_VIN_MAX(nr),
			 data->in[nr][index]);
	mutex_unlock(&data->update_lock);
Linus Torvalds's avatar
Linus Torvalds committed
	return count;
}
static SENSOR_DEVICE_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0);
static SENSOR_DEVICE_ATTR_2(in0_min, S_IRUGO | S_IWUSR, show_in, set_in,
			    0, 1);
static SENSOR_DEVICE_ATTR_2(in0_max, S_IRUGO | S_IWUSR, show_in, set_in,
			    0, 2);
static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 1, 0);
static SENSOR_DEVICE_ATTR_2(in1_min, S_IRUGO | S_IWUSR, show_in, set_in,
			    1, 1);
static SENSOR_DEVICE_ATTR_2(in1_max, S_IRUGO | S_IWUSR, show_in, set_in,
			    1, 2);
Linus Torvalds's avatar
Linus Torvalds committed

static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 2, 0);
static SENSOR_DEVICE_ATTR_2(in2_min, S_IRUGO | S_IWUSR, show_in, set_in,
			    2, 1);
static SENSOR_DEVICE_ATTR_2(in2_max, S_IRUGO | S_IWUSR, show_in, set_in,
			    2, 2);
Linus Torvalds's avatar
Linus Torvalds committed

static SENSOR_DEVICE_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 3, 0);
static SENSOR_DEVICE_ATTR_2(in3_min, S_IRUGO | S_IWUSR, show_in, set_in,
			    3, 1);
static SENSOR_DEVICE_ATTR_2(in3_max, S_IRUGO | S_IWUSR, show_in, set_in,
			    3, 2);

static SENSOR_DEVICE_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 4, 0);
static SENSOR_DEVICE_ATTR_2(in4_min, S_IRUGO | S_IWUSR, show_in, set_in,
			    4, 1);
static SENSOR_DEVICE_ATTR_2(in4_max, S_IRUGO | S_IWUSR, show_in, set_in,
			    4, 2);

static SENSOR_DEVICE_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 5, 0);
static SENSOR_DEVICE_ATTR_2(in5_min, S_IRUGO | S_IWUSR, show_in, set_in,
			    5, 1);
static SENSOR_DEVICE_ATTR_2(in5_max, S_IRUGO | S_IWUSR, show_in, set_in,
			    5, 2);

static SENSOR_DEVICE_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 6, 0);
static SENSOR_DEVICE_ATTR_2(in6_min, S_IRUGO | S_IWUSR, show_in, set_in,
			    6, 1);
static SENSOR_DEVICE_ATTR_2(in6_max, S_IRUGO | S_IWUSR, show_in, set_in,
			    6, 2);

static SENSOR_DEVICE_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 7, 0);
static SENSOR_DEVICE_ATTR_2(in7_min, S_IRUGO | S_IWUSR, show_in, set_in,
			    7, 1);
static SENSOR_DEVICE_ATTR_2(in7_max, S_IRUGO | S_IWUSR, show_in, set_in,
			    7, 2);

static SENSOR_DEVICE_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 8, 0);
static SENSOR_DEVICE_ATTR_2(in9_input, S_IRUGO, show_in, NULL, 9, 0);
static SENSOR_DEVICE_ATTR_2(in10_input, S_IRUGO, show_in, NULL, 10, 0);
static SENSOR_DEVICE_ATTR_2(in11_input, S_IRUGO, show_in, NULL, 11, 0);
static SENSOR_DEVICE_ATTR_2(in12_input, S_IRUGO, show_in, NULL, 12, 0);
Linus Torvalds's avatar
Linus Torvalds committed

static ssize_t show_temp(struct device *dev, struct device_attribute *attr,
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
	int nr = sattr->nr;
	int index = sattr->index;
Linus Torvalds's avatar
Linus Torvalds committed
	struct it87_data *data = it87_update_device(dev);
	return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[nr][index]));
Linus Torvalds's avatar
Linus Torvalds committed
}
static ssize_t set_temp(struct device *dev, struct device_attribute *attr,
			const char *buf, size_t count)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
	int nr = sattr->nr;
	int index = sattr->index;
	struct it87_data *data = dev_get_drvdata(dev);
	if (kstrtol(buf, 10, &val) < 0)
Linus Torvalds's avatar
Linus Torvalds committed

	mutex_lock(&data->update_lock);

	switch (index) {
	default:
	case 1:
		reg = IT87_REG_TEMP_LOW(nr);
		break;
	case 2:
		reg = IT87_REG_TEMP_HIGH(nr);
		break;
	case 3:
		regval = it87_read_value(data, IT87_REG_BEEP_ENABLE);
		if (!(regval & 0x80)) {
			regval |= 0x80;
			it87_write_value(data, IT87_REG_BEEP_ENABLE, regval);
		}
		data->valid = 0;
		reg = IT87_REG_TEMP_OFFSET[nr];
		break;
	}

	data->temp[nr][index] = TEMP_TO_REG(val);
	it87_write_value(data, reg, data->temp[nr][index]);
	mutex_unlock(&data->update_lock);
Linus Torvalds's avatar
Linus Torvalds committed
	return count;
}

static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0);
static SENSOR_DEVICE_ATTR_2(temp1_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
			    0, 1);
static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
			    0, 2);
static SENSOR_DEVICE_ATTR_2(temp1_offset, S_IRUGO | S_IWUSR, show_temp,
			    set_temp, 0, 3);
static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 1, 0);
static SENSOR_DEVICE_ATTR_2(temp2_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
			    1, 1);
static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
			    1, 2);
static SENSOR_DEVICE_ATTR_2(temp2_offset, S_IRUGO | S_IWUSR, show_temp,
			    set_temp, 1, 3);
static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 2, 0);
static SENSOR_DEVICE_ATTR_2(temp3_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
			    2, 1);
static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
			    2, 2);
static SENSOR_DEVICE_ATTR_2(temp3_offset, S_IRUGO | S_IWUSR, show_temp,
			    set_temp, 2, 3);
static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, show_temp, NULL, 3, 0);
static SENSOR_DEVICE_ATTR_2(temp5_input, S_IRUGO, show_temp, NULL, 4, 0);
static SENSOR_DEVICE_ATTR_2(temp6_input, S_IRUGO, show_temp, NULL, 5, 0);
Linus Torvalds's avatar
Linus Torvalds committed

static ssize_t show_temp_type(struct device *dev, struct device_attribute *attr,
			      char *buf)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);