18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci    Copyright (c) 2002,2003 Alexander Malysh <amalysh@web.de>
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ci*/
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci/*
88c2ecf20Sopenharmony_ci   Status: beta
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci   Supports:
118c2ecf20Sopenharmony_ci	SIS 630
128c2ecf20Sopenharmony_ci	SIS 730
138c2ecf20Sopenharmony_ci	SIS 964
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci   Notable differences between chips:
168c2ecf20Sopenharmony_ci	+------------------------+--------------------+-------------------+
178c2ecf20Sopenharmony_ci	|                        |     SIS630/730     |      SIS964       |
188c2ecf20Sopenharmony_ci	+------------------------+--------------------+-------------------+
198c2ecf20Sopenharmony_ci	| Clock                  | 14kHz/56kHz        | 55.56kHz/27.78kHz |
208c2ecf20Sopenharmony_ci	| SMBus registers offset | 0x80               | 0xE0              |
218c2ecf20Sopenharmony_ci	| SMB_CNT                | Bit 1 = Slave Busy | Bit 1 = Bus probe |
228c2ecf20Sopenharmony_ci	|         (not used yet) | Bit 3 is reserved  | Bit 3 = Last byte |
238c2ecf20Sopenharmony_ci	| SMB_PCOUNT		 | Offset + 0x06      | Offset + 0x14     |
248c2ecf20Sopenharmony_ci	| SMB_COUNT              | 4:0 bits           | 5:0 bits          |
258c2ecf20Sopenharmony_ci	+------------------------+--------------------+-------------------+
268c2ecf20Sopenharmony_ci	(Other differences don't affect the functions provided by the driver)
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci   Note: we assume there can only be one device, with one SMBus interface.
298c2ecf20Sopenharmony_ci*/
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#include <linux/kernel.h>
328c2ecf20Sopenharmony_ci#include <linux/module.h>
338c2ecf20Sopenharmony_ci#include <linux/delay.h>
348c2ecf20Sopenharmony_ci#include <linux/pci.h>
358c2ecf20Sopenharmony_ci#include <linux/ioport.h>
368c2ecf20Sopenharmony_ci#include <linux/i2c.h>
378c2ecf20Sopenharmony_ci#include <linux/acpi.h>
388c2ecf20Sopenharmony_ci#include <linux/io.h>
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci/* SIS964 id is defined here as we are the only file using it */
418c2ecf20Sopenharmony_ci#define PCI_DEVICE_ID_SI_964	0x0964
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci/* SIS630/730/964 SMBus registers */
448c2ecf20Sopenharmony_ci#define SMB_STS			0x00	/* status */
458c2ecf20Sopenharmony_ci#define SMB_CNT			0x02	/* control */
468c2ecf20Sopenharmony_ci#define SMBHOST_CNT		0x03	/* host control */
478c2ecf20Sopenharmony_ci#define SMB_ADDR		0x04	/* address */
488c2ecf20Sopenharmony_ci#define SMB_CMD			0x05	/* command */
498c2ecf20Sopenharmony_ci#define SMB_COUNT		0x07	/* byte count */
508c2ecf20Sopenharmony_ci#define SMB_BYTE		0x08	/* ~0x8F data byte field */
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci/* SMB_STS register */
538c2ecf20Sopenharmony_ci#define BYTE_DONE_STS		0x10	/* Byte Done Status / Block Array */
548c2ecf20Sopenharmony_ci#define SMBCOL_STS		0x04	/* Collision */
558c2ecf20Sopenharmony_ci#define SMBERR_STS		0x02	/* Device error */
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci/* SMB_CNT register */
588c2ecf20Sopenharmony_ci#define MSTO_EN			0x40	/* Host Master Timeout Enable */
598c2ecf20Sopenharmony_ci#define SMBCLK_SEL		0x20	/* Host master clock selection */
608c2ecf20Sopenharmony_ci#define SMB_PROBE		0x02	/* Bus Probe/Slave busy */
618c2ecf20Sopenharmony_ci#define SMB_HOSTBUSY		0x01	/* Host Busy */
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci/* SMBHOST_CNT register */
648c2ecf20Sopenharmony_ci#define SMB_KILL		0x20	/* Kill */
658c2ecf20Sopenharmony_ci#define SMB_START		0x10	/* Start */
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci/* register count for request_region
688c2ecf20Sopenharmony_ci * As we don't use SMB_PCOUNT, 20 is ok for SiS630 and SiS964
698c2ecf20Sopenharmony_ci */
708c2ecf20Sopenharmony_ci#define SIS630_SMB_IOREGION	20
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci/* PCI address constants */
738c2ecf20Sopenharmony_ci/* acpi base address register  */
748c2ecf20Sopenharmony_ci#define SIS630_ACPI_BASE_REG	0x74
758c2ecf20Sopenharmony_ci/* bios control register */
768c2ecf20Sopenharmony_ci#define SIS630_BIOS_CTL_REG	0x40
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci/* Other settings */
798c2ecf20Sopenharmony_ci#define MAX_TIMEOUT		500
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci/* SIS630 constants */
828c2ecf20Sopenharmony_ci#define SIS630_QUICK		0x00
838c2ecf20Sopenharmony_ci#define SIS630_BYTE		0x01
848c2ecf20Sopenharmony_ci#define SIS630_BYTE_DATA	0x02
858c2ecf20Sopenharmony_ci#define SIS630_WORD_DATA	0x03
868c2ecf20Sopenharmony_ci#define SIS630_PCALL		0x04
878c2ecf20Sopenharmony_ci#define SIS630_BLOCK_DATA	0x05
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_cistatic struct pci_driver sis630_driver;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci/* insmod parameters */
928c2ecf20Sopenharmony_cistatic bool high_clock;
938c2ecf20Sopenharmony_cistatic bool force;
948c2ecf20Sopenharmony_cimodule_param(high_clock, bool, 0);
958c2ecf20Sopenharmony_ciMODULE_PARM_DESC(high_clock,
968c2ecf20Sopenharmony_ci	"Set Host Master Clock to 56KHz (default 14KHz) (SIS630/730 only).");
978c2ecf20Sopenharmony_cimodule_param(force, bool, 0);
988c2ecf20Sopenharmony_ciMODULE_PARM_DESC(force, "Forcibly enable the SIS630. DANGEROUS!");
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci/* SMBus base adress */
1018c2ecf20Sopenharmony_cistatic unsigned short smbus_base;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci/* supported chips */
1048c2ecf20Sopenharmony_cistatic int supported[] = {
1058c2ecf20Sopenharmony_ci	PCI_DEVICE_ID_SI_630,
1068c2ecf20Sopenharmony_ci	PCI_DEVICE_ID_SI_730,
1078c2ecf20Sopenharmony_ci	PCI_DEVICE_ID_SI_760,
1088c2ecf20Sopenharmony_ci	0 /* terminates the list */
1098c2ecf20Sopenharmony_ci};
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic inline u8 sis630_read(u8 reg)
1128c2ecf20Sopenharmony_ci{
1138c2ecf20Sopenharmony_ci	return inb(smbus_base + reg);
1148c2ecf20Sopenharmony_ci}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic inline void sis630_write(u8 reg, u8 data)
1178c2ecf20Sopenharmony_ci{
1188c2ecf20Sopenharmony_ci	outb(data, smbus_base + reg);
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic int sis630_transaction_start(struct i2c_adapter *adap, int size,
1228c2ecf20Sopenharmony_ci				    u8 *oldclock)
1238c2ecf20Sopenharmony_ci{
1248c2ecf20Sopenharmony_ci	int temp;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	/* Make sure the SMBus host is ready to start transmitting. */
1278c2ecf20Sopenharmony_ci	temp = sis630_read(SMB_CNT);
1288c2ecf20Sopenharmony_ci	if ((temp & (SMB_PROBE | SMB_HOSTBUSY)) != 0x00) {
1298c2ecf20Sopenharmony_ci		dev_dbg(&adap->dev, "SMBus busy (%02x). Resetting...\n", temp);
1308c2ecf20Sopenharmony_ci		/* kill smbus transaction */
1318c2ecf20Sopenharmony_ci		sis630_write(SMBHOST_CNT, SMB_KILL);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci		temp = sis630_read(SMB_CNT);
1348c2ecf20Sopenharmony_ci		if (temp & (SMB_PROBE | SMB_HOSTBUSY)) {
1358c2ecf20Sopenharmony_ci			dev_dbg(&adap->dev, "Failed! (%02x)\n", temp);
1368c2ecf20Sopenharmony_ci			return -EBUSY;
1378c2ecf20Sopenharmony_ci		} else {
1388c2ecf20Sopenharmony_ci			dev_dbg(&adap->dev, "Successful!\n");
1398c2ecf20Sopenharmony_ci		}
1408c2ecf20Sopenharmony_ci	}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	/* save old clock, so we can prevent machine for hung */
1438c2ecf20Sopenharmony_ci	*oldclock = sis630_read(SMB_CNT);
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	dev_dbg(&adap->dev, "saved clock 0x%02x\n", *oldclock);
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	/* disable timeout interrupt,
1488c2ecf20Sopenharmony_ci	 * set Host Master Clock to 56KHz if requested */
1498c2ecf20Sopenharmony_ci	if (high_clock)
1508c2ecf20Sopenharmony_ci		sis630_write(SMB_CNT, SMBCLK_SEL);
1518c2ecf20Sopenharmony_ci	else
1528c2ecf20Sopenharmony_ci		sis630_write(SMB_CNT, (*oldclock & ~MSTO_EN));
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	/* clear all sticky bits */
1558c2ecf20Sopenharmony_ci	temp = sis630_read(SMB_STS);
1568c2ecf20Sopenharmony_ci	sis630_write(SMB_STS, temp & 0x1e);
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	/* start the transaction by setting bit 4 and size */
1598c2ecf20Sopenharmony_ci	sis630_write(SMBHOST_CNT, SMB_START | (size & 0x07));
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	return 0;
1628c2ecf20Sopenharmony_ci}
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic int sis630_transaction_wait(struct i2c_adapter *adap, int size)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	int temp, result = 0, timeout = 0;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	/* We will always wait for a fraction of a second! */
1698c2ecf20Sopenharmony_ci	do {
1708c2ecf20Sopenharmony_ci		msleep(1);
1718c2ecf20Sopenharmony_ci		temp = sis630_read(SMB_STS);
1728c2ecf20Sopenharmony_ci		/* check if block transmitted */
1738c2ecf20Sopenharmony_ci		if (size == SIS630_BLOCK_DATA && (temp & BYTE_DONE_STS))
1748c2ecf20Sopenharmony_ci			break;
1758c2ecf20Sopenharmony_ci	} while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT));
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	/* If the SMBus is still busy, we give up */
1788c2ecf20Sopenharmony_ci	if (timeout > MAX_TIMEOUT) {
1798c2ecf20Sopenharmony_ci		dev_dbg(&adap->dev, "SMBus Timeout!\n");
1808c2ecf20Sopenharmony_ci		result = -ETIMEDOUT;
1818c2ecf20Sopenharmony_ci	}
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	if (temp & SMBERR_STS) {
1848c2ecf20Sopenharmony_ci		dev_dbg(&adap->dev, "Error: Failed bus transaction\n");
1858c2ecf20Sopenharmony_ci		result = -ENXIO;
1868c2ecf20Sopenharmony_ci	}
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	if (temp & SMBCOL_STS) {
1898c2ecf20Sopenharmony_ci		dev_err(&adap->dev, "Bus collision!\n");
1908c2ecf20Sopenharmony_ci		result = -EAGAIN;
1918c2ecf20Sopenharmony_ci	}
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	return result;
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_cistatic void sis630_transaction_end(struct i2c_adapter *adap, u8 oldclock)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	/* clear all status "sticky" bits */
1998c2ecf20Sopenharmony_ci	sis630_write(SMB_STS, 0xFF);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	dev_dbg(&adap->dev,
2028c2ecf20Sopenharmony_ci		"SMB_CNT before clock restore 0x%02x\n", sis630_read(SMB_CNT));
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	/*
2058c2ecf20Sopenharmony_ci	 * restore old Host Master Clock if high_clock is set
2068c2ecf20Sopenharmony_ci	 * and oldclock was not 56KHz
2078c2ecf20Sopenharmony_ci	 */
2088c2ecf20Sopenharmony_ci	if (high_clock && !(oldclock & SMBCLK_SEL))
2098c2ecf20Sopenharmony_ci		sis630_write(SMB_CNT, sis630_read(SMB_CNT) & ~SMBCLK_SEL);
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	dev_dbg(&adap->dev,
2128c2ecf20Sopenharmony_ci		"SMB_CNT after clock restore 0x%02x\n", sis630_read(SMB_CNT));
2138c2ecf20Sopenharmony_ci}
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_cistatic int sis630_transaction(struct i2c_adapter *adap, int size)
2168c2ecf20Sopenharmony_ci{
2178c2ecf20Sopenharmony_ci	int result = 0;
2188c2ecf20Sopenharmony_ci	u8 oldclock = 0;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	result = sis630_transaction_start(adap, size, &oldclock);
2218c2ecf20Sopenharmony_ci	if (!result) {
2228c2ecf20Sopenharmony_ci		result = sis630_transaction_wait(adap, size);
2238c2ecf20Sopenharmony_ci		sis630_transaction_end(adap, oldclock);
2248c2ecf20Sopenharmony_ci	}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	return result;
2278c2ecf20Sopenharmony_ci}
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_cistatic int sis630_block_data(struct i2c_adapter *adap,
2308c2ecf20Sopenharmony_ci			     union i2c_smbus_data *data, int read_write)
2318c2ecf20Sopenharmony_ci{
2328c2ecf20Sopenharmony_ci	int i, len = 0, rc = 0;
2338c2ecf20Sopenharmony_ci	u8 oldclock = 0;
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	if (read_write == I2C_SMBUS_WRITE) {
2368c2ecf20Sopenharmony_ci		len = data->block[0];
2378c2ecf20Sopenharmony_ci		if (len < 0)
2388c2ecf20Sopenharmony_ci			len = 0;
2398c2ecf20Sopenharmony_ci		else if (len > 32)
2408c2ecf20Sopenharmony_ci			len = 32;
2418c2ecf20Sopenharmony_ci		sis630_write(SMB_COUNT, len);
2428c2ecf20Sopenharmony_ci		for (i = 1; i <= len; i++) {
2438c2ecf20Sopenharmony_ci			dev_dbg(&adap->dev,
2448c2ecf20Sopenharmony_ci				"set data 0x%02x\n", data->block[i]);
2458c2ecf20Sopenharmony_ci			/* set data */
2468c2ecf20Sopenharmony_ci			sis630_write(SMB_BYTE + (i - 1) % 8, data->block[i]);
2478c2ecf20Sopenharmony_ci			if (i == 8 || (len < 8 && i == len)) {
2488c2ecf20Sopenharmony_ci				dev_dbg(&adap->dev,
2498c2ecf20Sopenharmony_ci					"start trans len=%d i=%d\n", len, i);
2508c2ecf20Sopenharmony_ci				/* first transaction */
2518c2ecf20Sopenharmony_ci				rc = sis630_transaction_start(adap,
2528c2ecf20Sopenharmony_ci						SIS630_BLOCK_DATA, &oldclock);
2538c2ecf20Sopenharmony_ci				if (rc)
2548c2ecf20Sopenharmony_ci					return rc;
2558c2ecf20Sopenharmony_ci			} else if ((i - 1) % 8 == 7 || i == len) {
2568c2ecf20Sopenharmony_ci				dev_dbg(&adap->dev,
2578c2ecf20Sopenharmony_ci					"trans_wait len=%d i=%d\n", len, i);
2588c2ecf20Sopenharmony_ci				if (i > 8) {
2598c2ecf20Sopenharmony_ci					dev_dbg(&adap->dev,
2608c2ecf20Sopenharmony_ci						"clear smbary_sts"
2618c2ecf20Sopenharmony_ci						" len=%d i=%d\n", len, i);
2628c2ecf20Sopenharmony_ci					/*
2638c2ecf20Sopenharmony_ci					   If this is not first transaction,
2648c2ecf20Sopenharmony_ci					   we must clear sticky bit.
2658c2ecf20Sopenharmony_ci					   clear SMBARY_STS
2668c2ecf20Sopenharmony_ci					*/
2678c2ecf20Sopenharmony_ci					sis630_write(SMB_STS, BYTE_DONE_STS);
2688c2ecf20Sopenharmony_ci				}
2698c2ecf20Sopenharmony_ci				rc = sis630_transaction_wait(adap,
2708c2ecf20Sopenharmony_ci						SIS630_BLOCK_DATA);
2718c2ecf20Sopenharmony_ci				if (rc) {
2728c2ecf20Sopenharmony_ci					dev_dbg(&adap->dev,
2738c2ecf20Sopenharmony_ci						"trans_wait failed\n");
2748c2ecf20Sopenharmony_ci					break;
2758c2ecf20Sopenharmony_ci				}
2768c2ecf20Sopenharmony_ci			}
2778c2ecf20Sopenharmony_ci		}
2788c2ecf20Sopenharmony_ci	} else {
2798c2ecf20Sopenharmony_ci		/* read request */
2808c2ecf20Sopenharmony_ci		data->block[0] = len = 0;
2818c2ecf20Sopenharmony_ci		rc = sis630_transaction_start(adap,
2828c2ecf20Sopenharmony_ci				SIS630_BLOCK_DATA, &oldclock);
2838c2ecf20Sopenharmony_ci		if (rc)
2848c2ecf20Sopenharmony_ci			return rc;
2858c2ecf20Sopenharmony_ci		do {
2868c2ecf20Sopenharmony_ci			rc = sis630_transaction_wait(adap, SIS630_BLOCK_DATA);
2878c2ecf20Sopenharmony_ci			if (rc) {
2888c2ecf20Sopenharmony_ci				dev_dbg(&adap->dev, "trans_wait failed\n");
2898c2ecf20Sopenharmony_ci				break;
2908c2ecf20Sopenharmony_ci			}
2918c2ecf20Sopenharmony_ci			/* if this first transaction then read byte count */
2928c2ecf20Sopenharmony_ci			if (len == 0)
2938c2ecf20Sopenharmony_ci				data->block[0] = sis630_read(SMB_COUNT);
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci			/* just to be sure */
2968c2ecf20Sopenharmony_ci			if (data->block[0] > 32)
2978c2ecf20Sopenharmony_ci				data->block[0] = 32;
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci			dev_dbg(&adap->dev,
3008c2ecf20Sopenharmony_ci				"block data read len=0x%x\n", data->block[0]);
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci			for (i = 0; i < 8 && len < data->block[0]; i++, len++) {
3038c2ecf20Sopenharmony_ci				dev_dbg(&adap->dev,
3048c2ecf20Sopenharmony_ci					"read i=%d len=%d\n", i, len);
3058c2ecf20Sopenharmony_ci				data->block[len + 1] = sis630_read(SMB_BYTE +
3068c2ecf20Sopenharmony_ci								   i);
3078c2ecf20Sopenharmony_ci			}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci			dev_dbg(&adap->dev,
3108c2ecf20Sopenharmony_ci				"clear smbary_sts len=%d i=%d\n", len, i);
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci			/* clear SMBARY_STS */
3138c2ecf20Sopenharmony_ci			sis630_write(SMB_STS, BYTE_DONE_STS);
3148c2ecf20Sopenharmony_ci		} while (len < data->block[0]);
3158c2ecf20Sopenharmony_ci	}
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	sis630_transaction_end(adap, oldclock);
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	return rc;
3208c2ecf20Sopenharmony_ci}
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_ci/* Return negative errno on error. */
3238c2ecf20Sopenharmony_cistatic s32 sis630_access(struct i2c_adapter *adap, u16 addr,
3248c2ecf20Sopenharmony_ci			 unsigned short flags, char read_write,
3258c2ecf20Sopenharmony_ci			 u8 command, int size, union i2c_smbus_data *data)
3268c2ecf20Sopenharmony_ci{
3278c2ecf20Sopenharmony_ci	int status;
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	switch (size) {
3308c2ecf20Sopenharmony_ci	case I2C_SMBUS_QUICK:
3318c2ecf20Sopenharmony_ci		sis630_write(SMB_ADDR,
3328c2ecf20Sopenharmony_ci			     ((addr & 0x7f) << 1) | (read_write & 0x01));
3338c2ecf20Sopenharmony_ci		size = SIS630_QUICK;
3348c2ecf20Sopenharmony_ci		break;
3358c2ecf20Sopenharmony_ci	case I2C_SMBUS_BYTE:
3368c2ecf20Sopenharmony_ci		sis630_write(SMB_ADDR,
3378c2ecf20Sopenharmony_ci			     ((addr & 0x7f) << 1) | (read_write & 0x01));
3388c2ecf20Sopenharmony_ci		if (read_write == I2C_SMBUS_WRITE)
3398c2ecf20Sopenharmony_ci			sis630_write(SMB_CMD, command);
3408c2ecf20Sopenharmony_ci		size = SIS630_BYTE;
3418c2ecf20Sopenharmony_ci		break;
3428c2ecf20Sopenharmony_ci	case I2C_SMBUS_BYTE_DATA:
3438c2ecf20Sopenharmony_ci		sis630_write(SMB_ADDR,
3448c2ecf20Sopenharmony_ci			     ((addr & 0x7f) << 1) | (read_write & 0x01));
3458c2ecf20Sopenharmony_ci		sis630_write(SMB_CMD, command);
3468c2ecf20Sopenharmony_ci		if (read_write == I2C_SMBUS_WRITE)
3478c2ecf20Sopenharmony_ci			sis630_write(SMB_BYTE, data->byte);
3488c2ecf20Sopenharmony_ci		size = SIS630_BYTE_DATA;
3498c2ecf20Sopenharmony_ci		break;
3508c2ecf20Sopenharmony_ci	case I2C_SMBUS_PROC_CALL:
3518c2ecf20Sopenharmony_ci	case I2C_SMBUS_WORD_DATA:
3528c2ecf20Sopenharmony_ci		sis630_write(SMB_ADDR,
3538c2ecf20Sopenharmony_ci			     ((addr & 0x7f) << 1) | (read_write & 0x01));
3548c2ecf20Sopenharmony_ci		sis630_write(SMB_CMD, command);
3558c2ecf20Sopenharmony_ci		if (read_write == I2C_SMBUS_WRITE) {
3568c2ecf20Sopenharmony_ci			sis630_write(SMB_BYTE, data->word & 0xff);
3578c2ecf20Sopenharmony_ci			sis630_write(SMB_BYTE + 1, (data->word & 0xff00) >> 8);
3588c2ecf20Sopenharmony_ci		}
3598c2ecf20Sopenharmony_ci		size = (size == I2C_SMBUS_PROC_CALL ?
3608c2ecf20Sopenharmony_ci			SIS630_PCALL : SIS630_WORD_DATA);
3618c2ecf20Sopenharmony_ci		break;
3628c2ecf20Sopenharmony_ci	case I2C_SMBUS_BLOCK_DATA:
3638c2ecf20Sopenharmony_ci		sis630_write(SMB_ADDR,
3648c2ecf20Sopenharmony_ci			     ((addr & 0x7f) << 1) | (read_write & 0x01));
3658c2ecf20Sopenharmony_ci		sis630_write(SMB_CMD, command);
3668c2ecf20Sopenharmony_ci		size = SIS630_BLOCK_DATA;
3678c2ecf20Sopenharmony_ci		return sis630_block_data(adap, data, read_write);
3688c2ecf20Sopenharmony_ci	default:
3698c2ecf20Sopenharmony_ci		dev_warn(&adap->dev, "Unsupported transaction %d\n", size);
3708c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
3718c2ecf20Sopenharmony_ci	}
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci	status = sis630_transaction(adap, size);
3748c2ecf20Sopenharmony_ci	if (status)
3758c2ecf20Sopenharmony_ci		return status;
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_ci	if ((size != SIS630_PCALL) &&
3788c2ecf20Sopenharmony_ci		((read_write == I2C_SMBUS_WRITE) || (size == SIS630_QUICK))) {
3798c2ecf20Sopenharmony_ci		return 0;
3808c2ecf20Sopenharmony_ci	}
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	switch (size) {
3838c2ecf20Sopenharmony_ci	case SIS630_BYTE:
3848c2ecf20Sopenharmony_ci	case SIS630_BYTE_DATA:
3858c2ecf20Sopenharmony_ci		data->byte = sis630_read(SMB_BYTE);
3868c2ecf20Sopenharmony_ci		break;
3878c2ecf20Sopenharmony_ci	case SIS630_PCALL:
3888c2ecf20Sopenharmony_ci	case SIS630_WORD_DATA:
3898c2ecf20Sopenharmony_ci		data->word = sis630_read(SMB_BYTE) +
3908c2ecf20Sopenharmony_ci			     (sis630_read(SMB_BYTE + 1) << 8);
3918c2ecf20Sopenharmony_ci		break;
3928c2ecf20Sopenharmony_ci	}
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_ci	return 0;
3958c2ecf20Sopenharmony_ci}
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_cistatic u32 sis630_func(struct i2c_adapter *adapter)
3988c2ecf20Sopenharmony_ci{
3998c2ecf20Sopenharmony_ci	return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
4008c2ecf20Sopenharmony_ci		I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
4018c2ecf20Sopenharmony_ci		I2C_FUNC_SMBUS_PROC_CALL | I2C_FUNC_SMBUS_BLOCK_DATA;
4028c2ecf20Sopenharmony_ci}
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_cistatic int sis630_setup(struct pci_dev *sis630_dev)
4058c2ecf20Sopenharmony_ci{
4068c2ecf20Sopenharmony_ci	unsigned char b;
4078c2ecf20Sopenharmony_ci	struct pci_dev *dummy = NULL;
4088c2ecf20Sopenharmony_ci	int retval, i;
4098c2ecf20Sopenharmony_ci	/* acpi base address */
4108c2ecf20Sopenharmony_ci	unsigned short acpi_base;
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	/* check for supported SiS devices */
4138c2ecf20Sopenharmony_ci	for (i = 0; supported[i] > 0; i++) {
4148c2ecf20Sopenharmony_ci		dummy = pci_get_device(PCI_VENDOR_ID_SI, supported[i], dummy);
4158c2ecf20Sopenharmony_ci		if (dummy)
4168c2ecf20Sopenharmony_ci			break; /* found */
4178c2ecf20Sopenharmony_ci	}
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_ci	if (dummy) {
4208c2ecf20Sopenharmony_ci		pci_dev_put(dummy);
4218c2ecf20Sopenharmony_ci	} else if (force) {
4228c2ecf20Sopenharmony_ci		dev_err(&sis630_dev->dev,
4238c2ecf20Sopenharmony_ci			"WARNING: Can't detect SIS630 compatible device, but "
4248c2ecf20Sopenharmony_ci			"loading because of force option enabled\n");
4258c2ecf20Sopenharmony_ci	} else {
4268c2ecf20Sopenharmony_ci		return -ENODEV;
4278c2ecf20Sopenharmony_ci	}
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	/*
4308c2ecf20Sopenharmony_ci	   Enable ACPI first , so we can accsess reg 74-75
4318c2ecf20Sopenharmony_ci	   in acpi io space and read acpi base addr
4328c2ecf20Sopenharmony_ci	*/
4338c2ecf20Sopenharmony_ci	if (pci_read_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, &b)) {
4348c2ecf20Sopenharmony_ci		dev_err(&sis630_dev->dev, "Error: Can't read bios ctl reg\n");
4358c2ecf20Sopenharmony_ci		retval = -ENODEV;
4368c2ecf20Sopenharmony_ci		goto exit;
4378c2ecf20Sopenharmony_ci	}
4388c2ecf20Sopenharmony_ci	/* if ACPI already enabled , do nothing */
4398c2ecf20Sopenharmony_ci	if (!(b & 0x80) &&
4408c2ecf20Sopenharmony_ci	    pci_write_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, b | 0x80)) {
4418c2ecf20Sopenharmony_ci		dev_err(&sis630_dev->dev, "Error: Can't enable ACPI\n");
4428c2ecf20Sopenharmony_ci		retval = -ENODEV;
4438c2ecf20Sopenharmony_ci		goto exit;
4448c2ecf20Sopenharmony_ci	}
4458c2ecf20Sopenharmony_ci
4468c2ecf20Sopenharmony_ci	/* Determine the ACPI base address */
4478c2ecf20Sopenharmony_ci	if (pci_read_config_word(sis630_dev,
4488c2ecf20Sopenharmony_ci				 SIS630_ACPI_BASE_REG, &acpi_base)) {
4498c2ecf20Sopenharmony_ci		dev_err(&sis630_dev->dev,
4508c2ecf20Sopenharmony_ci			"Error: Can't determine ACPI base address\n");
4518c2ecf20Sopenharmony_ci		retval = -ENODEV;
4528c2ecf20Sopenharmony_ci		goto exit;
4538c2ecf20Sopenharmony_ci	}
4548c2ecf20Sopenharmony_ci
4558c2ecf20Sopenharmony_ci	dev_dbg(&sis630_dev->dev, "ACPI base at 0x%04hx\n", acpi_base);
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci	if (supported[i] == PCI_DEVICE_ID_SI_760)
4588c2ecf20Sopenharmony_ci		smbus_base = acpi_base + 0xE0;
4598c2ecf20Sopenharmony_ci	else
4608c2ecf20Sopenharmony_ci		smbus_base = acpi_base + 0x80;
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_ci	dev_dbg(&sis630_dev->dev, "SMBus base at 0x%04hx\n", smbus_base);
4638c2ecf20Sopenharmony_ci
4648c2ecf20Sopenharmony_ci	retval = acpi_check_region(smbus_base + SMB_STS, SIS630_SMB_IOREGION,
4658c2ecf20Sopenharmony_ci				   sis630_driver.name);
4668c2ecf20Sopenharmony_ci	if (retval)
4678c2ecf20Sopenharmony_ci		goto exit;
4688c2ecf20Sopenharmony_ci
4698c2ecf20Sopenharmony_ci	/* Everything is happy, let's grab the memory and set things up. */
4708c2ecf20Sopenharmony_ci	if (!request_region(smbus_base + SMB_STS, SIS630_SMB_IOREGION,
4718c2ecf20Sopenharmony_ci			    sis630_driver.name)) {
4728c2ecf20Sopenharmony_ci		dev_err(&sis630_dev->dev,
4738c2ecf20Sopenharmony_ci			"I/O Region 0x%04x-0x%04x for SMBus already in use.\n",
4748c2ecf20Sopenharmony_ci			smbus_base + SMB_STS,
4758c2ecf20Sopenharmony_ci			smbus_base + SMB_STS + SIS630_SMB_IOREGION - 1);
4768c2ecf20Sopenharmony_ci		retval = -EBUSY;
4778c2ecf20Sopenharmony_ci		goto exit;
4788c2ecf20Sopenharmony_ci	}
4798c2ecf20Sopenharmony_ci
4808c2ecf20Sopenharmony_ci	retval = 0;
4818c2ecf20Sopenharmony_ci
4828c2ecf20Sopenharmony_ciexit:
4838c2ecf20Sopenharmony_ci	if (retval)
4848c2ecf20Sopenharmony_ci		smbus_base = 0;
4858c2ecf20Sopenharmony_ci	return retval;
4868c2ecf20Sopenharmony_ci}
4878c2ecf20Sopenharmony_ci
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_cistatic const struct i2c_algorithm smbus_algorithm = {
4908c2ecf20Sopenharmony_ci	.smbus_xfer	= sis630_access,
4918c2ecf20Sopenharmony_ci	.functionality	= sis630_func,
4928c2ecf20Sopenharmony_ci};
4938c2ecf20Sopenharmony_ci
4948c2ecf20Sopenharmony_cistatic struct i2c_adapter sis630_adapter = {
4958c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
4968c2ecf20Sopenharmony_ci	.class		= I2C_CLASS_HWMON | I2C_CLASS_SPD,
4978c2ecf20Sopenharmony_ci	.algo		= &smbus_algorithm,
4988c2ecf20Sopenharmony_ci	.retries	= 3
4998c2ecf20Sopenharmony_ci};
5008c2ecf20Sopenharmony_ci
5018c2ecf20Sopenharmony_cistatic const struct pci_device_id sis630_ids[] = {
5028c2ecf20Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503) },
5038c2ecf20Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_LPC) },
5048c2ecf20Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_964) },
5058c2ecf20Sopenharmony_ci	{ 0, }
5068c2ecf20Sopenharmony_ci};
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, sis630_ids);
5098c2ecf20Sopenharmony_ci
5108c2ecf20Sopenharmony_cistatic int sis630_probe(struct pci_dev *dev, const struct pci_device_id *id)
5118c2ecf20Sopenharmony_ci{
5128c2ecf20Sopenharmony_ci	if (sis630_setup(dev)) {
5138c2ecf20Sopenharmony_ci		dev_err(&dev->dev,
5148c2ecf20Sopenharmony_ci			"SIS630 compatible bus not detected, "
5158c2ecf20Sopenharmony_ci			"module not inserted.\n");
5168c2ecf20Sopenharmony_ci		return -ENODEV;
5178c2ecf20Sopenharmony_ci	}
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_ci	/* set up the sysfs linkage to our parent device */
5208c2ecf20Sopenharmony_ci	sis630_adapter.dev.parent = &dev->dev;
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_ci	snprintf(sis630_adapter.name, sizeof(sis630_adapter.name),
5238c2ecf20Sopenharmony_ci		 "SMBus SIS630 adapter at %04x", smbus_base + SMB_STS);
5248c2ecf20Sopenharmony_ci
5258c2ecf20Sopenharmony_ci	return i2c_add_adapter(&sis630_adapter);
5268c2ecf20Sopenharmony_ci}
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_cistatic void sis630_remove(struct pci_dev *dev)
5298c2ecf20Sopenharmony_ci{
5308c2ecf20Sopenharmony_ci	if (smbus_base) {
5318c2ecf20Sopenharmony_ci		i2c_del_adapter(&sis630_adapter);
5328c2ecf20Sopenharmony_ci		release_region(smbus_base + SMB_STS, SIS630_SMB_IOREGION);
5338c2ecf20Sopenharmony_ci		smbus_base = 0;
5348c2ecf20Sopenharmony_ci	}
5358c2ecf20Sopenharmony_ci}
5368c2ecf20Sopenharmony_ci
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_cistatic struct pci_driver sis630_driver = {
5398c2ecf20Sopenharmony_ci	.name		= "sis630_smbus",
5408c2ecf20Sopenharmony_ci	.id_table	= sis630_ids,
5418c2ecf20Sopenharmony_ci	.probe		= sis630_probe,
5428c2ecf20Sopenharmony_ci	.remove		= sis630_remove,
5438c2ecf20Sopenharmony_ci};
5448c2ecf20Sopenharmony_ci
5458c2ecf20Sopenharmony_cimodule_pci_driver(sis630_driver);
5468c2ecf20Sopenharmony_ci
5478c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
5488c2ecf20Sopenharmony_ciMODULE_AUTHOR("Alexander Malysh <amalysh@web.de>");
5498c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SIS630 SMBus driver");
550