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