18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * IBM OPAL I2C driver
48c2ecf20Sopenharmony_ci * Copyright (C) 2014 IBM
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/device.h>
88c2ecf20Sopenharmony_ci#include <linux/i2c.h>
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <linux/mm.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/of.h>
138c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
148c2ecf20Sopenharmony_ci#include <linux/slab.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include <asm/firmware.h>
178c2ecf20Sopenharmony_ci#include <asm/opal.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistatic int i2c_opal_translate_error(int rc)
208c2ecf20Sopenharmony_ci{
218c2ecf20Sopenharmony_ci	switch (rc) {
228c2ecf20Sopenharmony_ci	case OPAL_NO_MEM:
238c2ecf20Sopenharmony_ci		return -ENOMEM;
248c2ecf20Sopenharmony_ci	case OPAL_PARAMETER:
258c2ecf20Sopenharmony_ci		return -EINVAL;
268c2ecf20Sopenharmony_ci	case OPAL_I2C_ARBT_LOST:
278c2ecf20Sopenharmony_ci		return -EAGAIN;
288c2ecf20Sopenharmony_ci	case OPAL_I2C_TIMEOUT:
298c2ecf20Sopenharmony_ci		return -ETIMEDOUT;
308c2ecf20Sopenharmony_ci	case OPAL_I2C_NACK_RCVD:
318c2ecf20Sopenharmony_ci		return -ENXIO;
328c2ecf20Sopenharmony_ci	case OPAL_I2C_STOP_ERR:
338c2ecf20Sopenharmony_ci		return -EBUSY;
348c2ecf20Sopenharmony_ci	default:
358c2ecf20Sopenharmony_ci		return -EIO;
368c2ecf20Sopenharmony_ci	}
378c2ecf20Sopenharmony_ci}
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic int i2c_opal_send_request(u32 bus_id, struct opal_i2c_request *req)
408c2ecf20Sopenharmony_ci{
418c2ecf20Sopenharmony_ci	struct opal_msg msg;
428c2ecf20Sopenharmony_ci	int token, rc;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	token = opal_async_get_token_interruptible();
458c2ecf20Sopenharmony_ci	if (token < 0) {
468c2ecf20Sopenharmony_ci		if (token != -ERESTARTSYS)
478c2ecf20Sopenharmony_ci			pr_err("Failed to get the async token\n");
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci		return token;
508c2ecf20Sopenharmony_ci	}
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	rc = opal_i2c_request(token, bus_id, req);
538c2ecf20Sopenharmony_ci	if (rc != OPAL_ASYNC_COMPLETION) {
548c2ecf20Sopenharmony_ci		rc = i2c_opal_translate_error(rc);
558c2ecf20Sopenharmony_ci		goto exit;
568c2ecf20Sopenharmony_ci	}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	rc = opal_async_wait_response(token, &msg);
598c2ecf20Sopenharmony_ci	if (rc)
608c2ecf20Sopenharmony_ci		goto exit;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	rc = opal_get_async_rc(msg);
638c2ecf20Sopenharmony_ci	if (rc != OPAL_SUCCESS) {
648c2ecf20Sopenharmony_ci		rc = i2c_opal_translate_error(rc);
658c2ecf20Sopenharmony_ci		goto exit;
668c2ecf20Sopenharmony_ci	}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ciexit:
698c2ecf20Sopenharmony_ci	opal_async_release_token(token);
708c2ecf20Sopenharmony_ci	return rc;
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic int i2c_opal_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
748c2ecf20Sopenharmony_ci				int num)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	unsigned long opal_id = (unsigned long)adap->algo_data;
778c2ecf20Sopenharmony_ci	struct opal_i2c_request req;
788c2ecf20Sopenharmony_ci	int rc, i;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	/* We only support fairly simple combinations here of one
818c2ecf20Sopenharmony_ci	 * or two messages
828c2ecf20Sopenharmony_ci	 */
838c2ecf20Sopenharmony_ci	memset(&req, 0, sizeof(req));
848c2ecf20Sopenharmony_ci	switch(num) {
858c2ecf20Sopenharmony_ci	case 1:
868c2ecf20Sopenharmony_ci		req.type = (msgs[0].flags & I2C_M_RD) ?
878c2ecf20Sopenharmony_ci			OPAL_I2C_RAW_READ : OPAL_I2C_RAW_WRITE;
888c2ecf20Sopenharmony_ci		req.addr = cpu_to_be16(msgs[0].addr);
898c2ecf20Sopenharmony_ci		req.size = cpu_to_be32(msgs[0].len);
908c2ecf20Sopenharmony_ci		req.buffer_ra = cpu_to_be64(__pa(msgs[0].buf));
918c2ecf20Sopenharmony_ci		break;
928c2ecf20Sopenharmony_ci	case 2:
938c2ecf20Sopenharmony_ci		req.type = (msgs[1].flags & I2C_M_RD) ?
948c2ecf20Sopenharmony_ci			OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE;
958c2ecf20Sopenharmony_ci		req.addr = cpu_to_be16(msgs[0].addr);
968c2ecf20Sopenharmony_ci		req.subaddr_sz = msgs[0].len;
978c2ecf20Sopenharmony_ci		for (i = 0; i < msgs[0].len; i++)
988c2ecf20Sopenharmony_ci			req.subaddr = (req.subaddr << 8) | msgs[0].buf[i];
998c2ecf20Sopenharmony_ci		req.subaddr = cpu_to_be32(req.subaddr);
1008c2ecf20Sopenharmony_ci		req.size = cpu_to_be32(msgs[1].len);
1018c2ecf20Sopenharmony_ci		req.buffer_ra = cpu_to_be64(__pa(msgs[1].buf));
1028c2ecf20Sopenharmony_ci		break;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	rc = i2c_opal_send_request(opal_id, &req);
1068c2ecf20Sopenharmony_ci	if (rc)
1078c2ecf20Sopenharmony_ci		return rc;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	return num;
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic int i2c_opal_smbus_xfer(struct i2c_adapter *adap, u16 addr,
1138c2ecf20Sopenharmony_ci			       unsigned short flags, char read_write,
1148c2ecf20Sopenharmony_ci			       u8 command, int size, union i2c_smbus_data *data)
1158c2ecf20Sopenharmony_ci{
1168c2ecf20Sopenharmony_ci	unsigned long opal_id = (unsigned long)adap->algo_data;
1178c2ecf20Sopenharmony_ci	struct opal_i2c_request req;
1188c2ecf20Sopenharmony_ci	u8 local[2];
1198c2ecf20Sopenharmony_ci	int rc;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	memset(&req, 0, sizeof(req));
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	req.addr = cpu_to_be16(addr);
1248c2ecf20Sopenharmony_ci	switch (size) {
1258c2ecf20Sopenharmony_ci	case I2C_SMBUS_BYTE:
1268c2ecf20Sopenharmony_ci		req.buffer_ra = cpu_to_be64(__pa(&data->byte));
1278c2ecf20Sopenharmony_ci		req.size = cpu_to_be32(1);
1288c2ecf20Sopenharmony_ci		fallthrough;
1298c2ecf20Sopenharmony_ci	case I2C_SMBUS_QUICK:
1308c2ecf20Sopenharmony_ci		req.type = (read_write == I2C_SMBUS_READ) ?
1318c2ecf20Sopenharmony_ci			OPAL_I2C_RAW_READ : OPAL_I2C_RAW_WRITE;
1328c2ecf20Sopenharmony_ci		break;
1338c2ecf20Sopenharmony_ci	case I2C_SMBUS_BYTE_DATA:
1348c2ecf20Sopenharmony_ci		req.buffer_ra = cpu_to_be64(__pa(&data->byte));
1358c2ecf20Sopenharmony_ci		req.size = cpu_to_be32(1);
1368c2ecf20Sopenharmony_ci		req.subaddr = cpu_to_be32(command);
1378c2ecf20Sopenharmony_ci		req.subaddr_sz = 1;
1388c2ecf20Sopenharmony_ci		req.type = (read_write == I2C_SMBUS_READ) ?
1398c2ecf20Sopenharmony_ci			OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE;
1408c2ecf20Sopenharmony_ci		break;
1418c2ecf20Sopenharmony_ci	case I2C_SMBUS_WORD_DATA:
1428c2ecf20Sopenharmony_ci		if (!read_write) {
1438c2ecf20Sopenharmony_ci			local[0] = data->word & 0xff;
1448c2ecf20Sopenharmony_ci			local[1] = (data->word >> 8) & 0xff;
1458c2ecf20Sopenharmony_ci		}
1468c2ecf20Sopenharmony_ci		req.buffer_ra = cpu_to_be64(__pa(local));
1478c2ecf20Sopenharmony_ci		req.size = cpu_to_be32(2);
1488c2ecf20Sopenharmony_ci		req.subaddr = cpu_to_be32(command);
1498c2ecf20Sopenharmony_ci		req.subaddr_sz = 1;
1508c2ecf20Sopenharmony_ci		req.type = (read_write == I2C_SMBUS_READ) ?
1518c2ecf20Sopenharmony_ci			OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE;
1528c2ecf20Sopenharmony_ci		break;
1538c2ecf20Sopenharmony_ci	case I2C_SMBUS_I2C_BLOCK_DATA:
1548c2ecf20Sopenharmony_ci		req.buffer_ra = cpu_to_be64(__pa(&data->block[1]));
1558c2ecf20Sopenharmony_ci		req.size = cpu_to_be32(data->block[0]);
1568c2ecf20Sopenharmony_ci		req.subaddr = cpu_to_be32(command);
1578c2ecf20Sopenharmony_ci		req.subaddr_sz = 1;
1588c2ecf20Sopenharmony_ci		req.type = (read_write == I2C_SMBUS_READ) ?
1598c2ecf20Sopenharmony_ci			OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE;
1608c2ecf20Sopenharmony_ci		break;
1618c2ecf20Sopenharmony_ci	default:
1628c2ecf20Sopenharmony_ci		return -EINVAL;
1638c2ecf20Sopenharmony_ci	}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	rc = i2c_opal_send_request(opal_id, &req);
1668c2ecf20Sopenharmony_ci	if (!rc && read_write && size == I2C_SMBUS_WORD_DATA) {
1678c2ecf20Sopenharmony_ci		data->word = ((u16)local[1]) << 8;
1688c2ecf20Sopenharmony_ci		data->word |= local[0];
1698c2ecf20Sopenharmony_ci	}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	return rc;
1728c2ecf20Sopenharmony_ci}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_cistatic u32 i2c_opal_func(struct i2c_adapter *adapter)
1758c2ecf20Sopenharmony_ci{
1768c2ecf20Sopenharmony_ci	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
1778c2ecf20Sopenharmony_ci	       I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
1788c2ecf20Sopenharmony_ci	       I2C_FUNC_SMBUS_I2C_BLOCK;
1798c2ecf20Sopenharmony_ci}
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_cistatic const struct i2c_algorithm i2c_opal_algo = {
1828c2ecf20Sopenharmony_ci	.master_xfer	= i2c_opal_master_xfer,
1838c2ecf20Sopenharmony_ci	.smbus_xfer	= i2c_opal_smbus_xfer,
1848c2ecf20Sopenharmony_ci	.functionality	= i2c_opal_func,
1858c2ecf20Sopenharmony_ci};
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci/*
1888c2ecf20Sopenharmony_ci * For two messages, we basically support simple smbus transactions of a
1898c2ecf20Sopenharmony_ci * write-then-anything.
1908c2ecf20Sopenharmony_ci */
1918c2ecf20Sopenharmony_cistatic const struct i2c_adapter_quirks i2c_opal_quirks = {
1928c2ecf20Sopenharmony_ci	.flags = I2C_AQ_COMB | I2C_AQ_COMB_WRITE_FIRST | I2C_AQ_COMB_SAME_ADDR,
1938c2ecf20Sopenharmony_ci	.max_comb_1st_msg_len = 4,
1948c2ecf20Sopenharmony_ci};
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_cistatic int i2c_opal_probe(struct platform_device *pdev)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	struct i2c_adapter	*adapter;
1998c2ecf20Sopenharmony_ci	const char		*pname;
2008c2ecf20Sopenharmony_ci	u32			opal_id;
2018c2ecf20Sopenharmony_ci	int			rc;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	if (!pdev->dev.of_node)
2048c2ecf20Sopenharmony_ci		return -ENODEV;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	rc = of_property_read_u32(pdev->dev.of_node, "ibm,opal-id", &opal_id);
2078c2ecf20Sopenharmony_ci	if (rc) {
2088c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Missing ibm,opal-id property !\n");
2098c2ecf20Sopenharmony_ci		return -EIO;
2108c2ecf20Sopenharmony_ci	}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	adapter = devm_kzalloc(&pdev->dev, sizeof(*adapter), GFP_KERNEL);
2138c2ecf20Sopenharmony_ci	if (!adapter)
2148c2ecf20Sopenharmony_ci		return -ENOMEM;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	adapter->algo = &i2c_opal_algo;
2178c2ecf20Sopenharmony_ci	adapter->algo_data = (void *)(unsigned long)opal_id;
2188c2ecf20Sopenharmony_ci	adapter->quirks = &i2c_opal_quirks;
2198c2ecf20Sopenharmony_ci	adapter->dev.parent = &pdev->dev;
2208c2ecf20Sopenharmony_ci	adapter->dev.of_node = of_node_get(pdev->dev.of_node);
2218c2ecf20Sopenharmony_ci	pname = of_get_property(pdev->dev.of_node, "ibm,port-name", NULL);
2228c2ecf20Sopenharmony_ci	if (pname)
2238c2ecf20Sopenharmony_ci		strlcpy(adapter->name, pname, sizeof(adapter->name));
2248c2ecf20Sopenharmony_ci	else
2258c2ecf20Sopenharmony_ci		strlcpy(adapter->name, "opal", sizeof(adapter->name));
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, adapter);
2288c2ecf20Sopenharmony_ci	rc = i2c_add_adapter(adapter);
2298c2ecf20Sopenharmony_ci	if (rc)
2308c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Failed to register the i2c adapter\n");
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	return rc;
2338c2ecf20Sopenharmony_ci}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_cistatic int i2c_opal_remove(struct platform_device *pdev)
2368c2ecf20Sopenharmony_ci{
2378c2ecf20Sopenharmony_ci	struct i2c_adapter *adapter = platform_get_drvdata(pdev);
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	i2c_del_adapter(adapter);
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	return 0;
2428c2ecf20Sopenharmony_ci}
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_cistatic const struct of_device_id i2c_opal_of_match[] = {
2458c2ecf20Sopenharmony_ci	{
2468c2ecf20Sopenharmony_ci		.compatible = "ibm,opal-i2c",
2478c2ecf20Sopenharmony_ci	},
2488c2ecf20Sopenharmony_ci	{ }
2498c2ecf20Sopenharmony_ci};
2508c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, i2c_opal_of_match);
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_cistatic struct platform_driver i2c_opal_driver = {
2538c2ecf20Sopenharmony_ci	.probe	= i2c_opal_probe,
2548c2ecf20Sopenharmony_ci	.remove	= i2c_opal_remove,
2558c2ecf20Sopenharmony_ci	.driver	= {
2568c2ecf20Sopenharmony_ci		.name		= "i2c-opal",
2578c2ecf20Sopenharmony_ci		.of_match_table	= i2c_opal_of_match,
2588c2ecf20Sopenharmony_ci	},
2598c2ecf20Sopenharmony_ci};
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cistatic int __init i2c_opal_init(void)
2628c2ecf20Sopenharmony_ci{
2638c2ecf20Sopenharmony_ci	if (!firmware_has_feature(FW_FEATURE_OPAL))
2648c2ecf20Sopenharmony_ci		return -ENODEV;
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	return platform_driver_register(&i2c_opal_driver);
2678c2ecf20Sopenharmony_ci}
2688c2ecf20Sopenharmony_cimodule_init(i2c_opal_init);
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_cistatic void __exit i2c_opal_exit(void)
2718c2ecf20Sopenharmony_ci{
2728c2ecf20Sopenharmony_ci	return platform_driver_unregister(&i2c_opal_driver);
2738c2ecf20Sopenharmony_ci}
2748c2ecf20Sopenharmony_cimodule_exit(i2c_opal_exit);
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ciMODULE_AUTHOR("Neelesh Gupta <neelegup@linux.vnet.ibm.com>");
2778c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("IBM OPAL I2C driver");
2788c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
279