123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- /*
- Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and
- Philip Edelbrock <phil@netroedge.com>
- 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.
- */
- /* Note: we assume there can only be one SIS5595 with one SMBus interface */
- /*
- Note: all have mfr. ID 0x1039.
- SUPPORTED PCI ID
- 5595 0008
- Note: these chips contain a 0008 device which is incompatible with the
- 5595. We recognize these by the presence of the listed
- "blacklist" PCI ID and refuse to load.
- NOT SUPPORTED PCI ID BLACKLIST PCI ID
- 540 0008 0540
- 550 0008 0550
- 5513 0008 5511
- 5581 0008 5597
- 5582 0008 5597
- 5597 0008 5597
- 5598 0008 5597/5598
- 630 0008 0630
- 645 0008 0645
- 646 0008 0646
- 648 0008 0648
- 650 0008 0650
- 651 0008 0651
- 730 0008 0730
- 735 0008 0735
- 745 0008 0745
- 746 0008 0746
- */
- /* TO DO:
- * Add Block Transfers (ugly, but supported by the adapter)
- * Add adapter resets
- */
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/delay.h>
- #include <linux/pci.h>
- #include <linux/ioport.h>
- #include <linux/init.h>
- #include <linux/i2c.h>
- #include <linux/acpi.h>
- #include <linux/io.h>
- static int blacklist[] = {
- PCI_DEVICE_ID_SI_540,
- PCI_DEVICE_ID_SI_550,
- PCI_DEVICE_ID_SI_630,
- PCI_DEVICE_ID_SI_645,
- PCI_DEVICE_ID_SI_646,
- PCI_DEVICE_ID_SI_648,
- PCI_DEVICE_ID_SI_650,
- PCI_DEVICE_ID_SI_651,
- PCI_DEVICE_ID_SI_730,
- PCI_DEVICE_ID_SI_735,
- PCI_DEVICE_ID_SI_745,
- PCI_DEVICE_ID_SI_746,
- PCI_DEVICE_ID_SI_5511, /* 5513 chip has the 0008 device but that ID
- shows up in other chips so we use the 5511
- ID for recognition */
- PCI_DEVICE_ID_SI_5597,
- PCI_DEVICE_ID_SI_5598,
- 0, /* terminates the list */
- };
- /* Length of ISA address segment */
- #define SIS5595_EXTENT 8
- /* SIS5595 SMBus registers */
- #define SMB_STS_LO 0x00
- #define SMB_STS_HI 0x01
- #define SMB_CTL_LO 0x02
- #define SMB_CTL_HI 0x03
- #define SMB_ADDR 0x04
- #define SMB_CMD 0x05
- #define SMB_PCNT 0x06
- #define SMB_CNT 0x07
- #define SMB_BYTE 0x08
- #define SMB_DEV 0x10
- #define SMB_DB0 0x11
- #define SMB_DB1 0x12
- #define SMB_HAA 0x13
- /* PCI Address Constants */
- #define SMB_INDEX 0x38
- #define SMB_DAT 0x39
- #define SIS5595_ENABLE_REG 0x40
- #define ACPI_BASE 0x90
- /* Other settings */
- #define MAX_TIMEOUT 500
- /* SIS5595 constants */
- #define SIS5595_QUICK 0x00
- #define SIS5595_BYTE 0x02
- #define SIS5595_BYTE_DATA 0x04
- #define SIS5595_WORD_DATA 0x06
- #define SIS5595_PROC_CALL 0x08
- #define SIS5595_BLOCK_DATA 0x0A
- /* insmod parameters */
- /* If force_addr is set to anything different from 0, we forcibly enable
- the device at the given address. */
- static u16 force_addr;
- module_param(force_addr, ushort, 0);
- MODULE_PARM_DESC(force_addr, "Initialize the base address of the i2c controller");
- static struct pci_driver sis5595_driver;
- static unsigned short sis5595_base;
- static struct pci_dev *sis5595_pdev;
- static u8 sis5595_read(u8 reg)
- {
- outb(reg, sis5595_base + SMB_INDEX);
- return inb(sis5595_base + SMB_DAT);
- }
- static void sis5595_write(u8 reg, u8 data)
- {
- outb(reg, sis5595_base + SMB_INDEX);
- outb(data, sis5595_base + SMB_DAT);
- }
- static int sis5595_setup(struct pci_dev *SIS5595_dev)
- {
- u16 a;
- u8 val;
- int *i;
- int retval;
- /* Look for imposters */
- for (i = blacklist; *i != 0; i++) {
- struct pci_dev *dev;
- dev = pci_get_device(PCI_VENDOR_ID_SI, *i, NULL);
- if (dev) {
- dev_err(&SIS5595_dev->dev, "Looked for SIS5595 but found unsupported device %.4x\n", *i);
- pci_dev_put(dev);
- return -ENODEV;
- }
- }
- /* Determine the address of the SMBus areas */
- pci_read_config_word(SIS5595_dev, ACPI_BASE, &sis5595_base);
- if (sis5595_base == 0 && force_addr == 0) {
- dev_err(&SIS5595_dev->dev, "ACPI base address uninitialized - upgrade BIOS or use force_addr=0xaddr\n");
- return -ENODEV;
- }
- if (force_addr)
- sis5595_base = force_addr & ~(SIS5595_EXTENT - 1);
- dev_dbg(&SIS5595_dev->dev, "ACPI Base address: %04x\n", sis5595_base);
- /* NB: We grab just the two SMBus registers here, but this may still
- * interfere with ACPI :-( */
- retval = acpi_check_region(sis5595_base + SMB_INDEX, 2,
- sis5595_driver.name);
- if (retval)
- return retval;
- if (!request_region(sis5595_base + SMB_INDEX, 2,
- sis5595_driver.name)) {
- dev_err(&SIS5595_dev->dev, "SMBus registers 0x%04x-0x%04x already in use!\n",
- sis5595_base + SMB_INDEX, sis5595_base + SMB_INDEX + 1);
- return -ENODEV;
- }
- if (force_addr) {
- dev_info(&SIS5595_dev->dev, "forcing ISA address 0x%04X\n", sis5595_base);
- if (pci_write_config_word(SIS5595_dev, ACPI_BASE, sis5595_base)
- != PCIBIOS_SUCCESSFUL)
- goto error;
- if (pci_read_config_word(SIS5595_dev, ACPI_BASE, &a)
- != PCIBIOS_SUCCESSFUL)
- goto error;
- if ((a & ~(SIS5595_EXTENT - 1)) != sis5595_base) {
- /* doesn't work for some chips! */
- dev_err(&SIS5595_dev->dev, "force address failed - not supported?\n");
- goto error;
- }
- }
- if (pci_read_config_byte(SIS5595_dev, SIS5595_ENABLE_REG, &val)
- != PCIBIOS_SUCCESSFUL)
- goto error;
- if ((val & 0x80) == 0) {
- dev_info(&SIS5595_dev->dev, "enabling ACPI\n");
- if (pci_write_config_byte(SIS5595_dev, SIS5595_ENABLE_REG, val | 0x80)
- != PCIBIOS_SUCCESSFUL)
- goto error;
- if (pci_read_config_byte(SIS5595_dev, SIS5595_ENABLE_REG, &val)
- != PCIBIOS_SUCCESSFUL)
- goto error;
- if ((val & 0x80) == 0) {
- /* doesn't work for some chips? */
- dev_err(&SIS5595_dev->dev, "ACPI enable failed - not supported?\n");
- goto error;
- }
- }
- /* Everything is happy */
- return 0;
- error:
- release_region(sis5595_base + SMB_INDEX, 2);
- return -ENODEV;
- }
- static int sis5595_transaction(struct i2c_adapter *adap)
- {
- int temp;
- int result = 0;
- int timeout = 0;
- /* Make sure the SMBus host is ready to start transmitting */
- temp = sis5595_read(SMB_STS_LO) + (sis5595_read(SMB_STS_HI) << 8);
- if (temp != 0x00) {
- dev_dbg(&adap->dev, "SMBus busy (%04x). Resetting...\n", temp);
- sis5595_write(SMB_STS_LO, temp & 0xff);
- sis5595_write(SMB_STS_HI, temp >> 8);
- if ((temp = sis5595_read(SMB_STS_LO) + (sis5595_read(SMB_STS_HI) << 8)) != 0x00) {
- dev_dbg(&adap->dev, "Failed! (%02x)\n", temp);
- return -EBUSY;
- } else {
- dev_dbg(&adap->dev, "Successful!\n");
- }
- }
- /* start the transaction by setting bit 4 */
- sis5595_write(SMB_CTL_LO, sis5595_read(SMB_CTL_LO) | 0x10);
- /* We will always wait for a fraction of a second! */
- do {
- msleep(1);
- temp = sis5595_read(SMB_STS_LO);
- } while (!(temp & 0x40) && (timeout++ < MAX_TIMEOUT));
- /* If the SMBus is still busy, we give up */
- if (timeout > MAX_TIMEOUT) {
- dev_dbg(&adap->dev, "SMBus Timeout!\n");
- result = -ETIMEDOUT;
- }
- if (temp & 0x10) {
- dev_dbg(&adap->dev, "Error: Failed bus transaction\n");
- result = -ENXIO;
- }
- if (temp & 0x20) {
- dev_err(&adap->dev, "Bus collision! SMBus may be locked until "
- "next hard reset (or not...)\n");
- /* Clock stops and slave is stuck in mid-transmission */
- result = -EIO;
- }
- temp = sis5595_read(SMB_STS_LO) + (sis5595_read(SMB_STS_HI) << 8);
- if (temp != 0x00) {
- sis5595_write(SMB_STS_LO, temp & 0xff);
- sis5595_write(SMB_STS_HI, temp >> 8);
- }
- temp = sis5595_read(SMB_STS_LO) + (sis5595_read(SMB_STS_HI) << 8);
- if (temp != 0x00)
- dev_dbg(&adap->dev, "Failed reset at end of transaction (%02x)\n", temp);
- return result;
- }
- /* Return negative errno on error. */
- static s32 sis5595_access(struct i2c_adapter *adap, u16 addr,
- unsigned short flags, char read_write,
- u8 command, int size, union i2c_smbus_data *data)
- {
- int status;
- switch (size) {
- case I2C_SMBUS_QUICK:
- sis5595_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
- size = SIS5595_QUICK;
- break;
- case I2C_SMBUS_BYTE:
- sis5595_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
- if (read_write == I2C_SMBUS_WRITE)
- sis5595_write(SMB_CMD, command);
- size = SIS5595_BYTE;
- break;
- case I2C_SMBUS_BYTE_DATA:
- sis5595_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
- sis5595_write(SMB_CMD, command);
- if (read_write == I2C_SMBUS_WRITE)
- sis5595_write(SMB_BYTE, data->byte);
- size = SIS5595_BYTE_DATA;
- break;
- case I2C_SMBUS_PROC_CALL:
- case I2C_SMBUS_WORD_DATA:
- sis5595_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
- sis5595_write(SMB_CMD, command);
- if (read_write == I2C_SMBUS_WRITE) {
- sis5595_write(SMB_BYTE, data->word & 0xff);
- sis5595_write(SMB_BYTE + 1,
- (data->word & 0xff00) >> 8);
- }
- size = (size == I2C_SMBUS_PROC_CALL) ? SIS5595_PROC_CALL : SIS5595_WORD_DATA;
- break;
- default:
- dev_warn(&adap->dev, "Unsupported transaction %d\n", size);
- return -EOPNOTSUPP;
- }
- sis5595_write(SMB_CTL_LO, ((size & 0x0E)));
- status = sis5595_transaction(adap);
- if (status)
- return status;
- if ((size != SIS5595_PROC_CALL) &&
- ((read_write == I2C_SMBUS_WRITE) || (size == SIS5595_QUICK)))
- return 0;
- switch (size) {
- case SIS5595_BYTE:
- case SIS5595_BYTE_DATA:
- data->byte = sis5595_read(SMB_BYTE);
- break;
- case SIS5595_WORD_DATA:
- case SIS5595_PROC_CALL:
- data->word = sis5595_read(SMB_BYTE) + (sis5595_read(SMB_BYTE + 1) << 8);
- break;
- }
- return 0;
- }
- static u32 sis5595_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_PROC_CALL;
- }
- static const struct i2c_algorithm smbus_algorithm = {
- .smbus_xfer = sis5595_access,
- .functionality = sis5595_func,
- };
- static struct i2c_adapter sis5595_adapter = {
- .owner = THIS_MODULE,
- .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
- .algo = &smbus_algorithm,
- };
- static const struct pci_device_id sis5595_ids[] = {
- { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503) },
- { 0, }
- };
- MODULE_DEVICE_TABLE (pci, sis5595_ids);
- static int sis5595_probe(struct pci_dev *dev, const struct pci_device_id *id)
- {
- int err;
- if (sis5595_setup(dev)) {
- dev_err(&dev->dev, "SIS5595 not detected, module not inserted.\n");
- return -ENODEV;
- }
- /* set up the sysfs linkage to our parent device */
- sis5595_adapter.dev.parent = &dev->dev;
- snprintf(sis5595_adapter.name, sizeof(sis5595_adapter.name),
- "SMBus SIS5595 adapter at %04x", sis5595_base + SMB_INDEX);
- err = i2c_add_adapter(&sis5595_adapter);
- if (err) {
- release_region(sis5595_base + SMB_INDEX, 2);
- return err;
- }
- /* Always return failure here. This is to allow other drivers to bind
- * to this pci device. We don't really want to have control over the
- * pci device, we only wanted to read as few register values from it.
- */
- sis5595_pdev = pci_dev_get(dev);
- return -ENODEV;
- }
- static struct pci_driver sis5595_driver = {
- .name = "sis5595_smbus",
- .id_table = sis5595_ids,
- .probe = sis5595_probe,
- };
- static int __init i2c_sis5595_init(void)
- {
- return pci_register_driver(&sis5595_driver);
- }
- static void __exit i2c_sis5595_exit(void)
- {
- pci_unregister_driver(&sis5595_driver);
- if (sis5595_pdev) {
- i2c_del_adapter(&sis5595_adapter);
- release_region(sis5595_base + SMB_INDEX, 2);
- pci_dev_put(sis5595_pdev);
- sis5595_pdev = NULL;
- }
- }
- MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
- MODULE_DESCRIPTION("SIS5595 SMBus driver");
- MODULE_LICENSE("GPL");
- module_init(i2c_sis5595_init);
- module_exit(i2c_sis5595_exit);
|