xref: /openbmc/linux/drivers/hwmon/occ/p9_sbe.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
1e2f05d60SEddie James // SPDX-License-Identifier: GPL-2.0+
2e2f05d60SEddie James // Copyright IBM Corp 2019
35b5513b8SEddie James 
45b5513b8SEddie James #include <linux/device.h>
55b5513b8SEddie James #include <linux/errno.h>
68587ca6fSMatthew Wilcox (Oracle) #include <linux/slab.h>
788be37c0SEddie James #include <linux/fsi-occ.h>
85027a34aSEddie James #include <linux/mm.h>
95b5513b8SEddie James #include <linux/module.h>
10*89a286fbSEddie James #include <linux/mod_devicetable.h>
115027a34aSEddie James #include <linux/mutex.h>
125b5513b8SEddie James #include <linux/platform_device.h>
135027a34aSEddie James #include <linux/string.h>
145027a34aSEddie James #include <linux/sysfs.h>
155b5513b8SEddie James 
165b5513b8SEddie James #include "common.h"
175b5513b8SEddie James 
18dbed963eSEddie James #define OCC_CHECKSUM_RETRIES	3
19dbed963eSEddie James 
205b5513b8SEddie James struct p9_sbe_occ {
215b5513b8SEddie James 	struct occ occ;
225027a34aSEddie James 	bool sbe_error;
235027a34aSEddie James 	void *ffdc;
245027a34aSEddie James 	size_t ffdc_len;
255027a34aSEddie James 	size_t ffdc_size;
265027a34aSEddie James 	struct mutex sbe_error_lock;	/* lock access to ffdc data */
275b5513b8SEddie James 	struct device *sbe;
285b5513b8SEddie James };
295b5513b8SEddie James 
305b5513b8SEddie James #define to_p9_sbe_occ(x)	container_of((x), struct p9_sbe_occ, occ)
315b5513b8SEddie James 
ffdc_read(struct file * filp,struct kobject * kobj,struct bin_attribute * battr,char * buf,loff_t pos,size_t count)325027a34aSEddie James static ssize_t ffdc_read(struct file *filp, struct kobject *kobj,
335027a34aSEddie James 			 struct bin_attribute *battr, char *buf, loff_t pos,
345027a34aSEddie James 			 size_t count)
355027a34aSEddie James {
365027a34aSEddie James 	ssize_t rc = 0;
375027a34aSEddie James 	struct occ *occ = dev_get_drvdata(kobj_to_dev(kobj));
385027a34aSEddie James 	struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
395027a34aSEddie James 
405027a34aSEddie James 	mutex_lock(&ctx->sbe_error_lock);
415027a34aSEddie James 	if (ctx->sbe_error) {
425027a34aSEddie James 		rc = memory_read_from_buffer(buf, count, &pos, ctx->ffdc,
435027a34aSEddie James 					     ctx->ffdc_len);
445027a34aSEddie James 		if (pos >= ctx->ffdc_len)
455027a34aSEddie James 			ctx->sbe_error = false;
465027a34aSEddie James 	}
475027a34aSEddie James 	mutex_unlock(&ctx->sbe_error_lock);
485027a34aSEddie James 
495027a34aSEddie James 	return rc;
505027a34aSEddie James }
515027a34aSEddie James static BIN_ATTR_RO(ffdc, OCC_MAX_RESP_WORDS * 4);
525027a34aSEddie James 
p9_sbe_occ_save_ffdc(struct p9_sbe_occ * ctx,const void * resp,size_t resp_len)535027a34aSEddie James static bool p9_sbe_occ_save_ffdc(struct p9_sbe_occ *ctx, const void *resp,
545027a34aSEddie James 				 size_t resp_len)
555027a34aSEddie James {
565027a34aSEddie James 	bool notify = false;
575027a34aSEddie James 
585027a34aSEddie James 	mutex_lock(&ctx->sbe_error_lock);
595027a34aSEddie James 	if (!ctx->sbe_error) {
605027a34aSEddie James 		if (resp_len > ctx->ffdc_size) {
615027a34aSEddie James 			kvfree(ctx->ffdc);
625027a34aSEddie James 			ctx->ffdc = kvmalloc(resp_len, GFP_KERNEL);
635027a34aSEddie James 			if (!ctx->ffdc) {
645027a34aSEddie James 				ctx->ffdc_len = 0;
655027a34aSEddie James 				ctx->ffdc_size = 0;
665027a34aSEddie James 				goto done;
675027a34aSEddie James 			}
685027a34aSEddie James 
695027a34aSEddie James 			ctx->ffdc_size = resp_len;
705027a34aSEddie James 		}
715027a34aSEddie James 
725027a34aSEddie James 		notify = true;
735027a34aSEddie James 		ctx->sbe_error = true;
745027a34aSEddie James 		ctx->ffdc_len = resp_len;
755027a34aSEddie James 		memcpy(ctx->ffdc, resp, resp_len);
765027a34aSEddie James 	}
775027a34aSEddie James 
785027a34aSEddie James done:
795027a34aSEddie James 	mutex_unlock(&ctx->sbe_error_lock);
805027a34aSEddie James 	return notify;
815027a34aSEddie James }
825027a34aSEddie James 
p9_sbe_occ_send_cmd(struct occ * occ,u8 * cmd,size_t len,void * resp,size_t resp_len)831bbb2809SEddie James static int p9_sbe_occ_send_cmd(struct occ *occ, u8 *cmd, size_t len,
841bbb2809SEddie James 			       void *resp, size_t resp_len)
855b5513b8SEddie James {
86dbed963eSEddie James 	size_t original_resp_len = resp_len;
8788be37c0SEddie James 	struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
88dbed963eSEddie James 	int rc, i;
8988be37c0SEddie James 
90dbed963eSEddie James 	for (i = 0; i < OCC_CHECKSUM_RETRIES; ++i) {
91908dbf02SEddie James 		rc = fsi_occ_submit(ctx->sbe, cmd, len, resp, &resp_len);
92dbed963eSEddie James 		if (rc >= 0)
93dbed963eSEddie James 			break;
945027a34aSEddie James 		if (resp_len) {
955027a34aSEddie James 			if (p9_sbe_occ_save_ffdc(ctx, resp, resp_len))
965027a34aSEddie James 				sysfs_notify(&occ->bus_dev->kobj, NULL,
975027a34aSEddie James 					     bin_attr_ffdc.attr.name);
9888be37c0SEddie James 			return rc;
995027a34aSEddie James 		}
100dbed963eSEddie James 		if (rc != -EBADE)
101dbed963eSEddie James 			return rc;
102dbed963eSEddie James 		resp_len = original_resp_len;
103dbed963eSEddie James 	}
10488be37c0SEddie James 
1051bbb2809SEddie James 	switch (((struct occ_response *)resp)->return_status) {
10688be37c0SEddie James 	case OCC_RESP_CMD_IN_PRG:
10788be37c0SEddie James 		rc = -ETIMEDOUT;
10888be37c0SEddie James 		break;
10988be37c0SEddie James 	case OCC_RESP_SUCCESS:
11088be37c0SEddie James 		rc = 0;
11188be37c0SEddie James 		break;
11288be37c0SEddie James 	case OCC_RESP_CMD_INVAL:
11388be37c0SEddie James 	case OCC_RESP_CMD_LEN_INVAL:
11488be37c0SEddie James 	case OCC_RESP_DATA_INVAL:
11588be37c0SEddie James 	case OCC_RESP_CHKSUM_ERR:
11688be37c0SEddie James 		rc = -EINVAL;
11788be37c0SEddie James 		break;
11888be37c0SEddie James 	case OCC_RESP_INT_ERR:
11988be37c0SEddie James 	case OCC_RESP_BAD_STATE:
12088be37c0SEddie James 	case OCC_RESP_CRIT_EXCEPT:
12188be37c0SEddie James 	case OCC_RESP_CRIT_INIT:
12288be37c0SEddie James 	case OCC_RESP_CRIT_WATCHDOG:
12388be37c0SEddie James 	case OCC_RESP_CRIT_OCB:
12488be37c0SEddie James 	case OCC_RESP_CRIT_HW:
12588be37c0SEddie James 		rc = -EREMOTEIO;
12688be37c0SEddie James 		break;
12788be37c0SEddie James 	default:
12888be37c0SEddie James 		rc = -EPROTO;
12988be37c0SEddie James 	}
13088be37c0SEddie James 
13188be37c0SEddie James 	return rc;
1325b5513b8SEddie James }
1335b5513b8SEddie James 
p9_sbe_occ_probe(struct platform_device * pdev)1345b5513b8SEddie James static int p9_sbe_occ_probe(struct platform_device *pdev)
1355b5513b8SEddie James {
1365b5513b8SEddie James 	int rc;
1375b5513b8SEddie James 	struct occ *occ;
1385b5513b8SEddie James 	struct p9_sbe_occ *ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx),
1395b5513b8SEddie James 					      GFP_KERNEL);
1405b5513b8SEddie James 	if (!ctx)
1415b5513b8SEddie James 		return -ENOMEM;
1425b5513b8SEddie James 
1435027a34aSEddie James 	mutex_init(&ctx->sbe_error_lock);
1445027a34aSEddie James 
1455b5513b8SEddie James 	ctx->sbe = pdev->dev.parent;
1465b5513b8SEddie James 	occ = &ctx->occ;
1475b5513b8SEddie James 	occ->bus_dev = &pdev->dev;
1485b5513b8SEddie James 	platform_set_drvdata(pdev, occ);
1495b5513b8SEddie James 
150c10e753dSEddie James 	occ->powr_sample_time_us = 500;
1515b5513b8SEddie James 	occ->poll_cmd_data = 0x20;		/* P9 OCC poll data */
1525b5513b8SEddie James 	occ->send_cmd = p9_sbe_occ_send_cmd;
1535b5513b8SEddie James 
154849b0156SEddie James 	rc = occ_setup(occ);
1555b5513b8SEddie James 	if (rc == -ESHUTDOWN)
1565b5513b8SEddie James 		rc = -ENODEV;	/* Host is shutdown, don't spew errors */
1575b5513b8SEddie James 
1585027a34aSEddie James 	if (!rc) {
1595027a34aSEddie James 		rc = device_create_bin_file(occ->bus_dev, &bin_attr_ffdc);
1605027a34aSEddie James 		if (rc) {
1615027a34aSEddie James 			dev_warn(occ->bus_dev,
1625027a34aSEddie James 				 "failed to create SBE error ffdc file\n");
1635027a34aSEddie James 			rc = 0;
1645027a34aSEddie James 		}
1655027a34aSEddie James 	}
1665027a34aSEddie James 
1675b5513b8SEddie James 	return rc;
1685b5513b8SEddie James }
1695b5513b8SEddie James 
p9_sbe_occ_remove(struct platform_device * pdev)1705b5513b8SEddie James static int p9_sbe_occ_remove(struct platform_device *pdev)
1715b5513b8SEddie James {
1725b5513b8SEddie James 	struct occ *occ = platform_get_drvdata(pdev);
1735b5513b8SEddie James 	struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
1745b5513b8SEddie James 
1755027a34aSEddie James 	device_remove_bin_file(occ->bus_dev, &bin_attr_ffdc);
1765027a34aSEddie James 
1775b5513b8SEddie James 	ctx->sbe = NULL;
178df04ced6SEddie James 	occ_shutdown(occ);
1795b5513b8SEddie James 
1805027a34aSEddie James 	kvfree(ctx->ffdc);
1815027a34aSEddie James 
1825b5513b8SEddie James 	return 0;
1835b5513b8SEddie James }
1845b5513b8SEddie James 
185*89a286fbSEddie James static const struct of_device_id p9_sbe_occ_of_match[] = {
186*89a286fbSEddie James 	{ .compatible = "ibm,p9-occ-hwmon" },
187*89a286fbSEddie James 	{ .compatible = "ibm,p10-occ-hwmon" },
188*89a286fbSEddie James 	{}
189*89a286fbSEddie James };
190*89a286fbSEddie James MODULE_DEVICE_TABLE(of, p9_sbe_occ_of_match);
191*89a286fbSEddie James 
1925b5513b8SEddie James static struct platform_driver p9_sbe_occ_driver = {
1935b5513b8SEddie James 	.driver = {
1945b5513b8SEddie James 		.name = "occ-hwmon",
195*89a286fbSEddie James 		.of_match_table = p9_sbe_occ_of_match,
1965b5513b8SEddie James 	},
1975b5513b8SEddie James 	.probe	= p9_sbe_occ_probe,
1985b5513b8SEddie James 	.remove = p9_sbe_occ_remove,
1995b5513b8SEddie James };
2005b5513b8SEddie James 
2015b5513b8SEddie James module_platform_driver(p9_sbe_occ_driver);
2025b5513b8SEddie James 
2035b5513b8SEddie James MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
2045b5513b8SEddie James MODULE_DESCRIPTION("BMC P9 OCC hwmon driver");
2055b5513b8SEddie James MODULE_LICENSE("GPL");
206