diff mbox

[V5] powerpc/powernv : Add support for OPAL-OCC command/response interface

Message ID 1498455138-6402-1-git-send-email-shilpa.bhat@linux.vnet.ibm.com (mailing list archive)
State Superseded
Headers show

Commit Message

Shilpasri G Bhat June 26, 2017, 5:32 a.m. UTC
In P9, OCC (On-Chip-Controller) supports shared memory based
commad-response interface. Within the shared memory there is an OPAL
command buffer and OCC response buffer that can be used to send
inband commands to OCC. This patch adds a platform driver to support
the command/response interface between OCC and the host.

Signed-off-by: Shilpasri G Bhat <shilpa.bhat@linux.vnet.ibm.com>
---
The skiboot patch for the interface is posted here:
https://lists.ozlabs.org/pipermail/skiboot/2017-June/007960.html

Changes from V4:
- Add token as a parameter to the opal_occ_command()
- Use per-occ counter for command request_id instead of using async
  token.

 arch/powerpc/include/asm/opal-api.h            |  41 +++-
 arch/powerpc/include/asm/opal.h                |   3 +
 arch/powerpc/platforms/powernv/Makefile        |   2 +-
 arch/powerpc/platforms/powernv/opal-occ.c      | 303 +++++++++++++++++++++++++
 arch/powerpc/platforms/powernv/opal-wrappers.S |   1 +
 arch/powerpc/platforms/powernv/opal.c          |   8 +
 6 files changed, 356 insertions(+), 2 deletions(-)
 create mode 100644 arch/powerpc/platforms/powernv/opal-occ.c

Comments

Cyril Bur June 27, 2017, 4:35 a.m. UTC | #1
On Mon, 2017-06-26 at 11:02 +0530, Shilpasri G Bhat wrote:
> In P9, OCC (On-Chip-Controller) supports shared memory based
> commad-response interface. Within the shared memory there is an OPAL
> command buffer and OCC response buffer that can be used to send
> inband commands to OCC. This patch adds a platform driver to support
> the command/response interface between OCC and the host.
> 

I feel obliged to check that an occ request_id can be zero, I'm sure
its fine - just have to ask, zero is so often special.

I should also point out I don't know must about how the OCC works or
anything so I would be best if there were other eyes on this.

Provided zero is an ok request_id:
Reviewed-by: Cyril Bur <cyrilbur@gmail.com>

> Signed-off-by: Shilpasri G Bhat <shilpa.bhat@linux.vnet.ibm.com>
> ---
> The skiboot patch for the interface is posted here:
> https://lists.ozlabs.org/pipermail/skiboot/2017-June/007960.html
> 
> Changes from V4:
> - Add token as a parameter to the opal_occ_command()
> - Use per-occ counter for command request_id instead of using async
>   token.
> 
>  arch/powerpc/include/asm/opal-api.h            |  41 +++-
>  arch/powerpc/include/asm/opal.h                |   3 +
>  arch/powerpc/platforms/powernv/Makefile        |   2 +-
>  arch/powerpc/platforms/powernv/opal-occ.c      | 303 +++++++++++++++++++++++++
>  arch/powerpc/platforms/powernv/opal-wrappers.S |   1 +
>  arch/powerpc/platforms/powernv/opal.c          |   8 +
>  6 files changed, 356 insertions(+), 2 deletions(-)
>  create mode 100644 arch/powerpc/platforms/powernv/opal-occ.c
> 
> diff --git a/arch/powerpc/include/asm/opal-api.h b/arch/powerpc/include/asm/opal-api.h
> index cb3e624..011d86c 100644
> --- a/arch/powerpc/include/asm/opal-api.h
> +++ b/arch/powerpc/include/asm/opal-api.h
> @@ -42,6 +42,10 @@
>  #define OPAL_I2C_STOP_ERR	-24
>  #define OPAL_XIVE_PROVISIONING	-31
>  #define OPAL_XIVE_FREE_ACTIVE	-32
> +#define OPAL_OCC_INVALID_STATE	-33
> +#define OPAL_OCC_BUSY		-34
> +#define OPAL_OCC_CMD_TIMEOUT	-35
> +#define OPAL_OCC_RSP_MISMATCH	-36
>  
>  /* API Tokens (in r0) */
>  #define OPAL_INVALID_CALL		       -1
> @@ -190,7 +194,8 @@
>  #define OPAL_NPU_INIT_CONTEXT			146
>  #define OPAL_NPU_DESTROY_CONTEXT		147
>  #define OPAL_NPU_MAP_LPAR			148
> -#define OPAL_LAST				148
> +#define OPAL_OCC_COMMAND			149
> +#define OPAL_LAST				149
>  
>  /* Device tree flags */
>  
> @@ -829,6 +834,40 @@ struct opal_prd_msg_header {
>  
>  struct opal_prd_msg;
>  
> +enum occ_cmd {
> +	OCC_CMD_AMESTER_PASS_THRU = 0,
> +	OCC_CMD_CLEAR_SENSOR_DATA,
> +	OCC_CMD_SET_POWER_CAP,
> +	OCC_CMD_SET_POWER_SHIFTING_RATIO,
> +	OCC_CMD_SELECT_SENSOR_GROUPS,
> +	OCC_CMD_LAST
> +};
> +
> +struct opal_occ_cmd_rsp_msg {
> +	__be64 cdata;
> +	__be64 rdata;
> +	__be16 cdata_size;
> +	__be16 rdata_size;
> +	u8 cmd;
> +	u8 request_id;
> +	u8 status;
> +};
> +
> +struct opal_occ_cmd_data {
> +	__be16 size;
> +	u8 cmd;
> +	u8 data[];
> +};
> +
> +struct opal_occ_rsp_data {
> +	__be16 size;
> +	u8 status;
> +	u8 data[];
> +};
> +
> +#define MAX_OPAL_CMD_DATA_LENGTH        4090
> +#define MAX_OCC_RSP_DATA_LENGTH         8698
> +
>  #define OCC_RESET                       0
>  #define OCC_LOAD                        1
>  #define OCC_THROTTLE                    2
> diff --git a/arch/powerpc/include/asm/opal.h b/arch/powerpc/include/asm/opal.h
> index 03ed493..84659bd 100644
> --- a/arch/powerpc/include/asm/opal.h
> +++ b/arch/powerpc/include/asm/opal.h
> @@ -346,6 +346,9 @@ static inline int opal_get_async_rc(struct opal_msg msg)
>  
>  void opal_wake_poller(void);
>  
> +int64_t opal_occ_command(int chip_id, struct opal_occ_cmd_rsp_msg *msg,
> +			 int token, bool retry);
> +
>  #endif /* __ASSEMBLY__ */
>  
>  #endif /* _ASM_POWERPC_OPAL_H */
> diff --git a/arch/powerpc/platforms/powernv/Makefile b/arch/powerpc/platforms/powernv/Makefile
> index b5d98cb..f5f0902 100644
> --- a/arch/powerpc/platforms/powernv/Makefile
> +++ b/arch/powerpc/platforms/powernv/Makefile
> @@ -2,7 +2,7 @@ obj-y			+= setup.o opal-wrappers.o opal.o opal-async.o idle.o
>  obj-y			+= opal-rtc.o opal-nvram.o opal-lpc.o opal-flash.o
>  obj-y			+= rng.o opal-elog.o opal-dump.o opal-sysparam.o opal-sensor.o
>  obj-y			+= opal-msglog.o opal-hmi.o opal-power.o opal-irqchip.o
> -obj-y			+= opal-kmsg.o
> +obj-y			+= opal-kmsg.o opal-occ.o
>  
>  obj-$(CONFIG_SMP)	+= smp.o subcore.o subcore-asm.o
>  obj-$(CONFIG_PCI)	+= pci.o pci-ioda.o npu-dma.o
> diff --git a/arch/powerpc/platforms/powernv/opal-occ.c b/arch/powerpc/platforms/powernv/opal-occ.c
> new file mode 100644
> index 0000000..440304f
> --- /dev/null
> +++ b/arch/powerpc/platforms/powernv/opal-occ.c
> @@ -0,0 +1,303 @@
> +/*
> + * Copyright IBM Corporation 2017
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * 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.
> + */
> +
> +#define pr_fmt(fmt) "opal-occ: " fmt
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/miscdevice.h>
> +#include <linux/highmem.h>
> +#include <linux/slab.h>
> +#include <asm/opal.h>
> +
> +struct occ {
> +	struct miscdevice dev;
> +	struct opal_occ_rsp_data *rsp;
> +	atomic_t session;
> +	atomic_t cmd_in_progress;
> +	atomic_t rsp_consumed;
> +	int id;
> +	u8 request_id;
> +} *occs;
> +static int nr_occs;
> +
> +static int __send_occ_command(struct opal_occ_cmd_rsp_msg *msg,
> +			      int chip_id, int token, bool retry)
> +{
> +	struct opal_msg async_msg;
> +	int rc;
> +
> +	rc = opal_occ_command(chip_id, msg, token, retry);
> +	if (rc == OPAL_ASYNC_COMPLETION) {
> +		rc = opal_async_wait_response(token, &async_msg);
> +		if (rc) {
> +			pr_devel("Failed to wait for async response %d\n", rc);
> +			return rc;
> +		}
> +	}
> +
> +	return rc ? rc : opal_get_async_rc(async_msg);
> +}
> +
> +static int send_occ_command(struct opal_occ_cmd_rsp_msg *msg, struct occ *occ)
> +{
> +	int token, rc;
> +
> +	token = opal_async_get_token_interruptible();
> +	if (token < 0) {
> +		pr_devel("Failed to get the token for OCC command %d (%d)\n",
> +			 msg->cmd, token);
> +		return token;
> +	}
> +
> +	msg->request_id = occ->request_id++;
> +	rc = __send_occ_command(msg, occ->id, token, false);
> +
> +	switch (rc) {
> +	case OPAL_OCC_CMD_TIMEOUT:
> +	case OPAL_OCC_RSP_MISMATCH:
> +		pr_devel("Failed OCC command with %d. Retrying it again\n", rc);
> +		msg->request_id = occ->request_id++;
> +		rc = __send_occ_command(msg, occ->id, token, true);
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	opal_async_release_token(token);
> +	return opal_error_code(rc);
> +}
> +
> +static int opal_occ_cmd_prepare(struct opal_occ_cmd_data *cmd, struct occ *occ)
> +{
> +	struct opal_occ_cmd_rsp_msg msg;
> +	int rc;
> +
> +	msg.cmd = cmd->cmd;
> +	msg.cdata = cpu_to_be64(__pa(cmd->data));
> +	msg.cdata_size = cpu_to_be16(cmd->size);
> +	msg.rdata = cpu_to_be64(__pa(occ->rsp->data));
> +
> +	rc = send_occ_command(&msg, occ);
> +	if (rc) {
> +		pr_info("Failed OCC command %d with %d\n", cmd->cmd, rc);
> +		return rc;
> +	}
> +
> +	occ->rsp->size = be16_to_cpu(msg.rdata_size);
> +	occ->rsp->status = msg.status;
> +	if (occ->rsp->size > MAX_OCC_RSP_DATA_LENGTH) {
> +		pr_devel("Bigger OCC response size, clipping to %d\n",
> +			 MAX_OCC_RSP_DATA_LENGTH);
> +		occ->rsp->size = MAX_OCC_RSP_DATA_LENGTH;
> +	}
> +
> +	atomic_set(&occ->rsp_consumed, 1);
> +	return rc;
> +}
> +
> +static ssize_t opal_occ_write(struct file *file, const char __user *buf,
> +			      size_t count, loff_t *ppos)
> +{
> +	struct miscdevice *dev = file->private_data;
> +	struct occ *occ = container_of(dev, struct occ, dev);
> +	struct opal_occ_cmd_data *cmd;
> +	int rc;
> +
> +	if (count < sizeof(*cmd))
> +		return -EINVAL;
> +
> +	if (atomic_cmpxchg(&occ->cmd_in_progress, 0, 1))
> +		return -EBUSY;
> +
> +	cmd = kmalloc(count, GFP_KERNEL);
> +	if (!cmd) {
> +		rc = -ENOMEM;
> +		goto out;
> +	}
> +
> +	rc = copy_from_user(cmd, buf, count);
> +	if (rc) {
> +		pr_err("Failed to copy OCC command request message\n");
> +		rc = -EFAULT;
> +		goto free_cmd;
> +	}
> +
> +	if (cmd->size > MAX_OPAL_CMD_DATA_LENGTH) {
> +		rc = -EINVAL;
> +		goto free_cmd;
> +	}
> +
> +	rc = opal_occ_cmd_prepare(cmd, occ);
> +	if (!rc)
> +		rc = count;
> +
> +free_cmd:
> +	kfree(cmd);
> +out:
> +	atomic_set(&occ->cmd_in_progress, 0);
> +	return rc;
> +}
> +
> +static ssize_t opal_occ_read(struct file *file, char __user *buf,
> +			     size_t count, loff_t *ppos)
> +{
> +	struct miscdevice *dev = file->private_data;
> +	struct occ *occ = container_of(dev, struct occ, dev);
> +	int rc;
> +
> +	if (count < sizeof(*occ->rsp) + occ->rsp->size)
> +		return -EINVAL;
> +
> +	if (atomic_cmpxchg(&occ->cmd_in_progress, 0, 1))
> +		return -EBUSY;
> +
> +	if (!atomic_cmpxchg(&occ->rsp_consumed, 1, 0)) {
> +		rc = -EBUSY;
> +		goto out;
> +	}
> +
> +	rc = copy_to_user((void __user *)buf, occ->rsp,
> +			  sizeof(occ->rsp) + occ->rsp->size);
> +	if (rc) {
> +		atomic_set(&occ->rsp_consumed, 1);
> +		pr_err("Failed to copy OCC response data to user\n");
> +	}
> +
> +out:
> +	atomic_set(&occ->cmd_in_progress, 0);
> +	return rc ? rc : sizeof(*occ->rsp) + occ->rsp->size;
> +}
> +
> +static int opal_occ_open(struct inode *inode, struct file *file)
> +{
> +	struct miscdevice *dev = file->private_data;
> +	struct occ *occ = container_of(dev, struct occ, dev);
> +
> +	return atomic_cmpxchg(&occ->session, 0, 1) ? -EBUSY : 0;
> +}
> +
> +static int opal_occ_release(struct inode *inode, struct file *file)
> +{
> +	struct miscdevice *dev = file->private_data;
> +	struct occ *occ = container_of(dev, struct occ, dev);
> +
> +	atomic_set(&occ->session, 0);
> +
> +	return 0;
> +}
> +
> +static const struct file_operations opal_occ_fops = {
> +	.open		= opal_occ_open,
> +	.read		= opal_occ_read,
> +	.write		= opal_occ_write,
> +	.release	= opal_occ_release,
> +	.owner		= THIS_MODULE,
> +};
> +
> +#define MAX_POSSIBLE_CHIPS	256
> +
> +static int opal_occ_probe(struct platform_device *pdev)
> +{
> +	unsigned int chip[MAX_POSSIBLE_CHIPS];
> +	unsigned int cpu;
> +	unsigned int prev_chip_id = UINT_MAX;
> +	int i, rc;
> +
> +	for_each_possible_cpu(cpu) {
> +		unsigned int id = cpu_to_chip_id(cpu);
> +
> +		if (prev_chip_id != id) {
> +			int j = nr_occs;
> +
> +			while (--j >= 0)
> +				if (chip[j] == id)
> +					continue;
> +
> +			prev_chip_id = id;
> +			chip[nr_occs++] = id;
> +			WARN_ON_ONCE(nr_occs >= MAX_POSSIBLE_CHIPS - 1);
> +		}
> +	}
> +
> +	occs = kcalloc(nr_occs, sizeof(*occs), GFP_KERNEL);
> +	if (!occs)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < nr_occs; i++) {
> +		char name[10];
> +
> +		occs[i].id = chip[i];
> +		occs[i].dev.minor = MISC_DYNAMIC_MINOR;
> +		snprintf(name, 10, "occ%d", chip[i]);
> +		occs[i].dev.name = name;
> +		occs[i].dev.fops = &opal_occ_fops;
> +		occs[i].rsp = kmalloc(sizeof(occs[i].rsp) +
> +				      MAX_OCC_RSP_DATA_LENGTH,
> +				      GFP_KERNEL);
> +		if (!occs[i].rsp) {
> +			rc = -ENOMEM;
> +			goto free_occs;
> +		}
> +
> +		rc = misc_register(&occs[i].dev);
> +		if (rc)
> +			goto free_occ_rsp_data;
> +	}
> +
> +	return 0;
> +
> +free_occ_rsp_data:
> +	kfree(occs[i].rsp);
> +free_occs:
> +	while (--i >= 0) {
> +		kfree(occs[i].rsp);
> +		misc_deregister(&occs[i].dev);
> +	}
> +	kfree(occs);
> +
> +	return rc;
> +}
> +
> +static int opal_occ_remove(struct platform_device *pdev)
> +{
> +	int i;
> +
> +	for (i = 0; i < nr_occs; i++) {
> +		kfree(occs[i].rsp);
> +		misc_deregister(&occs[i].dev);
> +	}
> +
> +	kfree(occs);
> +	return 0;
> +}
> +
> +static const struct of_device_id opal_occ_match[] = {
> +	{ .compatible = "ibm,opal-occ-cmd-rsp-interface" },
> +	{ },
> +};
> +
> +static struct platform_driver opal_occ_driver = {
> +	.driver = {
> +		.name           = "opal-occ",
> +		.of_match_table = opal_occ_match,
> +	},
> +	.probe	= opal_occ_probe,
> +	.remove	= opal_occ_remove,
> +};
> +
> +module_platform_driver(opal_occ_driver);
> +
> +MODULE_DESCRIPTION("PowerNV OPAL-OCC driver");
> +MODULE_LICENSE("GPL");
> diff --git a/arch/powerpc/platforms/powernv/opal-wrappers.S b/arch/powerpc/platforms/powernv/opal-wrappers.S
> index f620572..e6bf18d 100644
> --- a/arch/powerpc/platforms/powernv/opal-wrappers.S
> +++ b/arch/powerpc/platforms/powernv/opal-wrappers.S
> @@ -310,3 +310,4 @@ OPAL_CALL(opal_xive_dump,			OPAL_XIVE_DUMP);
>  OPAL_CALL(opal_npu_init_context,		OPAL_NPU_INIT_CONTEXT);
>  OPAL_CALL(opal_npu_destroy_context,		OPAL_NPU_DESTROY_CONTEXT);
>  OPAL_CALL(opal_npu_map_lpar,			OPAL_NPU_MAP_LPAR);
> +OPAL_CALL(opal_occ_command,			OPAL_OCC_COMMAND);
> diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c
> index 59684b4..d87c61b 100644
> --- a/arch/powerpc/platforms/powernv/opal.c
> +++ b/arch/powerpc/platforms/powernv/opal.c
> @@ -815,6 +815,9 @@ static int __init opal_init(void)
>  	opal_pdev_init("ibm,opal-flash");
>  	opal_pdev_init("ibm,opal-prd");
>  
> +	/* Initialize platform device: OCC_OPAL command-response interface */
> +	opal_pdev_init("ibm,opal-occ-cmd-rsp-interface");
> +
>  	/* Initialise platform device: oppanel interface */
>  	opal_pdev_init("ibm,opal-oppanel");
>  
> @@ -859,6 +862,7 @@ void opal_shutdown(void)
>  EXPORT_SYMBOL_GPL(opal_flash_write);
>  EXPORT_SYMBOL_GPL(opal_flash_erase);
>  EXPORT_SYMBOL_GPL(opal_prd_msg);
> +EXPORT_SYMBOL_GPL(opal_occ_command);
>  
>  /* Convert a region of vmalloc memory to an opal sg list */
>  struct opal_sg_list *opal_vmalloc_to_sg_list(void *vmalloc_addr,
> @@ -937,6 +941,10 @@ int opal_error_code(int rc)
>  	case OPAL_UNSUPPORTED:		return -EIO;
>  	case OPAL_HARDWARE:		return -EIO;
>  	case OPAL_INTERNAL_ERROR:	return -EIO;
> +	case OPAL_OCC_BUSY:		return -EBUSY;
> +	case OPAL_OCC_INVALID_STATE:
> +	case OPAL_OCC_CMD_TIMEOUT:
> +	case OPAL_OCC_RSP_MISMATCH:	return -EIO;
>  	default:
>  		pr_err("%s: unexpected OPAL error %d\n", __func__, rc);
>  		return -EIO;
Shilpasri G Bhat June 29, 2017, 6:29 p.m. UTC | #2
On 06/27/2017 10:05 AM, Cyril Bur wrote:
> On Mon, 2017-06-26 at 11:02 +0530, Shilpasri G Bhat wrote:
>> In P9, OCC (On-Chip-Controller) supports shared memory based
>> commad-response interface. Within the shared memory there is an OPAL
>> command buffer and OCC response buffer that can be used to send
>> inband commands to OCC. This patch adds a platform driver to support
>> the command/response interface between OCC and the host.
>>
> 
> I feel obliged to check that an occ request_id can be zero, I'm sure
> its fine - just have to ask, zero is so often special.
> 
> I should also point out I don't know must about how the OCC works or
> anything so I would be best if there were other eyes on this.
> 
> Provided zero is an ok request_id:

Yes zero is a valid request_id which can be sent to OCC.

> Reviewed-by: Cyril Bur <cyrilbur@gmail.com>
> 

Thanks for reviewing this patch.

Regards,
Shilpa

>> Signed-off-by: Shilpasri G Bhat <shilpa.bhat@linux.vnet.ibm.com>
>> ---
>> The skiboot patch for the interface is posted here:
>> https://lists.ozlabs.org/pipermail/skiboot/2017-June/007960.html
>>
>> Changes from V4:
>> - Add token as a parameter to the opal_occ_command()
>> - Use per-occ counter for command request_id instead of using async
>>   token.
>>
>>  arch/powerpc/include/asm/opal-api.h            |  41 +++-
>>  arch/powerpc/include/asm/opal.h                |   3 +
>>  arch/powerpc/platforms/powernv/Makefile        |   2 +-
>>  arch/powerpc/platforms/powernv/opal-occ.c      | 303 +++++++++++++++++++++++++
>>  arch/powerpc/platforms/powernv/opal-wrappers.S |   1 +
>>  arch/powerpc/platforms/powernv/opal.c          |   8 +
>>  6 files changed, 356 insertions(+), 2 deletions(-)
>>  create mode 100644 arch/powerpc/platforms/powernv/opal-occ.c
>>
>> diff --git a/arch/powerpc/include/asm/opal-api.h b/arch/powerpc/include/asm/opal-api.h
>> index cb3e624..011d86c 100644
>> --- a/arch/powerpc/include/asm/opal-api.h
>> +++ b/arch/powerpc/include/asm/opal-api.h
>> @@ -42,6 +42,10 @@
>>  #define OPAL_I2C_STOP_ERR	-24
>>  #define OPAL_XIVE_PROVISIONING	-31
>>  #define OPAL_XIVE_FREE_ACTIVE	-32
>> +#define OPAL_OCC_INVALID_STATE	-33
>> +#define OPAL_OCC_BUSY		-34
>> +#define OPAL_OCC_CMD_TIMEOUT	-35
>> +#define OPAL_OCC_RSP_MISMATCH	-36
>>  
>>  /* API Tokens (in r0) */
>>  #define OPAL_INVALID_CALL		       -1
>> @@ -190,7 +194,8 @@
>>  #define OPAL_NPU_INIT_CONTEXT			146
>>  #define OPAL_NPU_DESTROY_CONTEXT		147
>>  #define OPAL_NPU_MAP_LPAR			148
>> -#define OPAL_LAST				148
>> +#define OPAL_OCC_COMMAND			149
>> +#define OPAL_LAST				149
>>  
>>  /* Device tree flags */
>>  
>> @@ -829,6 +834,40 @@ struct opal_prd_msg_header {
>>  
>>  struct opal_prd_msg;
>>  
>> +enum occ_cmd {
>> +	OCC_CMD_AMESTER_PASS_THRU = 0,
>> +	OCC_CMD_CLEAR_SENSOR_DATA,
>> +	OCC_CMD_SET_POWER_CAP,
>> +	OCC_CMD_SET_POWER_SHIFTING_RATIO,
>> +	OCC_CMD_SELECT_SENSOR_GROUPS,
>> +	OCC_CMD_LAST
>> +};
>> +
>> +struct opal_occ_cmd_rsp_msg {
>> +	__be64 cdata;
>> +	__be64 rdata;
>> +	__be16 cdata_size;
>> +	__be16 rdata_size;
>> +	u8 cmd;
>> +	u8 request_id;
>> +	u8 status;
>> +};
>> +
>> +struct opal_occ_cmd_data {
>> +	__be16 size;
>> +	u8 cmd;
>> +	u8 data[];
>> +};
>> +
>> +struct opal_occ_rsp_data {
>> +	__be16 size;
>> +	u8 status;
>> +	u8 data[];
>> +};
>> +
>> +#define MAX_OPAL_CMD_DATA_LENGTH        4090
>> +#define MAX_OCC_RSP_DATA_LENGTH         8698
>> +
>>  #define OCC_RESET                       0
>>  #define OCC_LOAD                        1
>>  #define OCC_THROTTLE                    2
>> diff --git a/arch/powerpc/include/asm/opal.h b/arch/powerpc/include/asm/opal.h
>> index 03ed493..84659bd 100644
>> --- a/arch/powerpc/include/asm/opal.h
>> +++ b/arch/powerpc/include/asm/opal.h
>> @@ -346,6 +346,9 @@ static inline int opal_get_async_rc(struct opal_msg msg)
>>  
>>  void opal_wake_poller(void);
>>  
>> +int64_t opal_occ_command(int chip_id, struct opal_occ_cmd_rsp_msg *msg,
>> +			 int token, bool retry);
>> +
>>  #endif /* __ASSEMBLY__ */
>>  
>>  #endif /* _ASM_POWERPC_OPAL_H */
>> diff --git a/arch/powerpc/platforms/powernv/Makefile b/arch/powerpc/platforms/powernv/Makefile
>> index b5d98cb..f5f0902 100644
>> --- a/arch/powerpc/platforms/powernv/Makefile
>> +++ b/arch/powerpc/platforms/powernv/Makefile
>> @@ -2,7 +2,7 @@ obj-y			+= setup.o opal-wrappers.o opal.o opal-async.o idle.o
>>  obj-y			+= opal-rtc.o opal-nvram.o opal-lpc.o opal-flash.o
>>  obj-y			+= rng.o opal-elog.o opal-dump.o opal-sysparam.o opal-sensor.o
>>  obj-y			+= opal-msglog.o opal-hmi.o opal-power.o opal-irqchip.o
>> -obj-y			+= opal-kmsg.o
>> +obj-y			+= opal-kmsg.o opal-occ.o
>>  
>>  obj-$(CONFIG_SMP)	+= smp.o subcore.o subcore-asm.o
>>  obj-$(CONFIG_PCI)	+= pci.o pci-ioda.o npu-dma.o
>> diff --git a/arch/powerpc/platforms/powernv/opal-occ.c b/arch/powerpc/platforms/powernv/opal-occ.c
>> new file mode 100644
>> index 0000000..440304f
>> --- /dev/null
>> +++ b/arch/powerpc/platforms/powernv/opal-occ.c
>> @@ -0,0 +1,303 @@
>> +/*
>> + * Copyright IBM Corporation 2017
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + *
>> + * 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.
>> + */
>> +
>> +#define pr_fmt(fmt) "opal-occ: " fmt
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/miscdevice.h>
>> +#include <linux/highmem.h>
>> +#include <linux/slab.h>
>> +#include <asm/opal.h>
>> +
>> +struct occ {
>> +	struct miscdevice dev;
>> +	struct opal_occ_rsp_data *rsp;
>> +	atomic_t session;
>> +	atomic_t cmd_in_progress;
>> +	atomic_t rsp_consumed;
>> +	int id;
>> +	u8 request_id;
>> +} *occs;
>> +static int nr_occs;
>> +
>> +static int __send_occ_command(struct opal_occ_cmd_rsp_msg *msg,
>> +			      int chip_id, int token, bool retry)
>> +{
>> +	struct opal_msg async_msg;
>> +	int rc;
>> +
>> +	rc = opal_occ_command(chip_id, msg, token, retry);
>> +	if (rc == OPAL_ASYNC_COMPLETION) {
>> +		rc = opal_async_wait_response(token, &async_msg);
>> +		if (rc) {
>> +			pr_devel("Failed to wait for async response %d\n", rc);
>> +			return rc;
>> +		}
>> +	}
>> +
>> +	return rc ? rc : opal_get_async_rc(async_msg);
>> +}
>> +
>> +static int send_occ_command(struct opal_occ_cmd_rsp_msg *msg, struct occ *occ)
>> +{
>> +	int token, rc;
>> +
>> +	token = opal_async_get_token_interruptible();
>> +	if (token < 0) {
>> +		pr_devel("Failed to get the token for OCC command %d (%d)\n",
>> +			 msg->cmd, token);
>> +		return token;
>> +	}
>> +
>> +	msg->request_id = occ->request_id++;
>> +	rc = __send_occ_command(msg, occ->id, token, false);
>> +
>> +	switch (rc) {
>> +	case OPAL_OCC_CMD_TIMEOUT:
>> +	case OPAL_OCC_RSP_MISMATCH:
>> +		pr_devel("Failed OCC command with %d. Retrying it again\n", rc);
>> +		msg->request_id = occ->request_id++;
>> +		rc = __send_occ_command(msg, occ->id, token, true);
>> +		break;
>> +	default:
>> +		break;
>> +	}
>> +
>> +	opal_async_release_token(token);
>> +	return opal_error_code(rc);
>> +}
>> +
>> +static int opal_occ_cmd_prepare(struct opal_occ_cmd_data *cmd, struct occ *occ)
>> +{
>> +	struct opal_occ_cmd_rsp_msg msg;
>> +	int rc;
>> +
>> +	msg.cmd = cmd->cmd;
>> +	msg.cdata = cpu_to_be64(__pa(cmd->data));
>> +	msg.cdata_size = cpu_to_be16(cmd->size);
>> +	msg.rdata = cpu_to_be64(__pa(occ->rsp->data));
>> +
>> +	rc = send_occ_command(&msg, occ);
>> +	if (rc) {
>> +		pr_info("Failed OCC command %d with %d\n", cmd->cmd, rc);
>> +		return rc;
>> +	}
>> +
>> +	occ->rsp->size = be16_to_cpu(msg.rdata_size);
>> +	occ->rsp->status = msg.status;
>> +	if (occ->rsp->size > MAX_OCC_RSP_DATA_LENGTH) {
>> +		pr_devel("Bigger OCC response size, clipping to %d\n",
>> +			 MAX_OCC_RSP_DATA_LENGTH);
>> +		occ->rsp->size = MAX_OCC_RSP_DATA_LENGTH;
>> +	}
>> +
>> +	atomic_set(&occ->rsp_consumed, 1);
>> +	return rc;
>> +}
>> +
>> +static ssize_t opal_occ_write(struct file *file, const char __user *buf,
>> +			      size_t count, loff_t *ppos)
>> +{
>> +	struct miscdevice *dev = file->private_data;
>> +	struct occ *occ = container_of(dev, struct occ, dev);
>> +	struct opal_occ_cmd_data *cmd;
>> +	int rc;
>> +
>> +	if (count < sizeof(*cmd))
>> +		return -EINVAL;
>> +
>> +	if (atomic_cmpxchg(&occ->cmd_in_progress, 0, 1))
>> +		return -EBUSY;
>> +
>> +	cmd = kmalloc(count, GFP_KERNEL);
>> +	if (!cmd) {
>> +		rc = -ENOMEM;
>> +		goto out;
>> +	}
>> +
>> +	rc = copy_from_user(cmd, buf, count);
>> +	if (rc) {
>> +		pr_err("Failed to copy OCC command request message\n");
>> +		rc = -EFAULT;
>> +		goto free_cmd;
>> +	}
>> +
>> +	if (cmd->size > MAX_OPAL_CMD_DATA_LENGTH) {
>> +		rc = -EINVAL;
>> +		goto free_cmd;
>> +	}
>> +
>> +	rc = opal_occ_cmd_prepare(cmd, occ);
>> +	if (!rc)
>> +		rc = count;
>> +
>> +free_cmd:
>> +	kfree(cmd);
>> +out:
>> +	atomic_set(&occ->cmd_in_progress, 0);
>> +	return rc;
>> +}
>> +
>> +static ssize_t opal_occ_read(struct file *file, char __user *buf,
>> +			     size_t count, loff_t *ppos)
>> +{
>> +	struct miscdevice *dev = file->private_data;
>> +	struct occ *occ = container_of(dev, struct occ, dev);
>> +	int rc;
>> +
>> +	if (count < sizeof(*occ->rsp) + occ->rsp->size)
>> +		return -EINVAL;
>> +
>> +	if (atomic_cmpxchg(&occ->cmd_in_progress, 0, 1))
>> +		return -EBUSY;
>> +
>> +	if (!atomic_cmpxchg(&occ->rsp_consumed, 1, 0)) {
>> +		rc = -EBUSY;
>> +		goto out;
>> +	}
>> +
>> +	rc = copy_to_user((void __user *)buf, occ->rsp,
>> +			  sizeof(occ->rsp) + occ->rsp->size);
>> +	if (rc) {
>> +		atomic_set(&occ->rsp_consumed, 1);
>> +		pr_err("Failed to copy OCC response data to user\n");
>> +	}
>> +
>> +out:
>> +	atomic_set(&occ->cmd_in_progress, 0);
>> +	return rc ? rc : sizeof(*occ->rsp) + occ->rsp->size;
>> +}
>> +
>> +static int opal_occ_open(struct inode *inode, struct file *file)
>> +{
>> +	struct miscdevice *dev = file->private_data;
>> +	struct occ *occ = container_of(dev, struct occ, dev);
>> +
>> +	return atomic_cmpxchg(&occ->session, 0, 1) ? -EBUSY : 0;
>> +}
>> +
>> +static int opal_occ_release(struct inode *inode, struct file *file)
>> +{
>> +	struct miscdevice *dev = file->private_data;
>> +	struct occ *occ = container_of(dev, struct occ, dev);
>> +
>> +	atomic_set(&occ->session, 0);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct file_operations opal_occ_fops = {
>> +	.open		= opal_occ_open,
>> +	.read		= opal_occ_read,
>> +	.write		= opal_occ_write,
>> +	.release	= opal_occ_release,
>> +	.owner		= THIS_MODULE,
>> +};
>> +
>> +#define MAX_POSSIBLE_CHIPS	256
>> +
>> +static int opal_occ_probe(struct platform_device *pdev)
>> +{
>> +	unsigned int chip[MAX_POSSIBLE_CHIPS];
>> +	unsigned int cpu;
>> +	unsigned int prev_chip_id = UINT_MAX;
>> +	int i, rc;
>> +
>> +	for_each_possible_cpu(cpu) {
>> +		unsigned int id = cpu_to_chip_id(cpu);
>> +
>> +		if (prev_chip_id != id) {
>> +			int j = nr_occs;
>> +
>> +			while (--j >= 0)
>> +				if (chip[j] == id)
>> +					continue;
>> +
>> +			prev_chip_id = id;
>> +			chip[nr_occs++] = id;
>> +			WARN_ON_ONCE(nr_occs >= MAX_POSSIBLE_CHIPS - 1);
>> +		}
>> +	}
>> +
>> +	occs = kcalloc(nr_occs, sizeof(*occs), GFP_KERNEL);
>> +	if (!occs)
>> +		return -ENOMEM;
>> +
>> +	for (i = 0; i < nr_occs; i++) {
>> +		char name[10];
>> +
>> +		occs[i].id = chip[i];
>> +		occs[i].dev.minor = MISC_DYNAMIC_MINOR;
>> +		snprintf(name, 10, "occ%d", chip[i]);
>> +		occs[i].dev.name = name;
>> +		occs[i].dev.fops = &opal_occ_fops;
>> +		occs[i].rsp = kmalloc(sizeof(occs[i].rsp) +
>> +				      MAX_OCC_RSP_DATA_LENGTH,
>> +				      GFP_KERNEL);
>> +		if (!occs[i].rsp) {
>> +			rc = -ENOMEM;
>> +			goto free_occs;
>> +		}
>> +
>> +		rc = misc_register(&occs[i].dev);
>> +		if (rc)
>> +			goto free_occ_rsp_data;
>> +	}
>> +
>> +	return 0;
>> +
>> +free_occ_rsp_data:
>> +	kfree(occs[i].rsp);
>> +free_occs:
>> +	while (--i >= 0) {
>> +		kfree(occs[i].rsp);
>> +		misc_deregister(&occs[i].dev);
>> +	}
>> +	kfree(occs);
>> +
>> +	return rc;
>> +}
>> +
>> +static int opal_occ_remove(struct platform_device *pdev)
>> +{
>> +	int i;
>> +
>> +	for (i = 0; i < nr_occs; i++) {
>> +		kfree(occs[i].rsp);
>> +		misc_deregister(&occs[i].dev);
>> +	}
>> +
>> +	kfree(occs);
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id opal_occ_match[] = {
>> +	{ .compatible = "ibm,opal-occ-cmd-rsp-interface" },
>> +	{ },
>> +};
>> +
>> +static struct platform_driver opal_occ_driver = {
>> +	.driver = {
>> +		.name           = "opal-occ",
>> +		.of_match_table = opal_occ_match,
>> +	},
>> +	.probe	= opal_occ_probe,
>> +	.remove	= opal_occ_remove,
>> +};
>> +
>> +module_platform_driver(opal_occ_driver);
>> +
>> +MODULE_DESCRIPTION("PowerNV OPAL-OCC driver");
>> +MODULE_LICENSE("GPL");
>> diff --git a/arch/powerpc/platforms/powernv/opal-wrappers.S b/arch/powerpc/platforms/powernv/opal-wrappers.S
>> index f620572..e6bf18d 100644
>> --- a/arch/powerpc/platforms/powernv/opal-wrappers.S
>> +++ b/arch/powerpc/platforms/powernv/opal-wrappers.S
>> @@ -310,3 +310,4 @@ OPAL_CALL(opal_xive_dump,			OPAL_XIVE_DUMP);
>>  OPAL_CALL(opal_npu_init_context,		OPAL_NPU_INIT_CONTEXT);
>>  OPAL_CALL(opal_npu_destroy_context,		OPAL_NPU_DESTROY_CONTEXT);
>>  OPAL_CALL(opal_npu_map_lpar,			OPAL_NPU_MAP_LPAR);
>> +OPAL_CALL(opal_occ_command,			OPAL_OCC_COMMAND);
>> diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c
>> index 59684b4..d87c61b 100644
>> --- a/arch/powerpc/platforms/powernv/opal.c
>> +++ b/arch/powerpc/platforms/powernv/opal.c
>> @@ -815,6 +815,9 @@ static int __init opal_init(void)
>>  	opal_pdev_init("ibm,opal-flash");
>>  	opal_pdev_init("ibm,opal-prd");
>>  
>> +	/* Initialize platform device: OCC_OPAL command-response interface */
>> +	opal_pdev_init("ibm,opal-occ-cmd-rsp-interface");
>> +
>>  	/* Initialise platform device: oppanel interface */
>>  	opal_pdev_init("ibm,opal-oppanel");
>>  
>> @@ -859,6 +862,7 @@ void opal_shutdown(void)
>>  EXPORT_SYMBOL_GPL(opal_flash_write);
>>  EXPORT_SYMBOL_GPL(opal_flash_erase);
>>  EXPORT_SYMBOL_GPL(opal_prd_msg);
>> +EXPORT_SYMBOL_GPL(opal_occ_command);
>>  
>>  /* Convert a region of vmalloc memory to an opal sg list */
>>  struct opal_sg_list *opal_vmalloc_to_sg_list(void *vmalloc_addr,
>> @@ -937,6 +941,10 @@ int opal_error_code(int rc)
>>  	case OPAL_UNSUPPORTED:		return -EIO;
>>  	case OPAL_HARDWARE:		return -EIO;
>>  	case OPAL_INTERNAL_ERROR:	return -EIO;
>> +	case OPAL_OCC_BUSY:		return -EBUSY;
>> +	case OPAL_OCC_INVALID_STATE:
>> +	case OPAL_OCC_CMD_TIMEOUT:
>> +	case OPAL_OCC_RSP_MISMATCH:	return -EIO;
>>  	default:
>>  		pr_err("%s: unexpected OPAL error %d\n", __func__, rc);
>>  		return -EIO;
>
diff mbox

Patch

diff --git a/arch/powerpc/include/asm/opal-api.h b/arch/powerpc/include/asm/opal-api.h
index cb3e624..011d86c 100644
--- a/arch/powerpc/include/asm/opal-api.h
+++ b/arch/powerpc/include/asm/opal-api.h
@@ -42,6 +42,10 @@ 
 #define OPAL_I2C_STOP_ERR	-24
 #define OPAL_XIVE_PROVISIONING	-31
 #define OPAL_XIVE_FREE_ACTIVE	-32
+#define OPAL_OCC_INVALID_STATE	-33
+#define OPAL_OCC_BUSY		-34
+#define OPAL_OCC_CMD_TIMEOUT	-35
+#define OPAL_OCC_RSP_MISMATCH	-36
 
 /* API Tokens (in r0) */
 #define OPAL_INVALID_CALL		       -1
@@ -190,7 +194,8 @@ 
 #define OPAL_NPU_INIT_CONTEXT			146
 #define OPAL_NPU_DESTROY_CONTEXT		147
 #define OPAL_NPU_MAP_LPAR			148
-#define OPAL_LAST				148
+#define OPAL_OCC_COMMAND			149
+#define OPAL_LAST				149
 
 /* Device tree flags */
 
@@ -829,6 +834,40 @@  struct opal_prd_msg_header {
 
 struct opal_prd_msg;
 
+enum occ_cmd {
+	OCC_CMD_AMESTER_PASS_THRU = 0,
+	OCC_CMD_CLEAR_SENSOR_DATA,
+	OCC_CMD_SET_POWER_CAP,
+	OCC_CMD_SET_POWER_SHIFTING_RATIO,
+	OCC_CMD_SELECT_SENSOR_GROUPS,
+	OCC_CMD_LAST
+};
+
+struct opal_occ_cmd_rsp_msg {
+	__be64 cdata;
+	__be64 rdata;
+	__be16 cdata_size;
+	__be16 rdata_size;
+	u8 cmd;
+	u8 request_id;
+	u8 status;
+};
+
+struct opal_occ_cmd_data {
+	__be16 size;
+	u8 cmd;
+	u8 data[];
+};
+
+struct opal_occ_rsp_data {
+	__be16 size;
+	u8 status;
+	u8 data[];
+};
+
+#define MAX_OPAL_CMD_DATA_LENGTH        4090
+#define MAX_OCC_RSP_DATA_LENGTH         8698
+
 #define OCC_RESET                       0
 #define OCC_LOAD                        1
 #define OCC_THROTTLE                    2
diff --git a/arch/powerpc/include/asm/opal.h b/arch/powerpc/include/asm/opal.h
index 03ed493..84659bd 100644
--- a/arch/powerpc/include/asm/opal.h
+++ b/arch/powerpc/include/asm/opal.h
@@ -346,6 +346,9 @@  static inline int opal_get_async_rc(struct opal_msg msg)
 
 void opal_wake_poller(void);
 
+int64_t opal_occ_command(int chip_id, struct opal_occ_cmd_rsp_msg *msg,
+			 int token, bool retry);
+
 #endif /* __ASSEMBLY__ */
 
 #endif /* _ASM_POWERPC_OPAL_H */
diff --git a/arch/powerpc/platforms/powernv/Makefile b/arch/powerpc/platforms/powernv/Makefile
index b5d98cb..f5f0902 100644
--- a/arch/powerpc/platforms/powernv/Makefile
+++ b/arch/powerpc/platforms/powernv/Makefile
@@ -2,7 +2,7 @@  obj-y			+= setup.o opal-wrappers.o opal.o opal-async.o idle.o
 obj-y			+= opal-rtc.o opal-nvram.o opal-lpc.o opal-flash.o
 obj-y			+= rng.o opal-elog.o opal-dump.o opal-sysparam.o opal-sensor.o
 obj-y			+= opal-msglog.o opal-hmi.o opal-power.o opal-irqchip.o
-obj-y			+= opal-kmsg.o
+obj-y			+= opal-kmsg.o opal-occ.o
 
 obj-$(CONFIG_SMP)	+= smp.o subcore.o subcore-asm.o
 obj-$(CONFIG_PCI)	+= pci.o pci-ioda.o npu-dma.o
diff --git a/arch/powerpc/platforms/powernv/opal-occ.c b/arch/powerpc/platforms/powernv/opal-occ.c
new file mode 100644
index 0000000..440304f
--- /dev/null
+++ b/arch/powerpc/platforms/powernv/opal-occ.c
@@ -0,0 +1,303 @@ 
+/*
+ * Copyright IBM Corporation 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "opal-occ: " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+#include <linux/highmem.h>
+#include <linux/slab.h>
+#include <asm/opal.h>
+
+struct occ {
+	struct miscdevice dev;
+	struct opal_occ_rsp_data *rsp;
+	atomic_t session;
+	atomic_t cmd_in_progress;
+	atomic_t rsp_consumed;
+	int id;
+	u8 request_id;
+} *occs;
+static int nr_occs;
+
+static int __send_occ_command(struct opal_occ_cmd_rsp_msg *msg,
+			      int chip_id, int token, bool retry)
+{
+	struct opal_msg async_msg;
+	int rc;
+
+	rc = opal_occ_command(chip_id, msg, token, retry);
+	if (rc == OPAL_ASYNC_COMPLETION) {
+		rc = opal_async_wait_response(token, &async_msg);
+		if (rc) {
+			pr_devel("Failed to wait for async response %d\n", rc);
+			return rc;
+		}
+	}
+
+	return rc ? rc : opal_get_async_rc(async_msg);
+}
+
+static int send_occ_command(struct opal_occ_cmd_rsp_msg *msg, struct occ *occ)
+{
+	int token, rc;
+
+	token = opal_async_get_token_interruptible();
+	if (token < 0) {
+		pr_devel("Failed to get the token for OCC command %d (%d)\n",
+			 msg->cmd, token);
+		return token;
+	}
+
+	msg->request_id = occ->request_id++;
+	rc = __send_occ_command(msg, occ->id, token, false);
+
+	switch (rc) {
+	case OPAL_OCC_CMD_TIMEOUT:
+	case OPAL_OCC_RSP_MISMATCH:
+		pr_devel("Failed OCC command with %d. Retrying it again\n", rc);
+		msg->request_id = occ->request_id++;
+		rc = __send_occ_command(msg, occ->id, token, true);
+		break;
+	default:
+		break;
+	}
+
+	opal_async_release_token(token);
+	return opal_error_code(rc);
+}
+
+static int opal_occ_cmd_prepare(struct opal_occ_cmd_data *cmd, struct occ *occ)
+{
+	struct opal_occ_cmd_rsp_msg msg;
+	int rc;
+
+	msg.cmd = cmd->cmd;
+	msg.cdata = cpu_to_be64(__pa(cmd->data));
+	msg.cdata_size = cpu_to_be16(cmd->size);
+	msg.rdata = cpu_to_be64(__pa(occ->rsp->data));
+
+	rc = send_occ_command(&msg, occ);
+	if (rc) {
+		pr_info("Failed OCC command %d with %d\n", cmd->cmd, rc);
+		return rc;
+	}
+
+	occ->rsp->size = be16_to_cpu(msg.rdata_size);
+	occ->rsp->status = msg.status;
+	if (occ->rsp->size > MAX_OCC_RSP_DATA_LENGTH) {
+		pr_devel("Bigger OCC response size, clipping to %d\n",
+			 MAX_OCC_RSP_DATA_LENGTH);
+		occ->rsp->size = MAX_OCC_RSP_DATA_LENGTH;
+	}
+
+	atomic_set(&occ->rsp_consumed, 1);
+	return rc;
+}
+
+static ssize_t opal_occ_write(struct file *file, const char __user *buf,
+			      size_t count, loff_t *ppos)
+{
+	struct miscdevice *dev = file->private_data;
+	struct occ *occ = container_of(dev, struct occ, dev);
+	struct opal_occ_cmd_data *cmd;
+	int rc;
+
+	if (count < sizeof(*cmd))
+		return -EINVAL;
+
+	if (atomic_cmpxchg(&occ->cmd_in_progress, 0, 1))
+		return -EBUSY;
+
+	cmd = kmalloc(count, GFP_KERNEL);
+	if (!cmd) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	rc = copy_from_user(cmd, buf, count);
+	if (rc) {
+		pr_err("Failed to copy OCC command request message\n");
+		rc = -EFAULT;
+		goto free_cmd;
+	}
+
+	if (cmd->size > MAX_OPAL_CMD_DATA_LENGTH) {
+		rc = -EINVAL;
+		goto free_cmd;
+	}
+
+	rc = opal_occ_cmd_prepare(cmd, occ);
+	if (!rc)
+		rc = count;
+
+free_cmd:
+	kfree(cmd);
+out:
+	atomic_set(&occ->cmd_in_progress, 0);
+	return rc;
+}
+
+static ssize_t opal_occ_read(struct file *file, char __user *buf,
+			     size_t count, loff_t *ppos)
+{
+	struct miscdevice *dev = file->private_data;
+	struct occ *occ = container_of(dev, struct occ, dev);
+	int rc;
+
+	if (count < sizeof(*occ->rsp) + occ->rsp->size)
+		return -EINVAL;
+
+	if (atomic_cmpxchg(&occ->cmd_in_progress, 0, 1))
+		return -EBUSY;
+
+	if (!atomic_cmpxchg(&occ->rsp_consumed, 1, 0)) {
+		rc = -EBUSY;
+		goto out;
+	}
+
+	rc = copy_to_user((void __user *)buf, occ->rsp,
+			  sizeof(occ->rsp) + occ->rsp->size);
+	if (rc) {
+		atomic_set(&occ->rsp_consumed, 1);
+		pr_err("Failed to copy OCC response data to user\n");
+	}
+
+out:
+	atomic_set(&occ->cmd_in_progress, 0);
+	return rc ? rc : sizeof(*occ->rsp) + occ->rsp->size;
+}
+
+static int opal_occ_open(struct inode *inode, struct file *file)
+{
+	struct miscdevice *dev = file->private_data;
+	struct occ *occ = container_of(dev, struct occ, dev);
+
+	return atomic_cmpxchg(&occ->session, 0, 1) ? -EBUSY : 0;
+}
+
+static int opal_occ_release(struct inode *inode, struct file *file)
+{
+	struct miscdevice *dev = file->private_data;
+	struct occ *occ = container_of(dev, struct occ, dev);
+
+	atomic_set(&occ->session, 0);
+
+	return 0;
+}
+
+static const struct file_operations opal_occ_fops = {
+	.open		= opal_occ_open,
+	.read		= opal_occ_read,
+	.write		= opal_occ_write,
+	.release	= opal_occ_release,
+	.owner		= THIS_MODULE,
+};
+
+#define MAX_POSSIBLE_CHIPS	256
+
+static int opal_occ_probe(struct platform_device *pdev)
+{
+	unsigned int chip[MAX_POSSIBLE_CHIPS];
+	unsigned int cpu;
+	unsigned int prev_chip_id = UINT_MAX;
+	int i, rc;
+
+	for_each_possible_cpu(cpu) {
+		unsigned int id = cpu_to_chip_id(cpu);
+
+		if (prev_chip_id != id) {
+			int j = nr_occs;
+
+			while (--j >= 0)
+				if (chip[j] == id)
+					continue;
+
+			prev_chip_id = id;
+			chip[nr_occs++] = id;
+			WARN_ON_ONCE(nr_occs >= MAX_POSSIBLE_CHIPS - 1);
+		}
+	}
+
+	occs = kcalloc(nr_occs, sizeof(*occs), GFP_KERNEL);
+	if (!occs)
+		return -ENOMEM;
+
+	for (i = 0; i < nr_occs; i++) {
+		char name[10];
+
+		occs[i].id = chip[i];
+		occs[i].dev.minor = MISC_DYNAMIC_MINOR;
+		snprintf(name, 10, "occ%d", chip[i]);
+		occs[i].dev.name = name;
+		occs[i].dev.fops = &opal_occ_fops;
+		occs[i].rsp = kmalloc(sizeof(occs[i].rsp) +
+				      MAX_OCC_RSP_DATA_LENGTH,
+				      GFP_KERNEL);
+		if (!occs[i].rsp) {
+			rc = -ENOMEM;
+			goto free_occs;
+		}
+
+		rc = misc_register(&occs[i].dev);
+		if (rc)
+			goto free_occ_rsp_data;
+	}
+
+	return 0;
+
+free_occ_rsp_data:
+	kfree(occs[i].rsp);
+free_occs:
+	while (--i >= 0) {
+		kfree(occs[i].rsp);
+		misc_deregister(&occs[i].dev);
+	}
+	kfree(occs);
+
+	return rc;
+}
+
+static int opal_occ_remove(struct platform_device *pdev)
+{
+	int i;
+
+	for (i = 0; i < nr_occs; i++) {
+		kfree(occs[i].rsp);
+		misc_deregister(&occs[i].dev);
+	}
+
+	kfree(occs);
+	return 0;
+}
+
+static const struct of_device_id opal_occ_match[] = {
+	{ .compatible = "ibm,opal-occ-cmd-rsp-interface" },
+	{ },
+};
+
+static struct platform_driver opal_occ_driver = {
+	.driver = {
+		.name           = "opal-occ",
+		.of_match_table = opal_occ_match,
+	},
+	.probe	= opal_occ_probe,
+	.remove	= opal_occ_remove,
+};
+
+module_platform_driver(opal_occ_driver);
+
+MODULE_DESCRIPTION("PowerNV OPAL-OCC driver");
+MODULE_LICENSE("GPL");
diff --git a/arch/powerpc/platforms/powernv/opal-wrappers.S b/arch/powerpc/platforms/powernv/opal-wrappers.S
index f620572..e6bf18d 100644
--- a/arch/powerpc/platforms/powernv/opal-wrappers.S
+++ b/arch/powerpc/platforms/powernv/opal-wrappers.S
@@ -310,3 +310,4 @@  OPAL_CALL(opal_xive_dump,			OPAL_XIVE_DUMP);
 OPAL_CALL(opal_npu_init_context,		OPAL_NPU_INIT_CONTEXT);
 OPAL_CALL(opal_npu_destroy_context,		OPAL_NPU_DESTROY_CONTEXT);
 OPAL_CALL(opal_npu_map_lpar,			OPAL_NPU_MAP_LPAR);
+OPAL_CALL(opal_occ_command,			OPAL_OCC_COMMAND);
diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c
index 59684b4..d87c61b 100644
--- a/arch/powerpc/platforms/powernv/opal.c
+++ b/arch/powerpc/platforms/powernv/opal.c
@@ -815,6 +815,9 @@  static int __init opal_init(void)
 	opal_pdev_init("ibm,opal-flash");
 	opal_pdev_init("ibm,opal-prd");
 
+	/* Initialize platform device: OCC_OPAL command-response interface */
+	opal_pdev_init("ibm,opal-occ-cmd-rsp-interface");
+
 	/* Initialise platform device: oppanel interface */
 	opal_pdev_init("ibm,opal-oppanel");
 
@@ -859,6 +862,7 @@  void opal_shutdown(void)
 EXPORT_SYMBOL_GPL(opal_flash_write);
 EXPORT_SYMBOL_GPL(opal_flash_erase);
 EXPORT_SYMBOL_GPL(opal_prd_msg);
+EXPORT_SYMBOL_GPL(opal_occ_command);
 
 /* Convert a region of vmalloc memory to an opal sg list */
 struct opal_sg_list *opal_vmalloc_to_sg_list(void *vmalloc_addr,
@@ -937,6 +941,10 @@  int opal_error_code(int rc)
 	case OPAL_UNSUPPORTED:		return -EIO;
 	case OPAL_HARDWARE:		return -EIO;
 	case OPAL_INTERNAL_ERROR:	return -EIO;
+	case OPAL_OCC_BUSY:		return -EBUSY;
+	case OPAL_OCC_INVALID_STATE:
+	case OPAL_OCC_CMD_TIMEOUT:
+	case OPAL_OCC_RSP_MISMATCH:	return -EIO;
 	default:
 		pr_err("%s: unexpected OPAL error %d\n", __func__, rc);
 		return -EIO;