From 1238d9ab447a2e069a73de03af08501dcec5da23 Mon Sep 17 00:00:00 2001 From: Frodo Looijaard Date: Wed, 1 Dec 1999 00:33:33 +0000 Subject: [PATCH] New i2c-amd756 driver by Merlin Hughes sensors-detect detection is now complete; I think nothing else needs to be done in the tree?!? (except documentation of course) git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@643 7894878c-1315-0410-8ee3-d5d059ff63e0 --- kernel/busses/Module.mk | 3 +- kernel/busses/i2c-amd756.c | 526 +++++++++++++++++++++++++++++++++++++ prog/detect/sensors-detect | 3 +- 3 files changed, 530 insertions(+), 2 deletions(-) create mode 100644 kernel/busses/i2c-amd756.c diff --git a/kernel/busses/Module.mk b/kernel/busses/Module.mk index 41aaf414..45638fc2 100644 --- a/kernel/busses/Module.mk +++ b/kernel/busses/Module.mk @@ -25,7 +25,8 @@ KERNELBUSSESDIR := $(MODULE_DIR) # defined value verbatim into the command-list of rules... KERNELBUSSESTARGETS := $(MODULE_DIR)/i2c-i801.o \ $(MODULE_DIR)/i2c-viapro.o \ - $(MODULE_DIR)/i2c-voodoo3.o + $(MODULE_DIR)/i2c-voodoo3.o \ + $(MODULE_DIR)/i2c-amd756.o ifneq ($(shell if grep -q '^CONFIG_I2C_ALI15X3=y' $(LINUX)/.config; then echo 1; fi),1) KERNELBUSSESTARGETS += $(MODULE_DIR)/i2c-ali15x3.o endif diff --git a/kernel/busses/i2c-amd756.c b/kernel/busses/i2c-amd756.c new file mode 100644 index 00000000..f8e0488d --- /dev/null +++ b/kernel/busses/i2c-amd756.c @@ -0,0 +1,526 @@ +/* + amd756.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + + Copyright (c) 1999 Merlin Hughes + + Shamelessly ripped from i2c-piix4.c: + + Copyright (c) 1998, 1999 Frodo Looijaard and + Philip Edelbrock + + 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. +*/ + +/* Note: we assume there can only be one AMD756, with one SMBus interface */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "version.h" +#include "compat.h" + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,1,54)) +#include +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,53) +#include +#else +#define __init +#define __initdata +#endif + +/* AMD756 SMBus address offsets */ +#define SMB_GLOBAL_STATUS (0xE0 + amd756_smba) +#define SMB_GLOBAL_ENABLE (0xE2 + amd756_smba) +#define SMB_HOST_ADDRESS (0xE4 + amd756_smba) +#define SMB_HOST_DATA (0xE6 + amd756_smba) +#define SMB_HOST_COMMAND (0xE8 + amd756_smba) +#define SMB_HOST_BLOCK_DATA (0xE9 + amd756_smba) +#define SMB_HAS_DATA (0xEA + amd756_smba) +#define SMB_HAS_DEVICE_ADDRESS (0xEC + amd756_smba) +#define SMB_HAS_HOST_ADDRESS (0xEE + amd756_smba) +#define SMB_SNOOP_ADDRESS (0xEF + amd756_smba) + +/* PCI Address Constants */ + +/* address of I/O space */ +#define SMBBA 0x058 /* mh */ + +/* general configuration */ +#define SMBGCFG 0x041 /* mh */ + +/* silicon revision code */ +#define SMBREV 0x008 + +#define SMBSLVC 0x0D3 +#define SMBSHDW1 0x0D4 +#define SMBSHDW2 0x0D5 + +/* Other settings */ +#define MAX_TIMEOUT 500 +#define ENABLE_INT9 0 + +/* AMD756 constants */ +#define AMD756_QUICK 0x00 +#define AMD756_BYTE 0x01 +#define AMD756_BYTE_DATA 0x02 +#define AMD756_WORD_DATA 0x03 +#define AMD756_PROCESS_CALL 0x04 +#define AMD756_BLOCK_DATA 0x05 + +/* insmod parameters */ + +#ifdef MODULE +static +#else +extern +#endif + int __init i2c_amd756_init(void); +static int __init amd756_cleanup(void); +static int amd756_setup(void); +static s32 amd756_access(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data * data); +static void amd756_do_pause( unsigned int amount ); +static int amd756_transaction(void); +static void amd756_inc(struct i2c_adapter *adapter); +static void amd756_dec(struct i2c_adapter *adapter); +static u32 amd756_func(struct i2c_adapter *adapter); + +#ifdef MODULE +extern int init_module(void); +extern int cleanup_module(void); +#endif /* MODULE */ + +static struct i2c_algorithm smbus_algorithm = { + /* name */ "Non-I2C SMBus adapter", + /* id */ I2C_ALGO_SMBUS, + /* master_xfer */ NULL, + /* smbus_access */ amd756_access, + /* slave_send */ NULL, + /* slave_rcv */ NULL, + /* algo_control */ NULL, + /* functionality */ amd756_func, +}; + +/* externalize it */ +#define I2C_HW_SMBUS_AMD756 0x05 + +static struct i2c_adapter amd756_adapter = { + "unset", + I2C_ALGO_SMBUS | I2C_HW_SMBUS_AMD756, + &smbus_algorithm, + NULL, + amd756_inc, + amd756_dec, + NULL, + NULL, +}; + +static int __initdata amd756_initialized; +static unsigned short amd756_smba = 0; + +/* externalize it */ +#define PCI_DEVICE_ID_AMD_756 0x740B + +/* Detect whether a AMD756 can be found, and initialize it, where necessary. + Note the differences between kernels with the old PCI BIOS interface and + newer kernels with the real PCI interface. In compat.h some things are + defined to make the transition easier. */ +int amd756_setup(void) +{ + int error_return=0; + unsigned char temp; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,54)) + struct pci_dev *AMD756_dev; +#else + unsigned char AMD756_bus, AMD756_devfn; + int i,res; +#endif + + /* First check whether we can access PCI at all */ + if (pci_present() == 0) { + printk("i2c-amd756.o: Error: No PCI-bus found!\n"); + error_return=-ENODEV; + goto END; + } + + /* Look for the AMD756, function 3 */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,54)) + /* Note: we keep on searching until we have found 'function 3' */ + AMD756_dev = NULL; + do + AMD756_dev = pci_find_device(PCI_VENDOR_ID_AMD, + PCI_DEVICE_ID_AMD_756, AMD756_dev); + while (AMD756_dev && (PCI_FUNC(AMD756_dev->devfn) != 3)); + if(AMD756_dev == NULL) { +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,1,54) */ + for (i = 0; + ! (res = pcibios_find_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_82371AB_3, + i,&AMD756_bus, &AMD756_devfn)) && + PCI_FUNC(AMD756_devfn) != 3; + i++); + + if (res) { +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,54) */ + printk("i2c-amd756.o: Error: Can't detect AMD756, function 3!\n"); + error_return=-ENODEV; + goto END; + } + +/* Determine the address of the SMBus areas */ + + /* Technically it is a dword but... */ + pci_read_config_word_united(AMD756_dev, AMD756_bus, AMD756_devfn, + SMBBA, &amd756_smba); + amd756_smba &= 0xfff0; + + if (check_region(amd756_smba, 8)) { + printk("i2c-amd756.o: AMD756_smb region 0x%x already in use!\n", amd756_smba); + error_return=-ENODEV; + goto END; + } + + pci_read_config_byte_united(AMD756_dev, AMD756_bus, AMD756_devfn, + SMBGCFG, &temp); + + if ((temp & 128) == 0) { + printk("SMBUS: Error: Host SMBus controller I/O not enabled!\n"); + error_return=-ENODEV; + goto END; + } + + /* Everything is happy, let's grab the memory and set things up. */ + request_region(amd756_smba, 8, "amd756-smbus"); + +#ifdef DEBUG + /* + if ((temp & 0x0E) == 8) + printk("i2c-amd756.o: AMD756 using Interrupt 9 for SMBus.\n"); + else if ((temp & 0x0E) == 0) + printk("i2c-amd756.o: AMD756 using Interrupt SMI# for SMBus.\n"); + else + printk("i2c-amd756.o: AMD756: Illegal Interrupt configuration (or code out " + "of date)!\n"); + */ + + pci_read_config_byte_united(AMD756_dev, AMD756_bus, AMD756_devfn, SMBREV, + &temp); + printk("i2c-amd756.o: SMBREV = 0x%X\n",temp); + printk("i2c-amd756.o: AMD756_smba = 0x%X\n",amd756_smba); +#endif /* DEBUG */ + +END: + return error_return; +} + +/* + SMBUS event = I/O 28-29 bit 11 + see E0 for the status bits and enabled in E2 + +*/ + +/* Internally used pause function */ +void amd756_do_pause( unsigned int amount ) +{ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(amount); +} + +#define GS_ABRT_STS (1 << 0) +#define GS_COL_STS (1 << 1) +#define GS_PRERR_STS (1 << 2) +#define GS_HST_STS (1 << 3) +#define GS_HCYC_STS (1 << 4) +#define GS_TO_STS (1 << 5) + +#define GS_CLEAR_STS (GS_ABRT_STS | GS_COL_STS | GS_PRERR_STS | \ + GS_HCYC_STS | GS_TO_STS ) + +#define GE_CYC_TYPE_MASK (7) +#define GE_HOST_STC (1 << 3) + +/* Another internally used function */ +int amd756_transaction(void) +{ + int temp; + int result=0; + int timeout=0; + +#ifdef DEBUG + printk("i2c-amd756.o: Transaction (pre): GS=%04x, GE=%04x, ADD=%04x, DAT=%04x\n", + inw_p(SMB_GLOBAL_STATUS),inw_p(SMB_GLOBAL_ENABLE),inw_p(SMB_HOST_ADDRESS),inb_p(SMB_HOST_DATA)); +#endif + + /* Make sure the SMBus host is ready to start transmitting */ + /* TODO: What about SM_BSY? */ + if (((temp = inw_p(SMB_GLOBAL_STATUS)) & GS_HST_STS) != 0x00) { +#ifdef DEBUG + printk("i2c-amd756.o: SMBus busy (%04x). Resetting (NOT)... \n",temp); +#endif + /* TODO: How to reset the AMD? + outb_p(temp, SMBHSTSTS); + */ + if (((temp = inw_p(SMB_GLOBAL_STATUS)) & GS_HST_STS) != 0x00) { +#ifdef DEBUG + printk("i2c-amd756.o: Failed! (%04x)\n",temp); +#endif + return -1; + } else { +#ifdef DEBUG + printk("i2c-amd756.o: Successfull!\n"); +#endif + } + } + + /* start the transaction by setting bit 6 */ + outw_p(inw(SMB_GLOBAL_ENABLE) | GE_HOST_STC, SMB_GLOBAL_ENABLE); + + /* We will always wait for a fraction of a second! (See AMD756 docs errata) */ + do { + amd756_do_pause(1); + temp=inw_p(SMB_GLOBAL_STATUS); + } while ((temp & GS_HST_STS) && (timeout++ < MAX_TIMEOUT)); + + /* If the SMBus is still busy, we give up */ + if (timeout >= MAX_TIMEOUT) { +#ifdef DEBUG + printk("i2c-amd756.o: SMBus explicit timeout!\n"); + result = -1; +#endif + } + + if (temp & GS_PRERR_STS) { + result = -1; +#ifdef DEBUG + printk("i2c-amd756.o: SMBus Protocol error!\n"); +#endif + } + + if (temp & GS_COL_STS) { + result = -1; + printk("i2c-amd756.o: SMBus collision!\n"); + /* TODO: Clear Collision Status with a 1 */ + } + + if (temp & GS_TO_STS) { + result = -1; +#ifdef DEBUG + printk("i2c-amd756.o: SMBus protocol timeout!\n"); +#endif + } + +#ifdef DEBUG + if (temp & GS_HCYC_STS) { + printk("i2c-amd756.o: SMBus protocol success!\n"); + } +#endif + + outw_p (GS_CLEAR_STS, SMB_GLOBAL_STATUS); + + if (((temp = inw_p (SMB_GLOBAL_STATUS)) & GS_CLEAR_STS) != 0x00) { +#ifdef DEBUG + printk("i2c-amd756.o: Failed reset at end of transaction (%04x)\n",temp); +#endif + } + +#ifdef DEBUG + printk("i2c-amd756.o: Transaction (post): GS=%04x, GE=%04x, ADD=%04x, DAT=%04x\n", + inw_p(SMB_GLOBAL_STATUS),inw_p(SMB_GLOBAL_ENABLE),inw_p(SMB_HOST_ADDRESS),inb_p(SMB_HOST_DATA)); +#endif + + return result; +} + +/* Return -1 on error. See smbus.h for more information */ +s32 amd756_access(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data * data) +{ + int i,len; + + /** TODO: Should I supporte the 10-bit transfers? */ + switch(size) { + case I2C_SMBUS_PROC_CALL: + printk("i2c-amd756.o: I2C_SMBUS_PROC_CALL not supported!\n"); + /* TODO: Well... It is supported, I'm just not sure what to do here... */ + return -1; + case I2C_SMBUS_QUICK: + outw_p (((addr & 0x7f) << 1) | (read_write & 0x01), SMB_HOST_ADDRESS); + size = AMD756_QUICK; + break; + case I2C_SMBUS_BYTE: + outw_p (((addr & 0x7f) << 1) | (read_write & 0x01), SMB_HOST_ADDRESS); + /* TODO: Why only during write? */ + if (read_write == I2C_SMBUS_WRITE) + outb_p (command, SMB_HOST_COMMAND); + size = AMD756_BYTE; + break; + case I2C_SMBUS_BYTE_DATA: + outw_p (((addr & 0x7f) << 1) | (read_write & 0x01), SMB_HOST_ADDRESS); + outb_p (command, SMB_HOST_COMMAND); + if (read_write == I2C_SMBUS_WRITE) + outw_p(data->byte, SMB_HOST_DATA); + size = AMD756_BYTE_DATA; + break; + case I2C_SMBUS_WORD_DATA: + outw_p (((addr & 0x7f) << 1) | (read_write & 0x01), SMB_HOST_ADDRESS); + outb_p (command, SMB_HOST_COMMAND); + if (read_write == I2C_SMBUS_WRITE) + outw_p (data->word, SMB_HOST_DATA); /* TODO: endian???? */ + size = AMD756_WORD_DATA; + break; + case I2C_SMBUS_BLOCK_DATA: + outw_p (((addr & 0x7f) << 1) | (read_write & 0x01), SMB_HOST_ADDRESS); + outb_p (command, SMB_HOST_COMMAND); + if (read_write == I2C_SMBUS_WRITE) { + len = data->block[0]; + if (len < 0) + len = 0; + if (len > 32) + len = 32; + outw_p (len, SMB_HOST_DATA); + /* i = inw_p(SMBHSTCNT); Reset SMBBLKDAT */ + for (i = 1; i <= len; i ++) + outb_p (data->block[i], SMB_HOST_BLOCK_DATA); + } + size = AMD756_BLOCK_DATA; + break; + } + + /* How about enabling interrupts... */ + outw_p (size & GE_CYC_TYPE_MASK, SMB_GLOBAL_ENABLE); + + if (amd756_transaction()) /* Error in transaction */ + return -1; + + if ((read_write == I2C_SMBUS_WRITE) || (size == AMD756_QUICK)) + return 0; + + + switch(size) { + case AMD756_BYTE: + data->byte = inw_p (SMB_HOST_DATA); + break; + case AMD756_BYTE_DATA: + data->byte = inw_p (SMB_HOST_DATA); + break; + case AMD756_WORD_DATA: + data->word = inw_p (SMB_HOST_DATA); /* TODO: endian???? */ + break; + case AMD756_BLOCK_DATA: + data->block[0] = inw_p (SMB_HOST_DATA & 63); + /* i = inw_p(SMBHSTCNT); Reset SMBBLKDAT */ + for (i = 1; i <= data->block[0]; i++) + data->block[i] = inb_p (SMB_HOST_BLOCK_DATA); + break; + } + + return 0; +} + +void amd756_inc(struct i2c_adapter *adapter) +{ + MOD_INC_USE_COUNT; +} + +void amd756_dec(struct i2c_adapter *adapter) +{ + + MOD_DEC_USE_COUNT; +} + +u32 amd756_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_PROC_CALL; +} + +int __init i2c_amd756_init(void) +{ + int res; + printk("amd756.o version %s (%s)\n",LM_VERSION,LM_DATE); +#ifdef DEBUG +/* PE- It might be good to make this a permanent part of the code! */ + if (amd756_initialized) { + printk("i2c-amd756.o: Oops, amd756_init called a second time!\n"); + return -EBUSY; + } +#endif + amd756_initialized = 0; + if ((res = amd756_setup())) { + printk("i2c-amd756.o: AMD756 not detected, module not inserted.\n"); + amd756_cleanup(); + return res; + } + amd756_initialized ++; + sprintf(amd756_adapter.name,"SMBus AMD756 adapter at %04x",amd756_smba); + if ((res = i2c_add_adapter(&amd756_adapter))) { + printk("i2c-amd756.o: Adapter registration failed, module not inserted.\n"); + amd756_cleanup(); + return res; + } + amd756_initialized++; + printk("i2c-amd756.o: AMD756 bus detected and initialized\n"); + return 0; +} + +int __init amd756_cleanup(void) +{ + int res; + if (amd756_initialized >= 2) + { + if ((res = i2c_del_adapter(&amd756_adapter))) { + printk("i2c-amd756.o: i2c_del_adapter failed, module not removed\n"); + return res; + } else + amd756_initialized--; + } + if (amd756_initialized >= 1) { + release_region(amd756_smba, 8); + amd756_initialized--; + } + return 0; +} + +EXPORT_NO_SYMBOLS; + +#ifdef MODULE + +MODULE_AUTHOR("Merlni Hughes "); +MODULE_DESCRIPTION("AMD756 SMBus driver"); + +int init_module(void) +{ + return i2c_amd756_init(); +} + +int cleanup_module(void) +{ + return amd756_cleanup(); +} + +#endif /* MODULE */ + diff --git a/prog/detect/sensors-detect b/prog/detect/sensors-detect index e423b8d6..f3ab749d 100755 --- a/prog/detect/sensors-detect +++ b/prog/detect/sensors-detect @@ -124,7 +124,8 @@ use vars qw(@pci_adapters @chip_ids @undetectable_adapters); devid => 0x740b, func => 3, procid => "AMD-756 Athlon ACPI", -# driver to be written + driver => "i2c-amd756", + match => sub { $_[0] =~ /^SMBus AMD756 adapter at [0-9,a-f]{4}/ }, }, );