162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci    Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com>
462306a36Sopenharmony_ci
562306a36Sopenharmony_ci*/
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci/*
862306a36Sopenharmony_ci    This module must be considered BETA unless and until
962306a36Sopenharmony_ci    the chipset manufacturer releases a datasheet.
1062306a36Sopenharmony_ci    The register definitions are based on the SiS630.
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci    This module relies on quirk_sis_96x_smbus (drivers/pci/quirks.c)
1362306a36Sopenharmony_ci    for just about every machine for which users have reported.
1462306a36Sopenharmony_ci    If this module isn't detecting your 96x south bridge, have a
1562306a36Sopenharmony_ci    look there.
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci    We assume there can only be one SiS96x with one SMBus interface.
1862306a36Sopenharmony_ci*/
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include <linux/module.h>
2162306a36Sopenharmony_ci#include <linux/pci.h>
2262306a36Sopenharmony_ci#include <linux/kernel.h>
2362306a36Sopenharmony_ci#include <linux/delay.h>
2462306a36Sopenharmony_ci#include <linux/stddef.h>
2562306a36Sopenharmony_ci#include <linux/ioport.h>
2662306a36Sopenharmony_ci#include <linux/i2c.h>
2762306a36Sopenharmony_ci#include <linux/acpi.h>
2862306a36Sopenharmony_ci#include <linux/io.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci/* base address register in PCI config space */
3162306a36Sopenharmony_ci#define SIS96x_BAR 0x04
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci/* SiS96x SMBus registers */
3462306a36Sopenharmony_ci#define SMB_STS      0x00
3562306a36Sopenharmony_ci#define SMB_EN       0x01
3662306a36Sopenharmony_ci#define SMB_CNT      0x02
3762306a36Sopenharmony_ci#define SMB_HOST_CNT 0x03
3862306a36Sopenharmony_ci#define SMB_ADDR     0x04
3962306a36Sopenharmony_ci#define SMB_CMD      0x05
4062306a36Sopenharmony_ci#define SMB_PCOUNT   0x06
4162306a36Sopenharmony_ci#define SMB_COUNT    0x07
4262306a36Sopenharmony_ci#define SMB_BYTE     0x08
4362306a36Sopenharmony_ci#define SMB_DEV_ADDR 0x10
4462306a36Sopenharmony_ci#define SMB_DB0      0x11
4562306a36Sopenharmony_ci#define SMB_DB1      0x12
4662306a36Sopenharmony_ci#define SMB_SAA      0x13
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/* register count for request_region */
4962306a36Sopenharmony_ci#define SMB_IOSIZE 0x20
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci/* Other settings */
5262306a36Sopenharmony_ci#define MAX_TIMEOUT 500
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci/* SiS96x SMBus constants */
5562306a36Sopenharmony_ci#define SIS96x_QUICK      0x00
5662306a36Sopenharmony_ci#define SIS96x_BYTE       0x01
5762306a36Sopenharmony_ci#define SIS96x_BYTE_DATA  0x02
5862306a36Sopenharmony_ci#define SIS96x_WORD_DATA  0x03
5962306a36Sopenharmony_ci#define SIS96x_PROC_CALL  0x04
6062306a36Sopenharmony_ci#define SIS96x_BLOCK_DATA 0x05
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic struct pci_driver sis96x_driver;
6362306a36Sopenharmony_cistatic struct i2c_adapter sis96x_adapter;
6462306a36Sopenharmony_cistatic u16 sis96x_smbus_base;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic inline u8 sis96x_read(u8 reg)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	return inb(sis96x_smbus_base + reg) ;
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic inline void sis96x_write(u8 reg, u8 data)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	outb(data, sis96x_smbus_base + reg) ;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci/* Execute a SMBus transaction.
7762306a36Sopenharmony_ci   int size is from SIS96x_QUICK to SIS96x_BLOCK_DATA
7862306a36Sopenharmony_ci */
7962306a36Sopenharmony_cistatic int sis96x_transaction(int size)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	int temp;
8262306a36Sopenharmony_ci	int result = 0;
8362306a36Sopenharmony_ci	int timeout = 0;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	dev_dbg(&sis96x_adapter.dev, "SMBus transaction %d\n", size);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	/* Make sure the SMBus host is ready to start transmitting */
8862306a36Sopenharmony_ci	if (((temp = sis96x_read(SMB_CNT)) & 0x03) != 0x00) {
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci		dev_dbg(&sis96x_adapter.dev, "SMBus busy (0x%02x). "
9162306a36Sopenharmony_ci			"Resetting...\n", temp);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci		/* kill the transaction */
9462306a36Sopenharmony_ci		sis96x_write(SMB_HOST_CNT, 0x20);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		/* check it again */
9762306a36Sopenharmony_ci		if (((temp = sis96x_read(SMB_CNT)) & 0x03) != 0x00) {
9862306a36Sopenharmony_ci			dev_dbg(&sis96x_adapter.dev, "Failed (0x%02x)\n", temp);
9962306a36Sopenharmony_ci			return -EBUSY;
10062306a36Sopenharmony_ci		} else {
10162306a36Sopenharmony_ci			dev_dbg(&sis96x_adapter.dev, "Successful\n");
10262306a36Sopenharmony_ci		}
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	/* Turn off timeout interrupts, set fast host clock */
10662306a36Sopenharmony_ci	sis96x_write(SMB_CNT, 0x20);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	/* clear all (sticky) status flags */
10962306a36Sopenharmony_ci	temp = sis96x_read(SMB_STS);
11062306a36Sopenharmony_ci	sis96x_write(SMB_STS, temp & 0x1e);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	/* start the transaction by setting bit 4 and size bits */
11362306a36Sopenharmony_ci	sis96x_write(SMB_HOST_CNT, 0x10 | (size & 0x07));
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	/* We will always wait for a fraction of a second! */
11662306a36Sopenharmony_ci	do {
11762306a36Sopenharmony_ci		msleep(1);
11862306a36Sopenharmony_ci		temp = sis96x_read(SMB_STS);
11962306a36Sopenharmony_ci	} while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT));
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	/* If the SMBus is still busy, we give up */
12262306a36Sopenharmony_ci	if (timeout > MAX_TIMEOUT) {
12362306a36Sopenharmony_ci		dev_dbg(&sis96x_adapter.dev, "SMBus Timeout! (0x%02x)\n", temp);
12462306a36Sopenharmony_ci		result = -ETIMEDOUT;
12562306a36Sopenharmony_ci	}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	/* device error - probably missing ACK */
12862306a36Sopenharmony_ci	if (temp & 0x02) {
12962306a36Sopenharmony_ci		dev_dbg(&sis96x_adapter.dev, "Failed bus transaction!\n");
13062306a36Sopenharmony_ci		result = -ENXIO;
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	/* bus collision */
13462306a36Sopenharmony_ci	if (temp & 0x04) {
13562306a36Sopenharmony_ci		dev_dbg(&sis96x_adapter.dev, "Bus collision!\n");
13662306a36Sopenharmony_ci		result = -EIO;
13762306a36Sopenharmony_ci	}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	/* Finish up by resetting the bus */
14062306a36Sopenharmony_ci	sis96x_write(SMB_STS, temp);
14162306a36Sopenharmony_ci	if ((temp = sis96x_read(SMB_STS))) {
14262306a36Sopenharmony_ci		dev_dbg(&sis96x_adapter.dev, "Failed reset at "
14362306a36Sopenharmony_ci			"end of transaction! (0x%02x)\n", temp);
14462306a36Sopenharmony_ci	}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	return result;
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci/* Return negative errno on error. */
15062306a36Sopenharmony_cistatic s32 sis96x_access(struct i2c_adapter * adap, u16 addr,
15162306a36Sopenharmony_ci			 unsigned short flags, char read_write,
15262306a36Sopenharmony_ci			 u8 command, int size, union i2c_smbus_data * data)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	int status;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	switch (size) {
15762306a36Sopenharmony_ci	case I2C_SMBUS_QUICK:
15862306a36Sopenharmony_ci		sis96x_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
15962306a36Sopenharmony_ci		size = SIS96x_QUICK;
16062306a36Sopenharmony_ci		break;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	case I2C_SMBUS_BYTE:
16362306a36Sopenharmony_ci		sis96x_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
16462306a36Sopenharmony_ci		if (read_write == I2C_SMBUS_WRITE)
16562306a36Sopenharmony_ci			sis96x_write(SMB_CMD, command);
16662306a36Sopenharmony_ci		size = SIS96x_BYTE;
16762306a36Sopenharmony_ci		break;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	case I2C_SMBUS_BYTE_DATA:
17062306a36Sopenharmony_ci		sis96x_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
17162306a36Sopenharmony_ci		sis96x_write(SMB_CMD, command);
17262306a36Sopenharmony_ci		if (read_write == I2C_SMBUS_WRITE)
17362306a36Sopenharmony_ci			sis96x_write(SMB_BYTE, data->byte);
17462306a36Sopenharmony_ci		size = SIS96x_BYTE_DATA;
17562306a36Sopenharmony_ci		break;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	case I2C_SMBUS_PROC_CALL:
17862306a36Sopenharmony_ci	case I2C_SMBUS_WORD_DATA:
17962306a36Sopenharmony_ci		sis96x_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
18062306a36Sopenharmony_ci		sis96x_write(SMB_CMD, command);
18162306a36Sopenharmony_ci		if (read_write == I2C_SMBUS_WRITE) {
18262306a36Sopenharmony_ci			sis96x_write(SMB_BYTE, data->word & 0xff);
18362306a36Sopenharmony_ci			sis96x_write(SMB_BYTE + 1, (data->word & 0xff00) >> 8);
18462306a36Sopenharmony_ci		}
18562306a36Sopenharmony_ci		size = (size == I2C_SMBUS_PROC_CALL ?
18662306a36Sopenharmony_ci			SIS96x_PROC_CALL : SIS96x_WORD_DATA);
18762306a36Sopenharmony_ci		break;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	default:
19062306a36Sopenharmony_ci		dev_warn(&adap->dev, "Unsupported transaction %d\n", size);
19162306a36Sopenharmony_ci		return -EOPNOTSUPP;
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	status = sis96x_transaction(size);
19562306a36Sopenharmony_ci	if (status)
19662306a36Sopenharmony_ci		return status;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	if ((size != SIS96x_PROC_CALL) &&
19962306a36Sopenharmony_ci		((read_write == I2C_SMBUS_WRITE) || (size == SIS96x_QUICK)))
20062306a36Sopenharmony_ci		return 0;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	switch (size) {
20362306a36Sopenharmony_ci	case SIS96x_BYTE:
20462306a36Sopenharmony_ci	case SIS96x_BYTE_DATA:
20562306a36Sopenharmony_ci		data->byte = sis96x_read(SMB_BYTE);
20662306a36Sopenharmony_ci		break;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	case SIS96x_WORD_DATA:
20962306a36Sopenharmony_ci	case SIS96x_PROC_CALL:
21062306a36Sopenharmony_ci		data->word = sis96x_read(SMB_BYTE) +
21162306a36Sopenharmony_ci				(sis96x_read(SMB_BYTE + 1) << 8);
21262306a36Sopenharmony_ci		break;
21362306a36Sopenharmony_ci	}
21462306a36Sopenharmony_ci	return 0;
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic u32 sis96x_func(struct i2c_adapter *adapter)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
22062306a36Sopenharmony_ci	    I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
22162306a36Sopenharmony_ci	    I2C_FUNC_SMBUS_PROC_CALL;
22262306a36Sopenharmony_ci}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cistatic const struct i2c_algorithm smbus_algorithm = {
22562306a36Sopenharmony_ci	.smbus_xfer	= sis96x_access,
22662306a36Sopenharmony_ci	.functionality	= sis96x_func,
22762306a36Sopenharmony_ci};
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cistatic struct i2c_adapter sis96x_adapter = {
23062306a36Sopenharmony_ci	.owner		= THIS_MODULE,
23162306a36Sopenharmony_ci	.class		= I2C_CLASS_HWMON | I2C_CLASS_SPD,
23262306a36Sopenharmony_ci	.algo		= &smbus_algorithm,
23362306a36Sopenharmony_ci};
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic const struct pci_device_id sis96x_ids[] = {
23662306a36Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_SMBUS) },
23762306a36Sopenharmony_ci	{ 0, }
23862306a36Sopenharmony_ci};
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ciMODULE_DEVICE_TABLE (pci, sis96x_ids);
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_cistatic int sis96x_probe(struct pci_dev *dev,
24362306a36Sopenharmony_ci				const struct pci_device_id *id)
24462306a36Sopenharmony_ci{
24562306a36Sopenharmony_ci	u16 ww = 0;
24662306a36Sopenharmony_ci	int retval;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	if (sis96x_smbus_base) {
24962306a36Sopenharmony_ci		dev_err(&dev->dev, "Only one device supported.\n");
25062306a36Sopenharmony_ci		return -EBUSY;
25162306a36Sopenharmony_ci	}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	pci_read_config_word(dev, PCI_CLASS_DEVICE, &ww);
25462306a36Sopenharmony_ci	if (PCI_CLASS_SERIAL_SMBUS != ww) {
25562306a36Sopenharmony_ci		dev_err(&dev->dev, "Unsupported device class 0x%04x!\n", ww);
25662306a36Sopenharmony_ci		return -ENODEV;
25762306a36Sopenharmony_ci	}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	sis96x_smbus_base = pci_resource_start(dev, SIS96x_BAR);
26062306a36Sopenharmony_ci	if (!sis96x_smbus_base) {
26162306a36Sopenharmony_ci		dev_err(&dev->dev, "SiS96x SMBus base address "
26262306a36Sopenharmony_ci			"not initialized!\n");
26362306a36Sopenharmony_ci		return -EINVAL;
26462306a36Sopenharmony_ci	}
26562306a36Sopenharmony_ci	dev_info(&dev->dev, "SiS96x SMBus base address: 0x%04x\n",
26662306a36Sopenharmony_ci			sis96x_smbus_base);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	retval = acpi_check_resource_conflict(&dev->resource[SIS96x_BAR]);
26962306a36Sopenharmony_ci	if (retval)
27062306a36Sopenharmony_ci		return -ENODEV;
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	/* Everything is happy, let's grab the memory and set things up. */
27362306a36Sopenharmony_ci	if (!request_region(sis96x_smbus_base, SMB_IOSIZE,
27462306a36Sopenharmony_ci			    sis96x_driver.name)) {
27562306a36Sopenharmony_ci		dev_err(&dev->dev, "SMBus registers 0x%04x-0x%04x "
27662306a36Sopenharmony_ci			"already in use!\n", sis96x_smbus_base,
27762306a36Sopenharmony_ci			sis96x_smbus_base + SMB_IOSIZE - 1);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci		sis96x_smbus_base = 0;
28062306a36Sopenharmony_ci		return -EINVAL;
28162306a36Sopenharmony_ci	}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	/* set up the sysfs linkage to our parent device */
28462306a36Sopenharmony_ci	sis96x_adapter.dev.parent = &dev->dev;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	snprintf(sis96x_adapter.name, sizeof(sis96x_adapter.name),
28762306a36Sopenharmony_ci		"SiS96x SMBus adapter at 0x%04x", sis96x_smbus_base);
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	if ((retval = i2c_add_adapter(&sis96x_adapter))) {
29062306a36Sopenharmony_ci		dev_err(&dev->dev, "Couldn't register adapter!\n");
29162306a36Sopenharmony_ci		release_region(sis96x_smbus_base, SMB_IOSIZE);
29262306a36Sopenharmony_ci		sis96x_smbus_base = 0;
29362306a36Sopenharmony_ci	}
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	return retval;
29662306a36Sopenharmony_ci}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cistatic void sis96x_remove(struct pci_dev *dev)
29962306a36Sopenharmony_ci{
30062306a36Sopenharmony_ci	if (sis96x_smbus_base) {
30162306a36Sopenharmony_ci		i2c_del_adapter(&sis96x_adapter);
30262306a36Sopenharmony_ci		release_region(sis96x_smbus_base, SMB_IOSIZE);
30362306a36Sopenharmony_ci		sis96x_smbus_base = 0;
30462306a36Sopenharmony_ci	}
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic struct pci_driver sis96x_driver = {
30862306a36Sopenharmony_ci	.name		= "sis96x_smbus",
30962306a36Sopenharmony_ci	.id_table	= sis96x_ids,
31062306a36Sopenharmony_ci	.probe		= sis96x_probe,
31162306a36Sopenharmony_ci	.remove		= sis96x_remove,
31262306a36Sopenharmony_ci};
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_cimodule_pci_driver(sis96x_driver);
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ciMODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>");
31762306a36Sopenharmony_ciMODULE_DESCRIPTION("SiS96x SMBus driver");
31862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
319