diff mbox

[v3] i2c: Driver to expose PowerNV platform i2c busses

Message ID 20141208063039.14356.34770.stgit@localhost.localdomain
State Superseded, archived
Headers show

Commit Message

Neelesh Gupta Dec. 8, 2014, 6:36 a.m. UTC
The patch exposes the available i2c busses on the PowerNV platform
to the kernel and implements the bus driver to support i2c and
smbus commands.
The driver uses the platform device infrastructure to probe the busses
on the platform and registers them with the i2c driver framework.

Signed-off-by: Neelesh Gupta <neelegup@linux.vnet.ibm.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
---

v2 -> v3:
- Added the device tree binding documentation for the driver.
- Sorted the ordering of this new driver added in Makefile.
- Removed populating the superfluous .owner field in 'struct driver'.

 Documentation/devicetree/bindings/i2c/i2c-opal.txt |   37 +++
 arch/powerpc/include/asm/opal.h                    |   29 ++
 arch/powerpc/platforms/powernv/opal-wrappers.S     |    1 
 arch/powerpc/platforms/powernv/opal.c              |   11 +
 drivers/i2c/busses/Kconfig                         |   11 +
 drivers/i2c/busses/Makefile                        |    1 
 drivers/i2c/busses/i2c-opal.c                      |  294 ++++++++++++++++++++
 7 files changed, 384 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-opal.txt
 create mode 100644 drivers/i2c/busses/i2c-opal.c


--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Benjamin Herrenschmidt Dec. 8, 2014, 8:13 p.m. UTC | #1
On Mon, 2014-12-08 at 12:06 +0530, Neelesh Gupta wrote:
> The patch exposes the available i2c busses on the PowerNV platform
> to the kernel and implements the bus driver to support i2c and
> smbus commands.
> The driver uses the platform device infrastructure to probe the busses
> on the platform and registers them with the i2c driver framework.

Wolfram, what are you remaining objections here ? We need that in
distros ASAP ...

I still maintain that it's not reasonable to hold driver for the
additions of multi-byte smbus offsets. This is a new feature that will
require changes to a number of existing bus and device drivers, so a
very pervasive change, and which will be visible to user space, which
means that drivers will need to continue supporting the "old" way at
least for a while anyway...

Ben.

> Signed-off-by: Neelesh Gupta <neelegup@linux.vnet.ibm.com>
> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
> ---
> 
> v2 -> v3:
> - Added the device tree binding documentation for the driver.
> - Sorted the ordering of this new driver added in Makefile.
> - Removed populating the superfluous .owner field in 'struct driver'.
> 
>  Documentation/devicetree/bindings/i2c/i2c-opal.txt |   37 +++
>  arch/powerpc/include/asm/opal.h                    |   29 ++
>  arch/powerpc/platforms/powernv/opal-wrappers.S     |    1 
>  arch/powerpc/platforms/powernv/opal.c              |   11 +
>  drivers/i2c/busses/Kconfig                         |   11 +
>  drivers/i2c/busses/Makefile                        |    1 
>  drivers/i2c/busses/i2c-opal.c                      |  294 ++++++++++++++++++++
>  7 files changed, 384 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/i2c/i2c-opal.txt
>  create mode 100644 drivers/i2c/busses/i2c-opal.c
> 
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-opal.txt b/Documentation/devicetree/bindings/i2c/i2c-opal.txt
> new file mode 100644
> index 0000000..12bc614
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-opal.txt
> @@ -0,0 +1,37 @@
> +Device-tree bindings for I2C OPAL driver
> +----------------------------------------
> +
> +Most of the device node and properties layout is specific to the firmware and
> +used by the firmware itself for configuring the port. From the linux
> +perspective, the properties of use are "ibm,port-name" and "ibm,opal-id".
> +
> +Required properties:
> +
> +- reg: Port-id within a given master
> +- compatible: must be "ibm,opal-i2c"
> +- ibm,opal-id: Refers to a specific bus and used to identify it when calling
> +	       the relevant OPAL functions.
> +- bus-frequency: Operating frequency of the i2c bus (in HZ). Informational for
> +		 linux, used by the FW though.
> +
> +Optional properties:
> +- ibm,port-name: Firmware provides this name that uniquely identifies the i2c
> +		 port.
> +
> +The node contains a number of other properties that are used by the FW itself
> +and depend on the specific hardware implementation. The example below depicts
> +a P8 on-chip bus.
> +
> +Example:
> +
> +i2c-bus@0 {
> +	reg = <0x0>;
> +	bus-frequency = <0x61a80>;
> +	compatible = "ibm,power8-i2c-port", "ibm,opal-i2c";
> +	ibm,opal-id = <0x1>;
> +	ibm,port-name = "p8_00000000_e1p0";
> +	#address-cells = <0x1>;
> +	phandle = <0x10000006>;
> +	#size-cells = <0x0>;
> +	linux,phandle = <0x10000006>;
> +};
> diff --git a/arch/powerpc/include/asm/opal.h b/arch/powerpc/include/asm/opal.h
> index 9124b0e..537807b 100644
> --- a/arch/powerpc/include/asm/opal.h
> +++ b/arch/powerpc/include/asm/opal.h
> @@ -56,6 +56,14 @@ struct opal_sg_list {
>  #define OPAL_HARDWARE_FROZEN	-13
>  #define OPAL_WRONG_STATE	-14
>  #define OPAL_ASYNC_COMPLETION	-15
> +#define OPAL_I2C_TIMEOUT	-17
> +#define OPAL_I2C_INVALID_CMD	-18
> +#define OPAL_I2C_LBUS_PARITY	-19
> +#define OPAL_I2C_BKEND_OVERRUN	-20
> +#define OPAL_I2C_BKEND_ACCESS	-21
> +#define OPAL_I2C_ARBT_LOST	-22
> +#define OPAL_I2C_NACK_RCVD	-23
> +#define OPAL_I2C_STOP_ERR	-24
>  
>  /* API Tokens (in r0) */
>  #define OPAL_INVALID_CALL			-1
> @@ -154,6 +162,7 @@ struct opal_sg_list {
>  #define OPAL_HANDLE_HMI				98
>  #define OPAL_REGISTER_DUMP_REGION		101
>  #define OPAL_UNREGISTER_DUMP_REGION		102
> +#define OPAL_I2C_REQUEST			109
>  
>  #ifndef __ASSEMBLY__
>  
> @@ -801,6 +810,24 @@ typedef struct oppanel_line {
>  	uint64_t 	line_len;
>  } oppanel_line_t;
>  
> +/* OPAL I2C request */
> +struct opal_i2c_request {
> +	uint8_t	type;
> +#define OPAL_I2C_RAW_READ	0
> +#define OPAL_I2C_RAW_WRITE	1
> +#define OPAL_I2C_SM_READ	2
> +#define OPAL_I2C_SM_WRITE	3
> +	uint8_t flags;
> +#define OPAL_I2C_ADDR_10	0x01	/* Not supported yet */
> +	uint8_t	subaddr_sz;		/* Max 4 */
> +	uint8_t reserved;
> +	__be16 addr;			/* 7 or 10 bit address */
> +	__be16 reserved2;
> +	__be32 subaddr;		/* Sub-address if any */
> +	__be32 size;			/* Data size */
> +	__be64 buffer_ra;		/* Buffer real address */
> +};
> +
>  /* /sys/firmware/opal */
>  extern struct kobject *opal_kobj;
>  
> @@ -963,6 +990,8 @@ int64_t opal_handle_hmi(void);
>  int64_t opal_register_dump_region(uint32_t id, uint64_t start, uint64_t end);
>  int64_t opal_unregister_dump_region(uint32_t id);
>  int64_t opal_pci_set_phb_cxl_mode(uint64_t phb_id, uint64_t mode, uint64_t pe_number);
> +int64_t opal_i2c_request(uint64_t async_token, uint32_t bus_id,
> +			 struct opal_i2c_request *oreq);
>  
>  /* Internal functions */
>  extern int early_init_dt_scan_opal(unsigned long node, const char *uname,
> diff --git a/arch/powerpc/platforms/powernv/opal-wrappers.S b/arch/powerpc/platforms/powernv/opal-wrappers.S
> index feb549a..4673c02 100644
> --- a/arch/powerpc/platforms/powernv/opal-wrappers.S
> +++ b/arch/powerpc/platforms/powernv/opal-wrappers.S
> @@ -250,3 +250,4 @@ OPAL_CALL(opal_handle_hmi,			OPAL_HANDLE_HMI);
>  OPAL_CALL(opal_register_dump_region,		OPAL_REGISTER_DUMP_REGION);
>  OPAL_CALL(opal_unregister_dump_region,		OPAL_UNREGISTER_DUMP_REGION);
>  OPAL_CALL(opal_pci_set_phb_cxl_mode,		OPAL_PCI_SET_PHB_CXL_MODE);
> +OPAL_CALL(opal_i2c_request,			OPAL_I2C_REQUEST);
> diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c
> index bb90e6e..994975e 100644
> --- a/arch/powerpc/platforms/powernv/opal.c
> +++ b/arch/powerpc/platforms/powernv/opal.c
> @@ -667,6 +667,14 @@ static void opal_console_create_devs(void)
>  
>  }
>  
> +static void opal_i2c_create_devs(void)
> +{
> +	struct device_node *np;
> +
> +	for_each_compatible_node(np, NULL, "ibm,opal-i2c")
> +		of_platform_device_create(np, NULL, NULL);
> +}
> +
>  static void opal_request_interrupts(void)
>  {
>  	const __be32 *irqs;
> @@ -732,6 +740,9 @@ static int __init opal_init(void)
>  	/* Create console platform devices */
>  	opal_console_create_devs();
>  
> +	/* Create i2c platform devices */
> +	opal_i2c_create_devs();
> +
>  	/* Register OPAL interrupts */
>  	opal_request_interrupts();
>  
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 917c358..71ad6e1 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -1044,4 +1044,15 @@ config SCx200_ACB
>  	  This support is also available as a module.  If so, the module
>  	  will be called scx200_acb.
>  
> +config I2C_OPAL
> +	tristate "IBM OPAL I2C driver"
> +	depends on PPC_POWERNV
> +	default y
> +	help
> +	  This exposes the PowerNV platform i2c busses to the linux i2c layer,
> +	  the driver is based on the OPAL interfaces.
> +
> +	  This driver can also be built as a module. If so, the module will be
> +	  called as i2c-opal.
> +
>  endmenu
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 78d56c5..e23ec81 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -99,6 +99,7 @@ obj-$(CONFIG_I2C_ACORN)		+= i2c-acorn.o
>  obj-$(CONFIG_I2C_BCM_KONA)	+= i2c-bcm-kona.o
>  obj-$(CONFIG_I2C_CROS_EC_TUNNEL)	+= i2c-cros-ec-tunnel.o
>  obj-$(CONFIG_I2C_ELEKTOR)	+= i2c-elektor.o
> +obj-$(CONFIG_I2C_OPAL)		+= i2c-opal.o
>  obj-$(CONFIG_I2C_PCA_ISA)	+= i2c-pca-isa.o
>  obj-$(CONFIG_I2C_SIBYTE)	+= i2c-sibyte.o
>  obj-$(CONFIG_SCx200_ACB)	+= scx200_acb.o
> diff --git a/drivers/i2c/busses/i2c-opal.c b/drivers/i2c/busses/i2c-opal.c
> new file mode 100644
> index 0000000..16f90b1
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-opal.c
> @@ -0,0 +1,294 @@
> +/*
> + * IBM OPAL I2C driver
> + * Copyright (C) 2014 IBM
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.
> + */
> +
> +#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 = be64_to_cpu(msg.params[1]);
> +	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 0:
> +		return 0;
> +	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:
> +		/* For two messages, we basically support only simple
> +		 * smbus transactions of a write plus a read. We might
> +		 * want to allow also two writes but we'd have to bounce
> +		 * the data into a single buffer.
> +		 */
> +		if ((msgs[0].flags & I2C_M_RD) || !(msgs[1].flags & I2C_M_RD))
> +			return -EOPNOTSUPP;
> +		if (msgs[0].len > 4)
> +			return -EOPNOTSUPP;
> +		if (msgs[0].addr != msgs[1].addr)
> +			return -EOPNOTSUPP;
> +		req.type = OPAL_I2C_SM_READ;
> +		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;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	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);
> +		/* Fall through */
> +	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,
> +};
> +
> +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->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)
> +		strlcpy(adapter->name, pname, sizeof(adapter->name));
> +	else
> +		strlcpy(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 int i2c_opal_remove(struct platform_device *pdev)
> +{
> +	struct i2c_adapter *adapter = platform_get_drvdata(pdev);
> +
> +	i2c_del_adapter(adapter);
> +
> +	return 0;
> +}
> +
> +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	= 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");


--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Wolfram Sang Dec. 8, 2014, 8:55 p.m. UTC | #2
On Tue, Dec 09, 2014 at 07:13:15AM +1100, Benjamin Herrenschmidt wrote:
> On Mon, 2014-12-08 at 12:06 +0530, Neelesh Gupta wrote:
> > The patch exposes the available i2c busses on the PowerNV platform
> > to the kernel and implements the bus driver to support i2c and
> > smbus commands.
> > The driver uses the platform device infrastructure to probe the busses
> > on the platform and registers them with the i2c driver framework.
> 
> Wolfram, what are you remaining objections here ? We need that in
> distros ASAP ...

Oh, I thought we agreed that you take it via powerpc. I still think this
is the best solution.

> I still maintain that it's not reasonable to hold driver for the
> additions of multi-byte smbus offsets. This is a new feature that will
> require changes to a number of existing bus and device drivers, so a
> very pervasive change, and which will be visible to user space, which
> means that drivers will need to continue supporting the "old" way at
> least for a while anyway...

Yeah, I agree on that. I am still unsure about the port-name binding,
but well, if it is needed to fit your PowerNV scheme...

> > Signed-off-by: Neelesh Gupta <neelegup@linux.vnet.ibm.com>
> > Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
> > ---
> > 
> > v2 -> v3:
> > - Added the device tree binding documentation for the driver.
> > - Sorted the ordering of this new driver added in Makefile.
> > - Removed populating the superfluous .owner field in 'struct driver'.

Thanks for the updates!
Benjamin Herrenschmidt Dec. 8, 2014, 9:43 p.m. UTC | #3
On Mon, 2014-12-08 at 21:55 +0100, Wolfram Sang wrote:
> On Tue, Dec 09, 2014 at 07:13:15AM +1100, Benjamin Herrenschmidt wrote:
> > On Mon, 2014-12-08 at 12:06 +0530, Neelesh Gupta wrote:
> > > The patch exposes the available i2c busses on the PowerNV platform
> > > to the kernel and implements the bus driver to support i2c and
> > > smbus commands.
> > > The driver uses the platform device infrastructure to probe the busses
> > > on the platform and registers them with the i2c driver framework.
> > 
> > Wolfram, what are you remaining objections here ? We need that in
> > distros ASAP ...
> 
> Oh, I thought we agreed that you take it via powerpc. I still think this
> is the best solution.

I threatened to do that :-) I don't remember you replying, did I miss
it ? If you are ok with the driver and are happy for me to take it,
please send an Ack.

> > I still maintain that it's not reasonable to hold driver for the
> > additions of multi-byte smbus offsets. This is a new feature that will
> > require changes to a number of existing bus and device drivers, so a
> > very pervasive change, and which will be visible to user space, which
> > means that drivers will need to continue supporting the "old" way at
> > least for a while anyway...
> 
> Yeah, I agree on that. I am still unsure about the port-name binding,
> but well, if it is needed to fit your PowerNV scheme...

>From a binding perspective, it's just a piece of additional info that
the firmware provides for convenience. That we use it as the i2c port
name in Linux makes sense, it means that when listing the i2c ports,
it's immediately clear to the user which is which, it's not used
functionally by any driver or piece of code, but it's handy for people
doing things like manufacturing of machines, to know what bus to poke to
program a VPD EEPROM or test if a chip responds for example.

Cheers,
Ben.


--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Wolfram Sang Dec. 9, 2014, 8:54 a.m. UTC | #4
> > Oh, I thought we agreed that you take it via powerpc. I still think this
> > is the best solution.
> 
> I threatened to do that :-) I don't remember you replying, did I miss
> it ?

It is here:
http://thread.gmane.org/gmane.linux.drivers.i2c/20762/focus=21099

> If you are ok with the driver and are happy for me to take it,
> please send an Ack.

"Happy" is not the correct word, but let's just go over with it. Maybe
like this:

Acked-by: Wolfram Sang <wsa@the-dreams.de> (I2C part, excluding the bindings)

> From a binding perspective, it's just a piece of additional info that
> the firmware provides for convenience.

I do understand the use case. I even agree it makes sense to have
something like this. It is just that I'd prefer a generic, widely
acknowledged solution, with consensus where it belongs and how it should
be named. Not a custom solution which, frankly, feels forced on me
by time pressure I have nothing to do with. So, not happy here, but also
not looking for drama. Let's move on...
Benjamin Herrenschmidt Dec. 9, 2014, 9:43 a.m. UTC | #5
On Tue, 2014-12-09 at 09:54 +0100, Wolfram Sang wrote:
> > > Oh, I thought we agreed that you take it via powerpc. I still think this
> > > is the best solution.
> > 
> > I threatened to do that :-) I don't remember you replying, did I miss
> > it ?

> It is here:
> http://thread.gmane.org/gmane.linux.drivers.i2c/20762/focus=21099

Weird, it never made it to my mbox... anyway:

> I did not invent DT bindings.

No I did :) Or rather Mitch did with OF and I contributed heavily at
slapping it onto everbody's face :) Then I let Grant run with it and
deal with the carnage... but as you can imagine, I feel like whatever
rule I made for others don't apply to me :)

> I did not invent that DT is/should be a
>hardware description. 

This is the basic idea but it's flexible. It is in essence a description
of the environment, which is essentially the HW but there is no taboo
about adding various ancilliary pieces of informations that one deems
useful, especially if they are prefixed by a vendor prefix to avoid
collision with "well defined" properties.

I think we ended up being fairly strict about the rules initially to try
to rein in the crowd of ARM embedded folks who really went all over the
place but like every rule, it's meant to be broken (hear the french guy
talking ...).

>For me, it is a burden that I (as a subsystem
> maintainer for mainly drivers) have to prevent people from using DT ?>
> for software configuration (some people use it as an 1:1 mapping for
> platform data even.)

Well, yes and no... for example, it's perfectly legit to have a node
representing a UART, have the firmware slap into it the default expected
baud rate as a property (or whatever it has configured it to be) which
makes it then a reasonable default for the kernel to use.

Generally speaking there is nothing fundamentally wrong about having
configuration information in the device-tree, but it has to be clearly
identifiable as such.

The description of the HW in my world at least also implies how that HW
is meant to be configured for a given platform.

> Since there are no guidelines (probably there can't
> be), I developed a set of rules out of experience and when those don't
> match I ask for help. Having a different set of rules for
> powerpc/arm/... (or server/embedded for that matter) will increase
> this burden a lot. People will come and say "But they did it as 
> well..."

The basic rule is "does it make sense ?". Really. Apply your jugement
based on your experience as to whether something is a reasonable
comprimise or not and whether it will turn into a big mess in the long
run or, on the contrary, is a perfectly fine ad-hoc solution for a given
setup.

> It's getting quite tempting to just throw that driver into powerpc.git

Maybe this is the easiest. Just make sure that MAINTAINERS also point
this driver to you or PowerNV maintainers. And no Ack from me, please.
Then, I can always say "I dunno" if people start asking questions.

:)

Technically I need your ack if we are to follow the process for Linux
upstreaming. I doubt Linux will holler if I just put it in the tree but
I'd rather follow the process if possible.

>> And I don't give a flying crap about what random ARM SOC vendor
>> thinks of my powerpc FW interface for a powerpc unique FW interface.
>
> But you are not alone here. If you open the box for giving busses a
> configurable name, I can see other people (without FW) wanting this,
> too. So, this discussion will come anyhow IMO.

Right and I personally don't see a problem with that ... what's
fundamentally wrong with letting the platform description (ie,. the DT)
specify reasonable names for i2c busses ? It has pretty much no impact
on drivers nowadays but means things are easier to figure out/locate for
users/admin/developers and eases diagnostics.

> > If you are ok with the driver and are happy for me to take it,
> > please send an Ack.
> 
> "Happy" is not the correct word, but let's just go over with it. Maybe
> like this:
> 
> Acked-by: Wolfram Sang <wsa@the-dreams.de> (I2C part, excluding the bindings)

Forget about the binding mess, Olof reminded me that the result of one
of the recent KS was that the bindings no longer needed "approval", and
are to be sent to the list purely for informational purposes, otherwise
the process is a mess. We have to provide at least some trust here, and
we can reject the driver if we think the binding is really way too
gross.

> > From a binding perspective, it's just a piece of additional info that
> > the firmware provides for convenience.
> 
> I do understand the use case. I even agree it makes sense to have
> something like this. It is just that I'd prefer a generic, widely
> acknowledged solution, with consensus where it belongs and how it should
> be named. Not a custom solution which, frankly, feels forced on me
> by time pressure I have nothing to do with. So, not happy here, but also
> not looking for drama. Let's move on...

Adding a generic binding for i2c controllers to name their respective
busses sounds like a laudable idea, and if that happens I'll be happy to
update the driver to take that into account so that a future FW version
can add it (in addition to the old property for backward compat).

Ben.


--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Michael Ellerman Dec. 9, 2014, 9:45 a.m. UTC | #6
On Mon, 2014-08-12 at 06:36:16 UTC, Neelesh Gupta wrote:
> The patch exposes the available i2c busses on the PowerNV platform
> to the kernel and implements the bus driver to support i2c and
> smbus commands.
> The driver uses the platform device infrastructure to probe the busses
> on the platform and registers them with the i2c driver framework.

>  arch/powerpc/include/asm/opal.h                    |   29 ++
>  arch/powerpc/platforms/powernv/opal-wrappers.S     |    1 
>  arch/powerpc/platforms/powernv/opal.c              |   11 +

This had major conflicts in the above files.

I've fixed it up but please check I did it correctly:

  https://github.com/mpe/powerpc-merge/commits/pw/neelesh-i2c

It also doesn't build as a module:

  $ grep CONFIG_I2C_OPAL .config
  CONFIG_I2C_OPAL=m
  $ make ...
  ...
  ERROR: "opal_i2c_request" [drivers/i2c/busses/i2c-opal.ko] undefined!

cheers
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Benjamin Herrenschmidt Dec. 9, 2014, 10:21 a.m. UTC | #7
On Tue, 2014-12-09 at 20:45 +1100, Michael Ellerman wrote:
> On Mon, 2014-08-12 at 06:36:16 UTC, Neelesh Gupta wrote:
> > The patch exposes the available i2c busses on the PowerNV platform
> > to the kernel and implements the bus driver to support i2c and
> > smbus commands.
> > The driver uses the platform device infrastructure to probe the busses
> > on the platform and registers them with the i2c driver framework.
> 
> >  arch/powerpc/include/asm/opal.h                    |   29 ++
> >  arch/powerpc/platforms/powernv/opal-wrappers.S     |    1 
> >  arch/powerpc/platforms/powernv/opal.c              |   11 +
> 
> This had major conflicts in the above files.
> 
> I've fixed it up but please check I did it correctly:
> 
>   https://github.com/mpe/powerpc-merge/commits/pw/neelesh-i2c
> 
> It also doesn't build as a module:
> 
>   $ grep CONFIG_I2C_OPAL .config
>   CONFIG_I2C_OPAL=m
>   $ make ...
>   ...
>   ERROR: "opal_i2c_request" [drivers/i2c/busses/i2c-opal.ko] undefined!

Right that needs to be exported, however for those OPAL wrappers to work
when exported as modules on an LE kernel with ABI v2 (pfiew !) we need
another patch from jk to sort out the external entry to the asm...

Jeremy, is that already in or not yet ?

Cheers,
Ben.

> cheers
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Neelesh Gupta Dec. 9, 2014, 1:18 p.m. UTC | #8
On 12/09/2014 03:15 PM, Michael Ellerman wrote:
> On Mon, 2014-08-12 at 06:36:16 UTC, Neelesh Gupta wrote:
>> The patch exposes the available i2c busses on the PowerNV platform
>> to the kernel and implements the bus driver to support i2c and
>> smbus commands.
>> The driver uses the platform device infrastructure to probe the busses
>> on the platform and registers them with the i2c driver framework.
>>   arch/powerpc/include/asm/opal.h                    |   29 ++
>>   arch/powerpc/platforms/powernv/opal-wrappers.S     |    1
>>   arch/powerpc/platforms/powernv/opal.c              |   11 +
> This had major conflicts in the above files.
>
> I've fixed it up but please check I did it correctly:
>
>    https://github.com/mpe/powerpc-merge/commits/pw/neelesh-i2c
>
> It also doesn't build as a module:
>
>    $ grep CONFIG_I2C_OPAL .config
>    CONFIG_I2C_OPAL=m
>    $ make ...
>    ...
>    ERROR: "opal_i2c_request" [drivers/i2c/busses/i2c-opal.ko] undefined!

Yeah, this needs to be exported.
I think for the same reason, "opal-rtc" is still waiting, plus awaiting 
"ack" from
maintainers.

- Neelesh

>
> cheers
>

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Michael Ellerman Dec. 12, 2014, 5:30 a.m. UTC | #9
On Tue, 2014-12-09 at 21:21 +1100, Benjamin Herrenschmidt wrote:
> On Tue, 2014-12-09 at 20:45 +1100, Michael Ellerman wrote:
> > On Mon, 2014-08-12 at 06:36:16 UTC, Neelesh Gupta wrote:
> > > The patch exposes the available i2c busses on the PowerNV platform
> > > to the kernel and implements the bus driver to support i2c and
> > > smbus commands.
> > > The driver uses the platform device infrastructure to probe the busses
> > > on the platform and registers them with the i2c driver framework.
> > 
> > >  arch/powerpc/include/asm/opal.h                    |   29 ++
> > >  arch/powerpc/platforms/powernv/opal-wrappers.S     |    1 
> > >  arch/powerpc/platforms/powernv/opal.c              |   11 +
> > 
> > This had major conflicts in the above files.
> > 
> > I've fixed it up but please check I did it correctly:
> > 
> >   https://github.com/mpe/powerpc-merge/commits/pw/neelesh-i2c
> > 
> > It also doesn't build as a module:
> > 
> >   $ grep CONFIG_I2C_OPAL .config
> >   CONFIG_I2C_OPAL=m
> >   $ make ...
> >   ...
> >   ERROR: "opal_i2c_request" [drivers/i2c/busses/i2c-opal.ko] undefined!
> 
> Right that needs to be exported, however for those OPAL wrappers to work
> when exported as modules on an LE kernel with ABI v2 (pfiew !) we need
> another patch from jk to sort out the external entry to the asm...
> 
> Jeremy, is that already in or not yet ?

Yeah it's been in since October.

Please send a v4 with a fix for this Neelesh.

cheers


--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/i2c/i2c-opal.txt b/Documentation/devicetree/bindings/i2c/i2c-opal.txt
new file mode 100644
index 0000000..12bc614
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-opal.txt
@@ -0,0 +1,37 @@ 
+Device-tree bindings for I2C OPAL driver
+----------------------------------------
+
+Most of the device node and properties layout is specific to the firmware and
+used by the firmware itself for configuring the port. From the linux
+perspective, the properties of use are "ibm,port-name" and "ibm,opal-id".
+
+Required properties:
+
+- reg: Port-id within a given master
+- compatible: must be "ibm,opal-i2c"
+- ibm,opal-id: Refers to a specific bus and used to identify it when calling
+	       the relevant OPAL functions.
+- bus-frequency: Operating frequency of the i2c bus (in HZ). Informational for
+		 linux, used by the FW though.
+
+Optional properties:
+- ibm,port-name: Firmware provides this name that uniquely identifies the i2c
+		 port.
+
+The node contains a number of other properties that are used by the FW itself
+and depend on the specific hardware implementation. The example below depicts
+a P8 on-chip bus.
+
+Example:
+
+i2c-bus@0 {
+	reg = <0x0>;
+	bus-frequency = <0x61a80>;
+	compatible = "ibm,power8-i2c-port", "ibm,opal-i2c";
+	ibm,opal-id = <0x1>;
+	ibm,port-name = "p8_00000000_e1p0";
+	#address-cells = <0x1>;
+	phandle = <0x10000006>;
+	#size-cells = <0x0>;
+	linux,phandle = <0x10000006>;
+};
diff --git a/arch/powerpc/include/asm/opal.h b/arch/powerpc/include/asm/opal.h
index 9124b0e..537807b 100644
--- a/arch/powerpc/include/asm/opal.h
+++ b/arch/powerpc/include/asm/opal.h
@@ -56,6 +56,14 @@  struct opal_sg_list {
 #define OPAL_HARDWARE_FROZEN	-13
 #define OPAL_WRONG_STATE	-14
 #define OPAL_ASYNC_COMPLETION	-15
+#define OPAL_I2C_TIMEOUT	-17
+#define OPAL_I2C_INVALID_CMD	-18
+#define OPAL_I2C_LBUS_PARITY	-19
+#define OPAL_I2C_BKEND_OVERRUN	-20
+#define OPAL_I2C_BKEND_ACCESS	-21
+#define OPAL_I2C_ARBT_LOST	-22
+#define OPAL_I2C_NACK_RCVD	-23
+#define OPAL_I2C_STOP_ERR	-24
 
 /* API Tokens (in r0) */
 #define OPAL_INVALID_CALL			-1
@@ -154,6 +162,7 @@  struct opal_sg_list {
 #define OPAL_HANDLE_HMI				98
 #define OPAL_REGISTER_DUMP_REGION		101
 #define OPAL_UNREGISTER_DUMP_REGION		102
+#define OPAL_I2C_REQUEST			109
 
 #ifndef __ASSEMBLY__
 
@@ -801,6 +810,24 @@  typedef struct oppanel_line {
 	uint64_t 	line_len;
 } oppanel_line_t;
 
+/* OPAL I2C request */
+struct opal_i2c_request {
+	uint8_t	type;
+#define OPAL_I2C_RAW_READ	0
+#define OPAL_I2C_RAW_WRITE	1
+#define OPAL_I2C_SM_READ	2
+#define OPAL_I2C_SM_WRITE	3
+	uint8_t flags;
+#define OPAL_I2C_ADDR_10	0x01	/* Not supported yet */
+	uint8_t	subaddr_sz;		/* Max 4 */
+	uint8_t reserved;
+	__be16 addr;			/* 7 or 10 bit address */
+	__be16 reserved2;
+	__be32 subaddr;		/* Sub-address if any */
+	__be32 size;			/* Data size */
+	__be64 buffer_ra;		/* Buffer real address */
+};
+
 /* /sys/firmware/opal */
 extern struct kobject *opal_kobj;
 
@@ -963,6 +990,8 @@  int64_t opal_handle_hmi(void);
 int64_t opal_register_dump_region(uint32_t id, uint64_t start, uint64_t end);
 int64_t opal_unregister_dump_region(uint32_t id);
 int64_t opal_pci_set_phb_cxl_mode(uint64_t phb_id, uint64_t mode, uint64_t pe_number);
+int64_t opal_i2c_request(uint64_t async_token, uint32_t bus_id,
+			 struct opal_i2c_request *oreq);
 
 /* Internal functions */
 extern int early_init_dt_scan_opal(unsigned long node, const char *uname,
diff --git a/arch/powerpc/platforms/powernv/opal-wrappers.S b/arch/powerpc/platforms/powernv/opal-wrappers.S
index feb549a..4673c02 100644
--- a/arch/powerpc/platforms/powernv/opal-wrappers.S
+++ b/arch/powerpc/platforms/powernv/opal-wrappers.S
@@ -250,3 +250,4 @@  OPAL_CALL(opal_handle_hmi,			OPAL_HANDLE_HMI);
 OPAL_CALL(opal_register_dump_region,		OPAL_REGISTER_DUMP_REGION);
 OPAL_CALL(opal_unregister_dump_region,		OPAL_UNREGISTER_DUMP_REGION);
 OPAL_CALL(opal_pci_set_phb_cxl_mode,		OPAL_PCI_SET_PHB_CXL_MODE);
+OPAL_CALL(opal_i2c_request,			OPAL_I2C_REQUEST);
diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c
index bb90e6e..994975e 100644
--- a/arch/powerpc/platforms/powernv/opal.c
+++ b/arch/powerpc/platforms/powernv/opal.c
@@ -667,6 +667,14 @@  static void opal_console_create_devs(void)
 
 }
 
+static void opal_i2c_create_devs(void)
+{
+	struct device_node *np;
+
+	for_each_compatible_node(np, NULL, "ibm,opal-i2c")
+		of_platform_device_create(np, NULL, NULL);
+}
+
 static void opal_request_interrupts(void)
 {
 	const __be32 *irqs;
@@ -732,6 +740,9 @@  static int __init opal_init(void)
 	/* Create console platform devices */
 	opal_console_create_devs();
 
+	/* Create i2c platform devices */
+	opal_i2c_create_devs();
+
 	/* Register OPAL interrupts */
 	opal_request_interrupts();
 
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 917c358..71ad6e1 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1044,4 +1044,15 @@  config SCx200_ACB
 	  This support is also available as a module.  If so, the module
 	  will be called scx200_acb.
 
+config I2C_OPAL
+	tristate "IBM OPAL I2C driver"
+	depends on PPC_POWERNV
+	default y
+	help
+	  This exposes the PowerNV platform i2c busses to the linux i2c layer,
+	  the driver is based on the OPAL interfaces.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called as i2c-opal.
+
 endmenu
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 78d56c5..e23ec81 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -99,6 +99,7 @@  obj-$(CONFIG_I2C_ACORN)		+= i2c-acorn.o
 obj-$(CONFIG_I2C_BCM_KONA)	+= i2c-bcm-kona.o
 obj-$(CONFIG_I2C_CROS_EC_TUNNEL)	+= i2c-cros-ec-tunnel.o
 obj-$(CONFIG_I2C_ELEKTOR)	+= i2c-elektor.o
+obj-$(CONFIG_I2C_OPAL)		+= i2c-opal.o
 obj-$(CONFIG_I2C_PCA_ISA)	+= i2c-pca-isa.o
 obj-$(CONFIG_I2C_SIBYTE)	+= i2c-sibyte.o
 obj-$(CONFIG_SCx200_ACB)	+= scx200_acb.o
diff --git a/drivers/i2c/busses/i2c-opal.c b/drivers/i2c/busses/i2c-opal.c
new file mode 100644
index 0000000..16f90b1
--- /dev/null
+++ b/drivers/i2c/busses/i2c-opal.c
@@ -0,0 +1,294 @@ 
+/*
+ * IBM OPAL I2C driver
+ * Copyright (C) 2014 IBM
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.
+ */
+
+#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 = be64_to_cpu(msg.params[1]);
+	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 0:
+		return 0;
+	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:
+		/* For two messages, we basically support only simple
+		 * smbus transactions of a write plus a read. We might
+		 * want to allow also two writes but we'd have to bounce
+		 * the data into a single buffer.
+		 */
+		if ((msgs[0].flags & I2C_M_RD) || !(msgs[1].flags & I2C_M_RD))
+			return -EOPNOTSUPP;
+		if (msgs[0].len > 4)
+			return -EOPNOTSUPP;
+		if (msgs[0].addr != msgs[1].addr)
+			return -EOPNOTSUPP;
+		req.type = OPAL_I2C_SM_READ;
+		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;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	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);
+		/* Fall through */
+	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,
+};
+
+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->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)
+		strlcpy(adapter->name, pname, sizeof(adapter->name));
+	else
+		strlcpy(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 int i2c_opal_remove(struct platform_device *pdev)
+{
+	struct i2c_adapter *adapter = platform_get_drvdata(pdev);
+
+	i2c_del_adapter(adapter);
+
+	return 0;
+}
+
+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	= 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");