brintos

brintos / linux-shallow public Read only

0
0
Text · 5.7 KiB · 5817a09 Raw
257 lines · c
1// SPDX-License-Identifier: GPL-2.0+2// Copyright IBM Corp 20193 4#include <linux/device.h>5#include <linux/errno.h>6#include <linux/fsi-occ.h>7#include <linux/i2c.h>8#include <linux/jiffies.h>9#include <linux/module.h>10#include <linux/sched.h>11#include <linux/unaligned.h>12 13#include "common.h"14 15#define OCC_TIMEOUT_MS			100016#define OCC_CMD_IN_PRG_WAIT_MS		5017 18/* OCB (on-chip control bridge - interface to OCC) registers */19#define OCB_DATA1			0x6B03520#define OCB_ADDR			0x6B07021#define OCB_DATA3			0x6B07522 23/* OCC SRAM address space */24#define OCC_SRAM_ADDR_CMD		0xFFFF600025#define OCC_SRAM_ADDR_RESP		0xFFFF700026 27#define OCC_DATA_ATTN			0x2001000028 29struct p8_i2c_occ {30	struct occ occ;31	struct i2c_client *client;32};33 34#define to_p8_i2c_occ(x)	container_of((x), struct p8_i2c_occ, occ)35 36static int p8_i2c_occ_getscom(struct i2c_client *client, u32 address, u8 *data)37{38	ssize_t rc;39	__be64 buf;40	struct i2c_msg msgs[2];41 42	/* p8 i2c slave requires shift */43	address <<= 1;44 45	msgs[0].addr = client->addr;46	msgs[0].flags = client->flags & I2C_M_TEN;47	msgs[0].len = sizeof(u32);48	/* address is a scom address; bus-endian */49	msgs[0].buf = (char *)&address;50 51	/* data from OCC is big-endian */52	msgs[1].addr = client->addr;53	msgs[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD;54	msgs[1].len = sizeof(u64);55	msgs[1].buf = (char *)&buf;56 57	rc = i2c_transfer(client->adapter, msgs, 2);58	if (rc < 0)59		return rc;60 61	*(u64 *)data = be64_to_cpu(buf);62 63	return 0;64}65 66static int p8_i2c_occ_putscom(struct i2c_client *client, u32 address, u8 *data)67{68	u32 buf[3];69	ssize_t rc;70 71	/* p8 i2c slave requires shift */72	address <<= 1;73 74	/* address is bus-endian; data passed through from user as-is */75	buf[0] = address;76	memcpy(&buf[1], &data[4], sizeof(u32));77	memcpy(&buf[2], data, sizeof(u32));78 79	rc = i2c_master_send(client, (const char *)buf, sizeof(buf));80	if (rc < 0)81		return rc;82	else if (rc != sizeof(buf))83		return -EIO;84 85	return 0;86}87 88static int p8_i2c_occ_putscom_u32(struct i2c_client *client, u32 address,89				  u32 data0, u32 data1)90{91	u8 buf[8];92 93	memcpy(buf, &data0, 4);94	memcpy(buf + 4, &data1, 4);95 96	return p8_i2c_occ_putscom(client, address, buf);97}98 99static int p8_i2c_occ_putscom_be(struct i2c_client *client, u32 address,100				 u8 *data, size_t len)101{102	__be32 data0 = 0, data1 = 0;103 104	memcpy(&data0, data, min_t(size_t, len, 4));105	if (len > 4) {106		len -= 4;107		memcpy(&data1, data + 4, min_t(size_t, len, 4));108	}109 110	return p8_i2c_occ_putscom_u32(client, address, be32_to_cpu(data0),111				      be32_to_cpu(data1));112}113 114static int p8_i2c_occ_send_cmd(struct occ *occ, u8 *cmd, size_t len,115			       void *resp, size_t resp_len)116{117	int i, rc;118	unsigned long start;119	u16 data_length;120	const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS);121	const long wait_time = msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS);122	struct p8_i2c_occ *ctx = to_p8_i2c_occ(occ);123	struct i2c_client *client = ctx->client;124	struct occ_response *or = (struct occ_response *)resp;125 126	start = jiffies;127 128	/* set sram address for command */129	rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, OCC_SRAM_ADDR_CMD, 0);130	if (rc)131		return rc;132 133	/* write command (expected to already be BE), we need bus-endian... */134	rc = p8_i2c_occ_putscom_be(client, OCB_DATA3, cmd, len);135	if (rc)136		return rc;137 138	/* trigger OCC attention */139	rc = p8_i2c_occ_putscom_u32(client, OCB_DATA1, OCC_DATA_ATTN, 0);140	if (rc)141		return rc;142 143	do {144		/* set sram address for response */145		rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR,146					    OCC_SRAM_ADDR_RESP, 0);147		if (rc)148			return rc;149 150		rc = p8_i2c_occ_getscom(client, OCB_DATA3, (u8 *)resp);151		if (rc)152			return rc;153 154		/* wait for OCC */155		if (or->return_status == OCC_RESP_CMD_IN_PRG) {156			rc = -EALREADY;157 158			if (time_after(jiffies, start + timeout))159				break;160 161			set_current_state(TASK_INTERRUPTIBLE);162			schedule_timeout(wait_time);163		}164	} while (rc);165 166	/* check the OCC response */167	switch (or->return_status) {168	case OCC_RESP_CMD_IN_PRG:169		rc = -ETIMEDOUT;170		break;171	case OCC_RESP_SUCCESS:172		rc = 0;173		break;174	case OCC_RESP_CMD_INVAL:175	case OCC_RESP_CMD_LEN_INVAL:176	case OCC_RESP_DATA_INVAL:177	case OCC_RESP_CHKSUM_ERR:178		rc = -EINVAL;179		break;180	case OCC_RESP_INT_ERR:181	case OCC_RESP_BAD_STATE:182	case OCC_RESP_CRIT_EXCEPT:183	case OCC_RESP_CRIT_INIT:184	case OCC_RESP_CRIT_WATCHDOG:185	case OCC_RESP_CRIT_OCB:186	case OCC_RESP_CRIT_HW:187		rc = -EREMOTEIO;188		break;189	default:190		rc = -EPROTO;191	}192 193	if (rc < 0)194		return rc;195 196	data_length = get_unaligned_be16(&or->data_length);197	if ((data_length + 7) > resp_len)198		return -EMSGSIZE;199 200	/* fetch the rest of the response data */201	for (i = 8; i < data_length + 7; i += 8) {202		rc = p8_i2c_occ_getscom(client, OCB_DATA3, ((u8 *)resp) + i);203		if (rc)204			return rc;205	}206 207	return 0;208}209 210static int p8_i2c_occ_probe(struct i2c_client *client)211{212	struct occ *occ;213	struct p8_i2c_occ *ctx = devm_kzalloc(&client->dev, sizeof(*ctx),214					      GFP_KERNEL);215	if (!ctx)216		return -ENOMEM;217 218	ctx->client = client;219	occ = &ctx->occ;220	occ->bus_dev = &client->dev;221	dev_set_drvdata(&client->dev, occ);222 223	occ->powr_sample_time_us = 250;224	occ->poll_cmd_data = 0x10;		/* P8 OCC poll data */225	occ->send_cmd = p8_i2c_occ_send_cmd;226 227	return occ_setup(occ);228}229 230static void p8_i2c_occ_remove(struct i2c_client *client)231{232	struct occ *occ = dev_get_drvdata(&client->dev);233 234	occ_shutdown(occ);235}236 237static const struct of_device_id p8_i2c_occ_of_match[] = {238	{ .compatible = "ibm,p8-occ-hwmon" },239	{}240};241MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match);242 243static struct i2c_driver p8_i2c_occ_driver = {244	.driver = {245		.name = "occ-hwmon",246		.of_match_table = p8_i2c_occ_of_match,247	},248	.probe = p8_i2c_occ_probe,249	.remove = p8_i2c_occ_remove,250};251 252module_i2c_driver(p8_i2c_occ_driver);253 254MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");255MODULE_DESCRIPTION("BMC P8 OCC hwmon driver");256MODULE_LICENSE("GPL");257