[NEW DRIVER] nForce2 SMB support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



thank you very much.
checked in.

Hans-Frieder Vogt wrote:
> (resend because of errorneous sender identity in first e-mail)
> Hello,
> 
> I just wrote a driver for the SMBus (more precise: the SMBuses) in the nVidia
> nForce2 southbridge (based on the AMD-8111 driver). It runs both with version
> 2.6.5 and with CVS. Although it is not very well tested, it runs perfectly on
> my computer and I think it might be useful for others as well. Please include
> it in lm_sensors.
> 
> Cheers,
> Hans-Frieder
> --
> --
> Hans-Frieder Vogt                 e-mail: hfvogt at arcor.de
> 
> 
> ------------------------------------------------------------------------
> 
> Kernel driver `i2c-nforce2.o'
> 
> Status: Beta, although no problems found during one month usage with hwsensor
> 
> Supported adapters:
>     * nVidia nForce2 SMBus PCI interface (10de:0064, included in nForce2 MCP)
>       Datasheet: not publically available, but seems to be similar to the
>                  AMD-8111 SMBus 2.0 adapter.
> 
> Author: Hans-Frieder Vogt <hfvogt at arcor.de>
> 
> 
> Module Parameters
> -----------------
> 
> None.
> 
> 
> Description
> -----------
> 
> i2c-nforce2 is a driver for the SMBuses included in the nVidia nForce2 MCP.
> 
> If your 'lspci -v' listing shows something like the following,
> 
> 00:01.1 SMBus: nVidia Corporation: Unknown device 0064 (rev a2)
>         Subsystem: Asustek Computer, Inc.: Unknown device 0c11
>         Flags: 66Mhz, fast devsel, IRQ 5
>         I/O ports at c000 [size=32]
>         Capabilities: <available only to root>
> 
> then this driver should support the SMBuses of your motherboard.
> 
> 
> Notes
> -----
> 
> The SMBus adapter in the nForce2 chipset seems to be very similar to the
> SMBus 2.0 adapter in the AMD-8111 southbridge. However, I could only get the
> driver to work with direct I/O access, which is different to the EC interface
> of the AMD-8111.
> Tested on Asus A7N8X. The ACPI DSDT table of the Asus A7N8X lists two SMBuses,
> both of which are supported by this driver.
> 
> 
> ------------------------------------------------------------------------
> 
> /*
>     SMBus driver for nVidia nForce2 MCP
> 
>     Copyright (c) 2003  Hans-Frieder Vogt <hfvogt at arcor.de>,
>     Based on
>     SMBus 2.0 driver for AMD-8111 IO-Hub
>     Copyright (c) 2002 Vojtech Pavlik
> 
>     This program is free software; you can redistribute it and/or modify
>     it under the terms of the GNU General Public License as published by
>     the Free Software Foundation; either version 2 of the License, or
>     (at your option) any later version.
> 
>     This program is distributed in the hope that it will be useful,
>     but WITHOUT ANY WARRANTY; without even the implied warranty of
>     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     GNU General Public License for more details.
> 
>     You should have received a copy of the GNU General Public License
>     along with this program; if not, write to the Free Software
>     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> */
> 
> /*
>     SUPPORTED DEVICES	PCI ID
>     nForce2 MCP		0064       
> 
>     This driver supports the 2 SMBuses that are included in the MCP2 of the
>     nForce2 chipset.
> */
> 
> /* Note: we assume there can only be one nForce2, with two SMBus interfaces */
> 
> #include <linux/module.h>
> #include <linux/pci.h>
> #include <linux/kernel.h>
> #include <linux/stddef.h>
> #include <linux/sched.h>
> #include <linux/ioport.h>
> #include <linux/init.h>
> #include <linux/i2c.h>
> #include <linux/delay.h>
> #include <asm/io.h>
> #include "version.h"
> 
> #ifdef MODULE_LICENSE
> MODULE_LICENSE("GPL");
> #endif
> MODULE_AUTHOR ("Hans-Frieder Vogt <hfvogt at arcor.de>");
> MODULE_DESCRIPTION("nForce2 SMBus driver");
> 
> #ifndef PCI_DEVICE_ID_NVIDIA_NFORCE2_SMBUS
> #define PCI_DEVICE_ID_NVIDIA_NFORCE2_SMBUS   0x0064
> #endif
> /* TODO: sync with lm-sensors */
> #ifndef I2C_HW_SMBUS_NFORCE2
> #define I2C_HW_SMBUS_NFORCE2	0x0c
> #endif
> 
> 
> struct nforce2_smbus {
> 	struct pci_dev *dev;
> 	struct i2c_adapter adapter;
> 	int base;
> 	int size;
> };
> 
> 
> /*
>  * nVidia nForce2 SMBus control register definitions
>  */
> #define NFORCE_PCI_SMB1	0x50
> #define NFORCE_PCI_SMB2	0x54
> 
> 
> /*
>  * ACPI 2.0 chapter 13 SMBus 2.0 EC register model
>  */
> #define NVIDIA_SMB_PRTCL	(smbus->base + 0x00)	/* protocol, PEC */
> #define NVIDIA_SMB_STS		(smbus->base + 0x01)	/* status */
> #define NVIDIA_SMB_ADDR		(smbus->base + 0x02)	/* address */
> #define NVIDIA_SMB_CMD		(smbus->base + 0x03)	/* command */
> #define NVIDIA_SMB_DATA		(smbus->base + 0x04)	/* 32 data registers */
> #define NVIDIA_SMB_BCNT		(smbus->base + 0x24)	/* number of data bytes */
> #define NVIDIA_SMB_ALRM_A	(smbus->base + 0x25)	/* alarm address */
> #define NVIDIA_SMB_ALRM_D	(smbus->base + 0x26)	/* 2 bytes alarm data */
> 
> #define NVIDIA_SMB_STS_DONE	0x80
> #define NVIDIA_SMB_STS_ALRM	0x40
> #define NVIDIA_SMB_STS_RES	0x20
> #define NVIDIA_SMB_STS_STATUS	0x1f
> 
> #define NVIDIA_SMB_PRTCL_WRITE			0x00
> #define NVIDIA_SMB_PRTCL_READ			0x01
> #define NVIDIA_SMB_PRTCL_QUICK			0x02
> #define NVIDIA_SMB_PRTCL_BYTE			0x04
> #define NVIDIA_SMB_PRTCL_BYTE_DATA		0x06
> #define NVIDIA_SMB_PRTCL_WORD_DATA		0x08
> #define NVIDIA_SMB_PRTCL_BLOCK_DATA		0x0a
> #define NVIDIA_SMB_PRTCL_PROC_CALL		0x0c
> #define NVIDIA_SMB_PRTCL_BLOCK_PROC_CALL	0x0d
> #define NVIDIA_SMB_PRTCL_I2C_BLOCK_DATA		0x4a
> #define NVIDIA_SMB_PRTCL_PEC			0x80
> 
> 
> /* Other settings */
> #define MAX_TIMEOUT 256
> 
> 
> 
> static s32 nforce2_access(struct i2c_adapter *adap, u16 addr,
> 		       unsigned short flags, char read_write,
> 		       u8 command, int size, union i2c_smbus_data *data);
> static void nforce2_do_pause(unsigned int amount);
> /*
> static int nforce2_block_transaction(union i2c_smbus_data *data,
> 				  char read_write, int i2c_enable);
>  */
> static u32 nforce2_func(struct i2c_adapter *adapter);
> 
> 
> static struct i2c_algorithm smbus_algorithm = {
> 	.name = "Non-I2C SMBus adapter",
> 	.id = I2C_ALGO_SMBUS,
> 	.smbus_xfer = nforce2_access,
> 	.functionality = nforce2_func,
> };
> 
> 
> 
> /* Internally used pause function */
> void nforce2_do_pause(unsigned int amount)
> {
> 	current->state = TASK_INTERRUPTIBLE;
> 	schedule_timeout(amount);
> }
> 
> 
> /* Return -1 on error. See smbus.h for more information */
> s32 nforce2_access(struct i2c_adapter * adap, u16 addr, unsigned short flags,
> 		char read_write, u8 command, int size,
> 		union i2c_smbus_data * data)
> {
> 	struct nforce2_smbus *smbus = adap->algo_data;
> 	unsigned char protocol, len, pec, temp;
> 	int timeout = 0;
> 	int i;
> 
> 	protocol = (read_write == I2C_SMBUS_READ) ? NVIDIA_SMB_PRTCL_READ : NVIDIA_SMB_PRTCL_WRITE;
> 	pec = (flags & I2C_CLIENT_PEC) ? NVIDIA_SMB_PRTCL_PEC : 0;
> 
> 	switch (size) {
> 
> 		case I2C_SMBUS_QUICK:
> 			protocol |= NVIDIA_SMB_PRTCL_QUICK;
> 			read_write = I2C_SMBUS_WRITE;
> 			break;
> 
> 		case I2C_SMBUS_BYTE:
> 			if (read_write == I2C_SMBUS_WRITE)
> 				outb_p(data->byte, NVIDIA_SMB_DATA);
> 			protocol |= NVIDIA_SMB_PRTCL_BYTE;
> 			break;
> 
> 		case I2C_SMBUS_BYTE_DATA:
> 			outb_p(command, NVIDIA_SMB_CMD);
> 			if (read_write == I2C_SMBUS_WRITE)
> 				outb_p(data->byte, NVIDIA_SMB_DATA);
> 			protocol |= NVIDIA_SMB_PRTCL_BYTE_DATA;
> 			break;
> 
> 		case I2C_SMBUS_WORD_DATA:
> 			outb_p(command, NVIDIA_SMB_CMD);
> 			if (read_write == I2C_SMBUS_WRITE) {
> 				 outb_p(data->word, NVIDIA_SMB_DATA);
> 				 outb_p(data->word >> 8, NVIDIA_SMB_DATA+1);
> 			}
> 			protocol |= NVIDIA_SMB_PRTCL_WORD_DATA | pec;
> 			break;
> 
> 		case I2C_SMBUS_BLOCK_DATA:
> 			outb_p(command, NVIDIA_SMB_CMD);
> 			if (read_write == I2C_SMBUS_WRITE) {
> 				len = min_t(u8, data->block[0], 32);
> 				outb_p(len, NVIDIA_SMB_BCNT);
> 				for (i = 0; i < len; i++)
> 					outb_p(data->block[i + 1], NVIDIA_SMB_DATA+i);
> 			}
> 			protocol |= NVIDIA_SMB_PRTCL_BLOCK_DATA | pec;
> 			break;
> 
> 		case I2C_SMBUS_I2C_BLOCK_DATA:
> 			len = min_t(u8, data->block[0], 32);
> 			outb_p(command, NVIDIA_SMB_CMD);
> 			outb_p(len, NVIDIA_SMB_BCNT);
> 			if (read_write == I2C_SMBUS_WRITE)
> 				for (i = 0; i < len; i++)
> 					outb_p(data->block[i + 1], NVIDIA_SMB_DATA+i);
> 			protocol |= NVIDIA_SMB_PRTCL_I2C_BLOCK_DATA;
> 			break;
> 
> 		case I2C_SMBUS_PROC_CALL:
> 			printk(KERN_WARNING "i2c-nforce2.o: I2C_SMBUS_PROC_CALL not supported!\n");
> 			return -1;
> 			/*
> 			outb_p(command, NVIDIA_SMB_CMD);
> 			outb_p(data->word, NVIDIA_SMB_DATA);
> 			outb_p(data->word >> 8, NVIDIA_SMB_DATA + 1);
> 			protocol = NVIDIA_SMB_PRTCL_PROC_CALL | pec;
> 			read_write = I2C_SMBUS_READ;
> 			break;
> 			 */
> 
> 		case I2C_SMBUS_BLOCK_PROC_CALL:
> 			printk(KERN_WARNING "i2c-nforce2.o: I2C_SMBUS_BLOCK_PROC_CALL not supported!\n");
> 			return -1;
> 			/*
> 			protocol |= pec;
> 			len = min_t(u8, data->block[0], 31);
> 			outb_p(command, NVIDIA_SMB_CMD);
> 			outb_p(len, NVIDIA_SMB_BCNT);
> 			for (i = 0; i < len; i++)
> 				outb_p(data->block[i + 1], NVIDIA_SMB_DATA + i);
> 			protocol = NVIDIA_SMB_PRTCL_BLOCK_PROC_CALL | pec;
> 			read_write = I2C_SMBUS_READ;
> 			break;
> 			*/
> 
> 		case I2C_SMBUS_WORD_DATA_PEC:
> 		case I2C_SMBUS_BLOCK_DATA_PEC:
> 		case I2C_SMBUS_PROC_CALL_PEC:
> 		case I2C_SMBUS_BLOCK_PROC_CALL_PEC:
> 			printk(KERN_WARNING "i2c-nforce2.c: Unexpected software PEC transaction %d\n.", size);
> 			return -1;
> 
> 		default:
> 			printk(KERN_WARNING "i2c-nforce2.c: Unsupported transaction %d\n", size);
> 			return -1;
> 	}
> 
> 	outb_p((addr & 0x7f) << 1, NVIDIA_SMB_ADDR);
> 	outb_p(protocol, NVIDIA_SMB_PRTCL);
> 
> 	temp = inb_p(NVIDIA_SMB_STS);
> 
> #if 0
> 	do {
> 		nforce2_do_pause(1);
> 		temp = inb_p(NVIDIA_SMB_STS);
> 	} while (((temp & NVIDIA_SMB_STS_DONE) == 0) && (timeout++ < MAX_TIMEOUT));
> #endif
> 	if (~temp & NVIDIA_SMB_STS_DONE) {
> 		udelay(500);
> 		temp = inb_p(NVIDIA_SMB_STS);
> 	}
> 	if (~temp & NVIDIA_SMB_STS_DONE) {
> 		current->state = TASK_INTERRUPTIBLE;
> 		schedule_timeout(HZ/100);
> 		temp = inb_p(NVIDIA_SMB_STS);
> 	}
> 
> 	if ((timeout >= MAX_TIMEOUT) || (~temp & NVIDIA_SMB_STS_DONE) || (temp & NVIDIA_SMB_STS_STATUS))
> 		return -1;
> 
> 	if (read_write == I2C_SMBUS_WRITE)
> 		return 0;
> 
> 	switch (size) {
> 
> 		case I2C_SMBUS_BYTE:
> 		case I2C_SMBUS_BYTE_DATA:
> 			data->byte = inb_p(NVIDIA_SMB_DATA);
> 			break;
> 
> 		case I2C_SMBUS_WORD_DATA:
> 		case I2C_SMBUS_PROC_CALL:
> 			data->word = inb_p(NVIDIA_SMB_DATA) | (inb_p(NVIDIA_SMB_DATA+1) << 8);
> 			break;
> 
> 		case I2C_SMBUS_BLOCK_DATA:
> 		case I2C_SMBUS_BLOCK_PROC_CALL:
> 			len = inb_p(NVIDIA_SMB_BCNT);
> 			len = min_t(u8, len, 32);
> 		case I2C_SMBUS_I2C_BLOCK_DATA:
> 			for (i = 0; i < len; i++)
> 				data->block[i+1] = inb_p(NVIDIA_SMB_DATA + i);
> 			data->block[0] = len;
> 			break;
> 	}
> 
> 	return 0;
> }
> 
> 
> u32 nforce2_func(struct i2c_adapter *adapter)
> {
> 	/* other functionality might be possible, but is not tested */
> 	return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
> 	    I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA /* |
> 	    I2C_FUNC_SMBUS_BLOCK_DATA */;
> }
> 
> 
> static struct pci_device_id nforce2_ids[] __devinitdata = {
> 	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE2_SMBUS,
> 	       	PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
> 	{ 0 }
> };
> 
> 
> static int __devinit nforce2_probe_smb (struct pci_dev *dev, int reg, struct nforce2_smbus *smbus, char *name)
> {
> 	u16 iobase;
> 	int error;
> 
> 	if (pci_read_config_word(dev, reg, &iobase) != PCIBIOS_SUCCESSFUL) {
> 		printk (KERN_ERR "i2c-nforce2.o: Error reading PCI config for %s\n", name);
> 		return -1;
> 	}
> 	smbus->dev  = dev;
> 	smbus->base = iobase & 0xfffc;
> 	smbus->size = 8;
> 
> 	if (!request_region(smbus->base, smbus->size, "nForce2 SMBus")) {
> 		printk (KERN_ERR "i2c-nforce2.o: Error requesting region %02x .. %02X for %s\n", smbus->base, smbus->base+smbus->size-1, name);
> 		return -1;
> 	}
> 
> 	/* TODO: find a better way to find out whether this file is compiled
> 	 * with i2c 2.7.0 of earlier
> 	 */
> #ifdef I2C_HW_SMBUS_AMD8111
> 	smbus->adapter.owner = THIS_MODULE;
> #endif
> 	sprintf(smbus->adapter.name, "SMBus nForce2 adapter at %04x", smbus->base);
> 	smbus->adapter.id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_NFORCE2;
> 	smbus->adapter.algo = &smbus_algorithm;
> 	smbus->adapter.algo_data = smbus;
> 
> 	error = i2c_add_adapter(&smbus->adapter);
> 	if (error) {
> 		printk(KERN_WARNING "i2c-nforce2.o: Failed to register adapter.\n");
> 		release_region(smbus->base, smbus->size);
> 		return -1;
> 	}
> 	printk(KERN_INFO "i2c-nforce2.o: nForce2 SMBus adapter at %#x\n", smbus->base);
> 	return 0;
> }
> 
> 
> static int __devinit nforce2_probe(struct pci_dev *dev, const struct pci_device_id *id)
> {
> 	struct nforce2_smbus *smbuses;
> 	int res1, res2;
> 
> 	/* we support 2 SMBus adapters */
> 	if (!(smbuses = (void *)kmalloc(2*sizeof(struct nforce2_smbus),
> 				       	GFP_KERNEL)))
> 		return -ENOMEM;
> 	memset (smbuses, 0, 2*sizeof(struct nforce2_smbus));
> 	pci_set_drvdata(dev, smbuses);
> 
> 	/* SMBus adapter 1 */
> 	res1 = nforce2_probe_smb (dev, NFORCE_PCI_SMB1, &smbuses[0], "SMB1");
> 	if (res1 < 0) {
> 		printk (KERN_ERR "i2c-nforce2.o: Error probing SMB1.\n");
> 		smbuses[0].base = 0;	/* to have a check value */
> 	}
> 	res2 = nforce2_probe_smb (dev, NFORCE_PCI_SMB2, &smbuses[1], "SMB2");
> 	if (res2 < 0) {
> 		printk (KERN_ERR "i2c-nforce2.o: Error probing SMB2.\n");
> 		smbuses[1].base = 0;	/* to have a check value */
> 	}
> 	if ((res1 < 0) && (res2 < 0)) {
> 		/* we did not find even one of the SMBuses, so we give up */
> 		kfree(smbuses);
> 		return -ENODEV;
> 	}
> 
> 	return 0;
> }
> 
> 
> static void __devexit nforce2_remove(struct pci_dev *dev)
> {
> 	int res;
> 	struct nforce2_smbus *smbuses = (void*) pci_get_drvdata(dev);
> 
> 	if (smbuses[0].base) {
> 		i2c_del_adapter(&smbuses[0].adapter);
> 		release_region(smbuses[0].base, smbuses[0].size);
> 	}
> 	if (smbuses[1].base) {
> 		i2c_del_adapter(&smbuses[1].adapter);
> 		release_region(smbuses[1].base, smbuses[1].size);
> 	}
> 	kfree(smbuses);
> }
> 
> static struct pci_driver nforce2_driver = {
> 	.name		= "nForce2 SMBus",
> 	.id_table	= nforce2_ids,
> 	.probe		= nforce2_probe,
> 	.remove		= __devexit_p(nforce2_remove),
> };
> 
> int __init nforce2_init(void)
> {
> 	printk(KERN_INFO "i2c-nforce2.o version %s (%s)\n", LM_VERSION, LM_DATE);
> 	return pci_module_init(&nforce2_driver);
> }
> 
> void __exit nforce2_exit(void)
> {
> 	pci_unregister_driver(&nforce2_driver);
> }
> 
> module_init(nforce2_init);
> module_exit(nforce2_exit);
> 



[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux