// SPDX-License-Identifier: GPL-2.0-or-later /* * IBM OPAL I2C driver * Copyright (C) 2014 IBM */ #include <linux/device.h> #include <linux/i2c.h> #include <linux/kernel.h> #include <linux/mm.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <asm/firmware.h> #include <asm/opal.h> static int i2c_opal_translate_error(int rc) { switch (rc) { case OPAL_NO_MEM: return -ENOMEM; case OPAL_PARAMETER: return -EINVAL; case OPAL_I2C_ARBT_LOST: return -EAGAIN; case OPAL_I2C_TIMEOUT: return -ETIMEDOUT; case OPAL_I2C_NACK_RCVD: return -ENXIO; case OPAL_I2C_STOP_ERR: return -EBUSY; default: return -EIO; } } static int i2c_opal_send_request(u32 bus_id, struct opal_i2c_request *req) { struct opal_msg msg; int token, rc; token = opal_async_get_token_interruptible(); if (token < 0) { if (token != -ERESTARTSYS) pr_err("Failed to get the async token\n"); return token; } rc = opal_i2c_request(token, bus_id, req); if (rc != OPAL_ASYNC_COMPLETION) { rc = i2c_opal_translate_error(rc); goto exit; } rc = opal_async_wait_response(token, &msg); if (rc) goto exit; rc = opal_get_async_rc(msg); if (rc != OPAL_SUCCESS) { rc = i2c_opal_translate_error(rc); goto exit; } exit: opal_async_release_token(token); return rc; } static int i2c_opal_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { unsigned long opal_id = (unsigned long)adap->algo_data; struct opal_i2c_request req; int rc, i; /* We only support fairly simple combinations here of one * or two messages */ memset(&req, 0, sizeof(req)); switch(num) { case 1: req.type = (msgs[0].flags & I2C_M_RD) ? OPAL_I2C_RAW_READ : OPAL_I2C_RAW_WRITE; req.addr = cpu_to_be16(msgs[0].addr); req.size = cpu_to_be32(msgs[0].len); req.buffer_ra = cpu_to_be64(__pa(msgs[0].buf)); break; case 2: req.type = (msgs[1].flags & I2C_M_RD) ? OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE; req.addr = cpu_to_be16(msgs[0].addr); req.subaddr_sz = msgs[0].len; for (i = 0; i < msgs[0].len; i++) req.subaddr = (req.subaddr << 8) | msgs[0].buf[i]; req.subaddr = cpu_to_be32(req.subaddr); req.size = cpu_to_be32(msgs[1].len); req.buffer_ra = cpu_to_be64(__pa(msgs[1].buf)); break; } rc = i2c_opal_send_request(opal_id, &req); if (rc) return rc; return num; } static int i2c_opal_smbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data) { unsigned long opal_id = (unsigned long)adap->algo_data; struct opal_i2c_request req; u8 local[2]; int rc; memset(&req, 0, sizeof(req)); req.addr = cpu_to_be16(addr); switch (size) { case I2C_SMBUS_BYTE: req.buffer_ra = cpu_to_be64(__pa(&data->byte)); req.size = cpu_to_be32(1); fallthrough; case I2C_SMBUS_QUICK: req.type = (read_write == I2C_SMBUS_READ) ? OPAL_I2C_RAW_READ : OPAL_I2C_RAW_WRITE; break; case I2C_SMBUS_BYTE_DATA: req.buffer_ra = cpu_to_be64(__pa(&data->byte)); req.size = cpu_to_be32(1); req.subaddr = cpu_to_be32(command); req.subaddr_sz = 1; req.type = (read_write == I2C_SMBUS_READ) ? OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE; break; case I2C_SMBUS_WORD_DATA: if (!read_write) { local[0] = data->word & 0xff; local[1] = (data->word >> 8) & 0xff; } req.buffer_ra = cpu_to_be64(__pa(local)); req.size = cpu_to_be32(2); req.subaddr = cpu_to_be32(command); req.subaddr_sz = 1; req.type = (read_write == I2C_SMBUS_READ) ? OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE; break; case I2C_SMBUS_I2C_BLOCK_DATA: req.buffer_ra = cpu_to_be64(__pa(&data->block[1])); req.size = cpu_to_be32(data->block[0]); req.subaddr = cpu_to_be32(command); req.subaddr_sz = 1; req.type = (read_write == I2C_SMBUS_READ) ? OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE; break; default: return -EINVAL; } rc = i2c_opal_send_request(opal_id, &req); if (!rc && read_write && size == I2C_SMBUS_WORD_DATA) { data->word = ((u16)local[1]) << 8; data->word |= local[0]; } return rc; } static u32 i2c_opal_func(struct i2c_adapter *adapter) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK; } static const struct i2c_algorithm i2c_opal_algo = { .master_xfer = i2c_opal_master_xfer, .smbus_xfer = i2c_opal_smbus_xfer, .functionality = i2c_opal_func, }; /* * For two messages, we basically support simple smbus transactions of a * write-then-anything. */ static const struct i2c_adapter_quirks i2c_opal_quirks = { .flags = I2C_AQ_COMB | I2C_AQ_COMB_WRITE_FIRST | I2C_AQ_COMB_SAME_ADDR, .max_comb_1st_msg_len = 4, }; static int i2c_opal_probe(struct platform_device *pdev) { struct i2c_adapter *adapter; const char *pname; u32 opal_id; int rc; if (!pdev->dev.of_node) return -ENODEV; rc = of_property_read_u32(pdev->dev.of_node, "ibm,opal-id", &opal_id); if (rc) { dev_err(&pdev->dev, "Missing ibm,opal-id property !\n"); return -EIO; } adapter = devm_kzalloc(&pdev->dev, sizeof(*adapter), GFP_KERNEL); if (!adapter) return -ENOMEM; adapter->algo = &i2c_opal_algo; adapter->algo_data = (void *)(unsigned long)opal_id; adapter->quirks = &i2c_opal_quirks; adapter->dev.parent = &pdev->dev; adapter->dev.of_node = of_node_get(pdev->dev.of_node); pname = of_get_property(pdev->dev.of_node, "ibm,port-name", NULL); if (pname) strscpy(adapter->name, pname, sizeof(adapter->name)); else strscpy(adapter->name, "opal", sizeof(adapter->name)); platform_set_drvdata(pdev, adapter); rc = i2c_add_adapter(adapter); if (rc) dev_err(&pdev->dev, "Failed to register the i2c adapter\n"); return rc; } static void i2c_opal_remove(struct platform_device *pdev) { struct i2c_adapter *adapter = platform_get_drvdata(pdev); i2c_del_adapter(adapter); } static const struct of_device_id i2c_opal_of_match[] = { { .compatible = "ibm,opal-i2c", }, { } }; MODULE_DEVICE_TABLE(of, i2c_opal_of_match); static struct platform_driver i2c_opal_driver = { .probe = i2c_opal_probe, .remove_new = i2c_opal_remove, .driver = { .name = "i2c-opal", .of_match_table = i2c_opal_of_match, }, }; static int __init i2c_opal_init(void) { if (!firmware_has_feature(FW_FEATURE_OPAL)) return -ENODEV; return platform_driver_register(&i2c_opal_driver); } module_init(i2c_opal_init); static void __exit i2c_opal_exit(void) { return platform_driver_unregister(&i2c_opal_driver); } module_exit(i2c_opal_exit); MODULE_AUTHOR("Neelesh Gupta <neelegup@linux.vnet.ibm.com>"); MODULE_DESCRIPTION("IBM OPAL I2C driver"); MODULE_LICENSE("GPL");