xref: /openbmc/linux/drivers/fsi/fsi-occ.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
17ed98dddSEddie James // SPDX-License-Identifier: GPL-2.0
27ed98dddSEddie James 
37ed98dddSEddie James #include <linux/device.h>
47ed98dddSEddie James #include <linux/err.h>
57ed98dddSEddie James #include <linux/errno.h>
67ed98dddSEddie James #include <linux/fs.h>
77ed98dddSEddie James #include <linux/fsi-sbefifo.h>
87ed98dddSEddie James #include <linux/gfp.h>
97ed98dddSEddie James #include <linux/idr.h>
107ed98dddSEddie James #include <linux/kernel.h>
117ed98dddSEddie James #include <linux/list.h>
127ed98dddSEddie James #include <linux/miscdevice.h>
13008d3825SEddie James #include <linux/mm.h>
147ed98dddSEddie James #include <linux/module.h>
157ed98dddSEddie James #include <linux/mutex.h>
167ed98dddSEddie James #include <linux/fsi-occ.h>
177ed98dddSEddie James #include <linux/of.h>
18*288f1acfSRob Herring #include <linux/of_platform.h>
197ed98dddSEddie James #include <linux/platform_device.h>
207ed98dddSEddie James #include <linux/sched.h>
217ed98dddSEddie James #include <linux/slab.h>
227ed98dddSEddie James #include <linux/uaccess.h>
237ed98dddSEddie James #include <asm/unaligned.h>
247ed98dddSEddie James 
257ed98dddSEddie James #define OCC_SRAM_BYTES		4096
267ed98dddSEddie James #define OCC_CMD_DATA_BYTES	4090
277ed98dddSEddie James #define OCC_RESP_DATA_BYTES	4089
287ed98dddSEddie James 
295ec96d74SEddie James #define OCC_P9_SRAM_CMD_ADDR	0xFFFBE000
305ec96d74SEddie James #define OCC_P9_SRAM_RSP_ADDR	0xFFFBF000
315ec96d74SEddie James 
325ec96d74SEddie James #define OCC_P10_SRAM_CMD_ADDR	0xFFFFD000
335ec96d74SEddie James #define OCC_P10_SRAM_RSP_ADDR	0xFFFFE000
345ec96d74SEddie James 
355ec96d74SEddie James #define OCC_P10_SRAM_MODE	0x58	/* Normal mode, OCB channel 2 */
367ed98dddSEddie James 
377ed98dddSEddie James #define OCC_TIMEOUT_MS		1000
387ed98dddSEddie James #define OCC_CMD_IN_PRG_WAIT_MS	50
397ed98dddSEddie James 
405ec96d74SEddie James enum versions { occ_p9, occ_p10 };
415ec96d74SEddie James 
427ed98dddSEddie James struct occ {
437ed98dddSEddie James 	struct device *dev;
447ed98dddSEddie James 	struct device *sbefifo;
457ed98dddSEddie James 	char name[32];
467ed98dddSEddie James 	int idx;
470fead4fcSEddie James 	bool platform_hwmon;
4862f79f3dSEddie James 	u8 sequence_number;
49008d3825SEddie James 	void *buffer;
508ec3cc9fSEddie James 	void *client_buffer;
518ec3cc9fSEddie James 	size_t client_buffer_size;
528ec3cc9fSEddie James 	size_t client_response_size;
535ec96d74SEddie James 	enum versions version;
547ed98dddSEddie James 	struct miscdevice mdev;
557ed98dddSEddie James 	struct mutex occ_lock;
567ed98dddSEddie James };
577ed98dddSEddie James 
587ed98dddSEddie James #define to_occ(x)	container_of((x), struct occ, mdev)
597ed98dddSEddie James 
607ed98dddSEddie James struct occ_response {
617ed98dddSEddie James 	u8 seq_no;
627ed98dddSEddie James 	u8 cmd_type;
637ed98dddSEddie James 	u8 return_status;
647ed98dddSEddie James 	__be16 data_length;
657ed98dddSEddie James 	u8 data[OCC_RESP_DATA_BYTES + 2];	/* two bytes checksum */
667ed98dddSEddie James } __packed;
677ed98dddSEddie James 
687ed98dddSEddie James struct occ_client {
697ed98dddSEddie James 	struct occ *occ;
707ed98dddSEddie James 	struct mutex lock;
717ed98dddSEddie James 	size_t data_size;
727ed98dddSEddie James 	size_t read_offset;
737ed98dddSEddie James 	u8 *buffer;
747ed98dddSEddie James };
757ed98dddSEddie James 
767ed98dddSEddie James #define to_client(x)	container_of((x), struct occ_client, xfr)
777ed98dddSEddie James 
787ed98dddSEddie James static DEFINE_IDA(occ_ida);
797ed98dddSEddie James 
occ_open(struct inode * inode,struct file * file)807ed98dddSEddie James static int occ_open(struct inode *inode, struct file *file)
817ed98dddSEddie James {
827ed98dddSEddie James 	struct occ_client *client = kzalloc(sizeof(*client), GFP_KERNEL);
837ed98dddSEddie James 	struct miscdevice *mdev = file->private_data;
847ed98dddSEddie James 	struct occ *occ = to_occ(mdev);
857ed98dddSEddie James 
867ed98dddSEddie James 	if (!client)
877ed98dddSEddie James 		return -ENOMEM;
887ed98dddSEddie James 
897ed98dddSEddie James 	client->buffer = (u8 *)__get_free_page(GFP_KERNEL);
907ed98dddSEddie James 	if (!client->buffer) {
917ed98dddSEddie James 		kfree(client);
927ed98dddSEddie James 		return -ENOMEM;
937ed98dddSEddie James 	}
947ed98dddSEddie James 
957ed98dddSEddie James 	client->occ = occ;
967ed98dddSEddie James 	mutex_init(&client->lock);
977ed98dddSEddie James 	file->private_data = client;
98d3e1e246SEddie James 	get_device(occ->dev);
997ed98dddSEddie James 
1007ed98dddSEddie James 	/* We allocate a 1-page buffer, make sure it all fits */
1017ed98dddSEddie James 	BUILD_BUG_ON((OCC_CMD_DATA_BYTES + 3) > PAGE_SIZE);
1027ed98dddSEddie James 	BUILD_BUG_ON((OCC_RESP_DATA_BYTES + 7) > PAGE_SIZE);
1037ed98dddSEddie James 
1047ed98dddSEddie James 	return 0;
1057ed98dddSEddie James }
1067ed98dddSEddie James 
occ_read(struct file * file,char __user * buf,size_t len,loff_t * offset)1077ed98dddSEddie James static ssize_t occ_read(struct file *file, char __user *buf, size_t len,
1087ed98dddSEddie James 			loff_t *offset)
1097ed98dddSEddie James {
1107ed98dddSEddie James 	struct occ_client *client = file->private_data;
1117ed98dddSEddie James 	ssize_t rc = 0;
1127ed98dddSEddie James 
1137ed98dddSEddie James 	if (!client)
1147ed98dddSEddie James 		return -ENODEV;
1157ed98dddSEddie James 
1167ed98dddSEddie James 	if (len > OCC_SRAM_BYTES)
1177ed98dddSEddie James 		return -EINVAL;
1187ed98dddSEddie James 
1197ed98dddSEddie James 	mutex_lock(&client->lock);
1207ed98dddSEddie James 
1217ed98dddSEddie James 	/* This should not be possible ... */
1227ed98dddSEddie James 	if (WARN_ON_ONCE(client->read_offset > client->data_size)) {
1237ed98dddSEddie James 		rc = -EIO;
1247ed98dddSEddie James 		goto done;
1257ed98dddSEddie James 	}
1267ed98dddSEddie James 
1277ed98dddSEddie James 	/* Grab how much data we have to read */
1287ed98dddSEddie James 	rc = min(len, client->data_size - client->read_offset);
1297ed98dddSEddie James 	if (copy_to_user(buf, client->buffer + client->read_offset, rc))
1307ed98dddSEddie James 		rc = -EFAULT;
1317ed98dddSEddie James 	else
1327ed98dddSEddie James 		client->read_offset += rc;
1337ed98dddSEddie James 
1347ed98dddSEddie James  done:
1357ed98dddSEddie James 	mutex_unlock(&client->lock);
1367ed98dddSEddie James 
1377ed98dddSEddie James 	return rc;
1387ed98dddSEddie James }
1397ed98dddSEddie James 
occ_write(struct file * file,const char __user * buf,size_t len,loff_t * offset)1407ed98dddSEddie James static ssize_t occ_write(struct file *file, const char __user *buf,
1417ed98dddSEddie James 			 size_t len, loff_t *offset)
1427ed98dddSEddie James {
1437ed98dddSEddie James 	struct occ_client *client = file->private_data;
1447ed98dddSEddie James 	size_t rlen, data_length;
14562f79f3dSEddie James 	ssize_t rc;
1467ed98dddSEddie James 	u8 *cmd;
1477ed98dddSEddie James 
1487ed98dddSEddie James 	if (!client)
1497ed98dddSEddie James 		return -ENODEV;
1507ed98dddSEddie James 
1517ed98dddSEddie James 	if (len > (OCC_CMD_DATA_BYTES + 3) || len < 3)
1527ed98dddSEddie James 		return -EINVAL;
1537ed98dddSEddie James 
1547ed98dddSEddie James 	mutex_lock(&client->lock);
1557ed98dddSEddie James 
1567ed98dddSEddie James 	/* Construct the command */
1577ed98dddSEddie James 	cmd = client->buffer;
1587ed98dddSEddie James 
1597ed98dddSEddie James 	/*
1607ed98dddSEddie James 	 * Copy the user command (assume user data follows the occ command
1617ed98dddSEddie James 	 * format)
1627ed98dddSEddie James 	 * byte 0: command type
1637ed98dddSEddie James 	 * bytes 1-2: data length (msb first)
1647ed98dddSEddie James 	 * bytes 3-n: data
1657ed98dddSEddie James 	 */
1667ed98dddSEddie James 	if (copy_from_user(&cmd[1], buf, len)) {
1677ed98dddSEddie James 		rc = -EFAULT;
1687ed98dddSEddie James 		goto done;
1697ed98dddSEddie James 	}
1707ed98dddSEddie James 
1717ed98dddSEddie James 	/* Extract data length */
1727ed98dddSEddie James 	data_length = (cmd[2] << 8) + cmd[3];
1737ed98dddSEddie James 	if (data_length > OCC_CMD_DATA_BYTES) {
1747ed98dddSEddie James 		rc = -EINVAL;
1757ed98dddSEddie James 		goto done;
1767ed98dddSEddie James 	}
1777ed98dddSEddie James 
17862f79f3dSEddie James 	/* Submit command; 4 bytes before the data and 2 bytes after */
1797ed98dddSEddie James 	rlen = PAGE_SIZE;
1807ed98dddSEddie James 	rc = fsi_occ_submit(client->occ->dev, cmd, data_length + 6, cmd,
1817ed98dddSEddie James 			    &rlen);
1827ed98dddSEddie James 	if (rc)
1837ed98dddSEddie James 		goto done;
1847ed98dddSEddie James 
1857ed98dddSEddie James 	/* Set read tracking data */
1867ed98dddSEddie James 	client->data_size = rlen;
1877ed98dddSEddie James 	client->read_offset = 0;
1887ed98dddSEddie James 
1897ed98dddSEddie James 	/* Done */
1907ed98dddSEddie James 	rc = len;
1917ed98dddSEddie James 
1927ed98dddSEddie James  done:
1937ed98dddSEddie James 	mutex_unlock(&client->lock);
1947ed98dddSEddie James 
1957ed98dddSEddie James 	return rc;
1967ed98dddSEddie James }
1977ed98dddSEddie James 
occ_release(struct inode * inode,struct file * file)1987ed98dddSEddie James static int occ_release(struct inode *inode, struct file *file)
1997ed98dddSEddie James {
2007ed98dddSEddie James 	struct occ_client *client = file->private_data;
2017ed98dddSEddie James 
202d3e1e246SEddie James 	put_device(client->occ->dev);
2037ed98dddSEddie James 	free_page((unsigned long)client->buffer);
2047ed98dddSEddie James 	kfree(client);
2057ed98dddSEddie James 
2067ed98dddSEddie James 	return 0;
2077ed98dddSEddie James }
2087ed98dddSEddie James 
2097ed98dddSEddie James static const struct file_operations occ_fops = {
2107ed98dddSEddie James 	.owner = THIS_MODULE,
2117ed98dddSEddie James 	.open = occ_open,
2127ed98dddSEddie James 	.read = occ_read,
2137ed98dddSEddie James 	.write = occ_write,
2147ed98dddSEddie James 	.release = occ_release,
2157ed98dddSEddie James };
2167ed98dddSEddie James 
occ_save_ffdc(struct occ * occ,__be32 * resp,size_t parsed_len,size_t resp_len)2178ec3cc9fSEddie James static void occ_save_ffdc(struct occ *occ, __be32 *resp, size_t parsed_len,
2188ec3cc9fSEddie James 			  size_t resp_len)
2198ec3cc9fSEddie James {
2208ec3cc9fSEddie James 	if (resp_len > parsed_len) {
2218ec3cc9fSEddie James 		size_t dh = resp_len - parsed_len;
2228ec3cc9fSEddie James 		size_t ffdc_len = (dh - 1) * 4; /* SBE words are four bytes */
2238ec3cc9fSEddie James 		__be32 *ffdc = &resp[parsed_len];
2248ec3cc9fSEddie James 
2258ec3cc9fSEddie James 		if (ffdc_len > occ->client_buffer_size)
2268ec3cc9fSEddie James 			ffdc_len = occ->client_buffer_size;
2278ec3cc9fSEddie James 
2288ec3cc9fSEddie James 		memcpy(occ->client_buffer, ffdc, ffdc_len);
2298ec3cc9fSEddie James 		occ->client_response_size = ffdc_len;
2308ec3cc9fSEddie James 	}
2318ec3cc9fSEddie James }
2328ec3cc9fSEddie James 
occ_verify_checksum(struct occ * occ,struct occ_response * resp,u16 data_length)233614f0a50SEddie James static int occ_verify_checksum(struct occ *occ, struct occ_response *resp,
234614f0a50SEddie James 			       u16 data_length)
2357ed98dddSEddie James {
2367ed98dddSEddie James 	/* Fetch the two bytes after the data for the checksum. */
2377ed98dddSEddie James 	u16 checksum_resp = get_unaligned_be16(&resp->data[data_length]);
2387ed98dddSEddie James 	u16 checksum;
2397ed98dddSEddie James 	u16 i;
2407ed98dddSEddie James 
2417ed98dddSEddie James 	checksum = resp->seq_no;
2427ed98dddSEddie James 	checksum += resp->cmd_type;
2437ed98dddSEddie James 	checksum += resp->return_status;
2447ed98dddSEddie James 	checksum += (data_length >> 8) + (data_length & 0xFF);
2457ed98dddSEddie James 
2467ed98dddSEddie James 	for (i = 0; i < data_length; ++i)
2477ed98dddSEddie James 		checksum += resp->data[i];
2487ed98dddSEddie James 
249614f0a50SEddie James 	if (checksum != checksum_resp) {
250614f0a50SEddie James 		dev_err(occ->dev, "Bad checksum: %04x!=%04x\n", checksum,
251614f0a50SEddie James 			checksum_resp);
2527326939fSEddie James 		return -EBADE;
253614f0a50SEddie James 	}
2547ed98dddSEddie James 
2557ed98dddSEddie James 	return 0;
2567ed98dddSEddie James }
2577ed98dddSEddie James 
occ_getsram(struct occ * occ,u32 offset,void * data,ssize_t len)2585ec96d74SEddie James static int occ_getsram(struct occ *occ, u32 offset, void *data, ssize_t len)
2597ed98dddSEddie James {
2607ed98dddSEddie James 	u32 data_len = ((len + 7) / 8) * 8;	/* must be multiples of 8 B */
2618ec3cc9fSEddie James 	size_t cmd_len, parsed_len, resp_data_len;
262008d3825SEddie James 	size_t resp_len = OCC_MAX_RESP_WORDS;
263008d3825SEddie James 	__be32 *resp = occ->buffer;
264008d3825SEddie James 	__be32 cmd[6];
2655ec96d74SEddie James 	int idx = 0, rc;
2667ed98dddSEddie James 
2677ed98dddSEddie James 	/*
2687ed98dddSEddie James 	 * Magic sequence to do SBE getsram command. SBE will fetch data from
2697ed98dddSEddie James 	 * specified SRAM address.
2707ed98dddSEddie James 	 */
2715ec96d74SEddie James 	switch (occ->version) {
2725ec96d74SEddie James 	default:
2735ec96d74SEddie James 	case occ_p9:
2745ec96d74SEddie James 		cmd_len = 5;
2755ec96d74SEddie James 		cmd[2] = cpu_to_be32(1);	/* Normal mode */
2765ec96d74SEddie James 		cmd[3] = cpu_to_be32(OCC_P9_SRAM_RSP_ADDR + offset);
2775ec96d74SEddie James 		break;
2785ec96d74SEddie James 	case occ_p10:
2795ec96d74SEddie James 		idx = 1;
2805ec96d74SEddie James 		cmd_len = 6;
2815ec96d74SEddie James 		cmd[2] = cpu_to_be32(OCC_P10_SRAM_MODE);
2825ec96d74SEddie James 		cmd[3] = 0;
2835ec96d74SEddie James 		cmd[4] = cpu_to_be32(OCC_P10_SRAM_RSP_ADDR + offset);
2845ec96d74SEddie James 		break;
2855ec96d74SEddie James 	}
2865ec96d74SEddie James 
2875ec96d74SEddie James 	cmd[0] = cpu_to_be32(cmd_len);
2887ed98dddSEddie James 	cmd[1] = cpu_to_be32(SBEFIFO_CMD_GET_OCC_SRAM);
2895ec96d74SEddie James 	cmd[4 + idx] = cpu_to_be32(data_len);
2907ed98dddSEddie James 
2915ec96d74SEddie James 	rc = sbefifo_submit(occ->sbefifo, cmd, cmd_len, resp, &resp_len);
2927ed98dddSEddie James 	if (rc)
293008d3825SEddie James 		return rc;
2947ed98dddSEddie James 
2957ed98dddSEddie James 	rc = sbefifo_parse_status(occ->sbefifo, SBEFIFO_CMD_GET_OCC_SRAM,
2968ec3cc9fSEddie James 				  resp, resp_len, &parsed_len);
297008d3825SEddie James 	if (rc > 0) {
298008d3825SEddie James 		dev_err(occ->dev, "SRAM read returned failure status: %08x\n",
299008d3825SEddie James 			rc);
3008ec3cc9fSEddie James 		occ_save_ffdc(occ, resp, parsed_len, resp_len);
3018ec3cc9fSEddie James 		return -ECOMM;
302008d3825SEddie James 	} else if (rc) {
303008d3825SEddie James 		return rc;
304008d3825SEddie James 	}
3057ed98dddSEddie James 
3068ec3cc9fSEddie James 	resp_data_len = be32_to_cpu(resp[parsed_len - 1]);
3077ed98dddSEddie James 	if (resp_data_len != data_len) {
3087ed98dddSEddie James 		dev_err(occ->dev, "SRAM read expected %d bytes got %zd\n",
3097ed98dddSEddie James 			data_len, resp_data_len);
3107ed98dddSEddie James 		rc = -EBADMSG;
3117ed98dddSEddie James 	} else {
3127ed98dddSEddie James 		memcpy(data, resp, len);
3137ed98dddSEddie James 	}
3147ed98dddSEddie James 
3157ed98dddSEddie James 	return rc;
3167ed98dddSEddie James }
3177ed98dddSEddie James 
occ_putsram(struct occ * occ,const void * data,ssize_t len,u8 seq_no,u16 checksum)31862f79f3dSEddie James static int occ_putsram(struct occ *occ, const void *data, ssize_t len,
31962f79f3dSEddie James 		       u8 seq_no, u16 checksum)
3207ed98dddSEddie James {
3217ed98dddSEddie James 	u32 data_len = ((len + 7) / 8) * 8;	/* must be multiples of 8 B */
3228ec3cc9fSEddie James 	size_t cmd_len, parsed_len, resp_data_len;
323008d3825SEddie James 	size_t resp_len = OCC_MAX_RESP_WORDS;
324008d3825SEddie James 	__be32 *buf = occ->buffer;
32562f79f3dSEddie James 	u8 *byte_buf;
3265ec96d74SEddie James 	int idx = 0, rc;
3275ec96d74SEddie James 
3285ec96d74SEddie James 	cmd_len = (occ->version == occ_p10) ? 6 : 5;
3295ec96d74SEddie James 	cmd_len += data_len >> 2;
3307ed98dddSEddie James 
3317ed98dddSEddie James 	/*
3327ed98dddSEddie James 	 * Magic sequence to do SBE putsram command. SBE will transfer
3337ed98dddSEddie James 	 * data to specified SRAM address.
3347ed98dddSEddie James 	 */
3357ed98dddSEddie James 	buf[0] = cpu_to_be32(cmd_len);
3367ed98dddSEddie James 	buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM);
3377ed98dddSEddie James 
3385ec96d74SEddie James 	switch (occ->version) {
3395ec96d74SEddie James 	default:
3405ec96d74SEddie James 	case occ_p9:
3415ec96d74SEddie James 		buf[2] = cpu_to_be32(1);	/* Normal mode */
3425ec96d74SEddie James 		buf[3] = cpu_to_be32(OCC_P9_SRAM_CMD_ADDR);
3435ec96d74SEddie James 		break;
3445ec96d74SEddie James 	case occ_p10:
3455ec96d74SEddie James 		idx = 1;
3465ec96d74SEddie James 		buf[2] = cpu_to_be32(OCC_P10_SRAM_MODE);
3475ec96d74SEddie James 		buf[3] = 0;
3485ec96d74SEddie James 		buf[4] = cpu_to_be32(OCC_P10_SRAM_CMD_ADDR);
3495ec96d74SEddie James 		break;
3505ec96d74SEddie James 	}
3515ec96d74SEddie James 
3525ec96d74SEddie James 	buf[4 + idx] = cpu_to_be32(data_len);
3535ec96d74SEddie James 	memcpy(&buf[5 + idx], data, len);
3547ed98dddSEddie James 
35562f79f3dSEddie James 	byte_buf = (u8 *)&buf[5 + idx];
35662f79f3dSEddie James 	/*
35762f79f3dSEddie James 	 * Overwrite the first byte with our sequence number and the last two
35862f79f3dSEddie James 	 * bytes with the checksum.
35962f79f3dSEddie James 	 */
36062f79f3dSEddie James 	byte_buf[0] = seq_no;
36162f79f3dSEddie James 	byte_buf[len - 2] = checksum >> 8;
36262f79f3dSEddie James 	byte_buf[len - 1] = checksum & 0xff;
36362f79f3dSEddie James 
3647ed98dddSEddie James 	rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len);
3657ed98dddSEddie James 	if (rc)
366008d3825SEddie James 		return rc;
3677ed98dddSEddie James 
3687ed98dddSEddie James 	rc = sbefifo_parse_status(occ->sbefifo, SBEFIFO_CMD_PUT_OCC_SRAM,
3698ec3cc9fSEddie James 				  buf, resp_len, &parsed_len);
370008d3825SEddie James 	if (rc > 0) {
371008d3825SEddie James 		dev_err(occ->dev, "SRAM write returned failure status: %08x\n",
372008d3825SEddie James 			rc);
3738ec3cc9fSEddie James 		occ_save_ffdc(occ, buf, parsed_len, resp_len);
3748ec3cc9fSEddie James 		return -ECOMM;
375008d3825SEddie James 	} else if (rc) {
376008d3825SEddie James 		return rc;
377008d3825SEddie James 	}
3787ed98dddSEddie James 
3798ec3cc9fSEddie James 	if (parsed_len != 1) {
3807ed98dddSEddie James 		dev_err(occ->dev, "SRAM write response length invalid: %zd\n",
3818ec3cc9fSEddie James 			parsed_len);
3827ed98dddSEddie James 		rc = -EBADMSG;
3837ed98dddSEddie James 	} else {
3847ed98dddSEddie James 		resp_data_len = be32_to_cpu(buf[0]);
3857ed98dddSEddie James 		if (resp_data_len != data_len) {
3867ed98dddSEddie James 			dev_err(occ->dev,
3877ed98dddSEddie James 				"SRAM write expected %d bytes got %zd\n",
3887ed98dddSEddie James 				data_len, resp_data_len);
3897ed98dddSEddie James 			rc = -EBADMSG;
3907ed98dddSEddie James 		}
3917ed98dddSEddie James 	}
3927ed98dddSEddie James 
3937ed98dddSEddie James 	return rc;
3947ed98dddSEddie James }
3957ed98dddSEddie James 
occ_trigger_attn(struct occ * occ)3967ed98dddSEddie James static int occ_trigger_attn(struct occ *occ)
3977ed98dddSEddie James {
398008d3825SEddie James 	__be32 *buf = occ->buffer;
3998ec3cc9fSEddie James 	size_t cmd_len, parsed_len, resp_data_len;
400008d3825SEddie James 	size_t resp_len = OCC_MAX_RESP_WORDS;
4015ec96d74SEddie James 	int idx = 0, rc;
4027ed98dddSEddie James 
4035ec96d74SEddie James 	switch (occ->version) {
4045ec96d74SEddie James 	default:
4055ec96d74SEddie James 	case occ_p9:
4065ec96d74SEddie James 		cmd_len = 7;
4075ec96d74SEddie James 		buf[2] = cpu_to_be32(3); /* Circular mode */
4085ec96d74SEddie James 		buf[3] = 0;
4095ec96d74SEddie James 		break;
4105ec96d74SEddie James 	case occ_p10:
4115ec96d74SEddie James 		idx = 1;
4125ec96d74SEddie James 		cmd_len = 8;
4135ec96d74SEddie James 		buf[2] = cpu_to_be32(0xd0); /* Circular mode, OCB Channel 1 */
4145ec96d74SEddie James 		buf[3] = 0;
4155ec96d74SEddie James 		buf[4] = 0;
4165ec96d74SEddie James 		break;
4175ec96d74SEddie James 	}
4187ed98dddSEddie James 
4195ec96d74SEddie James 	buf[0] = cpu_to_be32(cmd_len);		/* Chip-op length in words */
4205ec96d74SEddie James 	buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM);
4215ec96d74SEddie James 	buf[4 + idx] = cpu_to_be32(8);		/* Data length in bytes */
4225ec96d74SEddie James 	buf[5 + idx] = cpu_to_be32(0x20010000);	/* Trigger OCC attention */
4235ec96d74SEddie James 	buf[6 + idx] = 0;
4245ec96d74SEddie James 
4255ec96d74SEddie James 	rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len);
4267ed98dddSEddie James 	if (rc)
427008d3825SEddie James 		return rc;
4287ed98dddSEddie James 
4297ed98dddSEddie James 	rc = sbefifo_parse_status(occ->sbefifo, SBEFIFO_CMD_PUT_OCC_SRAM,
4308ec3cc9fSEddie James 				  buf, resp_len, &parsed_len);
431008d3825SEddie James 	if (rc > 0) {
432008d3825SEddie James 		dev_err(occ->dev, "SRAM attn returned failure status: %08x\n",
433008d3825SEddie James 			rc);
4348ec3cc9fSEddie James 		occ_save_ffdc(occ, buf, parsed_len, resp_len);
4358ec3cc9fSEddie James 		return -ECOMM;
436008d3825SEddie James 	} else if (rc) {
437008d3825SEddie James 		return rc;
438008d3825SEddie James 	}
4397ed98dddSEddie James 
4408ec3cc9fSEddie James 	if (parsed_len != 1) {
4417ed98dddSEddie James 		dev_err(occ->dev, "SRAM attn response length invalid: %zd\n",
4428ec3cc9fSEddie James 			parsed_len);
4437ed98dddSEddie James 		rc = -EBADMSG;
4447ed98dddSEddie James 	} else {
4457ed98dddSEddie James 		resp_data_len = be32_to_cpu(buf[0]);
4467ed98dddSEddie James 		if (resp_data_len != 8) {
4477ed98dddSEddie James 			dev_err(occ->dev,
4487ed98dddSEddie James 				"SRAM attn expected 8 bytes got %zd\n",
4497ed98dddSEddie James 				resp_data_len);
4507ed98dddSEddie James 			rc = -EBADMSG;
4517ed98dddSEddie James 		}
4527ed98dddSEddie James 	}
4537ed98dddSEddie James 
4547ed98dddSEddie James 	return rc;
4557ed98dddSEddie James }
4567ed98dddSEddie James 
fsi_occ_response_not_ready(struct occ_response * resp,u8 seq_no,u8 cmd_type)4573dcf3c84SEddie James static bool fsi_occ_response_not_ready(struct occ_response *resp, u8 seq_no,
4583dcf3c84SEddie James 				       u8 cmd_type)
4593dcf3c84SEddie James {
4603dcf3c84SEddie James 	return resp->return_status == OCC_RESP_CMD_IN_PRG ||
4613dcf3c84SEddie James 		resp->return_status == OCC_RESP_CRIT_INIT ||
4623dcf3c84SEddie James 		resp->seq_no != seq_no || resp->cmd_type != cmd_type;
4633dcf3c84SEddie James }
4643dcf3c84SEddie James 
fsi_occ_submit(struct device * dev,const void * request,size_t req_len,void * response,size_t * resp_len)4657ed98dddSEddie James int fsi_occ_submit(struct device *dev, const void *request, size_t req_len,
4667ed98dddSEddie James 		   void *response, size_t *resp_len)
4677ed98dddSEddie James {
4687ed98dddSEddie James 	const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS);
4697ed98dddSEddie James 	const unsigned long wait_time =
4707ed98dddSEddie James 		msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS);
4717ed98dddSEddie James 	struct occ *occ = dev_get_drvdata(dev);
4727ed98dddSEddie James 	struct occ_response *resp = response;
4738ec3cc9fSEddie James 	size_t user_resp_len = *resp_len;
474afd26118SEddie James 	u8 seq_no;
4753dcf3c84SEddie James 	u8 cmd_type;
47662f79f3dSEddie James 	u16 checksum = 0;
4777ed98dddSEddie James 	u16 resp_data_length;
47862f79f3dSEddie James 	const u8 *byte_request = (const u8 *)request;
4793dcf3c84SEddie James 	unsigned long end;
4807ed98dddSEddie James 	int rc;
48162f79f3dSEddie James 	size_t i;
4827ed98dddSEddie James 
4838ec3cc9fSEddie James 	*resp_len = 0;
4848ec3cc9fSEddie James 
4857ed98dddSEddie James 	if (!occ)
4867ed98dddSEddie James 		return -ENODEV;
4877ed98dddSEddie James 
4888ec3cc9fSEddie James 	if (user_resp_len < 7) {
4898ec3cc9fSEddie James 		dev_dbg(dev, "Bad resplen %zd\n", user_resp_len);
4907ed98dddSEddie James 		return -EINVAL;
4917ed98dddSEddie James 	}
4927ed98dddSEddie James 
4933dcf3c84SEddie James 	cmd_type = byte_request[1];
4943dcf3c84SEddie James 
49562f79f3dSEddie James 	/* Checksum the request, ignoring first byte (sequence number). */
49662f79f3dSEddie James 	for (i = 1; i < req_len - 2; ++i)
49762f79f3dSEddie James 		checksum += byte_request[i];
49862f79f3dSEddie James 
499d3e1e246SEddie James 	rc = mutex_lock_interruptible(&occ->occ_lock);
500d3e1e246SEddie James 	if (rc)
501d3e1e246SEddie James 		return rc;
5027ed98dddSEddie James 
5038ec3cc9fSEddie James 	occ->client_buffer = response;
5048ec3cc9fSEddie James 	occ->client_buffer_size = user_resp_len;
5058ec3cc9fSEddie James 	occ->client_response_size = 0;
5068ec3cc9fSEddie James 
507d3e1e246SEddie James 	if (!occ->buffer) {
508d3e1e246SEddie James 		rc = -ENOENT;
509d3e1e246SEddie James 		goto done;
510d3e1e246SEddie James 	}
511d3e1e246SEddie James 
51262f79f3dSEddie James 	/*
51362f79f3dSEddie James 	 * Get a sequence number and update the counter. Avoid a sequence
51462f79f3dSEddie James 	 * number of 0 which would pass the response check below even if the
51562f79f3dSEddie James 	 * OCC response is uninitialized. Any sequence number the user is
51662f79f3dSEddie James 	 * trying to send is overwritten since this function is the only common
51762f79f3dSEddie James 	 * interface to the OCC and therefore the only place we can guarantee
51862f79f3dSEddie James 	 * unique sequence numbers.
51962f79f3dSEddie James 	 */
52062f79f3dSEddie James 	seq_no = occ->sequence_number++;
52162f79f3dSEddie James 	if (!occ->sequence_number)
52262f79f3dSEddie James 		occ->sequence_number = 1;
52362f79f3dSEddie James 	checksum += seq_no;
52462f79f3dSEddie James 
52562f79f3dSEddie James 	rc = occ_putsram(occ, request, req_len, seq_no, checksum);
5267ed98dddSEddie James 	if (rc)
5277ed98dddSEddie James 		goto done;
5287ed98dddSEddie James 
5297ed98dddSEddie James 	rc = occ_trigger_attn(occ);
5307ed98dddSEddie James 	if (rc)
5317ed98dddSEddie James 		goto done;
5327ed98dddSEddie James 
5333dcf3c84SEddie James 	end = jiffies + timeout;
5343dcf3c84SEddie James 	while (true) {
5357ed98dddSEddie James 		/* Read occ response header */
5365ec96d74SEddie James 		rc = occ_getsram(occ, 0, resp, 8);
5377ed98dddSEddie James 		if (rc)
5387ed98dddSEddie James 			goto done;
5397ed98dddSEddie James 
5403dcf3c84SEddie James 		if (fsi_occ_response_not_ready(resp, seq_no, cmd_type)) {
5413dcf3c84SEddie James 			if (time_after(jiffies, end)) {
5423dcf3c84SEddie James 				dev_err(occ->dev,
5433dcf3c84SEddie James 					"resp timeout status=%02x seq=%d cmd=%d, our seq=%d cmd=%d\n",
544afd26118SEddie James 					resp->return_status, resp->seq_no,
5453dcf3c84SEddie James 					resp->cmd_type, seq_no, cmd_type);
5463dcf3c84SEddie James 				rc = -ETIMEDOUT;
547afd26118SEddie James 				goto done;
548afd26118SEddie James 			}
5497ed98dddSEddie James 
5507ed98dddSEddie James 			set_current_state(TASK_UNINTERRUPTIBLE);
5517ed98dddSEddie James 			schedule_timeout(wait_time);
5523dcf3c84SEddie James 		} else {
5537ed98dddSEddie James 			/* Extract size of response data */
5543dcf3c84SEddie James 			resp_data_length =
5553dcf3c84SEddie James 				get_unaligned_be16(&resp->data_length);
5567ed98dddSEddie James 
5573dcf3c84SEddie James 			/*
5583dcf3c84SEddie James 			 * Message size is data length + 5 bytes header + 2
5593dcf3c84SEddie James 			 * bytes checksum
5603dcf3c84SEddie James 			 */
5618ec3cc9fSEddie James 			if ((resp_data_length + 7) > user_resp_len) {
5627ed98dddSEddie James 				rc = -EMSGSIZE;
5637ed98dddSEddie James 				goto done;
5647ed98dddSEddie James 			}
5657ed98dddSEddie James 
5663dcf3c84SEddie James 			/*
5673dcf3c84SEddie James 			 * Get the entire response including the header again,
5683dcf3c84SEddie James 			 * in case it changed
5693dcf3c84SEddie James 			 */
5707ed98dddSEddie James 			if (resp_data_length > 1) {
5713dcf3c84SEddie James 				rc = occ_getsram(occ, 0, resp,
5723dcf3c84SEddie James 						 resp_data_length + 7);
5737ed98dddSEddie James 				if (rc)
5747ed98dddSEddie James 					goto done;
5753dcf3c84SEddie James 
5763dcf3c84SEddie James 				if (!fsi_occ_response_not_ready(resp, seq_no,
5773dcf3c84SEddie James 								cmd_type))
5783dcf3c84SEddie James 					break;
5793dcf3c84SEddie James 			} else {
5803dcf3c84SEddie James 				break;
5817ed98dddSEddie James 			}
5823dcf3c84SEddie James 		}
5833dcf3c84SEddie James 	}
5843dcf3c84SEddie James 
5853dcf3c84SEddie James 	dev_dbg(dev, "resp_status=%02x resp_data_len=%d\n",
5863dcf3c84SEddie James 		resp->return_status, resp_data_length);
5877ed98dddSEddie James 
588614f0a50SEddie James 	rc = occ_verify_checksum(occ, resp, resp_data_length);
5897326939fSEddie James 	if (rc)
5907326939fSEddie James 		goto done;
5917326939fSEddie James 
5927326939fSEddie James 	occ->client_response_size = resp_data_length + 7;
5937ed98dddSEddie James 
5947ed98dddSEddie James  done:
5958ec3cc9fSEddie James 	*resp_len = occ->client_response_size;
5967ed98dddSEddie James 	mutex_unlock(&occ->occ_lock);
5977ed98dddSEddie James 
5987ed98dddSEddie James 	return rc;
5997ed98dddSEddie James }
6007ed98dddSEddie James EXPORT_SYMBOL_GPL(fsi_occ_submit);
6017ed98dddSEddie James 
occ_unregister_platform_child(struct device * dev,void * data)6020fead4fcSEddie James static int occ_unregister_platform_child(struct device *dev, void *data)
6037ed98dddSEddie James {
6047ed98dddSEddie James 	struct platform_device *hwmon_dev = to_platform_device(dev);
6057ed98dddSEddie James 
6067ed98dddSEddie James 	platform_device_unregister(hwmon_dev);
6077ed98dddSEddie James 
6087ed98dddSEddie James 	return 0;
6097ed98dddSEddie James }
6107ed98dddSEddie James 
occ_unregister_of_child(struct device * dev,void * data)6110fead4fcSEddie James static int occ_unregister_of_child(struct device *dev, void *data)
6120fead4fcSEddie James {
6130fead4fcSEddie James 	struct platform_device *hwmon_dev = to_platform_device(dev);
6140fead4fcSEddie James 
6150fead4fcSEddie James 	of_device_unregister(hwmon_dev);
6160fead4fcSEddie James 	if (dev->of_node)
6170fead4fcSEddie James 		of_node_clear_flag(dev->of_node, OF_POPULATED);
6180fead4fcSEddie James 
6190fead4fcSEddie James 	return 0;
6200fead4fcSEddie James }
6210fead4fcSEddie James 
occ_probe(struct platform_device * pdev)6227ed98dddSEddie James static int occ_probe(struct platform_device *pdev)
6237ed98dddSEddie James {
6247ed98dddSEddie James 	int rc;
6257ed98dddSEddie James 	u32 reg;
6260fead4fcSEddie James 	char child_name[32];
6277ed98dddSEddie James 	struct occ *occ;
6280fead4fcSEddie James 	struct platform_device *hwmon_dev = NULL;
6290fead4fcSEddie James 	struct device_node *hwmon_node;
6307ed98dddSEddie James 	struct device *dev = &pdev->dev;
6317ed98dddSEddie James 	struct platform_device_info hwmon_dev_info = {
6327ed98dddSEddie James 		.parent = dev,
6337ed98dddSEddie James 		.name = "occ-hwmon",
6347ed98dddSEddie James 	};
6357ed98dddSEddie James 
6367ed98dddSEddie James 	occ = devm_kzalloc(dev, sizeof(*occ), GFP_KERNEL);
6377ed98dddSEddie James 	if (!occ)
6387ed98dddSEddie James 		return -ENOMEM;
6397ed98dddSEddie James 
640008d3825SEddie James 	/* SBE words are always four bytes */
641008d3825SEddie James 	occ->buffer = kvmalloc(OCC_MAX_RESP_WORDS * 4, GFP_KERNEL);
642008d3825SEddie James 	if (!occ->buffer)
643008d3825SEddie James 		return -ENOMEM;
644008d3825SEddie James 
6455ec96d74SEddie James 	occ->version = (uintptr_t)of_device_get_match_data(dev);
6467ed98dddSEddie James 	occ->dev = dev;
6477ed98dddSEddie James 	occ->sbefifo = dev->parent;
6483dcf3c84SEddie James 	/*
6493dcf3c84SEddie James 	 * Quickly derive a pseudo-random number from jiffies so that
6503dcf3c84SEddie James 	 * re-probing the driver doesn't accidentally overlap sequence numbers.
6513dcf3c84SEddie James 	 */
6523dcf3c84SEddie James 	occ->sequence_number = (u8)((jiffies % 0xff) + 1);
6537ed98dddSEddie James 	mutex_init(&occ->occ_lock);
6547ed98dddSEddie James 
6557ed98dddSEddie James 	if (dev->of_node) {
6567ed98dddSEddie James 		rc = of_property_read_u32(dev->of_node, "reg", &reg);
6577ed98dddSEddie James 		if (!rc) {
6587ed98dddSEddie James 			/* make sure we don't have a duplicate from dts */
6597ed98dddSEddie James 			occ->idx = ida_simple_get(&occ_ida, reg, reg + 1,
6607ed98dddSEddie James 						  GFP_KERNEL);
6617ed98dddSEddie James 			if (occ->idx < 0)
6627ed98dddSEddie James 				occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX,
6637ed98dddSEddie James 							  GFP_KERNEL);
6647ed98dddSEddie James 		} else {
6657ed98dddSEddie James 			occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX,
6667ed98dddSEddie James 						  GFP_KERNEL);
6677ed98dddSEddie James 		}
6687ed98dddSEddie James 	} else {
6697ed98dddSEddie James 		occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX, GFP_KERNEL);
6707ed98dddSEddie James 	}
6717ed98dddSEddie James 
6727ed98dddSEddie James 	platform_set_drvdata(pdev, occ);
6737ed98dddSEddie James 
6747ed98dddSEddie James 	snprintf(occ->name, sizeof(occ->name), "occ%d", occ->idx);
6757ed98dddSEddie James 	occ->mdev.fops = &occ_fops;
6767ed98dddSEddie James 	occ->mdev.minor = MISC_DYNAMIC_MINOR;
6777ed98dddSEddie James 	occ->mdev.name = occ->name;
6787ed98dddSEddie James 	occ->mdev.parent = dev;
6797ed98dddSEddie James 
6807ed98dddSEddie James 	rc = misc_register(&occ->mdev);
6817ed98dddSEddie James 	if (rc) {
6827ed98dddSEddie James 		dev_err(dev, "failed to register miscdevice: %d\n", rc);
6837ed98dddSEddie James 		ida_simple_remove(&occ_ida, occ->idx);
684008d3825SEddie James 		kvfree(occ->buffer);
6857ed98dddSEddie James 		return rc;
6867ed98dddSEddie James 	}
6877ed98dddSEddie James 
6880fead4fcSEddie James 	hwmon_node = of_get_child_by_name(dev->of_node, hwmon_dev_info.name);
6890fead4fcSEddie James 	if (hwmon_node) {
6900fead4fcSEddie James 		snprintf(child_name, sizeof(child_name), "%s.%d", hwmon_dev_info.name, occ->idx);
6910fead4fcSEddie James 		hwmon_dev = of_platform_device_create(hwmon_node, child_name, dev);
6920fead4fcSEddie James 		of_node_put(hwmon_node);
6930fead4fcSEddie James 	}
6940fead4fcSEddie James 
6950fead4fcSEddie James 	if (!hwmon_dev) {
6960fead4fcSEddie James 		occ->platform_hwmon = true;
6977ed98dddSEddie James 		hwmon_dev_info.id = occ->idx;
6987ed98dddSEddie James 		hwmon_dev = platform_device_register_full(&hwmon_dev_info);
6993c3c4848SXu Wang 		if (IS_ERR(hwmon_dev))
7007ed98dddSEddie James 			dev_warn(dev, "failed to create hwmon device\n");
7010fead4fcSEddie James 	}
7027ed98dddSEddie James 
7037ed98dddSEddie James 	return 0;
7047ed98dddSEddie James }
7057ed98dddSEddie James 
occ_remove(struct platform_device * pdev)7067ed98dddSEddie James static int occ_remove(struct platform_device *pdev)
7077ed98dddSEddie James {
7087ed98dddSEddie James 	struct occ *occ = platform_get_drvdata(pdev);
7097ed98dddSEddie James 
7107ed98dddSEddie James 	misc_deregister(&occ->mdev);
7117ed98dddSEddie James 
712d3e1e246SEddie James 	mutex_lock(&occ->occ_lock);
713d3e1e246SEddie James 	kvfree(occ->buffer);
714d3e1e246SEddie James 	occ->buffer = NULL;
715d3e1e246SEddie James 	mutex_unlock(&occ->occ_lock);
716d3e1e246SEddie James 
7170fead4fcSEddie James 	if (occ->platform_hwmon)
7180fead4fcSEddie James 		device_for_each_child(&pdev->dev, NULL, occ_unregister_platform_child);
7190fead4fcSEddie James 	else
7200fead4fcSEddie James 		device_for_each_child(&pdev->dev, NULL, occ_unregister_of_child);
7217ed98dddSEddie James 
7227ed98dddSEddie James 	ida_simple_remove(&occ_ida, occ->idx);
7237ed98dddSEddie James 
7247ed98dddSEddie James 	return 0;
7257ed98dddSEddie James }
7267ed98dddSEddie James 
7277ed98dddSEddie James static const struct of_device_id occ_match[] = {
7285ec96d74SEddie James 	{
7295ec96d74SEddie James 		.compatible = "ibm,p9-occ",
7305ec96d74SEddie James 		.data = (void *)occ_p9
7315ec96d74SEddie James 	},
7325ec96d74SEddie James 	{
7335ec96d74SEddie James 		.compatible = "ibm,p10-occ",
7345ec96d74SEddie James 		.data = (void *)occ_p10
7355ec96d74SEddie James 	},
7367ed98dddSEddie James 	{ },
7377ed98dddSEddie James };
73819a52178SZou Wei MODULE_DEVICE_TABLE(of, occ_match);
7397ed98dddSEddie James 
7407ed98dddSEddie James static struct platform_driver occ_driver = {
7417ed98dddSEddie James 	.driver = {
7427ed98dddSEddie James 		.name = "occ",
7437ed98dddSEddie James 		.of_match_table	= occ_match,
7447ed98dddSEddie James 	},
7457ed98dddSEddie James 	.probe	= occ_probe,
7467ed98dddSEddie James 	.remove = occ_remove,
7477ed98dddSEddie James };
7487ed98dddSEddie James 
occ_init(void)7497ed98dddSEddie James static int occ_init(void)
7507ed98dddSEddie James {
7517ed98dddSEddie James 	return platform_driver_register(&occ_driver);
7527ed98dddSEddie James }
7537ed98dddSEddie James 
occ_exit(void)7547ed98dddSEddie James static void occ_exit(void)
7557ed98dddSEddie James {
7567ed98dddSEddie James 	platform_driver_unregister(&occ_driver);
7577ed98dddSEddie James 
7587ed98dddSEddie James 	ida_destroy(&occ_ida);
7597ed98dddSEddie James }
7607ed98dddSEddie James 
7617ed98dddSEddie James module_init(occ_init);
7627ed98dddSEddie James module_exit(occ_exit);
7637ed98dddSEddie James 
7647ed98dddSEddie James MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
7657ed98dddSEddie James MODULE_DESCRIPTION("BMC P9 OCC driver");
7667ed98dddSEddie James MODULE_LICENSE("GPL");
767