xref: /openbmc/linux/drivers/fsi/fsi-master-i2cr.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
153e89e3eSEddie James // SPDX-License-Identifier: GPL-2.0
253e89e3eSEddie James /* Copyright (C) IBM Corporation 2023 */
353e89e3eSEddie James 
453e89e3eSEddie James #include <linux/device.h>
553e89e3eSEddie James #include <linux/fsi.h>
653e89e3eSEddie James #include <linux/i2c.h>
753e89e3eSEddie James #include <linux/module.h>
853e89e3eSEddie James #include <linux/mod_devicetable.h>
953e89e3eSEddie James #include <linux/mutex.h>
1053e89e3eSEddie James 
1153e89e3eSEddie James #include "fsi-master-i2cr.h"
1253e89e3eSEddie James 
1353e89e3eSEddie James #define CREATE_TRACE_POINTS
1453e89e3eSEddie James #include <trace/events/fsi_master_i2cr.h>
1553e89e3eSEddie James 
1653e89e3eSEddie James #define I2CR_ADDRESS_CFAM(a)	((a) >> 2)
1753e89e3eSEddie James #define I2CR_INITIAL_PARITY	true
1853e89e3eSEddie James 
1953e89e3eSEddie James #define I2CR_STATUS_CMD		0x60002
2053e89e3eSEddie James #define  I2CR_STATUS_ERR	 BIT_ULL(61)
2153e89e3eSEddie James #define I2CR_ERROR_CMD		0x60004
2253e89e3eSEddie James #define I2CR_LOG_CMD		0x60008
2353e89e3eSEddie James 
2453e89e3eSEddie James static const u8 i2cr_cfam[] = {
2553e89e3eSEddie James 	0xc0, 0x02, 0x0d, 0xa6,
2653e89e3eSEddie James 	0x80, 0x01, 0x10, 0x02,
2753e89e3eSEddie James 	0x80, 0x01, 0x10, 0x02,
2853e89e3eSEddie James 	0x80, 0x01, 0x10, 0x02,
2953e89e3eSEddie James 	0x80, 0x01, 0x80, 0x52,
3053e89e3eSEddie James 	0x80, 0x01, 0x10, 0x02,
3153e89e3eSEddie James 	0x80, 0x01, 0x10, 0x02,
3253e89e3eSEddie James 	0x80, 0x01, 0x10, 0x02,
3353e89e3eSEddie James 	0x80, 0x01, 0x10, 0x02,
3453e89e3eSEddie James 	0x80, 0x01, 0x22, 0x2d,
3553e89e3eSEddie James 	0x00, 0x00, 0x00, 0x00,
3653e89e3eSEddie James 	0xde, 0xad, 0xc0, 0xde
3753e89e3eSEddie James };
3853e89e3eSEddie James 
i2cr_check_parity32(u32 v,bool parity)3953e89e3eSEddie James static bool i2cr_check_parity32(u32 v, bool parity)
4053e89e3eSEddie James {
4153e89e3eSEddie James 	u32 i;
4253e89e3eSEddie James 
4353e89e3eSEddie James 	for (i = 0; i < 32; ++i) {
4453e89e3eSEddie James 		if (v & (1u << i))
4553e89e3eSEddie James 			parity = !parity;
4653e89e3eSEddie James 	}
4753e89e3eSEddie James 
4853e89e3eSEddie James 	return parity;
4953e89e3eSEddie James }
5053e89e3eSEddie James 
i2cr_check_parity64(u64 v)5153e89e3eSEddie James static bool i2cr_check_parity64(u64 v)
5253e89e3eSEddie James {
5353e89e3eSEddie James 	u32 i;
5453e89e3eSEddie James 	bool parity = I2CR_INITIAL_PARITY;
5553e89e3eSEddie James 
5653e89e3eSEddie James 	for (i = 0; i < 64; ++i) {
5753e89e3eSEddie James 		if (v & (1llu << i))
5853e89e3eSEddie James 			parity = !parity;
5953e89e3eSEddie James 	}
6053e89e3eSEddie James 
6153e89e3eSEddie James 	return parity;
6253e89e3eSEddie James }
6353e89e3eSEddie James 
i2cr_get_command(u32 address,bool parity)6453e89e3eSEddie James static u32 i2cr_get_command(u32 address, bool parity)
6553e89e3eSEddie James {
6653e89e3eSEddie James 	address <<= 1;
6753e89e3eSEddie James 
6853e89e3eSEddie James 	if (i2cr_check_parity32(address, parity))
6953e89e3eSEddie James 		address |= 1;
7053e89e3eSEddie James 
7153e89e3eSEddie James 	return address;
7253e89e3eSEddie James }
7353e89e3eSEddie James 
i2cr_transfer(struct i2c_client * client,u32 command,u64 * data)7453e89e3eSEddie James static int i2cr_transfer(struct i2c_client *client, u32 command, u64 *data)
7553e89e3eSEddie James {
7653e89e3eSEddie James 	struct i2c_msg msgs[2];
7753e89e3eSEddie James 	int ret;
7853e89e3eSEddie James 
7953e89e3eSEddie James 	msgs[0].addr = client->addr;
8053e89e3eSEddie James 	msgs[0].flags = 0;
8153e89e3eSEddie James 	msgs[0].len = sizeof(command);
8253e89e3eSEddie James 	msgs[0].buf = (__u8 *)&command;
8353e89e3eSEddie James 	msgs[1].addr = client->addr;
8453e89e3eSEddie James 	msgs[1].flags = I2C_M_RD;
8553e89e3eSEddie James 	msgs[1].len = sizeof(*data);
8653e89e3eSEddie James 	msgs[1].buf = (__u8 *)data;
8753e89e3eSEddie James 
8853e89e3eSEddie James 	ret = i2c_transfer(client->adapter, msgs, 2);
8953e89e3eSEddie James 	if (ret == 2)
9053e89e3eSEddie James 		return 0;
9153e89e3eSEddie James 
9253e89e3eSEddie James 	trace_i2cr_i2c_error(client, command, ret);
9353e89e3eSEddie James 
9453e89e3eSEddie James 	if (ret < 0)
9553e89e3eSEddie James 		return ret;
9653e89e3eSEddie James 
9753e89e3eSEddie James 	return -EIO;
9853e89e3eSEddie James }
9953e89e3eSEddie James 
i2cr_check_status(struct i2c_client * client)10053e89e3eSEddie James static int i2cr_check_status(struct i2c_client *client)
10153e89e3eSEddie James {
10253e89e3eSEddie James 	u64 status;
10353e89e3eSEddie James 	int ret;
10453e89e3eSEddie James 
10553e89e3eSEddie James 	ret = i2cr_transfer(client, I2CR_STATUS_CMD, &status);
10653e89e3eSEddie James 	if (ret)
10753e89e3eSEddie James 		return ret;
10853e89e3eSEddie James 
10953e89e3eSEddie James 	if (status & I2CR_STATUS_ERR) {
11053e89e3eSEddie James 		u32 buf[3] = { 0, 0, 0 };
11153e89e3eSEddie James 		u64 error;
11253e89e3eSEddie James 		u64 log;
11353e89e3eSEddie James 
11453e89e3eSEddie James 		i2cr_transfer(client, I2CR_ERROR_CMD, &error);
11553e89e3eSEddie James 		i2cr_transfer(client, I2CR_LOG_CMD, &log);
11653e89e3eSEddie James 
11753e89e3eSEddie James 		trace_i2cr_status_error(client, status, error, log);
11853e89e3eSEddie James 
11953e89e3eSEddie James 		buf[0] = I2CR_STATUS_CMD;
12053e89e3eSEddie James 		i2c_master_send(client, (const char *)buf, sizeof(buf));
12153e89e3eSEddie James 
12253e89e3eSEddie James 		buf[0] = I2CR_ERROR_CMD;
12353e89e3eSEddie James 		i2c_master_send(client, (const char *)buf, sizeof(buf));
12453e89e3eSEddie James 
12553e89e3eSEddie James 		buf[0] = I2CR_LOG_CMD;
12653e89e3eSEddie James 		i2c_master_send(client, (const char *)buf, sizeof(buf));
12753e89e3eSEddie James 
12853e89e3eSEddie James 		dev_err(&client->dev, "status:%016llx error:%016llx log:%016llx\n", status, error,
12953e89e3eSEddie James 			log);
13053e89e3eSEddie James 		return -EREMOTEIO;
13153e89e3eSEddie James 	}
13253e89e3eSEddie James 
13353e89e3eSEddie James 	trace_i2cr_status(client, status);
13453e89e3eSEddie James 	return 0;
13553e89e3eSEddie James }
13653e89e3eSEddie James 
fsi_master_i2cr_read(struct fsi_master_i2cr * i2cr,u32 addr,u64 * data)13753e89e3eSEddie James int fsi_master_i2cr_read(struct fsi_master_i2cr *i2cr, u32 addr, u64 *data)
13853e89e3eSEddie James {
13953e89e3eSEddie James 	u32 command = i2cr_get_command(addr, I2CR_INITIAL_PARITY);
14053e89e3eSEddie James 	int ret;
14153e89e3eSEddie James 
14253e89e3eSEddie James 	mutex_lock(&i2cr->lock);
14353e89e3eSEddie James 
14453e89e3eSEddie James 	ret = i2cr_transfer(i2cr->client, command, data);
14553e89e3eSEddie James 	if (ret)
14653e89e3eSEddie James 		goto unlock;
14753e89e3eSEddie James 
14853e89e3eSEddie James 	ret = i2cr_check_status(i2cr->client);
14953e89e3eSEddie James 	if (ret)
15053e89e3eSEddie James 		goto unlock;
15153e89e3eSEddie James 
15253e89e3eSEddie James 	trace_i2cr_read(i2cr->client, command, data);
15353e89e3eSEddie James 
15453e89e3eSEddie James unlock:
15553e89e3eSEddie James 	mutex_unlock(&i2cr->lock);
15653e89e3eSEddie James 	return ret;
15753e89e3eSEddie James }
15853e89e3eSEddie James EXPORT_SYMBOL_GPL(fsi_master_i2cr_read);
15953e89e3eSEddie James 
fsi_master_i2cr_write(struct fsi_master_i2cr * i2cr,u32 addr,u64 data)16053e89e3eSEddie James int fsi_master_i2cr_write(struct fsi_master_i2cr *i2cr, u32 addr, u64 data)
16153e89e3eSEddie James {
16253e89e3eSEddie James 	u32 buf[3] = { 0 };
16353e89e3eSEddie James 	int ret;
16453e89e3eSEddie James 
16553e89e3eSEddie James 	buf[0] = i2cr_get_command(addr, i2cr_check_parity64(data));
16653e89e3eSEddie James 	memcpy(&buf[1], &data, sizeof(data));
16753e89e3eSEddie James 
16853e89e3eSEddie James 	mutex_lock(&i2cr->lock);
16953e89e3eSEddie James 
17053e89e3eSEddie James 	ret = i2c_master_send(i2cr->client, (const char *)buf, sizeof(buf));
17153e89e3eSEddie James 	if (ret == sizeof(buf)) {
17253e89e3eSEddie James 		ret = i2cr_check_status(i2cr->client);
17353e89e3eSEddie James 		if (!ret)
17453e89e3eSEddie James 			trace_i2cr_write(i2cr->client, buf[0], data);
17553e89e3eSEddie James 	} else {
17653e89e3eSEddie James 		trace_i2cr_i2c_error(i2cr->client, buf[0], ret);
17753e89e3eSEddie James 
17853e89e3eSEddie James 		if (ret >= 0)
17953e89e3eSEddie James 			ret = -EIO;
18053e89e3eSEddie James 	}
18153e89e3eSEddie James 
18253e89e3eSEddie James 	mutex_unlock(&i2cr->lock);
18353e89e3eSEddie James 	return ret;
18453e89e3eSEddie James }
18553e89e3eSEddie James EXPORT_SYMBOL_GPL(fsi_master_i2cr_write);
18653e89e3eSEddie James 
i2cr_read(struct fsi_master * master,int link,uint8_t id,uint32_t addr,void * val,size_t size)18753e89e3eSEddie James static int i2cr_read(struct fsi_master *master, int link, uint8_t id, uint32_t addr, void *val,
18853e89e3eSEddie James 		     size_t size)
18953e89e3eSEddie James {
19053e89e3eSEddie James 	struct fsi_master_i2cr *i2cr = container_of(master, struct fsi_master_i2cr, master);
19153e89e3eSEddie James 	u64 data;
19253e89e3eSEddie James 	size_t i;
19353e89e3eSEddie James 	int ret;
19453e89e3eSEddie James 
19553e89e3eSEddie James 	if (link || id || (addr & 0xffff0000) || !(size == 1 || size == 2 || size == 4))
19653e89e3eSEddie James 		return -EINVAL;
19753e89e3eSEddie James 
19853e89e3eSEddie James 	/*
19953e89e3eSEddie James 	 * The I2CR doesn't have CFAM or FSI slave address space - only the
20053e89e3eSEddie James 	 * engines. In order for this to work with the FSI core, we need to
20153e89e3eSEddie James 	 * emulate at minimum the CFAM config table so that the appropriate
20253e89e3eSEddie James 	 * engines are discovered.
20353e89e3eSEddie James 	 */
20453e89e3eSEddie James 	if (addr < 0xc00) {
20553e89e3eSEddie James 		if (addr > sizeof(i2cr_cfam) - 4)
20653e89e3eSEddie James 			addr = (addr & 0x3) + (sizeof(i2cr_cfam) - 4);
20753e89e3eSEddie James 
20853e89e3eSEddie James 		memcpy(val, &i2cr_cfam[addr], size);
20953e89e3eSEddie James 		return 0;
21053e89e3eSEddie James 	}
21153e89e3eSEddie James 
21253e89e3eSEddie James 	ret = fsi_master_i2cr_read(i2cr, I2CR_ADDRESS_CFAM(addr), &data);
21353e89e3eSEddie James 	if (ret)
21453e89e3eSEddie James 		return ret;
21553e89e3eSEddie James 
21653e89e3eSEddie James 	/*
21753e89e3eSEddie James 	 * FSI core expects up to 4 bytes BE back, while I2CR replied with LE
21853e89e3eSEddie James 	 * bytes on the wire.
21953e89e3eSEddie James 	 */
22053e89e3eSEddie James 	for (i = 0; i < size; ++i)
22153e89e3eSEddie James 		((u8 *)val)[i] = ((u8 *)&data)[7 - i];
22253e89e3eSEddie James 
22353e89e3eSEddie James 	return 0;
22453e89e3eSEddie James }
22553e89e3eSEddie James 
i2cr_write(struct fsi_master * master,int link,uint8_t id,uint32_t addr,const void * val,size_t size)22653e89e3eSEddie James static int i2cr_write(struct fsi_master *master, int link, uint8_t id, uint32_t addr,
22753e89e3eSEddie James 		      const void *val, size_t size)
22853e89e3eSEddie James {
22953e89e3eSEddie James 	struct fsi_master_i2cr *i2cr = container_of(master, struct fsi_master_i2cr, master);
23053e89e3eSEddie James 	u64 data = 0;
23153e89e3eSEddie James 	size_t i;
23253e89e3eSEddie James 
23353e89e3eSEddie James 	if (link || id || (addr & 0xffff0000) || !(size == 1 || size == 2 || size == 4))
23453e89e3eSEddie James 		return -EINVAL;
23553e89e3eSEddie James 
23653e89e3eSEddie James 	/* I2CR writes to CFAM or FSI slave address are a successful no-op. */
23753e89e3eSEddie James 	if (addr < 0xc00)
23853e89e3eSEddie James 		return 0;
23953e89e3eSEddie James 
24053e89e3eSEddie James 	/*
24153e89e3eSEddie James 	 * FSI core passes up to 4 bytes BE, while the I2CR expects LE bytes on
24253e89e3eSEddie James 	 * the wire.
24353e89e3eSEddie James 	 */
24453e89e3eSEddie James 	for (i = 0; i < size; ++i)
24553e89e3eSEddie James 		((u8 *)&data)[7 - i] = ((u8 *)val)[i];
24653e89e3eSEddie James 
24753e89e3eSEddie James 	return fsi_master_i2cr_write(i2cr, I2CR_ADDRESS_CFAM(addr), data);
24853e89e3eSEddie James }
24953e89e3eSEddie James 
i2cr_release(struct device * dev)25053e89e3eSEddie James static void i2cr_release(struct device *dev)
25153e89e3eSEddie James {
25253e89e3eSEddie James 	struct fsi_master_i2cr *i2cr = to_fsi_master_i2cr(to_fsi_master(dev));
25353e89e3eSEddie James 
25453e89e3eSEddie James 	of_node_put(dev->of_node);
25553e89e3eSEddie James 
25653e89e3eSEddie James 	kfree(i2cr);
25753e89e3eSEddie James }
25853e89e3eSEddie James 
i2cr_probe(struct i2c_client * client)25953e89e3eSEddie James static int i2cr_probe(struct i2c_client *client)
26053e89e3eSEddie James {
26153e89e3eSEddie James 	struct fsi_master_i2cr *i2cr;
26253e89e3eSEddie James 	int ret;
26353e89e3eSEddie James 
26453e89e3eSEddie James 	i2cr = kzalloc(sizeof(*i2cr), GFP_KERNEL);
26553e89e3eSEddie James 	if (!i2cr)
26653e89e3eSEddie James 		return -ENOMEM;
26753e89e3eSEddie James 
26853e89e3eSEddie James 	/* Only one I2CR on any given I2C bus (fixed I2C device address) */
26953e89e3eSEddie James 	i2cr->master.idx = client->adapter->nr;
27053e89e3eSEddie James 	dev_set_name(&i2cr->master.dev, "i2cr%d", i2cr->master.idx);
27153e89e3eSEddie James 	i2cr->master.dev.parent = &client->dev;
27253e89e3eSEddie James 	i2cr->master.dev.of_node = of_node_get(dev_of_node(&client->dev));
27353e89e3eSEddie James 	i2cr->master.dev.release = i2cr_release;
27453e89e3eSEddie James 
27553e89e3eSEddie James 	i2cr->master.n_links = 1;
27653e89e3eSEddie James 	i2cr->master.read = i2cr_read;
27753e89e3eSEddie James 	i2cr->master.write = i2cr_write;
27853e89e3eSEddie James 
27953e89e3eSEddie James 	mutex_init(&i2cr->lock);
28053e89e3eSEddie James 	i2cr->client = client;
28153e89e3eSEddie James 
28253e89e3eSEddie James 	ret = fsi_master_register(&i2cr->master);
28353e89e3eSEddie James 	if (ret)
28453e89e3eSEddie James 		return ret;
28553e89e3eSEddie James 
28653e89e3eSEddie James 	i2c_set_clientdata(client, i2cr);
28753e89e3eSEddie James 	return 0;
28853e89e3eSEddie James }
28953e89e3eSEddie James 
i2cr_remove(struct i2c_client * client)29053e89e3eSEddie James static void i2cr_remove(struct i2c_client *client)
29153e89e3eSEddie James {
29253e89e3eSEddie James 	struct fsi_master_i2cr *i2cr = i2c_get_clientdata(client);
29353e89e3eSEddie James 
29453e89e3eSEddie James 	fsi_master_unregister(&i2cr->master);
29553e89e3eSEddie James }
29653e89e3eSEddie James 
29753e89e3eSEddie James static const struct of_device_id i2cr_ids[] = {
29853e89e3eSEddie James 	{ .compatible = "ibm,i2cr-fsi-master" },
29953e89e3eSEddie James 	{ }
30053e89e3eSEddie James };
30153e89e3eSEddie James MODULE_DEVICE_TABLE(of, i2cr_ids);
30253e89e3eSEddie James 
30353e89e3eSEddie James static struct i2c_driver i2cr_driver = {
304*b587cb72SUwe Kleine-König 	.probe = i2cr_probe,
30553e89e3eSEddie James 	.remove = i2cr_remove,
30653e89e3eSEddie James 	.driver = {
30753e89e3eSEddie James 		.name = "fsi-master-i2cr",
30853e89e3eSEddie James 		.of_match_table = i2cr_ids,
30953e89e3eSEddie James 	},
31053e89e3eSEddie James };
31153e89e3eSEddie James 
31253e89e3eSEddie James module_i2c_driver(i2cr_driver)
31353e89e3eSEddie James 
31453e89e3eSEddie James MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
31553e89e3eSEddie James MODULE_DESCRIPTION("IBM I2C Responder virtual FSI master driver");
31653e89e3eSEddie James MODULE_LICENSE("GPL");
317