162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * IBM OPAL I2C driver
462306a36Sopenharmony_ci * Copyright (C) 2014 IBM
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/device.h>
862306a36Sopenharmony_ci#include <linux/i2c.h>
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/mm.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/of.h>
1362306a36Sopenharmony_ci#include <linux/platform_device.h>
1462306a36Sopenharmony_ci#include <linux/slab.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <asm/firmware.h>
1762306a36Sopenharmony_ci#include <asm/opal.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistatic int i2c_opal_translate_error(int rc)
2062306a36Sopenharmony_ci{
2162306a36Sopenharmony_ci	switch (rc) {
2262306a36Sopenharmony_ci	case OPAL_NO_MEM:
2362306a36Sopenharmony_ci		return -ENOMEM;
2462306a36Sopenharmony_ci	case OPAL_PARAMETER:
2562306a36Sopenharmony_ci		return -EINVAL;
2662306a36Sopenharmony_ci	case OPAL_I2C_ARBT_LOST:
2762306a36Sopenharmony_ci		return -EAGAIN;
2862306a36Sopenharmony_ci	case OPAL_I2C_TIMEOUT:
2962306a36Sopenharmony_ci		return -ETIMEDOUT;
3062306a36Sopenharmony_ci	case OPAL_I2C_NACK_RCVD:
3162306a36Sopenharmony_ci		return -ENXIO;
3262306a36Sopenharmony_ci	case OPAL_I2C_STOP_ERR:
3362306a36Sopenharmony_ci		return -EBUSY;
3462306a36Sopenharmony_ci	default:
3562306a36Sopenharmony_ci		return -EIO;
3662306a36Sopenharmony_ci	}
3762306a36Sopenharmony_ci}
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic int i2c_opal_send_request(u32 bus_id, struct opal_i2c_request *req)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	struct opal_msg msg;
4262306a36Sopenharmony_ci	int token, rc;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	token = opal_async_get_token_interruptible();
4562306a36Sopenharmony_ci	if (token < 0) {
4662306a36Sopenharmony_ci		if (token != -ERESTARTSYS)
4762306a36Sopenharmony_ci			pr_err("Failed to get the async token\n");
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci		return token;
5062306a36Sopenharmony_ci	}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	rc = opal_i2c_request(token, bus_id, req);
5362306a36Sopenharmony_ci	if (rc != OPAL_ASYNC_COMPLETION) {
5462306a36Sopenharmony_ci		rc = i2c_opal_translate_error(rc);
5562306a36Sopenharmony_ci		goto exit;
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	rc = opal_async_wait_response(token, &msg);
5962306a36Sopenharmony_ci	if (rc)
6062306a36Sopenharmony_ci		goto exit;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	rc = opal_get_async_rc(msg);
6362306a36Sopenharmony_ci	if (rc != OPAL_SUCCESS) {
6462306a36Sopenharmony_ci		rc = i2c_opal_translate_error(rc);
6562306a36Sopenharmony_ci		goto exit;
6662306a36Sopenharmony_ci	}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ciexit:
6962306a36Sopenharmony_ci	opal_async_release_token(token);
7062306a36Sopenharmony_ci	return rc;
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic int i2c_opal_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
7462306a36Sopenharmony_ci				int num)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	unsigned long opal_id = (unsigned long)adap->algo_data;
7762306a36Sopenharmony_ci	struct opal_i2c_request req;
7862306a36Sopenharmony_ci	int rc, i;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	/* We only support fairly simple combinations here of one
8162306a36Sopenharmony_ci	 * or two messages
8262306a36Sopenharmony_ci	 */
8362306a36Sopenharmony_ci	memset(&req, 0, sizeof(req));
8462306a36Sopenharmony_ci	switch(num) {
8562306a36Sopenharmony_ci	case 1:
8662306a36Sopenharmony_ci		req.type = (msgs[0].flags & I2C_M_RD) ?
8762306a36Sopenharmony_ci			OPAL_I2C_RAW_READ : OPAL_I2C_RAW_WRITE;
8862306a36Sopenharmony_ci		req.addr = cpu_to_be16(msgs[0].addr);
8962306a36Sopenharmony_ci		req.size = cpu_to_be32(msgs[0].len);
9062306a36Sopenharmony_ci		req.buffer_ra = cpu_to_be64(__pa(msgs[0].buf));
9162306a36Sopenharmony_ci		break;
9262306a36Sopenharmony_ci	case 2:
9362306a36Sopenharmony_ci		req.type = (msgs[1].flags & I2C_M_RD) ?
9462306a36Sopenharmony_ci			OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE;
9562306a36Sopenharmony_ci		req.addr = cpu_to_be16(msgs[0].addr);
9662306a36Sopenharmony_ci		req.subaddr_sz = msgs[0].len;
9762306a36Sopenharmony_ci		for (i = 0; i < msgs[0].len; i++)
9862306a36Sopenharmony_ci			req.subaddr = (req.subaddr << 8) | msgs[0].buf[i];
9962306a36Sopenharmony_ci		req.subaddr = cpu_to_be32(req.subaddr);
10062306a36Sopenharmony_ci		req.size = cpu_to_be32(msgs[1].len);
10162306a36Sopenharmony_ci		req.buffer_ra = cpu_to_be64(__pa(msgs[1].buf));
10262306a36Sopenharmony_ci		break;
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	rc = i2c_opal_send_request(opal_id, &req);
10662306a36Sopenharmony_ci	if (rc)
10762306a36Sopenharmony_ci		return rc;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	return num;
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic int i2c_opal_smbus_xfer(struct i2c_adapter *adap, u16 addr,
11362306a36Sopenharmony_ci			       unsigned short flags, char read_write,
11462306a36Sopenharmony_ci			       u8 command, int size, union i2c_smbus_data *data)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	unsigned long opal_id = (unsigned long)adap->algo_data;
11762306a36Sopenharmony_ci	struct opal_i2c_request req;
11862306a36Sopenharmony_ci	u8 local[2];
11962306a36Sopenharmony_ci	int rc;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	memset(&req, 0, sizeof(req));
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	req.addr = cpu_to_be16(addr);
12462306a36Sopenharmony_ci	switch (size) {
12562306a36Sopenharmony_ci	case I2C_SMBUS_BYTE:
12662306a36Sopenharmony_ci		req.buffer_ra = cpu_to_be64(__pa(&data->byte));
12762306a36Sopenharmony_ci		req.size = cpu_to_be32(1);
12862306a36Sopenharmony_ci		fallthrough;
12962306a36Sopenharmony_ci	case I2C_SMBUS_QUICK:
13062306a36Sopenharmony_ci		req.type = (read_write == I2C_SMBUS_READ) ?
13162306a36Sopenharmony_ci			OPAL_I2C_RAW_READ : OPAL_I2C_RAW_WRITE;
13262306a36Sopenharmony_ci		break;
13362306a36Sopenharmony_ci	case I2C_SMBUS_BYTE_DATA:
13462306a36Sopenharmony_ci		req.buffer_ra = cpu_to_be64(__pa(&data->byte));
13562306a36Sopenharmony_ci		req.size = cpu_to_be32(1);
13662306a36Sopenharmony_ci		req.subaddr = cpu_to_be32(command);
13762306a36Sopenharmony_ci		req.subaddr_sz = 1;
13862306a36Sopenharmony_ci		req.type = (read_write == I2C_SMBUS_READ) ?
13962306a36Sopenharmony_ci			OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE;
14062306a36Sopenharmony_ci		break;
14162306a36Sopenharmony_ci	case I2C_SMBUS_WORD_DATA:
14262306a36Sopenharmony_ci		if (!read_write) {
14362306a36Sopenharmony_ci			local[0] = data->word & 0xff;
14462306a36Sopenharmony_ci			local[1] = (data->word >> 8) & 0xff;
14562306a36Sopenharmony_ci		}
14662306a36Sopenharmony_ci		req.buffer_ra = cpu_to_be64(__pa(local));
14762306a36Sopenharmony_ci		req.size = cpu_to_be32(2);
14862306a36Sopenharmony_ci		req.subaddr = cpu_to_be32(command);
14962306a36Sopenharmony_ci		req.subaddr_sz = 1;
15062306a36Sopenharmony_ci		req.type = (read_write == I2C_SMBUS_READ) ?
15162306a36Sopenharmony_ci			OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE;
15262306a36Sopenharmony_ci		break;
15362306a36Sopenharmony_ci	case I2C_SMBUS_I2C_BLOCK_DATA:
15462306a36Sopenharmony_ci		req.buffer_ra = cpu_to_be64(__pa(&data->block[1]));
15562306a36Sopenharmony_ci		req.size = cpu_to_be32(data->block[0]);
15662306a36Sopenharmony_ci		req.subaddr = cpu_to_be32(command);
15762306a36Sopenharmony_ci		req.subaddr_sz = 1;
15862306a36Sopenharmony_ci		req.type = (read_write == I2C_SMBUS_READ) ?
15962306a36Sopenharmony_ci			OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE;
16062306a36Sopenharmony_ci		break;
16162306a36Sopenharmony_ci	default:
16262306a36Sopenharmony_ci		return -EINVAL;
16362306a36Sopenharmony_ci	}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	rc = i2c_opal_send_request(opal_id, &req);
16662306a36Sopenharmony_ci	if (!rc && read_write && size == I2C_SMBUS_WORD_DATA) {
16762306a36Sopenharmony_ci		data->word = ((u16)local[1]) << 8;
16862306a36Sopenharmony_ci		data->word |= local[0];
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	return rc;
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic u32 i2c_opal_func(struct i2c_adapter *adapter)
17562306a36Sopenharmony_ci{
17662306a36Sopenharmony_ci	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
17762306a36Sopenharmony_ci	       I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
17862306a36Sopenharmony_ci	       I2C_FUNC_SMBUS_I2C_BLOCK;
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistatic const struct i2c_algorithm i2c_opal_algo = {
18262306a36Sopenharmony_ci	.master_xfer	= i2c_opal_master_xfer,
18362306a36Sopenharmony_ci	.smbus_xfer	= i2c_opal_smbus_xfer,
18462306a36Sopenharmony_ci	.functionality	= i2c_opal_func,
18562306a36Sopenharmony_ci};
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci/*
18862306a36Sopenharmony_ci * For two messages, we basically support simple smbus transactions of a
18962306a36Sopenharmony_ci * write-then-anything.
19062306a36Sopenharmony_ci */
19162306a36Sopenharmony_cistatic const struct i2c_adapter_quirks i2c_opal_quirks = {
19262306a36Sopenharmony_ci	.flags = I2C_AQ_COMB | I2C_AQ_COMB_WRITE_FIRST | I2C_AQ_COMB_SAME_ADDR,
19362306a36Sopenharmony_ci	.max_comb_1st_msg_len = 4,
19462306a36Sopenharmony_ci};
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic int i2c_opal_probe(struct platform_device *pdev)
19762306a36Sopenharmony_ci{
19862306a36Sopenharmony_ci	struct i2c_adapter	*adapter;
19962306a36Sopenharmony_ci	const char		*pname;
20062306a36Sopenharmony_ci	u32			opal_id;
20162306a36Sopenharmony_ci	int			rc;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	if (!pdev->dev.of_node)
20462306a36Sopenharmony_ci		return -ENODEV;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	rc = of_property_read_u32(pdev->dev.of_node, "ibm,opal-id", &opal_id);
20762306a36Sopenharmony_ci	if (rc) {
20862306a36Sopenharmony_ci		dev_err(&pdev->dev, "Missing ibm,opal-id property !\n");
20962306a36Sopenharmony_ci		return -EIO;
21062306a36Sopenharmony_ci	}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	adapter = devm_kzalloc(&pdev->dev, sizeof(*adapter), GFP_KERNEL);
21362306a36Sopenharmony_ci	if (!adapter)
21462306a36Sopenharmony_ci		return -ENOMEM;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	adapter->algo = &i2c_opal_algo;
21762306a36Sopenharmony_ci	adapter->algo_data = (void *)(unsigned long)opal_id;
21862306a36Sopenharmony_ci	adapter->quirks = &i2c_opal_quirks;
21962306a36Sopenharmony_ci	adapter->dev.parent = &pdev->dev;
22062306a36Sopenharmony_ci	adapter->dev.of_node = of_node_get(pdev->dev.of_node);
22162306a36Sopenharmony_ci	pname = of_get_property(pdev->dev.of_node, "ibm,port-name", NULL);
22262306a36Sopenharmony_ci	if (pname)
22362306a36Sopenharmony_ci		strscpy(adapter->name, pname, sizeof(adapter->name));
22462306a36Sopenharmony_ci	else
22562306a36Sopenharmony_ci		strscpy(adapter->name, "opal", sizeof(adapter->name));
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	platform_set_drvdata(pdev, adapter);
22862306a36Sopenharmony_ci	rc = i2c_add_adapter(adapter);
22962306a36Sopenharmony_ci	if (rc)
23062306a36Sopenharmony_ci		dev_err(&pdev->dev, "Failed to register the i2c adapter\n");
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	return rc;
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic void i2c_opal_remove(struct platform_device *pdev)
23662306a36Sopenharmony_ci{
23762306a36Sopenharmony_ci	struct i2c_adapter *adapter = platform_get_drvdata(pdev);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	i2c_del_adapter(adapter);
24062306a36Sopenharmony_ci}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_cistatic const struct of_device_id i2c_opal_of_match[] = {
24362306a36Sopenharmony_ci	{
24462306a36Sopenharmony_ci		.compatible = "ibm,opal-i2c",
24562306a36Sopenharmony_ci	},
24662306a36Sopenharmony_ci	{ }
24762306a36Sopenharmony_ci};
24862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, i2c_opal_of_match);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic struct platform_driver i2c_opal_driver = {
25162306a36Sopenharmony_ci	.probe	= i2c_opal_probe,
25262306a36Sopenharmony_ci	.remove_new = i2c_opal_remove,
25362306a36Sopenharmony_ci	.driver	= {
25462306a36Sopenharmony_ci		.name		= "i2c-opal",
25562306a36Sopenharmony_ci		.of_match_table	= i2c_opal_of_match,
25662306a36Sopenharmony_ci	},
25762306a36Sopenharmony_ci};
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cistatic int __init i2c_opal_init(void)
26062306a36Sopenharmony_ci{
26162306a36Sopenharmony_ci	if (!firmware_has_feature(FW_FEATURE_OPAL))
26262306a36Sopenharmony_ci		return -ENODEV;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	return platform_driver_register(&i2c_opal_driver);
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_cimodule_init(i2c_opal_init);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic void __exit i2c_opal_exit(void)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	return platform_driver_unregister(&i2c_opal_driver);
27162306a36Sopenharmony_ci}
27262306a36Sopenharmony_cimodule_exit(i2c_opal_exit);
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ciMODULE_AUTHOR("Neelesh Gupta <neelegup@linux.vnet.ibm.com>");
27562306a36Sopenharmony_ciMODULE_DESCRIPTION("IBM OPAL I2C driver");
27662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
277