162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci    i2c-isch.c - Linux kernel driver for Intel SCH chipset SMBus
462306a36Sopenharmony_ci    - Based on i2c-piix4.c
562306a36Sopenharmony_ci    Copyright (c) 1998 - 2002 Frodo Looijaard <frodol@dds.nl> and
662306a36Sopenharmony_ci    Philip Edelbrock <phil@netroedge.com>
762306a36Sopenharmony_ci    - Intel SCH support
862306a36Sopenharmony_ci    Copyright (c) 2007 - 2008 Jacob Jun Pan <jacob.jun.pan@intel.com>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci*/
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci/*
1362306a36Sopenharmony_ci   Supports:
1462306a36Sopenharmony_ci	Intel SCH chipsets (AF82US15W, AF82US15L, AF82UL11L)
1562306a36Sopenharmony_ci   Note: we assume there can only be one device, with one SMBus interface.
1662306a36Sopenharmony_ci*/
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include <linux/module.h>
1962306a36Sopenharmony_ci#include <linux/platform_device.h>
2062306a36Sopenharmony_ci#include <linux/kernel.h>
2162306a36Sopenharmony_ci#include <linux/delay.h>
2262306a36Sopenharmony_ci#include <linux/stddef.h>
2362306a36Sopenharmony_ci#include <linux/ioport.h>
2462306a36Sopenharmony_ci#include <linux/i2c.h>
2562306a36Sopenharmony_ci#include <linux/io.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/* SCH SMBus address offsets */
2862306a36Sopenharmony_ci#define SMBHSTCNT	(0 + sch_smba)
2962306a36Sopenharmony_ci#define SMBHSTSTS	(1 + sch_smba)
3062306a36Sopenharmony_ci#define SMBHSTCLK	(2 + sch_smba)
3162306a36Sopenharmony_ci#define SMBHSTADD	(4 + sch_smba) /* TSA */
3262306a36Sopenharmony_ci#define SMBHSTCMD	(5 + sch_smba)
3362306a36Sopenharmony_ci#define SMBHSTDAT0	(6 + sch_smba)
3462306a36Sopenharmony_ci#define SMBHSTDAT1	(7 + sch_smba)
3562306a36Sopenharmony_ci#define SMBBLKDAT	(0x20 + sch_smba)
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci/* Other settings */
3862306a36Sopenharmony_ci#define MAX_RETRIES	5000
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci/* I2C constants */
4162306a36Sopenharmony_ci#define SCH_QUICK		0x00
4262306a36Sopenharmony_ci#define SCH_BYTE		0x01
4362306a36Sopenharmony_ci#define SCH_BYTE_DATA		0x02
4462306a36Sopenharmony_ci#define SCH_WORD_DATA		0x03
4562306a36Sopenharmony_ci#define SCH_BLOCK_DATA		0x05
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic unsigned short sch_smba;
4862306a36Sopenharmony_cistatic struct i2c_adapter sch_adapter;
4962306a36Sopenharmony_cistatic int backbone_speed = 33000; /* backbone speed in kHz */
5062306a36Sopenharmony_cimodule_param(backbone_speed, int, S_IRUSR | S_IWUSR);
5162306a36Sopenharmony_ciMODULE_PARM_DESC(backbone_speed, "Backbone speed in kHz, (default = 33000)");
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci/*
5462306a36Sopenharmony_ci * Start the i2c transaction -- the i2c_access will prepare the transaction
5562306a36Sopenharmony_ci * and this function will execute it.
5662306a36Sopenharmony_ci * return 0 for success and others for failure.
5762306a36Sopenharmony_ci */
5862306a36Sopenharmony_cistatic int sch_transaction(void)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	int temp;
6162306a36Sopenharmony_ci	int result = 0;
6262306a36Sopenharmony_ci	int retries = 0;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	dev_dbg(&sch_adapter.dev, "Transaction (pre): CNT=%02x, CMD=%02x, "
6562306a36Sopenharmony_ci		"ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb(SMBHSTCNT),
6662306a36Sopenharmony_ci		inb(SMBHSTCMD), inb(SMBHSTADD), inb(SMBHSTDAT0),
6762306a36Sopenharmony_ci		inb(SMBHSTDAT1));
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	/* Make sure the SMBus host is ready to start transmitting */
7062306a36Sopenharmony_ci	temp = inb(SMBHSTSTS) & 0x0f;
7162306a36Sopenharmony_ci	if (temp) {
7262306a36Sopenharmony_ci		/* Can not be busy since we checked it in sch_access */
7362306a36Sopenharmony_ci		if (temp & 0x01) {
7462306a36Sopenharmony_ci			dev_dbg(&sch_adapter.dev, "Completion (%02x). "
7562306a36Sopenharmony_ci				"Clear...\n", temp);
7662306a36Sopenharmony_ci		}
7762306a36Sopenharmony_ci		if (temp & 0x06) {
7862306a36Sopenharmony_ci			dev_dbg(&sch_adapter.dev, "SMBus error (%02x). "
7962306a36Sopenharmony_ci				"Resetting...\n", temp);
8062306a36Sopenharmony_ci		}
8162306a36Sopenharmony_ci		outb(temp, SMBHSTSTS);
8262306a36Sopenharmony_ci		temp = inb(SMBHSTSTS) & 0x0f;
8362306a36Sopenharmony_ci		if (temp) {
8462306a36Sopenharmony_ci			dev_err(&sch_adapter.dev,
8562306a36Sopenharmony_ci				"SMBus is not ready: (%02x)\n", temp);
8662306a36Sopenharmony_ci			return -EAGAIN;
8762306a36Sopenharmony_ci		}
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	/* start the transaction by setting bit 4 */
9162306a36Sopenharmony_ci	outb(inb(SMBHSTCNT) | 0x10, SMBHSTCNT);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	do {
9462306a36Sopenharmony_ci		usleep_range(100, 200);
9562306a36Sopenharmony_ci		temp = inb(SMBHSTSTS) & 0x0f;
9662306a36Sopenharmony_ci	} while ((temp & 0x08) && (retries++ < MAX_RETRIES));
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	/* If the SMBus is still busy, we give up */
9962306a36Sopenharmony_ci	if (retries > MAX_RETRIES) {
10062306a36Sopenharmony_ci		dev_err(&sch_adapter.dev, "SMBus Timeout!\n");
10162306a36Sopenharmony_ci		result = -ETIMEDOUT;
10262306a36Sopenharmony_ci	}
10362306a36Sopenharmony_ci	if (temp & 0x04) {
10462306a36Sopenharmony_ci		result = -EIO;
10562306a36Sopenharmony_ci		dev_dbg(&sch_adapter.dev, "Bus collision! SMBus may be "
10662306a36Sopenharmony_ci			"locked until next hard reset. (sorry!)\n");
10762306a36Sopenharmony_ci		/* Clock stops and slave is stuck in mid-transmission */
10862306a36Sopenharmony_ci	} else if (temp & 0x02) {
10962306a36Sopenharmony_ci		result = -EIO;
11062306a36Sopenharmony_ci		dev_err(&sch_adapter.dev, "Error: no response!\n");
11162306a36Sopenharmony_ci	} else if (temp & 0x01) {
11262306a36Sopenharmony_ci		dev_dbg(&sch_adapter.dev, "Post complete!\n");
11362306a36Sopenharmony_ci		outb(temp, SMBHSTSTS);
11462306a36Sopenharmony_ci		temp = inb(SMBHSTSTS) & 0x07;
11562306a36Sopenharmony_ci		if (temp & 0x06) {
11662306a36Sopenharmony_ci			/* Completion clear failed */
11762306a36Sopenharmony_ci			dev_dbg(&sch_adapter.dev, "Failed reset at end of "
11862306a36Sopenharmony_ci				"transaction (%02x), Bus error!\n", temp);
11962306a36Sopenharmony_ci		}
12062306a36Sopenharmony_ci	} else {
12162306a36Sopenharmony_ci		result = -ENXIO;
12262306a36Sopenharmony_ci		dev_dbg(&sch_adapter.dev, "No such address.\n");
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci	dev_dbg(&sch_adapter.dev, "Transaction (post): CNT=%02x, CMD=%02x, "
12562306a36Sopenharmony_ci		"ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb(SMBHSTCNT),
12662306a36Sopenharmony_ci		inb(SMBHSTCMD), inb(SMBHSTADD), inb(SMBHSTDAT0),
12762306a36Sopenharmony_ci		inb(SMBHSTDAT1));
12862306a36Sopenharmony_ci	return result;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci/*
13262306a36Sopenharmony_ci * This is the main access entry for i2c-sch access
13362306a36Sopenharmony_ci * adap is i2c_adapter pointer, addr is the i2c device bus address, read_write
13462306a36Sopenharmony_ci * (0 for read and 1 for write), size is i2c transaction type and data is the
13562306a36Sopenharmony_ci * union of transaction for data to be transferred or data read from bus.
13662306a36Sopenharmony_ci * return 0 for success and others for failure.
13762306a36Sopenharmony_ci */
13862306a36Sopenharmony_cistatic s32 sch_access(struct i2c_adapter *adap, u16 addr,
13962306a36Sopenharmony_ci		 unsigned short flags, char read_write,
14062306a36Sopenharmony_ci		 u8 command, int size, union i2c_smbus_data *data)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	int i, len, temp, rc;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	/* Make sure the SMBus host is not busy */
14562306a36Sopenharmony_ci	temp = inb(SMBHSTSTS) & 0x0f;
14662306a36Sopenharmony_ci	if (temp & 0x08) {
14762306a36Sopenharmony_ci		dev_dbg(&sch_adapter.dev, "SMBus busy (%02x)\n", temp);
14862306a36Sopenharmony_ci		return -EAGAIN;
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci	temp = inw(SMBHSTCLK);
15162306a36Sopenharmony_ci	if (!temp) {
15262306a36Sopenharmony_ci		/*
15362306a36Sopenharmony_ci		 * We can't determine if we have 33 or 25 MHz clock for
15462306a36Sopenharmony_ci		 * SMBus, so expect 33 MHz and calculate a bus clock of
15562306a36Sopenharmony_ci		 * 100 kHz. If we actually run at 25 MHz the bus will be
15662306a36Sopenharmony_ci		 * run ~75 kHz instead which should do no harm.
15762306a36Sopenharmony_ci		 */
15862306a36Sopenharmony_ci		dev_notice(&sch_adapter.dev,
15962306a36Sopenharmony_ci			"Clock divider uninitialized. Setting defaults\n");
16062306a36Sopenharmony_ci		outw(backbone_speed / (4 * 100), SMBHSTCLK);
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	dev_dbg(&sch_adapter.dev, "access size: %d %s\n", size,
16462306a36Sopenharmony_ci		(read_write)?"READ":"WRITE");
16562306a36Sopenharmony_ci	switch (size) {
16662306a36Sopenharmony_ci	case I2C_SMBUS_QUICK:
16762306a36Sopenharmony_ci		outb((addr << 1) | read_write, SMBHSTADD);
16862306a36Sopenharmony_ci		size = SCH_QUICK;
16962306a36Sopenharmony_ci		break;
17062306a36Sopenharmony_ci	case I2C_SMBUS_BYTE:
17162306a36Sopenharmony_ci		outb((addr << 1) | read_write, SMBHSTADD);
17262306a36Sopenharmony_ci		if (read_write == I2C_SMBUS_WRITE)
17362306a36Sopenharmony_ci			outb(command, SMBHSTCMD);
17462306a36Sopenharmony_ci		size = SCH_BYTE;
17562306a36Sopenharmony_ci		break;
17662306a36Sopenharmony_ci	case I2C_SMBUS_BYTE_DATA:
17762306a36Sopenharmony_ci		outb((addr << 1) | read_write, SMBHSTADD);
17862306a36Sopenharmony_ci		outb(command, SMBHSTCMD);
17962306a36Sopenharmony_ci		if (read_write == I2C_SMBUS_WRITE)
18062306a36Sopenharmony_ci			outb(data->byte, SMBHSTDAT0);
18162306a36Sopenharmony_ci		size = SCH_BYTE_DATA;
18262306a36Sopenharmony_ci		break;
18362306a36Sopenharmony_ci	case I2C_SMBUS_WORD_DATA:
18462306a36Sopenharmony_ci		outb((addr << 1) | read_write, SMBHSTADD);
18562306a36Sopenharmony_ci		outb(command, SMBHSTCMD);
18662306a36Sopenharmony_ci		if (read_write == I2C_SMBUS_WRITE) {
18762306a36Sopenharmony_ci			outb(data->word & 0xff, SMBHSTDAT0);
18862306a36Sopenharmony_ci			outb((data->word & 0xff00) >> 8, SMBHSTDAT1);
18962306a36Sopenharmony_ci		}
19062306a36Sopenharmony_ci		size = SCH_WORD_DATA;
19162306a36Sopenharmony_ci		break;
19262306a36Sopenharmony_ci	case I2C_SMBUS_BLOCK_DATA:
19362306a36Sopenharmony_ci		outb((addr << 1) | read_write, SMBHSTADD);
19462306a36Sopenharmony_ci		outb(command, SMBHSTCMD);
19562306a36Sopenharmony_ci		if (read_write == I2C_SMBUS_WRITE) {
19662306a36Sopenharmony_ci			len = data->block[0];
19762306a36Sopenharmony_ci			if (len == 0 || len > I2C_SMBUS_BLOCK_MAX)
19862306a36Sopenharmony_ci				return -EINVAL;
19962306a36Sopenharmony_ci			outb(len, SMBHSTDAT0);
20062306a36Sopenharmony_ci			for (i = 1; i <= len; i++)
20162306a36Sopenharmony_ci				outb(data->block[i], SMBBLKDAT+i-1);
20262306a36Sopenharmony_ci		}
20362306a36Sopenharmony_ci		size = SCH_BLOCK_DATA;
20462306a36Sopenharmony_ci		break;
20562306a36Sopenharmony_ci	default:
20662306a36Sopenharmony_ci		dev_warn(&adap->dev, "Unsupported transaction %d\n", size);
20762306a36Sopenharmony_ci		return -EOPNOTSUPP;
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci	dev_dbg(&sch_adapter.dev, "write size %d to 0x%04x\n", size, SMBHSTCNT);
21062306a36Sopenharmony_ci	outb((inb(SMBHSTCNT) & 0xb0) | (size & 0x7), SMBHSTCNT);
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	rc = sch_transaction();
21362306a36Sopenharmony_ci	if (rc)	/* Error in transaction */
21462306a36Sopenharmony_ci		return rc;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	if ((read_write == I2C_SMBUS_WRITE) || (size == SCH_QUICK))
21762306a36Sopenharmony_ci		return 0;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	switch (size) {
22062306a36Sopenharmony_ci	case SCH_BYTE:
22162306a36Sopenharmony_ci	case SCH_BYTE_DATA:
22262306a36Sopenharmony_ci		data->byte = inb(SMBHSTDAT0);
22362306a36Sopenharmony_ci		break;
22462306a36Sopenharmony_ci	case SCH_WORD_DATA:
22562306a36Sopenharmony_ci		data->word = inb(SMBHSTDAT0) + (inb(SMBHSTDAT1) << 8);
22662306a36Sopenharmony_ci		break;
22762306a36Sopenharmony_ci	case SCH_BLOCK_DATA:
22862306a36Sopenharmony_ci		data->block[0] = inb(SMBHSTDAT0);
22962306a36Sopenharmony_ci		if (data->block[0] == 0 || data->block[0] > I2C_SMBUS_BLOCK_MAX)
23062306a36Sopenharmony_ci			return -EPROTO;
23162306a36Sopenharmony_ci		for (i = 1; i <= data->block[0]; i++)
23262306a36Sopenharmony_ci			data->block[i] = inb(SMBBLKDAT+i-1);
23362306a36Sopenharmony_ci		break;
23462306a36Sopenharmony_ci	}
23562306a36Sopenharmony_ci	return 0;
23662306a36Sopenharmony_ci}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_cistatic u32 sch_func(struct i2c_adapter *adapter)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
24162306a36Sopenharmony_ci	    I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
24262306a36Sopenharmony_ci	    I2C_FUNC_SMBUS_BLOCK_DATA;
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_cistatic const struct i2c_algorithm smbus_algorithm = {
24662306a36Sopenharmony_ci	.smbus_xfer	= sch_access,
24762306a36Sopenharmony_ci	.functionality	= sch_func,
24862306a36Sopenharmony_ci};
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic struct i2c_adapter sch_adapter = {
25162306a36Sopenharmony_ci	.owner		= THIS_MODULE,
25262306a36Sopenharmony_ci	.class		= I2C_CLASS_HWMON | I2C_CLASS_SPD,
25362306a36Sopenharmony_ci	.algo		= &smbus_algorithm,
25462306a36Sopenharmony_ci};
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cistatic int smbus_sch_probe(struct platform_device *dev)
25762306a36Sopenharmony_ci{
25862306a36Sopenharmony_ci	struct resource *res;
25962306a36Sopenharmony_ci	int retval;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	res = platform_get_resource(dev, IORESOURCE_IO, 0);
26262306a36Sopenharmony_ci	if (!res)
26362306a36Sopenharmony_ci		return -EBUSY;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	if (!devm_request_region(&dev->dev, res->start, resource_size(res),
26662306a36Sopenharmony_ci				 dev->name)) {
26762306a36Sopenharmony_ci		dev_err(&dev->dev, "SMBus region 0x%x already in use!\n",
26862306a36Sopenharmony_ci			sch_smba);
26962306a36Sopenharmony_ci		return -EBUSY;
27062306a36Sopenharmony_ci	}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	sch_smba = res->start;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	dev_dbg(&dev->dev, "SMBA = 0x%X\n", sch_smba);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	/* set up the sysfs linkage to our parent device */
27762306a36Sopenharmony_ci	sch_adapter.dev.parent = &dev->dev;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	snprintf(sch_adapter.name, sizeof(sch_adapter.name),
28062306a36Sopenharmony_ci		"SMBus SCH adapter at %04x", sch_smba);
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	retval = i2c_add_adapter(&sch_adapter);
28362306a36Sopenharmony_ci	if (retval)
28462306a36Sopenharmony_ci		sch_smba = 0;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	return retval;
28762306a36Sopenharmony_ci}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_cistatic void smbus_sch_remove(struct platform_device *pdev)
29062306a36Sopenharmony_ci{
29162306a36Sopenharmony_ci	if (sch_smba) {
29262306a36Sopenharmony_ci		i2c_del_adapter(&sch_adapter);
29362306a36Sopenharmony_ci		sch_smba = 0;
29462306a36Sopenharmony_ci	}
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_cistatic struct platform_driver smbus_sch_driver = {
29862306a36Sopenharmony_ci	.driver = {
29962306a36Sopenharmony_ci		.name = "isch_smbus",
30062306a36Sopenharmony_ci	},
30162306a36Sopenharmony_ci	.probe		= smbus_sch_probe,
30262306a36Sopenharmony_ci	.remove_new	= smbus_sch_remove,
30362306a36Sopenharmony_ci};
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_cimodule_platform_driver(smbus_sch_driver);
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ciMODULE_AUTHOR("Jacob Pan <jacob.jun.pan@intel.com>");
30862306a36Sopenharmony_ciMODULE_DESCRIPTION("Intel SCH SMBus driver");
30962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
31062306a36Sopenharmony_ciMODULE_ALIAS("platform:isch_smbus");
311